Passed
Push — release-2.1 ( 7401ba...5d05c6 )
by Mathias
08:16 queued 14s
created

checkFolders()   F

Complexity

Conditions 33
Paths > 20000

Size

Total Lines 126
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 33
eloc 61
c 5
b 0
f 0
nc 24300
nop 0
dl 0
loc 126
rs 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines https://www.simplemachines.org
8
 * @copyright 2023 Simple Machines and individual contributors
9
 * @license https://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1.4
12
 */
13
14
// Version information...
15
define('SMF_VERSION', '2.1.4');
16
define('SMF_FULL_VERSION', 'SMF ' . SMF_VERSION);
17
define('SMF_SOFTWARE_YEAR', '2023');
18
define('SMF_LANG_VERSION', '2.1.3');
19
define('SMF_INSTALLING', 1);
20
21
define('JQUERY_VERSION', '3.6.3');
22
define('POSTGRE_TITLE', 'PostgreSQL');
23
define('MYSQL_TITLE', 'MySQL');
24
define('SMF_USER_AGENT', 'Mozilla/5.0 (' . php_uname('s') . ' ' . php_uname('m') . ') AppleWebKit/605.1.15 (KHTML, like Gecko)  SMF/' . strtr(SMF_VERSION, ' ', '.'));
25
if (!defined('TIME_START'))
26
	define('TIME_START', microtime(true));
27
28
/**
29
 * The minimum required PHP version.
30
 *
31
 * @var string
32
 */
33
$GLOBALS['required_php_version'] = '7.0.0';
34
35
/**
36
 * A list of supported database systems.
37
 *
38
 * @var array
39
 */
40
$databases = array(
41
	'mysql' => array(
42
		'name' => 'MySQL',
43
		'version' => '5.6.0',
44
		'version_check' => function() {
45
			global $db_connection;
46
			if (!function_exists('mysqli_fetch_row'))
47
				return false;
48
			return mysqli_fetch_row(mysqli_query($db_connection, 'SELECT VERSION();'))[0];
0 ignored issues
show
Bug introduced by
It seems like mysqli_query($db_connection, 'SELECT VERSION();') can also be of type true; however, parameter $result of mysqli_fetch_row() does only seem to accept mysqli_result, 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

48
			return mysqli_fetch_row(/** @scrutinizer ignore-type */ mysqli_query($db_connection, 'SELECT VERSION();'))[0];
Loading history...
49
		},
50
		'alter_support' => true,
51
	),
52
	'postgresql' => array(
53
		'name' => 'PostgreSQL',
54
		'version' => '9.6',
55
		'version_check' => function() {
56
			if (!function_exists('pg_version'))
57
				return false;
58
			$version = pg_version();
59
			return $version['client'];
60
		},
61
		'always_has_db' => true,
62
	),
63
);
64
65
/**
66
 * The maximum time a single substep may take, in seconds.
67
 *
68
 * @var int
69
 */
70
$timeLimitThreshold = 3;
71
72
/**
73
 * The current path to the upgrade.php file.
74
 *
75
 * @var string
76
 */
77
$upgrade_path = dirname(__FILE__);
78
79
/**
80
 * The URL of the current page.
81
 *
82
 * @var string
83
 */
84
$upgradeurl = $_SERVER['PHP_SELF'];
85
86
/**
87
 * Flag to disable the required administrator login.
88
 *
89
 * @var bool
90
 */
91
$disable_security = false;
92
93
/**
94
 * The amount of seconds allowed between logins.
95
 * If the first user to login is inactive for this amount of seconds, a second login is allowed.
96
 *
97
 * @var int
98
 */
99
$upcontext['inactive_timeout'] = 10;
100
101
global $txt;
102
103
// All the steps in detail.
104
// Number,Name,Function,Progress Weight.
105
$upcontext['steps'] = array(
106
	0 => array(1, 'upgrade_step_login', 'WelcomeLogin', 2),
107
	1 => array(2, 'upgrade_step_options', 'UpgradeOptions', 2),
108
	2 => array(3, 'upgrade_step_backup', 'BackupDatabase', 10),
109
	3 => array(4, 'upgrade_step_database', 'DatabaseChanges', 50),
110
	4 => array(5, 'upgrade_step_convertjson', 'serialize_to_json', 10),
111
	5 => array(6, 'upgrade_step_convertutf', 'ConvertUtf8', 20),
112
	6 => array(7, 'upgrade_step_delete', 'DeleteUpgrade', 1),
113
);
114
// Just to remember which one has files in it.
115
$upcontext['database_step'] = 3;
116
117
// Secure some resources
118
@ini_set('mysql.connect_timeout', -1);
119
@ini_set('default_socket_timeout', 900);
120
@ini_set('memory_limit', '512M');
121
122
// Clean the upgrade path if this is from the client.
123
if (!empty($_SERVER['argv']) && php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
124
	for ($i = 1; $i < $_SERVER['argc']; $i++)
125
	{
126
		// Provide the help without possible errors if the environment isn't sane.
127
		if (in_array($_SERVER['argv'][$i], array('-h', '--help')))
128
		{
129
			cmdStep0();
130
			exit;
131
		}
132
133
		if (preg_match('~^--path=(.+)$~', $_SERVER['argv'][$i], $match) != 0)
134
			$upgrade_path = realpath(substr($match[1], -1) == '/' ? substr($match[1], 0, -1) : $match[1]);
135
136
		// Cases where we do php other/upgrade.php --path=./
137
		if ($upgrade_path == './' && isset($_SERVER['PWD']))
138
			$upgrade_path = realpath($_SERVER['PWD']);
139
		// Cases where we do php upgrade.php --path=../
140
		elseif ($upgrade_path == '../' && isset($_SERVER['PWD']))
141
			$upgrade_path = dirname(realpath($_SERVER['PWD']));
142
	}
143
144
// Are we from the client?
145
if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
146
{
147
	$command_line = true;
148
	$disable_security = true;
149
}
150
else
151
	$command_line = false;
152
153
// We can't do anything without these files.
154
foreach (array(
155
	dirname(__FILE__) . '/upgrade-helper.php',
156
	$upgrade_path . '/Settings.php'
157
) as $required_file)
158
{
159
	if (!file_exists($required_file))
160
		die(basename($required_file) . ' was not found where it was expected: ' . $required_file . '! Make sure you have uploaded ALL files from the upgrade package to your forum\'s root directory. The upgrader cannot continue.');
161
162
	require_once($required_file);
163
}
164
165
// We don't use "-utf8" anymore...  Tweak the entry that may have been loaded by Settings.php
166
if (isset($language))
167
	$language = str_ireplace('-utf8', '', basename($language, '.lng'));
168
169
// Figure out a valid language request (if any)
170
// 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.
171
if (isset($_SERVER['QUERY_STRING']) && preg_match('~\blang=(\w+)~', $_SERVER['QUERY_STRING'], $matches))
172
	$upcontext['lang'] = $matches[1];
173
174
// Are we logged in?
175
if (isset($upgradeData))
176
{
177
	$upcontext['user'] = json_decode(base64_decode($upgradeData), true);
178
179
	// Check for sensible values.
180
	if (empty($upcontext['user']['started']) || $upcontext['user']['started'] < time() - 86400)
181
		$upcontext['user']['started'] = time();
182
	if (empty($upcontext['user']['updated']) || $upcontext['user']['updated'] < time() - 86400)
183
		$upcontext['user']['updated'] = 0;
184
185
	$upcontext['started'] = $upcontext['user']['started'];
186
	$upcontext['updated'] = $upcontext['user']['updated'];
187
188
	$is_debug = !empty($upcontext['user']['debug']) ? true : false;
189
190
	$upcontext['skip_db_substeps'] = !empty($upcontext['user']['skip_db_substeps']);
191
}
192
193
// Nothing sensible?
194
if (empty($upcontext['updated']))
195
{
196
	$upcontext['started'] = time();
197
	$upcontext['updated'] = 0;
198
	$upcontext['skip_db_substeps'] = false;
199
	$upcontext['user'] = array(
200
		'id' => 0,
201
		'name' => 'Guest',
202
		'pass' => 0,
203
		'started' => $upcontext['started'],
204
		'updated' => $upcontext['updated'],
205
	);
206
}
207
208
// Try to load the language file... or at least define a few necessary strings for now.
209
load_lang_file();
210
211
// Load up some essential data...
212
loadEssentialData();
213
214
// Are we going to be mimic'ing SSI at this point?
215
if (isset($_GET['ssi']))
216
{
217
	require_once($sourcedir . '/Errors.php');
218
	require_once($sourcedir . '/Logging.php');
219
	require_once($sourcedir . '/Load.php');
220
	require_once($sourcedir . '/Security.php');
221
	require_once($sourcedir . '/Subs-Package.php');
222
223
	// SMF isn't started up properly, but loadUserSettings calls our cookies.
224
	if (!isset($smcFunc['json_encode']))
225
	{
226
		$smcFunc['json_encode'] = 'json_encode';
227
		$smcFunc['json_decode'] = 'smf_json_decode';
228
	}
229
230
	loadUserSettings();
231
	loadPermissions();
232
	reloadSettings();
233
}
234
235
// Include our helper functions.
236
require_once($sourcedir . '/Subs.php');
237
require_once($sourcedir . '/LogInOut.php');
238
require_once($sourcedir . '/Subs-Editor.php');
239
240
// Don't do security check if on Yabbse
241
if (!isset($modSettings['smfVersion']))
242
	$disable_security = true;
243
244
// This only exists if we're on SMF ;)
245
if (isset($modSettings['smfVersion']))
246
{
247
	$request = $smcFunc['db_query']('', '
248
		SELECT variable, value
249
		FROM {db_prefix}themes
250
		WHERE id_theme = {int:id_theme}
251
			AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
252
		array(
253
			'id_theme' => 1,
254
			'theme_url' => 'theme_url',
255
			'theme_dir' => 'theme_dir',
256
			'images_url' => 'images_url',
257
			'db_error_skip' => true,
258
		)
259
	);
260
	while ($row = $smcFunc['db_fetch_assoc']($request))
261
		$modSettings[$row['variable']] = $row['value'];
262
	$smcFunc['db_free_result']($request);
263
}
264
265
if (!isset($modSettings['theme_url']))
266
{
267
	$modSettings['theme_dir'] = $boarddir . '/Themes/default';
268
	$modSettings['theme_url'] = 'Themes/default';
269
	$modSettings['images_url'] = 'Themes/default/images';
270
}
271
if (!isset($settings['default_theme_url']))
272
	$settings['default_theme_url'] = $modSettings['theme_url'];
273
if (!isset($settings['default_theme_dir']))
274
	$settings['default_theme_dir'] = $modSettings['theme_dir'];
275
276
// Old DBs won't have this
277
if (!isset($modSettings['rand_seed']))
278
{
279
	if (!function_exists('cache_put_data'))
280
		require_once($sourcedir . '/Load.php');
281
	smf_seed_generator();
282
}
283
284
// This is needed in case someone invokes the upgrader using https when upgrading an http forum
285
if (httpsOn())
286
	$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
287
288
$upcontext['is_large_forum'] = (empty($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && !empty($modSettings['totalMessages']) && $modSettings['totalMessages'] > 75000;
289
290
// Have we got tracking data - if so use it (It will be clean!)
291
if (isset($_GET['data']))
292
{
293
	global $is_debug;
294
295
	$upcontext['upgrade_status'] = json_decode(base64_decode($_GET['data']), true);
296
	$upcontext['current_step'] = $upcontext['upgrade_status']['curstep'];
297
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
298
	$upcontext['rid'] = $upcontext['upgrade_status']['rid'];
299
	$support_js = $upcontext['upgrade_status']['js'];
300
301
	// Only set this if the upgrader status says so.
302
	if (empty($is_debug))
303
		$is_debug = $upcontext['upgrade_status']['debug'];
304
}
305
// Set the defaults.
306
else
307
{
308
	$upcontext['current_step'] = 0;
309
	$upcontext['rid'] = mt_rand(0, 5000);
310
	$upcontext['upgrade_status'] = array(
311
		'curstep' => 0,
312
		'lang' => isset($upcontext['lang']) ? $upcontext['lang'] : basename($language, '.lng'),
313
		'rid' => $upcontext['rid'],
314
		'pass' => 0,
315
		'debug' => 0,
316
		'js' => 0,
317
	);
318
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
319
}
320
321
// Now that we have the necessary info, make sure we loaded the right language file.
322
load_lang_file();
323
324
// Default title...
325
$upcontext['page_title'] = $txt['updating_smf_installation'];
326
327
// If this isn't the first stage see whether they are logging in and resuming.
328
if ($upcontext['current_step'] != 0 || !empty($upcontext['user']['step']))
329
	checkLogin();
330
331
if ($command_line)
332
	cmdStep0();
333
334
// Don't error if we're using xml.
335
if (isset($_GET['xml']))
336
	$upcontext['return_error'] = true;
337
338
// Loop through all the steps doing each one as required.
339
$upcontext['overall_percent'] = 0;
340
foreach ($upcontext['steps'] as $num => $step)
341
{
342
	if ($num >= $upcontext['current_step'])
343
	{
344
		// The current weight of this step in terms of overall progress.
345
		$upcontext['step_weight'] = $step[3];
346
		// Make sure we reset the skip button.
347
		$upcontext['skip'] = false;
348
349
		// We cannot proceed if we're not logged in.
350
		if ($num != 0 && !$disable_security && $upcontext['user']['pass'] != $upcontext['upgrade_status']['pass'])
351
		{
352
			$upcontext['steps'][0][2]();
353
			break;
354
		}
355
356
		// Call the step and if it returns false that means pause!
357
		if (function_exists($step[2]) && $step[2]() === false)
358
			break;
359
		elseif (function_exists($step[2]))
360
		{
361
			//Start each new step with this unset, so the 'normal' template is called first
362
			unset($_GET['xml']);
363
			//Clear out warnings at the start of each step
364
			unset($upcontext['custom_warning']);
365
			$_GET['substep'] = 0;
366
			$upcontext['current_step']++;
367
		}
368
	}
369
	$upcontext['overall_percent'] += $step[3];
370
}
371
372
upgradeExit();
373
374
// Exit the upgrade script.
375
function upgradeExit($fallThrough = false)
376
{
377
	global $upcontext, $upgradeurl, $sourcedir, $command_line, $is_debug, $txt;
378
379
	// Save where we are...
380
	if (!empty($upcontext['current_step']) && !empty($upcontext['user']['id']))
381
	{
382
		$upcontext['user']['step'] = $upcontext['current_step'];
383
		$upcontext['user']['substep'] = $_GET['substep'];
384
		$upcontext['user']['updated'] = time();
385
		$upcontext['user']['skip_db_substeps'] = !empty($upcontext['skip_db_substeps']);
386
		$upcontext['debug'] = $is_debug;
387
		$upgradeData = base64_encode(json_encode($upcontext['user']));
388
		require_once($sourcedir . '/Subs.php');
389
		require_once($sourcedir . '/Subs-Admin.php');
390
		updateSettingsFile(array('upgradeData' => $upgradeData));
391
		updateDbLastError(0);
392
	}
393
394
	// Handle the progress of the step, if any.
395
	if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']]))
396
	{
397
		$upcontext['step_progress'] = round($upcontext['step_progress'], 1);
398
		$upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100);
399
	}
400
	$upcontext['overall_percent'] = (int) $upcontext['overall_percent'];
401
402
	// We usually dump our templates out.
403
	if (!$fallThrough)
404
	{
405
		// This should not happen my dear... HELP ME DEVELOPERS!!
406
		if (!empty($command_line))
407
		{
408
			if (function_exists('debug_print_backtrace'))
409
				debug_print_backtrace();
410
411
			printf("\n" . $txt['error_unexpected_template_call'], isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '');
412
			flush();
413
			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...
414
		}
415
416
		if (!isset($_GET['xml']))
417
			template_upgrade_above();
418
		else
419
		{
420
			header('content-type: text/xml; charset=UTF-8');
421
			// Sadly we need to retain the $_GET data thanks to the old upgrade scripts.
422
			$upcontext['get_data'] = array();
423
			foreach ($_GET as $k => $v)
424
			{
425
				if (substr($k, 0, 3) != 'amp' && !in_array($k, array('xml', 'substep', 'lang', 'data', 'step', 'filecount')))
426
				{
427
					$upcontext['get_data'][$k] = $v;
428
				}
429
			}
430
			template_xml_above();
431
		}
432
433
		// Call the template.
434
		if (isset($upcontext['sub_template']))
435
		{
436
			$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
437
			$upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&amp;substep=' . $_GET['substep'] . '&amp;data=' . base64_encode(json_encode($upcontext['upgrade_status']));
438
439
			// Custom stuff to pass back?
440
			if (!empty($upcontext['query_string']))
441
				$upcontext['form_url'] .= $upcontext['query_string'];
442
443
			// Call the appropriate subtemplate
444
			if (is_callable('template_' . $upcontext['sub_template']))
445
				call_user_func('template_' . $upcontext['sub_template']);
446
			else
447
				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...
448
		}
449
450
		// Was there an error?
451
		if (!empty($upcontext['forced_error_message']))
452
			echo $upcontext['forced_error_message'];
453
454
		// Show the footer.
455
		if (!isset($_GET['xml']))
456
			template_upgrade_below();
457
		else
458
			template_xml_below();
459
	}
460
461
	// Show the upgrade time for CLI when we are completely done, if in debug mode.
462
	if (!empty($command_line) && $is_debug)
463
	{
464
		$active = time() - $upcontext['started'];
465
		$hours = floor($active / 3600);
466
		$minutes = intval(($active / 60) % 60);
467
		$seconds = intval($active % 60);
468
469
		if ($hours > 0)
470
			echo "\n" . '', sprintf($txt['upgrade_completed_time_hms'], $hours, $minutes, $seconds), '' . "\n";
471
		elseif ($minutes > 0)
472
			echo "\n" . '', sprintf($txt['upgrade_completed_time_ms'], $minutes, $seconds), '' . "\n";
473
		elseif ($seconds > 0)
474
			echo "\n" . '', sprintf($txt['upgrade_completed_time_s'], $seconds), '' . "\n";
475
	}
476
477
	// Bang - gone!
478
	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...
479
}
480
481
// Load the list of language files, and the current language file.
482
function load_lang_file()
483
{
484
	global $txt, $upcontext, $language, $modSettings, $upgrade_path, $command_line;
485
486
	static $lang_dir = '', $detected_languages = array(), $loaded_langfile = '';
487
488
	// Do we know where to look for the language files, or shall we just guess for now?
489
	$temp = isset($modSettings['theme_dir']) ? $modSettings['theme_dir'] . '/languages' : $upgrade_path . '/Themes/default/languages';
490
491
	if ($lang_dir != $temp)
492
	{
493
		$lang_dir = $temp;
494
		$detected_languages = array();
495
	}
496
497
	// Override the language file?
498
	if (isset($upcontext['language']))
499
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['language'] . '.php';
500
	elseif (isset($upcontext['lang']))
501
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['lang'] . '.php';
502
	elseif (isset($language))
503
		$_SESSION['upgrader_langfile'] = 'Install.' . $language . '.php';
504
505
	// Avoid pointless repetition
506
	if (isset($_SESSION['upgrader_langfile']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_langfile'])
507
		return;
508
509
	// Now try to find the language files
510
	if (empty($detected_languages))
511
	{
512
		// Make sure the languages directory actually exists.
513
		if (file_exists($lang_dir))
514
		{
515
			// Find all the "Install" language files in the directory.
516
			$dir = dir($lang_dir);
517
			while ($entry = $dir->read())
518
			{
519
				// Skip any old '-utf8' language files that might be lying around
520
				if (strpos($entry, '-utf8') !== false)
521
					continue;
522
523
				if (substr($entry, 0, 8) == 'Install.' && substr($entry, -4) == '.php')
524
					$detected_languages[$entry] = ucfirst(substr($entry, 8, strlen($entry) - 12));
525
			}
526
			$dir->close();
527
		}
528
		// Our guess was wrong, but that's fine. We'll try again after $modSettings['theme_dir'] is defined.
529
		elseif (!isset($modSettings['theme_dir']))
530
		{
531
			// Define a few essential strings for now.
532
			$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.';
533
			$txt['error_sourcefile_missing'] = 'Unable to find the Sources/%1$s file. Please make sure it was uploaded properly, and then try again.';
534
535
			$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.';
536
			$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.';
537
538
			return;
539
		}
540
	}
541
542
	// Didn't find any, show an error message!
543
	if (empty($detected_languages))
544
	{
545
		$from = explode('/', $command_line ? $upgrade_path : $_SERVER['PHP_SELF']);
546
		$to = explode('/', $lang_dir);
547
		$relPath = $to;
548
549
		foreach($from as $depth => $dir)
550
		{
551
			if ($dir === $to[$depth])
552
				array_shift($relPath);
553
			else
554
			{
555
				$remaining = count($from) - $depth;
556
				if ($remaining > 1)
557
				{
558
					$padLength = (count($relPath) + $remaining - 1) * -1;
559
					$relPath = array_pad($relPath, $padLength, '..');
560
					break;
561
				}
562
				else
563
					$relPath[0] = './' . $relPath[0];
564
			}
565
		}
566
		$relPath = implode(DIRECTORY_SEPARATOR, $relPath);
567
568
		// Command line?
569
		if ($command_line)
570
		{
571
			echo 'This upgrader was unable to find the upgrader\'s language file or files.  They should be found under:', "\n",
572
				$relPath, "\n",
573
				'In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you have uploaded all the files in the distribution', "\n",
574
				'If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.', "\n";
575
			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...
576
		}
577
578
		// Let's not cache this message, eh?
579
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
580
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
581
		header('Cache-Control: no-cache');
582
583
		echo '<!DOCTYPE html>
584
			<html>
585
				<head>
586
					<title>SMF Upgrader: Error!</title>
587
						<style>
588
							body {
589
								font-family: sans-serif;
590
								max-width: 700px; }
591
592
								h1 {
593
									font-size: 14pt; }
594
595
								.directory {
596
									margin: 0.3em;
597
									font-family: monospace;
598
									font-weight: bold; }
599
						</style>
600
				</head>
601
				<body>
602
					<h1>A critical error has occurred.</h1>
603
						<p>This upgrader was unable to find the upgrader\'s language file or files.  They should be found under:</p>
604
						<div class="directory">', $relPath, '</div>
605
						<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>
606
						<p>If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.</p>
607
						<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>
608
				</body>
609
			</html>';
610
		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...
611
	}
612
613
	// Make sure it exists. If it doesn't, reset it.
614
	if (!isset($_SESSION['upgrader_langfile']) || preg_match('~[^\w.-]~', $_SESSION['upgrader_langfile']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_langfile']))
615
	{
616
		// Use the first one...
617
		list ($_SESSION['upgrader_langfile']) = array_keys($detected_languages);
618
619
		// If we have English and some other language, use the other language.
620
		if ($_SESSION['upgrader_langfile'] == 'Install.english.php' && count($detected_languages) > 1)
621
			list (, $_SESSION['upgrader_langfile']) = array_keys($detected_languages);
622
	}
623
624
	// For backup we load English at first, then the second language will overwrite it.
625
	if ($_SESSION['upgrader_langfile'] != 'Install.english.php')
626
	{
627
		require_once($lang_dir . '/index.english.php');
628
		require_once($lang_dir . '/Install.english.php');
629
	}
630
631
	// And now include the actual language file itself.
632
	require_once($lang_dir . '/' . str_replace('Install.', 'index.', $_SESSION['upgrader_langfile']));
633
	require_once($lang_dir . '/' . $_SESSION['upgrader_langfile']);
634
635
	// Remember what we've done
636
	$loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_langfile'];
637
}
638
639
// Used to direct the user to another location.
640
function redirectLocation($location, $addForm = true)
641
{
642
	global $upgradeurl, $upcontext, $command_line;
643
644
	// Command line users can't be redirected.
645
	if ($command_line)
646
		upgradeExit(true);
647
648
	// Are we providing the core info?
649
	if ($addForm)
650
	{
651
		$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
652
		$location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])) . $location;
653
	}
654
655
	while (@ob_end_clean())
656
		header('location: ' . strtr($location, array('&amp;' => '&')));
657
658
	// Exit - saving status as we go.
659
	upgradeExit(true);
660
}
661
662
// Load all essential data and connect to the DB as this is pre SSI.php
663
function loadEssentialData()
664
{
665
	global $db_server, $db_user, $db_passwd, $db_name, $db_connection;
666
	global $db_prefix, $db_character_set, $db_type, $db_port, $db_show_debug;
667
	global $db_mb4, $modSettings, $sourcedir, $smcFunc, $txt, $utf8;
668
669
	// Report all errors if admin wants them or this is a pre-release version.
670
	if (!empty($db_show_debug) || strspn(SMF_VERSION, '1234567890.') !== strlen(SMF_VERSION))
671
		error_reporting(E_ALL);
672
	// Otherwise, report all errors except for deprecation notices.
673
	else
674
		error_reporting(E_ALL & ~E_DEPRECATED);
675
676
	define('SMF', 1);
677
	header('X-Frame-Options: SAMEORIGIN');
678
	header('X-XSS-Protection: 1');
679
	header('X-Content-Type-Options: nosniff');
680
681
	// Start the session.
682
	if (@ini_get('session.save_handler') == 'user')
683
		@ini_set('session.save_handler', 'files');
684
	@session_start();
685
686
	if (empty($smcFunc))
687
		$smcFunc = array();
688
689
	require_once($sourcedir . '/Subs.php');
690
691
	if (version_compare(PHP_VERSION, '8.0.0', '>='))
692
		require_once($sourcedir . '/Subs-Compat.php');
693
694
	@set_time_limit(600);
695
696
	$smcFunc['random_int'] = function($min = 0, $max = PHP_INT_MAX)
697
	{
698
		global $sourcedir;
699
700
		// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
701
		if (!is_callable('random_int'))
702
			require_once($sourcedir . '/random_compat/random.php');
703
704
		return random_int($min, $max);
705
	};
706
707
	// This is now needed for loadUserSettings()
708
	$smcFunc['random_bytes'] = function($bytes)
709
	{
710
		global $sourcedir;
711
712
		if (!is_callable('random_bytes'))
713
			require_once($sourcedir . '/random_compat/random.php');
714
715
		return random_bytes($bytes);
716
	};
717
718
	// We need this for authentication and some upgrade code
719
	require_once($sourcedir . '/Subs-Auth.php');
720
	require_once($sourcedir . '/Class-Package.php');
721
722
	$smcFunc['strtolower'] = 'smf_strtolower';
723
724
	// Initialize everything...
725
	initialize_inputs();
726
727
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
728
729
	$smcFunc['normalize'] = function($string, $form = 'c') use ($utf8)
730
	{
731
		global $sourcedir;
732
733
		if (!$utf8)
734
			return $string;
735
736
		require_once($sourcedir . '/Subs-Charset.php');
737
738
		$normalize_func = 'utf8_normalize_' . strtolower((string) $form);
739
740
		if (!function_exists($normalize_func))
741
			return false;
742
743
		return $normalize_func($string);
744
	};
745
746
	// Get the database going!
747
	if (empty($db_type) || $db_type == 'mysqli')
748
	{
749
		$db_type = 'mysql';
750
		// If overriding $db_type, need to set its settings.php entry too
751
		$changes = array();
752
		$changes['db_type'] = 'mysql';
753
		require_once($sourcedir . '/Subs-Admin.php');
754
		updateSettingsFile($changes);
755
	}
756
757
	if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
758
	{
759
		require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
760
761
		// Make the connection...
762
		if (empty($db_connection))
763
		{
764
			$options = array('non_fatal' => true);
765
			// Add in the port if needed
766
			if (!empty($db_port))
767
				$options['port'] = $db_port;
768
769
			if (!empty($db_mb4))
770
				$options['db_mb4'] = $db_mb4;
771
772
			$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
773
		}
774
		else
775
			// If we've returned here, ping/reconnect to be safe
776
			$smcFunc['db_ping']($db_connection);
777
778
		// Oh dear god!!
779
		if ($db_connection === null)
780
		{
781
			// Get error info...  Recast just in case we get false or 0...
782
			$error_message = $smcFunc['db_connect_error']();
783
			if (empty($error_message))
784
				$error_message = '';
785
			$error_number = $smcFunc['db_connect_errno']();
786
			if (empty($error_number))
787
				$error_number = '';
788
			$db_error = (!empty($error_number) ? $error_number . ': ' : '') . $error_message;
789
790
			die($txt['error_db_connect_settings'] . '<br><br>' . $db_error);
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...
791
		}
792
793
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
794
			$smcFunc['db_query']('', '
795
				SET NAMES {string:db_character_set}',
796
				array(
797
					'db_error_skip' => true,
798
					'db_character_set' => $db_character_set,
799
				)
800
			);
801
802
		// Load the modSettings data...
803
		$request = $smcFunc['db_query']('', '
804
			SELECT variable, value
805
			FROM {db_prefix}settings',
806
			array(
807
				'db_error_skip' => true,
808
			)
809
		);
810
		$modSettings = array();
811
		while ($row = $smcFunc['db_fetch_assoc']($request))
812
			$modSettings[$row['variable']] = $row['value'];
813
		$smcFunc['db_free_result']($request);
814
	}
815
	else
816
		return throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
0 ignored issues
show
Bug introduced by
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

816
		return /** @scrutinizer ignore-call */ throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
Loading history...
817
818
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
819
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
820
	{
821
		require_once($sourcedir . '/QueryString.php');
822
		cleanRequest();
823
	}
824
825
	if (!isset($_GET['substep']))
826
		$_GET['substep'] = 0;
827
}
828
829
function initialize_inputs()
830
{
831
	global $start_time, $db_type, $upgrade_path;
832
833
	$start_time = time();
834
835
	umask(0);
836
837
	ob_start();
838
839
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
840
	ignore_user_abort(true);
841
842
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
843
	if (isset($_GET['delete']))
844
	{
845
		deleteFile(__FILE__);
0 ignored issues
show
Bug introduced by
The function deleteFile was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

845
		/** @scrutinizer ignore-call */ 
846
  deleteFile(__FILE__);
Loading history...
846
847
		// And the extra little files ;).
848
		deleteFile(dirname(__FILE__) . '/upgrade_1-0.sql');
849
		deleteFile(dirname(__FILE__) . '/upgrade_1-1.sql');
850
		deleteFile(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
851
		deleteFile(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
852
		deleteFile(dirname(__FILE__) . '/upgrade-helper.php');
853
854
		$dh = opendir(dirname(__FILE__));
855
		while ($file = readdir($dh))
856
		{
857
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
858
				deleteFile(dirname(__FILE__) . '/' . $file);
859
		}
860
		closedir($dh);
861
862
		// Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there.
863
		// 1.1 Sources files not in 2.0+
864
		deleteFile($upgrade_path . '/Sources/ModSettings.php');
865
		// 1.1 Templates that don't exist any more (e.g. renamed)
866
		deleteFile($upgrade_path . '/Themes/default/Combat.template.php');
867
		deleteFile($upgrade_path . '/Themes/default/Modlog.template.php');
868
		// 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder
869
		deleteFile($upgrade_path . '/Themes/default/fader.js');
870
		deleteFile($upgrade_path . '/Themes/default/script.js');
871
		deleteFile($upgrade_path . '/Themes/default/spellcheck.js');
872
		deleteFile($upgrade_path . '/Themes/default/xml_board.js');
873
		deleteFile($upgrade_path . '/Themes/default/xml_topic.js');
874
875
		// 2.0 Sources files not in 2.1+
876
		deleteFile($upgrade_path . '/Sources/DumpDatabase.php');
877
		deleteFile($upgrade_path . '/Sources/LockTopic.php');
878
879
		header('location: http' . (httpsOn() ? 's' : '') . '://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png');
880
		exit;
881
	}
882
883
	// Something is causing this to happen, and it's annoying.  Stop it.
884
	$temp = 'upgrade_php?step';
885
	while (strlen($temp) > 4)
886
	{
887
		if (isset($_GET[$temp]))
888
			unset($_GET[$temp]);
889
		$temp = substr($temp, 1);
890
	}
891
892
	// Force a step, defaulting to 0.
893
	$_GET['step'] = (int) @$_GET['step'];
894
	$_GET['substep'] = (int) @$_GET['substep'];
895
}
896
897
// Step 0 - Let's welcome them in and ask them to login!
898
function WelcomeLogin()
899
{
900
	global $boarddir, $sourcedir, $modSettings, $cachedir, $upgradeurl, $upcontext;
901
	global $smcFunc, $db_type, $databases, $boardurl, $upgrade_path;
902
903
	// We global $txt here so that the language files can add to them. This variable is NOT unused.
904
	global $txt;
905
906
	$upcontext['sub_template'] = 'welcome_message';
907
908
	// Check for some key files - one template, one language, and a new and an old source file.
909
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
910
		&& @file_exists($sourcedir . '/QueryString.php')
911
		&& @file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')
912
		&& @file_exists(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
913
914
	// Need legacy scripts?
915
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.1)
916
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
917
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.0)
918
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql');
919
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 1.1)
920
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql');
921
922
	// We don't need "-utf8" files anymore...
923
	$upcontext['language'] = str_ireplace('-utf8', '', $upcontext['language']);
924
925
	if (!$check)
926
		// 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.
927
		return throw_error($txt['error_upgrade_files_missing']);
0 ignored issues
show
Bug introduced by
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

927
		return /** @scrutinizer ignore-call */ throw_error($txt['error_upgrade_files_missing']);
Loading history...
928
929
	// Do they meet the install requirements?
930
	if (!php_version_check())
931
		return throw_error($txt['error_php_too_low']);
932
933
	if (!db_version_check())
934
		return throw_error(sprintf($txt['error_db_too_low'], $databases[$db_type]['name']));
935
936
	// Do some checks to make sure they have proper privileges
937
	db_extend('packages');
938
939
	// CREATE
940
	$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');
941
942
	// ALTER
943
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
944
945
	// DROP
946
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
947
948
	// Sorry... we need CREATE, ALTER and DROP
949
	if (!$create || !$alter || !$drop)
950
		return throw_error(sprintf($txt['error_db_privileges'], $databases[$db_type]['name']));
951
952
	// Do a quick version spot check.
953
	$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

953
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
954
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
955
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
956
		return throw_error($txt['error_upgrade_old_files']);
957
958
	// What absolutely needs to be writable?
959
	$writable_files = array(
960
		$boarddir . '/Settings.php',
961
		$boarddir . '/Settings_bak.php',
962
	);
963
964
	// Only check for minified writable files if we have it enabled or not set.
965
	if (!empty($modSettings['minimize_files']) || !isset($modSettings['minimize_files']))
966
		$writable_files += array(
967
			$modSettings['theme_dir'] . '/css/minified.css',
968
			$modSettings['theme_dir'] . '/scripts/minified.js',
969
			$modSettings['theme_dir'] . '/scripts/minified_deferred.js',
970
		);
971
972
	// Do we need to add this setting?
973
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
974
975
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $GLOBALS['boarddir'] . '/custom_avatar';
976
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
977
978
	// This little fellow has to cooperate...
979
	quickFileWritable($custom_av_dir);
0 ignored issues
show
Bug introduced by
The function quickFileWritable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

979
	/** @scrutinizer ignore-call */ 
980
 quickFileWritable($custom_av_dir);
Loading history...
980
981
	// Are we good now?
982
	if (!is_writable($custom_av_dir))
983
		return throw_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
984
	elseif ($need_settings_update)
985
	{
986
		if (!function_exists('cache_put_data'))
987
			require_once($sourcedir . '/Load.php');
988
989
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
990
		updateSettings(array('custom_avatar_url' => $custom_av_url));
991
	}
992
993
	require_once($sourcedir . '/Security.php');
994
995
	// Check the cache directory.
996
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
997
	if (!file_exists($cachedir_temp))
998
		@mkdir($cachedir_temp);
999
1000
	if (!file_exists($cachedir_temp))
1001
		return throw_error($txt['error_cache_not_found']);
1002
1003
	quickFileWritable($cachedir_temp . '/db_last_error.php');
1004
1005
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
1006
		return throw_error(sprintf($txt['error_lang_index_missing'], $upcontext['language'], $upgradeurl));
1007
	elseif (!isset($_GET['skiplang']))
1008
	{
1009
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
1010
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1011
1012
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1013
			return throw_error(sprintf($txt['error_upgrade_old_lang_files'], $upcontext['language'], $upgradeurl));
1014
	}
1015
1016
	if (!makeFilesWritable($writable_files))
0 ignored issues
show
Bug introduced by
The function makeFilesWritable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1016
	if (!/** @scrutinizer ignore-call */ makeFilesWritable($writable_files))
Loading history...
1017
		return false;
1018
1019
	// Check agreement.txt. (it may not exist, in which case $boarddir must be writable.)
1020
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
1021
		return throw_error($txt['error_agreement_not_writable']);
1022
1023
	// Upgrade the agreement.
1024
	elseif (isset($modSettings['agreement']))
1025
	{
1026
		$fp = fopen($boarddir . '/agreement.txt', 'w');
1027
		fwrite($fp, $modSettings['agreement']);
1028
		fclose($fp);
1029
	}
1030
1031
	// We're going to check that their board dir setting is right in case they've been moving stuff around.
1032
	if (strtr($boarddir, array('/' => '', '\\' => '')) != strtr($upgrade_path, array('/' => '', '\\' => '')))
1033
		$upcontext['warning'] = '
1034
			' . sprintf($txt['upgrade_forumdir_settings'], $boarddir, $upgrade_path) . '<br>
1035
			<ul>
1036
				<li>' . $txt['upgrade_forumdir'] . '  ' . $boarddir . '</li>
1037
				<li>' . $txt['upgrade_sourcedir'] . '  ' . $boarddir . '</li>
1038
				<li>' . $txt['upgrade_cachedir'] . '  ' . $cachedir_temp . '</li>
1039
			</ul>
1040
			' . $txt['upgrade_incorrect_settings'] . '';
1041
1042
	// Confirm mbstring is loaded...
1043
	if (!extension_loaded('mbstring'))
1044
		return throw_error($txt['install_no_mbstring']);
1045
1046
	// Confirm fileinfo is loaded...
1047
	if (!extension_loaded('fileinfo'))
1048
		return throw_error($txt['install_no_fileinfo']);
1049
1050
	// Check for https stream support.
1051
	$supported_streams = stream_get_wrappers();
1052
	if (!in_array('https', $supported_streams))
1053
		$upcontext['custom_warning'] = $txt['install_no_https'];
1054
1055
	// Make sure attachment & avatar folders exist.  Big problem if folks move or restructure sites upon upgrade.
1056
	checkFolders();
1057
1058
	// Either we're logged in or we're going to present the login.
1059
	if (checkLogin())
1060
		return true;
1061
1062
	$upcontext += createToken('login');
1063
1064
	return false;
1065
}
1066
1067
// Do a number of attachment & avatar folder checks.
1068
// Display a warning if issues found.  Does not force a hard stop.
1069
function checkFolders()
1070
{
1071
	global $modSettings, $upcontext, $txt, $command_line;
1072
1073
	$warnings = '';
1074
1075
	// First, check the avatar directory...
1076
	// Note it wasn't specified in yabbse, but there was no smfVersion either.
1077
	if (!empty($modSettings['smfVersion']) && !is_dir($modSettings['avatar_directory']))
1078
		$warnings .= $txt['warning_av_missing'];
1079
1080
	// Next, check the custom avatar directory...  Note this is optional in 2.0.
1081
	if (!empty($modSettings['custom_avatar_dir']) && !is_dir($modSettings['custom_avatar_dir']))
1082
	{
1083
		if (empty($warnings))
1084
			$warnings = $txt['warning_custom_av_missing'];
1085
		else
1086
			$warnings .= '<br><br>' . $txt['warning_custom_av_missing'];
1087
	}
1088
1089
	// Finally, attachment folders.
1090
	// A bit more complex, since it may be json or serialized, and it may be an array or just a string...
1091
1092
	// PHP currently has a terrible handling with unserialize in which errors are fatal and not catchable.  Lets borrow some code from the RFC that intends to fix this
1093
	// https://wiki.php.net/rfc/improve_unserialize_error_handling
1094
	try {
1095
    	set_error_handler(static function ($severity, $message, $file, $line) {
1096
			throw new \ErrorException($message, 0, $severity, $file, $line);
1097
		});
1098
		$ser_test = @unserialize($modSettings['attachmentUploadDir']);
1099
	} catch (\Throwable $e) {
1100
		$ser_test = false;
1101
	}
1102
	finally {
1103
	 	restore_error_handler();
1104
	}
1105
1106
	// Json is simple, it can be caught.
1107
	try {
1108
		$json_test = @json_decode($modSettings['attachmentUploadDir'], true);
1109
	} catch (\Throwable $e) {
1110
		$json_test = null;
1111
	}
1112
1113
	$string_test = !empty($modSettings['attachmentUploadDir']) && is_string($modSettings['attachmentUploadDir']) && is_dir($modSettings['attachmentUploadDir']);
1114
1115
	// String?
1116
	$attdr_problem_found = false;
1117
	if ($string_test === true)
1118
	{
1119
		// OK...
1120
	}
1121
	// An array already?
1122
	elseif (is_array($modSettings['attachmentUploadDir']))
1123
	{
1124
		foreach($modSettings['attachmentUploadDir'] AS $dir)
1125
			if (!empty($dir) && !is_dir($dir))
1126
				$attdr_problem_found = true;
1127
	}
1128
	// Serialized?
1129
	elseif ($ser_test !== false)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ser_test does not seem to be defined for all execution paths leading up to this point.
Loading history...
1130
	{
1131
		if (is_array($ser_test))
1132
		{
1133
			foreach($ser_test AS $dir)
1134
			{
1135
				if (!empty($dir) && !is_dir($dir))
1136
					$attdr_problem_found = true;
1137
			}	
1138
		}
1139
		else
1140
		{
1141
			if (!empty($ser_test) && !is_dir($ser_test))
1142
				$attdr_problem_found = true;
1143
		}
1144
	}
1145
	// Json?  Note the test returns null if encoding was unsuccessful
1146
	elseif ($json_test !== null)
1147
	{
1148
		if (is_array($json_test))
1149
		{
1150
			foreach($json_test AS $dir)
1151
			{
1152
				if (!is_dir($dir))
1153
					$attdr_problem_found = true;
1154
			}	
1155
		}
1156
		else
1157
		{
1158
			if (!is_dir($json_test))
1159
				$attdr_problem_found = true;
1160
		}
1161
	}
1162
	// Unclear, needs a look...
1163
	else
1164
	{
1165
		$attdr_problem_found = true;
1166
	}
1167
1168
	if ($attdr_problem_found)
1169
	{
1170
		if (empty($warnings))
1171
			$warnings = $txt['warning_att_dir_missing'];
1172
		else
1173
			$warnings .= '<br><br>' . $txt['warning_att_dir_missing'];
1174
	}
1175
1176
	// Might be using CLI
1177
	if ($command_line)
1178
	{
1179
		// Change brs to new lines & display
1180
		if (!empty($warnings))
1181
		{
1182
			$warnings = str_replace('<br>', "\n", $warnings);
1183
			echo "\n\n" . $warnings . "\n\n";
1184
		}
1185
	}
1186
	else
1187
	{
1188
		// Might be adding to an existing warning...
1189
		if (!empty($warnings))
1190
		{
1191
			if (empty($upcontext['custom_warning']))
1192
				$upcontext['custom_warning'] = $warnings;
1193
			else
1194
				$upcontext['custom_warning'] .= '<br><br>' . $warnings;
1195
		}
1196
	}
1197
}
1198
1199
// Step 0.5: Does the login work?
1200
function checkLogin()
1201
{
1202
	global $modSettings, $upcontext, $disable_security;
1203
	global $smcFunc, $db_type, $support_js, $sourcedir, $txt;
1204
1205
	// Are we trying to login?
1206
	if (isset($_POST['contbutt']) && (!empty($_POST['user']) || $disable_security))
1207
	{
1208
		// If we've disabled security pick a suitable name!
1209
		if (empty($_POST['user']))
1210
			$_POST['user'] = 'Administrator';
1211
1212
		// Before 2.0 these column names were different!
1213
		$oldDB = false;
1214
		if (empty($db_type) || $db_type == 'mysql')
1215
		{
1216
			$request = $smcFunc['db_query']('', '
1217
				SHOW COLUMNS
1218
				FROM {db_prefix}members
1219
				LIKE {string:member_name}',
1220
				array(
1221
					'member_name' => 'memberName',
1222
					'db_error_skip' => true,
1223
				)
1224
			);
1225
			if ($smcFunc['db_num_rows']($request) != 0)
1226
				$oldDB = true;
1227
			$smcFunc['db_free_result']($request);
1228
		}
1229
1230
		// Get what we believe to be their details.
1231
		if (!$disable_security)
1232
		{
1233
			if ($oldDB)
1234
				$request = $smcFunc['db_query']('', '
1235
					SELECT id_member, memberName AS member_name, passwd, id_group,
1236
						additionalGroups AS additional_groups, lngfile
1237
					FROM {db_prefix}members
1238
					WHERE memberName = {string:member_name}',
1239
					array(
1240
						'member_name' => $_POST['user'],
1241
						'db_error_skip' => true,
1242
					)
1243
				);
1244
			else
1245
				$request = $smcFunc['db_query']('', '
1246
					SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile
1247
					FROM {db_prefix}members
1248
					WHERE member_name = {string:member_name}',
1249
					array(
1250
						'member_name' => $_POST['user'],
1251
						'db_error_skip' => true,
1252
					)
1253
				);
1254
			if ($smcFunc['db_num_rows']($request) != 0)
1255
			{
1256
				list ($id_member, $name, $password, $id_group, $addGroups, $user_language) = $smcFunc['db_fetch_row']($request);
1257
1258
				$groups = explode(',', $addGroups);
1259
				$groups[] = $id_group;
1260
1261
				foreach ($groups as $k => $v)
1262
					$groups[$k] = (int) $v;
1263
1264
				$sha_passwd = sha1(strtolower($name) . $_REQUEST['passwrd']);
1265
1266
				// We don't use "-utf8" anymore...
1267
				$user_language = str_ireplace('-utf8', '', $user_language);
1268
			}
1269
			else
1270
				$upcontext['username_incorrect'] = true;
1271
1272
			$smcFunc['db_free_result']($request);
1273
		}
1274
		$upcontext['username'] = $_POST['user'];
1275
1276
		// Track whether javascript works!
1277
		if (isset($_POST['js_works']))
1278
		{
1279
			if (!empty($_POST['js_works']))
1280
			{
1281
				$upcontext['upgrade_status']['js'] = 1;
1282
				$support_js = 1;
1283
			}
1284
			else
1285
				$support_js = 0;
1286
		}
1287
1288
		// Note down the version we are coming from.
1289
		if (!empty($modSettings['smfVersion']) && empty($upcontext['user']['version']))
1290
			$upcontext['user']['version'] = $modSettings['smfVersion'];
1291
1292
		// Didn't get anywhere?
1293
		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']))
1294
		{
1295
			// MD5?
1296
			$md5pass = md5_hmac($_REQUEST['passwrd'], strtolower($_POST['user']));
1297
			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...
1298
			{
1299
				$upcontext['password_failed'] = true;
1300
				// Disable the hashing this time.
1301
				$upcontext['disable_login_hashing'] = true;
1302
			}
1303
		}
1304
1305
		if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security)
1306
		{
1307
			// Set the password.
1308
			if (!$disable_security)
1309
			{
1310
				// Do we actually have permission?
1311
				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...
1312
				{
1313
					$request = $smcFunc['db_query']('', '
1314
						SELECT permission
1315
						FROM {db_prefix}permissions
1316
						WHERE id_group IN ({array_int:groups})
1317
							AND permission = {string:admin_forum}',
1318
						array(
1319
							'groups' => $groups,
1320
							'admin_forum' => 'admin_forum',
1321
							'db_error_skip' => true,
1322
						)
1323
					);
1324
					if ($smcFunc['db_num_rows']($request) == 0)
1325
						return throw_error($txt['error_not_admin']);
0 ignored issues
show
Bug introduced by
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1325
						return /** @scrutinizer ignore-call */ throw_error($txt['error_not_admin']);
Loading history...
1326
					$smcFunc['db_free_result']($request);
1327
				}
1328
1329
				$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...
1330
				$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...
1331
			}
1332
			else
1333
			{
1334
				$upcontext['user']['id'] = 1;
1335
				$upcontext['user']['name'] = 'Administrator';
1336
			}
1337
1338
			if (!is_callable('random_int'))
1339
				require_once('Sources/random_compat/random.php');
1340
1341
			$upcontext['user']['pass'] = random_int(0, 60000);
1342
			// This basically is used to match the GET variables to Settings.php.
1343
			$upcontext['upgrade_status']['pass'] = $upcontext['user']['pass'];
1344
1345
			// Set the language to that of the user?
1346
			if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($modSettings['theme_dir'] . '/languages/index.' . basename($user_language, '.lng') . '.php'))
1347
			{
1348
				$user_language = basename($user_language, '.lng');
1349
				$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

1349
				$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
Loading history...
1350
				preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1351
1352
				if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1353
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_old'], $user_language, $upcontext['language']);
1354
				elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'))
1355
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_missing'], $user_language, $upcontext['language']);
1356
				else
1357
				{
1358
					// Set this as the new language.
1359
					$upcontext['language'] = $user_language;
1360
					$upcontext['upgrade_status']['lang'] = $upcontext['language'];
1361
1362
					// Include the file.
1363
					load_lang_file();
1364
				}
1365
			}
1366
1367
			// If we're resuming set the step and substep to be correct.
1368
			if (isset($_POST['cont']))
1369
			{
1370
				$upcontext['current_step'] = $upcontext['user']['step'];
1371
				$_GET['substep'] = $upcontext['user']['substep'];
1372
			}
1373
1374
			return true;
1375
		}
1376
	}
1377
1378
	return false;
1379
}
1380
1381
// Step 1: Do the maintenance and backup.
1382
function UpgradeOptions()
1383
{
1384
	global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc, $packagesdir, $tasksdir, $language, $txt, $db_port;
1385
	global $boarddir, $boardurl, $sourcedir, $maintenance, $cachedir, $upcontext, $db_type, $db_server, $image_proxy_enabled;
1386
	global $auth_secret;
1387
1388
	$upcontext['sub_template'] = 'upgrade_options';
1389
	$upcontext['page_title'] = $txt['upgrade_options'];
1390
1391
	db_extend('packages');
1392
	$upcontext['karma_installed'] = array('good' => false, 'bad' => false);
1393
	$member_columns = $smcFunc['db_list_columns']('{db_prefix}members');
1394
1395
	$upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns);
1396
	$upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns);
1397
1398
	$upcontext['migrate_settings_recommended'] = empty($modSettings['smfVersion']) || version_compare(strtolower($modSettings['smfVersion']), substr(SMF_VERSION, 0, strpos(SMF_VERSION, '.') + 1 + strspn(SMF_VERSION, '1234567890', strpos(SMF_VERSION, '.') + 1)) . ' foo', '<');
1399
1400
	unset($member_columns);
1401
1402
	// If we've not submitted then we're done.
1403
	if (empty($_POST['upcont']))
1404
		return false;
1405
1406
	// We cannot execute this step in strict mode - strict mode data fixes are not applied yet
1407
	setSqlMode(false);
1408
1409
	// Firstly, if they're enabling SM stat collection just do it.
1410
	if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']))
1411
	{
1412
		$upcontext['allow_sm_stats'] = true;
1413
1414
		// Don't register if we still have a key.
1415
		if (empty($modSettings['sm_stats_key']))
1416
		{
1417
			// Attempt to register the site etc.
1418
			$fp = @fsockopen('www.simplemachines.org', 443, $errno, $errstr);
1419
			if (!$fp)
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1420
				$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1421
			if ($fp)
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1422
			{
1423
				$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1424
				$out .= 'Host: www.simplemachines.org' . "\r\n";
1425
				$out .= 'Connection: Close' . "\r\n\r\n";
1426
				fwrite($fp, $out);
1427
1428
				$return_data = '';
1429
				while (!feof($fp))
1430
					$return_data .= fgets($fp, 128);
1431
1432
				fclose($fp);
1433
1434
				// Get the unique site ID.
1435
				preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1436
1437
				if (!empty($ID[1]))
1438
					$smcFunc['db_insert']('replace',
1439
						$db_prefix . 'settings',
1440
						array('variable' => 'string', 'value' => 'string'),
1441
						array(
1442
							array('sm_stats_key', $ID[1]),
1443
							array('enable_sm_stats', 1),
1444
						),
1445
						array('variable')
1446
					);
1447
			}
1448
		}
1449
		else
1450
		{
1451
			$smcFunc['db_insert']('replace',
1452
				$db_prefix . 'settings',
1453
				array('variable' => 'string', 'value' => 'string'),
1454
				array('enable_sm_stats', 1),
1455
				array('variable')
1456
			);
1457
		}
1458
	}
1459
	// Don't remove stat collection unless we unchecked the box for real, not from the loop.
1460
	elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats']))
1461
		$smcFunc['db_query']('', '
1462
			DELETE FROM {db_prefix}settings
1463
			WHERE variable = {string:enable_sm_stats}',
1464
			array(
1465
				'enable_sm_stats' => 'enable_sm_stats',
1466
				'db_error_skip' => true,
1467
			)
1468
		);
1469
1470
	// Deleting old karma stuff?
1471
	$_SESSION['delete_karma'] = !empty($_POST['delete_karma']);
1472
1473
	// Emptying the error log?
1474
	$_SESSION['empty_error'] = !empty($_POST['empty_error']);
1475
1476
	// Reprocessing attachments?
1477
	$_SESSION['reprocess_attachments'] = !empty($_POST['reprocess_attachments']);
1478
1479
	$changes = array();
1480
1481
	// Add proxy settings.
1482
	if (!isset($GLOBALS['image_proxy_secret']) || $GLOBALS['image_proxy_secret'] == 'smfisawesome')
1483
		$changes['image_proxy_secret'] = substr(sha1(mt_rand()), 0, 20);
1484
	if (!isset($GLOBALS['image_proxy_maxsize']))
1485
		$changes['image_proxy_maxsize'] = 5190;
1486
	if (!isset($GLOBALS['image_proxy_enabled']))
1487
		$changes['image_proxy_enabled'] = false;
1488
1489
	// If $boardurl reflects https, set force_ssl
1490
	if (!function_exists('cache_put_data'))
1491
		require_once($sourcedir . '/Load.php');
1492
	if (stripos($boardurl, 'https://') !== false && !isset($modSettings['force_ssl']))
1493
		updateSettings(array('force_ssl' => '1'));
1494
1495
	// If we're overriding the language follow it through.
1496
	if (isset($upcontext['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['lang'] . '.php'))
1497
		$changes['language'] = $upcontext['lang'];
1498
1499
	if (!empty($_POST['maint']))
1500
	{
1501
		$changes['maintenance'] = 2;
1502
		// Remember what it was...
1503
		$upcontext['user']['main'] = $maintenance;
1504
1505
		if (!empty($_POST['maintitle']))
1506
		{
1507
			$changes['mtitle'] = $_POST['maintitle'];
1508
			$changes['mmessage'] = $_POST['mainmessage'];
1509
		}
1510
		else
1511
		{
1512
			$changes['mtitle'] = $txt['mtitle'];
1513
			$changes['mmessage'] = $txt['mmessage'];
1514
		}
1515
	}
1516
1517
	if ($command_line)
1518
		echo ' * Updating Settings.php...';
1519
1520
	// Fix some old paths.
1521
	if (substr($boarddir, 0, 1) == '.')
1522
		$changes['boarddir'] = fixRelativePath($boarddir);
1523
1524
	if (substr($sourcedir, 0, 1) == '.')
1525
		$changes['sourcedir'] = fixRelativePath($sourcedir);
1526
1527
	if (empty($cachedir) || substr($cachedir, 0, 1) == '.')
1528
		$changes['cachedir'] = fixRelativePath($boarddir) . '/cache';
1529
1530
	// Migrate cache settings.
1531
	// Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled.
1532
	if (!isset($GLOBALS['cache_enable']))
1533
		$changes += array(
1534
			'cache_accelerator' => upgradeCacheSettings(),
0 ignored issues
show
Bug introduced by
The function upgradeCacheSettings was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1534
			'cache_accelerator' => /** @scrutinizer ignore-call */ upgradeCacheSettings(),
Loading history...
1535
			'cache_enable' => !empty($modSettings['cache_enable']) ? $modSettings['cache_enable'] : 0,
1536
			'cache_memcached' => !empty($modSettings['cache_memcached']) ? $modSettings['cache_memcached'] : '',
1537
		);
1538
1539
	// If they have a "host:port" setup for the host, split that into separate values
1540
	// You should never have a : in the hostname if you're not on MySQL, but better safe than sorry
1541
	if (strpos($db_server, ':') !== false && $db_type == 'mysql')
1542
	{
1543
		list ($db_server, $db_port) = explode(':', $db_server);
1544
1545
		$changes['db_server'] = $db_server;
1546
1547
		// Only set this if we're not using the default port
1548
		if ($db_port != ini_get('mysqli.default_port'))
1549
			$changes['db_port'] = (int) $db_port;
1550
	}
1551
1552
	// If db_port is set and is the same as the default, set it to 0.
1553
	if (!empty($db_port))
1554
	{
1555
		if ($db_type == 'mysql' && $db_port == ini_get('mysqli.default_port'))
1556
			$changes['db_port'] = 0;
1557
1558
		elseif ($db_type == 'postgresql' && $db_port == 5432)
1559
			$changes['db_port'] = 0;
1560
	}
1561
1562
	// Maybe we haven't had this option yet?
1563
	if (empty($packagesdir))
1564
		$changes['packagesdir'] = fixRelativePath($boarddir) . '/Packages';
1565
1566
	// Add support for $tasksdir var.
1567
	if (empty($tasksdir))
1568
		$changes['tasksdir'] = fixRelativePath($sourcedir) . '/tasks';
1569
1570
	// Make sure we fix the language as well.
1571
	if (stristr($language, '-utf8'))
1572
		$changes['language'] = str_ireplace('-utf8', '', $language);
1573
1574
	// @todo Maybe change the cookie name if going to 1.1, too?
1575
1576
	// Ensure this doesn't get lost in translation.
1577
	$changes['upgradeData'] = base64_encode(json_encode($upcontext['user']));
1578
1579
	// Update Settings.php with the new settings, and rebuild if they selected that option.
1580
	require_once($sourcedir . '/Subs.php');
1581
	require_once($sourcedir . '/Subs-Admin.php');
1582
	$res = updateSettingsFile($changes, false, !empty($_POST['migrateSettings']));
1583
1584
	if ($command_line && $res)
1585
		echo ' Successful.' . "\n";
1586
	elseif ($command_line && !$res)
1587
	{
1588
		echo ' FAILURE.' . "\n";
1589
		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...
1590
	}
1591
1592
	// Are we doing debug?
1593
	if (isset($_POST['debug']))
1594
	{
1595
		$upcontext['upgrade_status']['debug'] = true;
1596
		$is_debug = true;
1597
	}
1598
1599
	// If we're not backing up then jump one.
1600
	if (empty($_POST['backup']))
1601
		$upcontext['current_step']++;
1602
1603
	// If we've got here then let's proceed to the next step!
1604
	return true;
1605
}
1606
1607
// Backup the database - why not...
1608
function BackupDatabase()
1609
{
1610
	global $upcontext, $db_prefix, $command_line, $support_js, $file_steps, $smcFunc, $txt;
1611
1612
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database';
1613
	$upcontext['page_title'] = $txt['backup_database'];
1614
1615
	// Done it already - js wise?
1616
	if (!empty($_POST['backup_done']))
1617
		return true;
1618
1619
	// We cannot execute this step in strict mode - strict mode data fixes are not applied yet
1620
	setSqlMode(false);
1621
1622
	// Some useful stuff here.
1623
	db_extend();
1624
1625
	// Might need this as well
1626
	db_extend('packages');
1627
1628
	// Get all the table names.
1629
	$filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%';
1630
	$db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false;
1631
	$tables = $smcFunc['db_list_tables']($db, $filter);
1632
1633
	$table_names = array();
1634
	foreach ($tables as $table)
1635
		if (substr($table, 0, 7) !== 'backup_')
1636
			$table_names[] = $table;
1637
1638
	$upcontext['table_count'] = count($table_names);
1639
	$upcontext['cur_table_num'] = $_GET['substep'];
1640
	$upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]);
1641
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1642
	// For non-java auto submit...
1643
	$file_steps = $upcontext['table_count'];
1644
1645
	// What ones have we already done?
1646
	foreach ($table_names as $id => $table)
1647
		if ($id < $_GET['substep'])
1648
			$upcontext['previous_tables'][] = $table;
1649
1650
	if ($command_line)
1651
		echo 'Backing Up Tables.';
1652
1653
	// If we don't support javascript we backup here.
1654
	if (!$support_js || isset($_GET['xml']))
1655
	{
1656
		// Backup each table!
1657
		for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++)
1658
		{
1659
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep]));
1660
			$upcontext['cur_table_num'] = $substep + 1;
1661
1662
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1663
1664
			// Do we need to pause?
1665
			nextSubstep($substep);
1666
1667
			backupTable($table_names[$substep]);
1668
1669
			// If this is XML to keep it nice for the user do one table at a time anyway!
1670
			if (isset($_GET['xml']))
1671
				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...
1672
		}
1673
1674
		if ($command_line)
1675
		{
1676
			echo "\n" . ' Successful.\'' . "\n";
1677
			flush();
1678
		}
1679
		$upcontext['step_progress'] = 100;
1680
1681
		$_GET['substep'] = 0;
1682
		// Make sure we move on!
1683
		return true;
1684
	}
1685
1686
	// Either way next place to post will be database changes!
1687
	$_GET['substep'] = 0;
1688
	return false;
1689
}
1690
1691
// Backup one table...
1692
function backupTable($table)
1693
{
1694
	global $command_line, $db_prefix, $smcFunc;
1695
1696
	if ($command_line)
1697
	{
1698
		echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...';
1699
		flush();
1700
	}
1701
1702
	$smcFunc['db_backup_table']($table, 'backup_' . $table);
1703
1704
	if ($command_line)
1705
		echo ' done.';
1706
}
1707
1708
// Step 2: Everything.
1709
function DatabaseChanges()
1710
{
1711
	global $db_prefix, $modSettings, $smcFunc, $txt;
1712
	global $upcontext, $support_js, $db_type, $boarddir;
1713
1714
	// Have we just completed this?
1715
	if (!empty($_POST['database_done']))
1716
		return true;
1717
1718
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes';
1719
	$upcontext['page_title'] = $txt['database_changes'];
1720
1721
	$upcontext['delete_karma'] = !empty($_SESSION['delete_karma']);
1722
	$upcontext['empty_error'] = !empty($_SESSION['empty_error']);
1723
	$upcontext['reprocess_attachments'] = !empty($_SESSION['reprocess_attachments']);
1724
1725
	// All possible files.
1726
	// Name, < version, insert_on_complete
1727
	// Last entry in array indicates whether to use sql_mode of STRICT or not.
1728
	$files = array(
1729
		array('upgrade_1-0.sql', '1.1', '1.1 RC0', false),
1730
		array('upgrade_1-1.sql', '2.0', '2.0 a', false),
1731
		array('upgrade_2-0_' . $db_type . '.sql', '2.1', '2.1 dev0', false),
1732
		array('upgrade_2-1_' . $db_type . '.sql', '3.0', SMF_VERSION, true),
1733
	);
1734
1735
	// How many files are there in total?
1736
	if (isset($_GET['filecount']))
1737
		$upcontext['file_count'] = (int) $_GET['filecount'];
1738
	else
1739
	{
1740
		$upcontext['file_count'] = 0;
1741
		foreach ($files as $file)
1742
		{
1743
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1744
				$upcontext['file_count']++;
1745
		}
1746
	}
1747
1748
	// Do each file!
1749
	$did_not_do = count($files) - $upcontext['file_count'];
1750
	$upcontext['step_progress'] = 0;
1751
	$upcontext['cur_file_num'] = 0;
1752
	foreach ($files as $file)
1753
	{
1754
		if ($did_not_do)
1755
			$did_not_do--;
1756
		else
1757
		{
1758
			$upcontext['cur_file_num']++;
1759
			$upcontext['cur_file_name'] = $file[0];
1760
			// Do we actually need to do this still?
1761
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1762
			{
1763
				// Use STRICT mode on more recent steps
1764
				setSqlMode($file[3]);
1765
1766
				// Reload modSettings to capture any adds/updates made along the way
1767
				$request = $smcFunc['db_query']('', '
1768
					SELECT variable, value
1769
					FROM {db_prefix}settings',
1770
					array(
1771
						'db_error_skip' => true,
1772
					)
1773
				);
1774
1775
				$modSettings = array();
1776
				while ($row = $smcFunc['db_fetch_assoc']($request))
1777
					$modSettings[$row['variable']] = $row['value'];
1778
1779
				$smcFunc['db_free_result']($request);
1780
1781
				// Some theme settings are in $modSettings
1782
				// Note we still might be doing yabbse (no smf ver)
1783
				if (isset($modSettings['smfVersion']))
1784
				{
1785
					$request = $smcFunc['db_query']('', '
1786
						SELECT variable, value
1787
						FROM {db_prefix}themes
1788
						WHERE id_theme = {int:id_theme}
1789
							AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
1790
						array(
1791
							'id_theme' => 1,
1792
							'theme_url' => 'theme_url',
1793
							'theme_dir' => 'theme_dir',
1794
							'images_url' => 'images_url',
1795
							'db_error_skip' => true,
1796
						)
1797
					);
1798
1799
					while ($row = $smcFunc['db_fetch_assoc']($request))
1800
						$modSettings[$row['variable']] = $row['value'];
1801
1802
					$smcFunc['db_free_result']($request);
1803
				}
1804
1805
				if (!isset($modSettings['theme_url']))
1806
				{
1807
					$modSettings['theme_dir'] = $boarddir . '/Themes/default';
1808
					$modSettings['theme_url'] = 'Themes/default';
1809
					$modSettings['images_url'] = 'Themes/default/images';
1810
				}
1811
1812
				// Now process the file...
1813
				$nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]);
1814
				if ($nextFile)
1815
				{
1816
					// Only update the version of this if complete.
1817
					$smcFunc['db_insert']('replace',
1818
						$db_prefix . 'settings',
1819
						array('variable' => 'string', 'value' => 'string'),
1820
						array('smfVersion', $file[2]),
1821
						array('variable')
1822
					);
1823
1824
					$modSettings['smfVersion'] = $file[2];
1825
				}
1826
1827
				// If this is XML we only do this stuff once.
1828
				if (isset($_GET['xml']))
1829
				{
1830
					// Flag to move on to the next.
1831
					$upcontext['completed_step'] = true;
1832
					// Did we complete the whole file?
1833
					if ($nextFile)
1834
						$upcontext['current_debug_item_num'] = -1;
1835
					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...
1836
				}
1837
				elseif ($support_js)
1838
					break;
1839
			}
1840
			// Set the progress bar to be right as if we had - even if we hadn't...
1841
			$upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100;
1842
		}
1843
	}
1844
1845
	$_GET['substep'] = 0;
1846
	// So the template knows we're done.
1847
	if (!$support_js)
1848
	{
1849
		$upcontext['changes_complete'] = true;
1850
1851
		return true;
1852
	}
1853
	return false;
1854
}
1855
1856
// Different versions of the files use different sql_modes
1857
function setSqlMode($strict = true)
1858
{
1859
	global $db_type, $db_connection;
1860
1861
	if ($db_type != 'mysql')
1862
		return;
1863
1864
	if ($strict)
1865
		$mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT';
1866
	else
1867
		$mode = '';
1868
1869
	mysqli_query($db_connection, 'SET SESSION sql_mode = \'' . $mode . '\'');
1870
1871
	return;
1872
}
1873
1874
// Delete the damn thing!
1875
function DeleteUpgrade()
1876
{
1877
	global $command_line, $language, $upcontext, $sourcedir;
1878
	global $user_info, $maintenance, $smcFunc, $db_type, $txt, $settings;
1879
1880
	// Now it's nice to have some of the basic SMF source files.
1881
	if (!isset($_GET['ssi']) && !$command_line)
1882
		redirectLocation('&ssi=1');
1883
1884
	$upcontext['sub_template'] = 'upgrade_complete';
1885
	$upcontext['page_title'] = $txt['upgrade_complete'];
1886
1887
	$endl = $command_line ? "\n" : '<br>' . "\n";
1888
1889
	$changes = array(
1890
		'language' => (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language),
1891
		'db_error_send' => true,
1892
		'upgradeData' => null,
1893
	);
1894
1895
	// Are we in maintenance mode?
1896
	if (isset($upcontext['user']['main']))
1897
	{
1898
		if ($command_line)
1899
			echo ' * ';
1900
		$upcontext['removed_maintenance'] = true;
1901
		$changes['maintenance'] = $upcontext['user']['main'];
1902
	}
1903
	// Otherwise if somehow we are in 2 let's go to 1.
1904
	elseif (!empty($maintenance) && $maintenance == 2)
1905
		$changes['maintenance'] = 1;
1906
1907
	// Wipe this out...
1908
	$upcontext['user'] = array();
1909
1910
	require_once($sourcedir . '/Subs.php');
1911
	require_once($sourcedir . '/Subs-Admin.php');
1912
	updateSettingsFile($changes);
1913
1914
	// Clean any old cache files away.
1915
	upgrade_clean_cache();
0 ignored issues
show
Bug introduced by
The function upgrade_clean_cache was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1915
	/** @scrutinizer ignore-call */ 
1916
 upgrade_clean_cache();
Loading history...
1916
1917
	// Can we delete the file?
1918
	$upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__);
1919
1920
	// Now is the perfect time to fetch the SM files.
1921
	if ($command_line)
1922
		cli_scheduled_fetchSMfiles();
1923
	else
1924
	{
1925
		require_once($sourcedir . '/ScheduledTasks.php');
1926
		scheduled_fetchSMfiles(); // Now go get those files!
1927
		// This is needed in case someone invokes the upgrader using https when upgrading an http forum
1928
		if (httpsOn())
1929
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
1930
	}
1931
1932
	// Log what we've done.
1933
	if (empty($user_info['id']))
1934
		$user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0;
1935
1936
	// Log the action manually, so CLI still works.
1937
	$smcFunc['db_insert']('',
1938
		'{db_prefix}log_actions',
1939
		array(
1940
			'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
1941
			'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
1942
		),
1943
		array(
1944
			time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade',
1945
			0, 0, 0, json_encode(array('version' => SMF_FULL_VERSION, 'member' => $user_info['id'])),
1946
		),
1947
		array('id_action')
1948
	);
1949
	$user_info['id'] = 0;
1950
1951
	if ($command_line)
1952
	{
1953
		echo $endl;
1954
		echo 'Upgrade Complete!', $endl;
1955
		echo 'Please delete this file as soon as possible for security reasons.', $endl;
1956
		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...
1957
	}
1958
1959
	// Make sure it says we're done.
1960
	$upcontext['overall_percent'] = 100;
1961
	if (isset($upcontext['step_progress']))
1962
		unset($upcontext['step_progress']);
1963
1964
	$_GET['substep'] = 0;
1965
	return false;
1966
}
1967
1968
// Just like the built in one, but setup for CLI to not use themes.
1969
function cli_scheduled_fetchSMfiles()
1970
{
1971
	global $sourcedir, $language, $modSettings, $smcFunc;
1972
1973
	if (empty($modSettings['time_format']))
1974
		$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p';
1975
1976
	// What files do we want to get
1977
	$request = $smcFunc['db_query']('', '
1978
		SELECT id_file, filename, path, parameters
1979
		FROM {db_prefix}admin_info_files',
1980
		array(
1981
		)
1982
	);
1983
1984
	$js_files = array();
1985
	while ($row = $smcFunc['db_fetch_assoc']($request))
1986
	{
1987
		$js_files[$row['id_file']] = array(
1988
			'filename' => $row['filename'],
1989
			'path' => $row['path'],
1990
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
1991
		);
1992
	}
1993
	$smcFunc['db_free_result']($request);
1994
1995
	// We're gonna need fetch_web_data() to pull this off.
1996
	require_once($sourcedir . '/Subs.php');
1997
1998
	foreach ($js_files as $ID_FILE => $file)
1999
	{
2000
		// Create the url
2001
		$server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'https://www.simplemachines.org' : '';
2002
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
2003
2004
		// Get the file
2005
		$file_data = fetch_web_data($url);
2006
2007
		// If we got an error - give up - the site might be down.
2008
		if ($file_data === false)
2009
			return throw_error(sprintf('Could not retrieve the file %1$s.', $url));
0 ignored issues
show
Bug introduced by
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2009
			return /** @scrutinizer ignore-call */ throw_error(sprintf('Could not retrieve the file %1$s.', $url));
Loading history...
2010
2011
		// Save the file to the database.
2012
		$smcFunc['db_query']('substring', '
2013
			UPDATE {db_prefix}admin_info_files
2014
			SET data = SUBSTRING({string:file_data}, 1, 65534)
2015
			WHERE id_file = {int:id_file}',
2016
			array(
2017
				'id_file' => $ID_FILE,
2018
				'file_data' => $file_data,
2019
			)
2020
		);
2021
	}
2022
	return true;
2023
}
2024
2025
function convertSettingsToTheme()
2026
{
2027
	global $db_prefix, $modSettings, $smcFunc;
2028
2029
	$values = array(
2030
		'show_latest_member' => @$GLOBALS['showlatestmember'],
2031
		'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'],
2032
		'show_modify' => @$GLOBALS['showmodify'],
2033
		'show_user_images' => @$GLOBALS['showuserpic'],
2034
		'show_blurb' => @$GLOBALS['showusertext'],
2035
		'show_gender' => @$GLOBALS['showgenderimage'],
2036
		'show_newsfader' => @$GLOBALS['shownewsfader'],
2037
		'display_recent_bar' => @$GLOBALS['Show_RecentBar'],
2038
		'show_member_bar' => @$GLOBALS['Show_MemberBar'],
2039
		'linktree_link' => @$GLOBALS['curposlinks'],
2040
		'show_profile_buttons' => @$GLOBALS['profilebutton'],
2041
		'show_mark_read' => @$GLOBALS['showmarkread'],
2042
		'show_board_desc' => @$GLOBALS['ShowBDescrip'],
2043
		'newsfader_time' => @$GLOBALS['fadertime'],
2044
		'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0,
2045
		'enable_news' => @$GLOBALS['enable_news'],
2046
		'linktree_inline' => @$modSettings['enableInlineLinks'],
2047
		'return_to_post' => @$modSettings['returnToPost'],
2048
	);
2049
2050
	$themeData = array();
2051
	foreach ($values as $variable => $value)
2052
	{
2053
		if (!isset($value) || $value === null)
2054
			$value = 0;
2055
2056
		$themeData[] = array(0, 1, $variable, $value);
2057
	}
2058
	if (!empty($themeData))
2059
	{
2060
		$smcFunc['db_insert']('ignore',
2061
			$db_prefix . 'themes',
2062
			array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
2063
			$themeData,
2064
			array('id_member', 'id_theme', 'variable')
2065
		);
2066
	}
2067
}
2068
2069
// This function only works with MySQL but that's fine as it is only used for v1.0.
2070
function convertSettingstoOptions()
2071
{
2072
	global $modSettings, $smcFunc;
2073
2074
	// Format: new_setting -> old_setting_name.
2075
	$values = array(
2076
		'calendar_start_day' => 'cal_startmonday',
2077
		'view_newest_first' => 'viewNewestFirst',
2078
		'view_newest_pm_first' => 'viewNewestFirst',
2079
	);
2080
2081
	foreach ($values as $variable => $value)
2082
	{
2083
		if (empty($modSettings[$value[0]]))
2084
			continue;
2085
2086
		$smcFunc['db_query']('', '
2087
			INSERT IGNORE INTO {db_prefix}themes
2088
				(id_member, id_theme, variable, value)
2089
			SELECT id_member, 1, {string:variable}, {string:value}
2090
			FROM {db_prefix}members',
2091
			array(
2092
				'variable' => $variable,
2093
				'value' => $modSettings[$value[0]],
2094
				'db_error_skip' => true,
2095
			)
2096
		);
2097
2098
		$smcFunc['db_query']('', '
2099
			INSERT IGNORE INTO {db_prefix}themes
2100
				(id_member, id_theme, variable, value)
2101
			VALUES (-1, 1, {string:variable}, {string:value})',
2102
			array(
2103
				'variable' => $variable,
2104
				'value' => $modSettings[$value[0]],
2105
				'db_error_skip' => true,
2106
			)
2107
		);
2108
	}
2109
}
2110
2111
function php_version_check()
2112
{
2113
	return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>=');
2114
}
2115
2116
function db_version_check()
2117
{
2118
	global $db_type, $databases;
2119
2120
	$curver = $databases[$db_type]['version_check']();
2121
	$curver = preg_replace('~\-.+?$~', '', $curver);
2122
2123
	return version_compare($databases[$db_type]['version'], $curver, '<=');
2124
}
2125
2126
function fixRelativePath($path)
2127
{
2128
	global $install_path;
2129
2130
	// Fix the . at the start, clear any duplicate slashes, and fix any trailing slash...
2131
	return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path));
2132
}
2133
2134
function parse_sql($filename)
2135
{
2136
	global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning;
2137
	global $upcontext, $support_js, $is_debug, $db_type, $db_character_set, $smcFunc;
2138
2139
/*
2140
	Failure allowed on:
2141
		- INSERT INTO but not INSERT IGNORE INTO.
2142
		- UPDATE IGNORE but not UPDATE.
2143
		- ALTER TABLE and ALTER IGNORE TABLE.
2144
		- DROP TABLE.
2145
	Yes, I realize that this is a bit confusing... maybe it should be done differently?
2146
2147
	If a comment...
2148
		- begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.)
2149
		- begins with ---# it is a debugging statement, no break - only shown at all in debug.
2150
		- is only ---#, it is "done." and then a break - only shown in debug.
2151
		- begins with ---{ it is a code block terminating at ---}.
2152
2153
	Every block of between "--- ..."s is a step.  Every "---#" section represents a substep.
2154
2155
	Replaces the following variables:
2156
		- {$boarddir}
2157
		- {$boardurl}
2158
		- {$db_prefix}
2159
		- {$db_collation}
2160
*/
2161
2162
	// May want to use extended functionality.
2163
	db_extend();
2164
	db_extend('packages');
2165
2166
	// Our custom error handler - does nothing but does stop public errors from XML!
2167
	// Note that php error suppression - @ - used heavily in the upgrader, calls the error handler
2168
	// but error_reporting() will return 0 as it does so (pre php8).
2169
	// Note error handling in php8+ no longer fails silently on many errors, but error_reporting()
2170
	// will return 4437 (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE)
2171
	// as it does so.
2172
	set_error_handler(
2173
		function($errno, $errstr, $errfile, $errline) use ($support_js)
2174
		{
2175
			if ($support_js)
2176
				return true;
2177
			elseif ((error_reporting() != 0) && (error_reporting() != (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE)))
2178
				echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline;
2179
		}
2180
	);
2181
2182
	// If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8
2183
	// Note it is expected to be in the format: ENGINE=MyISAM{$db_collation};
2184
	if ($db_type == 'mysql')
2185
		$db_collation = ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
2186
	else
2187
		$db_collation = '';
2188
2189
	$endl = $command_line ? "\n" : '<br>' . "\n";
2190
2191
	$lines = file($filename);
2192
2193
	$current_type = 'sql';
2194
	$current_data = '';
2195
	$substep = 0;
2196
	$last_step = '';
2197
2198
	// Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php
2199
	$lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines);
2200
2201
	// Count the total number of steps within this file - for progress.
2202
	$file_steps = substr_count(implode('', $lines), '---#');
2203
	$upcontext['total_items'] = substr_count(implode('', $lines), '--- ');
2204
	$upcontext['debug_items'] = $file_steps;
2205
	$upcontext['current_item_num'] = 0;
2206
	$upcontext['current_item_name'] = '';
2207
	$upcontext['current_debug_item_num'] = 0;
2208
	$upcontext['current_debug_item_name'] = '';
2209
	// This array keeps a record of what we've done in case java is dead...
2210
	$upcontext['actioned_items'] = array();
2211
2212
	$done_something = false;
2213
2214
	foreach ($lines as $line_number => $line)
2215
	{
2216
		$do_current = $substep >= $_GET['substep'];
2217
2218
		// Get rid of any comments in the beginning of the line...
2219
		if (substr(trim($line), 0, 2) === '/*')
2220
			$line = preg_replace('~/\*.+?\*/~', '', $line);
2221
2222
		// Always flush.  Flush, flush, flush.  Flush, flush, flush, flush!  FLUSH!
2223
		if ($is_debug && !$support_js && $command_line)
2224
			flush();
2225
2226
		if (trim($line) === '')
2227
			continue;
2228
2229
		if (trim(substr($line, 0, 3)) === '---')
2230
		{
2231
			$type = substr($line, 3, 1);
2232
2233
			// An error??
2234
			if (trim($current_data) != '' && $type !== '}')
2235
			{
2236
				$upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl;
2237
				if ($command_line)
2238
					echo $upcontext['error_message'];
2239
			}
2240
2241
			if ($type == ' ')
2242
			{
2243
				if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line)
2244
				{
2245
					echo ' Successful.', $endl;
2246
					flush();
2247
				}
2248
2249
				$last_step = htmlspecialchars(rtrim(substr($line, 4)));
2250
				$upcontext['current_item_num']++;
2251
				$upcontext['current_item_name'] = $last_step;
2252
2253
				if ($do_current)
2254
				{
2255
					$upcontext['actioned_items'][] = $last_step;
2256
					if ($command_line)
2257
						echo ' * ';
2258
2259
					// Starting a new main step in our DB changes, so it's time to reset this.
2260
					$upcontext['skip_db_substeps'] = false;
2261
				}
2262
			}
2263
			elseif ($type == '#')
2264
			{
2265
				$upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps;
2266
2267
				$upcontext['current_debug_item_num']++;
2268
				if (trim($line) != '---#')
2269
					$upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4)));
2270
2271
				// Have we already done something?
2272
				if (isset($_GET['xml']) && $done_something)
2273
				{
2274
					restore_error_handler();
2275
					return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false;
2276
				}
2277
2278
				if ($do_current)
2279
				{
2280
					if (trim($line) == '---#' && $command_line)
2281
						echo ' done.', $endl;
2282
					elseif ($command_line)
2283
						echo ' +++ ', rtrim(substr($line, 4));
2284
					elseif (trim($line) != '---#')
2285
					{
2286
						if ($is_debug)
2287
							$upcontext['actioned_items'][] = $upcontext['current_debug_item_name'];
2288
					}
2289
				}
2290
2291
				if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep'])
2292
				{
2293
					if ($command_line)
2294
						echo ' * ';
2295
					else
2296
						$upcontext['actioned_items'][] = $last_step;
2297
				}
2298
2299
				// Small step - only if we're actually doing stuff.
2300
				if ($do_current)
2301
					nextSubstep(++$substep);
2302
				else
2303
					$substep++;
2304
			}
2305
			elseif ($type == '{')
2306
				$current_type = 'code';
2307
			elseif ($type == '}')
2308
			{
2309
				$current_type = 'sql';
2310
2311
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2312
				{
2313
					$current_data = '';
2314
2315
					// Avoid confusion when skipping something we normally would have done
2316
					if ($do_current)
2317
						$done_something = true;
2318
2319
					continue;
2320
				}
2321
2322
				// @todo Update this to a try/catch for PHP 7+, because eval() now throws an exception for parse errors instead of returning false
2323
				if (eval('global $db_prefix, $modSettings, $smcFunc, $txt, $upcontext, $db_name; ' . $current_data) === false)
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
2324
				{
2325
					$upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl;
2326
					if ($command_line)
2327
						echo $upcontext['error_message'];
2328
				}
2329
2330
				// Done with code!
2331
				$current_data = '';
2332
				$done_something = true;
2333
			}
2334
2335
			continue;
2336
		}
2337
2338
		$current_data .= $line;
2339
		if (substr(rtrim($current_data), -1) === ';' && $current_type === 'sql')
2340
		{
2341
			if ((!$support_js || isset($_GET['xml'])))
2342
			{
2343
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2344
				{
2345
					$current_data = '';
2346
2347
					if ($do_current)
2348
						$done_something = true;
2349
2350
					continue;
2351
				}
2352
2353
				// {$sboarddir} is deprecated, but blah blah backward compatibility blah...
2354
				$current_data = strtr(substr(rtrim($current_data), 0, -1), array('{$db_prefix}' => $db_prefix, '{$boarddir}' => $smcFunc['db_escape_string']($boarddir), '{$sboarddir}' => $smcFunc['db_escape_string']($boarddir), '{$boardurl}' => $boardurl, '{$db_collation}' => $db_collation));
2355
2356
				upgrade_query($current_data);
2357
2358
				// @todo This will be how it kinda does it once mysql all stripped out - needed for postgre (etc).
2359
				/*
2360
				$result = $smcFunc['db_query']('', $current_data, false, false);
2361
				// Went wrong?
2362
				if (!$result)
2363
				{
2364
					// Bit of a bodge - do we want the error?
2365
					if (!empty($upcontext['return_error']))
2366
					{
2367
						$upcontext['error_message'] = $smcFunc['db_error']($db_connection);
2368
						return false;
2369
					}
2370
				}*/
2371
				$done_something = true;
2372
			}
2373
			$current_data = '';
2374
		}
2375
		// If this is xml based and we're just getting the item name then that's grand.
2376
		elseif ($support_js && !isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current)
2377
		{
2378
			restore_error_handler();
2379
			return false;
2380
		}
2381
2382
		// Clean up by cleaning any step info.
2383
		$step_progress = array();
2384
		$custom_warning = '';
2385
	}
2386
2387
	// Put back the error handler.
2388
	restore_error_handler();
2389
2390
	if ($command_line)
2391
	{
2392
		echo ' Successful.' . "\n";
2393
		flush();
2394
	}
2395
2396
	$_GET['substep'] = 0;
2397
	return true;
2398
}
2399
2400
function upgrade_query($string, $unbuffered = false)
2401
{
2402
	global $db_connection, $db_server, $db_user, $db_passwd, $db_type;
2403
	global $command_line, $upcontext, $upgradeurl, $modSettings;
2404
	global $db_name, $db_unbuffered, $smcFunc, $txt;
2405
2406
	// Get the query result - working around some SMF specific security - just this once!
2407
	$modSettings['disableQueryCheck'] = true;
2408
	$db_unbuffered = $unbuffered;
2409
	$ignore_insert_error = false;
2410
2411
	$result = $smcFunc['db_query']('', $string, array('security_override' => true, 'db_error_skip' => true));
2412
	$db_unbuffered = false;
2413
2414
	// Failure?!
2415
	if ($result !== false)
2416
		return $result;
2417
2418
	$db_error_message = $smcFunc['db_error']($db_connection);
2419
	// If MySQL we do something more clever.
2420
	if ($db_type == 'mysql')
2421
	{
2422
		$mysqli_errno = mysqli_errno($db_connection);
2423
		$error_query = in_array(substr(trim($string), 0, 11), array('INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR', 'INSERT IGNO'));
2424
2425
		// Error numbers:
2426
		//    1016: Can't open file '....MYI'
2427
		//    1050: Table already exists.
2428
		//    1054: Unknown column name.
2429
		//    1060: Duplicate column name.
2430
		//    1061: Duplicate key name.
2431
		//    1062: Duplicate entry for unique key.
2432
		//    1068: Multiple primary keys.
2433
		//    1072: Key column '%s' doesn't exist in table.
2434
		//    1091: Can't drop key, doesn't exist.
2435
		//    1146: Table doesn't exist.
2436
		//    2013: Lost connection to server during query.
2437
2438
		if ($mysqli_errno == 1016)
2439
		{
2440
			if (preg_match('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1]))
2441
			{
2442
				mysqli_query($db_connection, 'REPAIR TABLE `' . $match[1] . '`');
2443
				$result = mysqli_query($db_connection, $string);
2444
				if ($result !== false)
2445
					return $result;
2446
			}
2447
		}
2448
		elseif ($mysqli_errno == 2013)
2449
		{
2450
			$db_connection = mysqli_connect($db_server, $db_user, $db_passwd);
0 ignored issues
show
Bug introduced by
The call to mysqli_connect() has too few arguments starting with database. ( Ignorable by Annotation )

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

2450
			$db_connection = /** @scrutinizer ignore-call */ mysqli_connect($db_server, $db_user, $db_passwd);

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
2451
			mysqli_select_db($db_connection, $db_name);
2452
			if ($db_connection)
2453
			{
2454
				$result = mysqli_query($db_connection, $string);
2455
				if ($result !== false)
2456
					return $result;
2457
			}
2458
		}
2459
		// Duplicate column name... should be okay ;).
2460
		elseif (in_array($mysqli_errno, array(1060, 1061, 1068, 1091)))
2461
			return false;
2462
		// Duplicate insert... make sure it's the proper type of query ;).
2463
		elseif (in_array($mysqli_errno, array(1054, 1062, 1146)) && $error_query)
2464
			return false;
2465
		// Creating an index on a non-existent column.
2466
		elseif ($mysqli_errno == 1072)
2467
			return false;
2468
		elseif ($mysqli_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE')
2469
			return false;
2470
		// Testing for legacy tables or columns? Needed for 1.0 & 1.1 scripts.
2471
		elseif (in_array($mysqli_errno, array(1054, 1146)) && in_array(substr(trim($string), 0, 7), array('SELECT ', 'SHOW CO')))
2472
			return false;
2473
	}
2474
	// If a table already exists don't go potty.
2475
	else
2476
	{
2477
		if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U')))
2478
		{
2479
			if (strpos($db_error_message, 'exist') !== false)
2480
				return true;
2481
		}
2482
		elseif (strpos(trim($string), 'INSERT ') !== false)
2483
		{
2484
			if (strpos($db_error_message, 'duplicate') !== false || $ignore_insert_error)
2485
				return true;
2486
		}
2487
	}
2488
2489
	// Get the query string so we pass everything.
2490
	$query_string = '';
2491
	foreach ($_GET as $k => $v)
2492
		$query_string .= ';' . $k . '=' . $v;
2493
	if (strlen($query_string) != 0)
2494
		$query_string = '?' . substr($query_string, 1);
2495
2496
	if ($command_line)
2497
	{
2498
		echo 'Unsuccessful!  Database error message:', "\n", $db_error_message, "\n";
2499
		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...
2500
	}
2501
2502
	// Bit of a bodge - do we want the error?
2503
	if (!empty($upcontext['return_error']))
2504
	{
2505
		$upcontext['error_message'] = $db_error_message;
2506
		$upcontext['error_string'] = $string;
2507
		return false;
2508
	}
2509
2510
	// Otherwise we have to display this somewhere appropriate if possible.
2511
	$upcontext['forced_error_message'] = '
2512
			<strong>' . $txt['upgrade_unsuccessful'] . '</strong><br>
2513
2514
			<div style="margin: 2ex;">
2515
				' . $txt['upgrade_thisquery'] . '
2516
				<blockquote><pre>' . nl2br(htmlspecialchars(trim($string))) . ';</pre></blockquote>
2517
2518
				' . $txt['upgrade_causerror'] . '
2519
				<blockquote>' . nl2br(htmlspecialchars($db_error_message)) . '</blockquote>
2520
			</div>
2521
2522
			<form action="' . $upgradeurl . $query_string . '" method="post">
2523
				<input type="submit" value="' . $txt['upgrade_respondtime_clickhere'] . '" class="button">
2524
			</form>
2525
		</div>';
2526
2527
	upgradeExit();
2528
}
2529
2530
// This performs a table alter, but does it unbuffered so the script can time out professionally.
2531
function protected_alter($change, $substep, $is_test = false)
2532
{
2533
	global $db_prefix, $smcFunc;
2534
2535
	db_extend('packages');
2536
2537
	// Firstly, check whether the current index/column exists.
2538
	$found = false;
2539
	if ($change['type'] === 'column')
2540
	{
2541
		$columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true);
2542
		foreach ($columns as $column)
2543
		{
2544
			// Found it?
2545
			if ($column['name'] === $change['name'])
2546
			{
2547
				$found |= true;
2548
				// Do some checks on the data if we have it set.
2549
				if (isset($change['col_type']))
2550
					$found &= $change['col_type'] === $column['type'];
2551
				if (isset($change['null_allowed']))
2552
					$found &= $column['null'] == $change['null_allowed'];
2553
				if (isset($change['default']))
2554
					$found &= $change['default'] === $column['default'];
2555
			}
2556
		}
2557
	}
2558
	elseif ($change['type'] === 'index')
2559
	{
2560
		$request = upgrade_query('
2561
			SHOW INDEX
2562
			FROM ' . $db_prefix . $change['table']);
2563
		if ($request !== false)
2564
		{
2565
			$cur_index = array();
2566
2567
			while ($row = $smcFunc['db_fetch_assoc']($request))
2568
				if ($row['Key_name'] === $change['name'])
2569
					$cur_index[(int) $row['Seq_in_index']] = $row['Column_name'];
2570
2571
			ksort($cur_index, SORT_NUMERIC);
2572
			$found = array_values($cur_index) === $change['target_columns'];
2573
2574
			$smcFunc['db_free_result']($request);
2575
		}
2576
	}
2577
2578
	// If we're trying to add and it's added, we're done.
2579
	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...
2580
		return true;
2581
	// Otherwise if we're removing and it wasn't found we're also done.
2582
	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...
2583
		return true;
2584
	// Otherwise is it just a test?
2585
	elseif ($is_test)
2586
		return false;
2587
2588
	// Not found it yet? Bummer! How about we see if we're currently doing it?
2589
	$running = false;
2590
	$found = false;
2591
	while (1 == 1)
2592
	{
2593
		$request = upgrade_query('
2594
			SHOW FULL PROCESSLIST');
2595
		while ($row = $smcFunc['db_fetch_assoc']($request))
2596
		{
2597
			if (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false)
2598
				$found = true;
2599
		}
2600
2601
		// Can't find it? Then we need to run it fools!
2602
		if (!$found && !$running)
2603
		{
2604
			$smcFunc['db_free_result']($request);
2605
2606
			$success = upgrade_query('
2607
				ALTER TABLE ' . $db_prefix . $change['table'] . '
2608
				' . $change['text'], true) !== false;
2609
2610
			if (!$success)
2611
				return false;
2612
2613
			// Return
2614
			$running = true;
2615
		}
2616
		// What if we've not found it, but we'd ran it already? Must of completed.
2617
		elseif (!$found)
2618
		{
2619
			$smcFunc['db_free_result']($request);
2620
			return true;
2621
		}
2622
2623
		// Pause execution for a sec or three.
2624
		sleep(3);
2625
2626
		// Can never be too well protected.
2627
		nextSubstep($substep);
2628
	}
2629
2630
	// Protect it.
2631
	nextSubstep($substep);
2632
}
2633
2634
/**
2635
 * Alter a text column definition preserving its character set.
2636
 *
2637
 * @param array $change
2638
 * @param int $substep
2639
 */
2640
function textfield_alter($change, $substep)
2641
{
2642
	global $db_prefix, $smcFunc;
2643
2644
	$request = $smcFunc['db_query']('', '
2645
		SHOW FULL COLUMNS
2646
		FROM {db_prefix}' . $change['table'] . '
2647
		LIKE {string:column}',
2648
		array(
2649
			'column' => $change['column'],
2650
			'db_error_skip' => true,
2651
		)
2652
	);
2653
	if ($smcFunc['db_num_rows']($request) === 0)
2654
		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...
2655
	$table_row = $smcFunc['db_fetch_assoc']($request);
2656
	$smcFunc['db_free_result']($request);
2657
2658
	// If something of the current column definition is different, fix it.
2659
	$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']);
2660
2661
	// Columns that previously allowed null, need to be converted first.
2662
	$null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed'];
2663
2664
	// Get the character set that goes with the collation of the column.
2665
	if ($column_fix && !empty($table_row['Collation']))
2666
	{
2667
		$request = $smcFunc['db_query']('', '
2668
			SHOW COLLATION
2669
			LIKE {string:collation}',
2670
			array(
2671
				'collation' => $table_row['Collation'],
2672
				'db_error_skip' => true,
2673
			)
2674
		);
2675
		// No results? Just forget it all together.
2676
		if ($smcFunc['db_num_rows']($request) === 0)
2677
			unset($table_row['Collation']);
2678
		else
2679
			$collation_info = $smcFunc['db_fetch_assoc']($request);
2680
		$smcFunc['db_free_result']($request);
2681
	}
2682
2683
	if ($column_fix)
2684
	{
2685
		// Make sure there are no NULL's left.
2686
		if ($null_fix)
2687
			$smcFunc['db_query']('', '
2688
				UPDATE {db_prefix}' . $change['table'] . '
2689
				SET ' . $change['column'] . ' = {string:default}
2690
				WHERE ' . $change['column'] . ' IS NULL',
2691
				array(
2692
					'default' => isset($change['default']) ? $change['default'] : '',
2693
					'db_error_skip' => true,
2694
				)
2695
			);
2696
2697
		// Do the actual alteration.
2698
		$smcFunc['db_query']('', '
2699
			ALTER TABLE {db_prefix}' . $change['table'] . '
2700
			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}' : ''),
2701
			array(
2702
				'default' => isset($change['default']) ? $change['default'] : '',
2703
				'db_error_skip' => true,
2704
			)
2705
		);
2706
	}
2707
	nextSubstep($substep);
2708
}
2709
2710
// The next substep.
2711
function nextSubstep($substep)
2712
{
2713
	global $start_time, $timeLimitThreshold, $command_line, $custom_warning;
2714
	global $step_progress, $is_debug, $upcontext;
2715
2716
	if ($_GET['substep'] < $substep)
2717
		$_GET['substep'] = $substep;
2718
2719
	if ($command_line)
2720
	{
2721
		if (time() - $start_time > 1 && empty($is_debug))
2722
		{
2723
			echo '.';
2724
			$start_time = time();
2725
		}
2726
		return;
2727
	}
2728
2729
	@set_time_limit(300);
2730
	if (function_exists('apache_reset_timeout'))
2731
		@apache_reset_timeout();
2732
2733
	if (time() - $start_time <= $timeLimitThreshold)
2734
		return;
2735
2736
	// Do we have some custom step progress stuff?
2737
	if (!empty($step_progress))
2738
	{
2739
		$upcontext['substep_progress'] = 0;
2740
		$upcontext['substep_progress_name'] = $step_progress['name'];
2741
		if ($step_progress['current'] > $step_progress['total'])
2742
			$upcontext['substep_progress'] = 99.9;
2743
		else
2744
			$upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100;
2745
2746
		// Make it nicely rounded.
2747
		$upcontext['substep_progress'] = round($upcontext['substep_progress'], 1);
2748
	}
2749
2750
	// If this is XML we just exit right away!
2751
	if (isset($_GET['xml']))
2752
		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...
2753
2754
	// We're going to pause after this!
2755
	$upcontext['pause'] = true;
2756
2757
	$upcontext['query_string'] = '';
2758
	foreach ($_GET as $k => $v)
2759
	{
2760
		if ($k != 'data' && $k != 'substep' && $k != 'step')
2761
			$upcontext['query_string'] .= ';' . $k . '=' . $v;
2762
	}
2763
2764
	// Custom warning?
2765
	if (!empty($custom_warning))
2766
		$upcontext['custom_warning'] = $custom_warning;
2767
2768
	upgradeExit();
2769
}
2770
2771
function cmdStep0()
2772
{
2773
	global $boarddir, $sourcedir, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext;
2774
	global $is_debug, $boardurl, $txt;
2775
	$start_time = time();
2776
2777
	while (ob_get_level() > 0)
2778
		ob_end_clean();
2779
	ob_implicit_flush(1);
2780
2781
	if (!isset($_SERVER['argv']))
2782
		$_SERVER['argv'] = array();
2783
	$_GET['maint'] = 1;
2784
2785
	foreach ($_SERVER['argv'] as $i => $arg)
2786
	{
2787
		if (preg_match('~^--language=(.+)$~', $arg, $match) != 0)
2788
			$upcontext['lang'] = $match[1];
2789
		elseif (preg_match('~^--path=(.+)$~', $arg) != 0)
2790
			continue;
2791
		elseif ($arg == '--no-maintenance')
2792
			$_GET['maint'] = 0;
2793
		elseif ($arg == '--debug')
2794
			$is_debug = true;
2795
		elseif ($arg == '--backup')
2796
			$_POST['backup'] = 1;
2797
		elseif ($arg == '--rebuild-settings')
2798
			$_POST['migrateSettings'] = 1;
2799
		elseif ($arg == '--allow-stats')
2800
			$_POST['stats'] = 1;
2801
		elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted')))
2802
			$_GET['conv'] = 1;
2803
		elseif ($i != 0)
2804
		{
2805
			echo 'SMF Command-line Upgrader
2806
Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]...
2807
2808
	--path=/path/to/SMF     Specify custom SMF root directory.
2809
	--language=LANG         Reset the forum\'s language to LANG.
2810
	--no-maintenance        Don\'t put the forum into maintenance mode.
2811
	--debug                 Output debugging information.
2812
	--backup                Create backups of tables with "backup_" prefix.
2813
	--allow-stats           Allow Simple Machines stat collection
2814
	--rebuild-settings      Rebuild the Settings.php file';
2815
			echo "\n";
2816
			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...
2817
		}
2818
	}
2819
2820
	if (!php_version_check())
2821
		print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
0 ignored issues
show
Bug introduced by
The function print_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2821
		/** @scrutinizer ignore-call */ 
2822
  print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
Loading history...
2822
	if (!db_version_check())
2823
		print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true);
2824
2825
	// Do some checks to make sure they have proper privileges
2826
	db_extend('packages');
2827
2828
	// CREATE
2829
	$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');
2830
2831
	// ALTER
2832
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
2833
2834
	// DROP
2835
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
2836
2837
	// Sorry... we need CREATE, ALTER and DROP
2838
	if (!$create || !$alter || !$drop)
2839
		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);
2840
2841
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
2842
		&& @file_exists($sourcedir . '/QueryString.php')
2843
		&& @file_exists($sourcedir . '/ManageBoards.php');
2844
	if (!$check && !isset($modSettings['smfVersion']))
2845
		print_error('Error: Some files are missing or out-of-date.', true);
2846
2847
	// Do a quick version spot check.
2848
	$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

2848
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
2849
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
2850
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
2851
		print_error('Error: Some files have not yet been updated properly.');
2852
2853
	// Make sure Settings.php is writable.
2854
	quickFileWritable($boarddir . '/Settings.php');
0 ignored issues
show
Bug introduced by
The function quickFileWritable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

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