Completed
Push — release-2.1 ( e55abf...f15ab1 )
by
unknown
08:31
created

other/upgrade.php (2 issues)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

Code
1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines http://www.simplemachines.org
8
 * @copyright 2018 Simple Machines and individual contributors
9
 * @license http://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 Beta 4
12
 */
13
14
// Version information...
15
define('SMF_VERSION', '2.1 Beta 4');
16
define('SMF_LANG_VERSION', '2.1 Beta 4');
17
18
/**
19
 * The minimum required PHP version.
20
 * @var string
21
 */
22
$GLOBALS['required_php_version'] = '5.4.0';
23
24
/**
25
 * A list of supported database systems.
26
 * @var array
27
 */
28
$databases = array(
29
	'mysql' => array(
30
		'name' => 'MySQL',
31
		'version' => '5.0.22',
32
		'version_check' => 'global $db_connection; return min(mysqli_get_server_info($db_connection), mysqli_get_client_info());',
33
		'utf8_support' => true,
34
		'utf8_version' => '5.0.22',
35
		'utf8_version_check' => 'global $db_connection; return mysqli_get_server_info($db_connection);',
36
		'alter_support' => true,
37
	),
38
	'postgresql' => array(
39
		'name' => 'PostgreSQL',
40
		'version' => '9.2',
41
		'version_check' => '$version = pg_version(); return $version[\'client\'];',
42
		'always_has_db' => true,
43
	),
44
);
45
46
/**
47
 * The maximum time a single substep may take, in seconds.
48
 * @var int
49
 */
50
$timeLimitThreshold = 3;
51
52
/**
53
 * The current path to the upgrade.php file.
54
 * @var string
55
 */
56
$upgrade_path = dirname(__FILE__);
57
58
/**
59
 * The URL of the current page.
60
 * @var string
61
 */
62
$upgradeurl = $_SERVER['PHP_SELF'];
63
64
/**
65
 * Flag to disable the required administrator login.
66
 * @var bool
67
 */
68
$disable_security = false;
69
70
/**
71
 * The amount of seconds allowed between logins.
72
 * If the first user to login is inactive for this amount of seconds, a second login is allowed.
73
 * @var int
74
 */
75
$upcontext['inactive_timeout'] = 10;
76
77
// The helper is crucial. Include it first thing.
78
if (!file_exists($upgrade_path . '/upgrade-helper.php'))
79
    die('upgrade-helper.php not found where it was expected: ' . $upgrade_path . '/upgrade-helper.php! Make sure you have uploaded ALL files from the upgrade package. The upgrader cannot continue.');
80
81
require_once($upgrade_path . '/upgrade-helper.php');
82
83
// All the steps in detail.
84
// Number,Name,Function,Progress Weight.
85
$upcontext['steps'] = array(
86
	0 => array(1, 'Login', 'WelcomeLogin', 2),
87
	1 => array(2, 'Upgrade Options', 'UpgradeOptions', 2),
88
	2 => array(3, 'Backup', 'BackupDatabase', 10),
89
	3 => array(4, 'Database Changes', 'DatabaseChanges', 50),
90
	4 => array(5, 'Convert to UTF-8', 'ConvertUtf8', 20),
91
	5 => array(6, 'Convert serialized strings to JSON', 'serialize_to_json', 10),
92
	6 => array(7, 'Delete Upgrade.php', 'DeleteUpgrade', 1),
93
);
94
// Just to remember which one has files in it.
95
$upcontext['database_step'] = 3;
96
@set_time_limit(600);
97
if (!ini_get('safe_mode'))
98
{
99
	ini_set('mysql.connect_timeout', -1);
100
	ini_set('default_socket_timeout', 900);
101
}
102
// Clean the upgrade path if this is from the client.
103
if (!empty($_SERVER['argv']) && php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
104
	for ($i = 1; $i < $_SERVER['argc']; $i++)
105
	{
106
		if (preg_match('~^--path=(.+)$~', $_SERVER['argv'][$i], $match) != 0)
107
			$upgrade_path = substr($match[1], -1) == '/' ? substr($match[1], 0, -1) : $match[1];
108
	}
109
110
// Are we from the client?
111
if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
112
{
113
	$command_line = true;
114
	$disable_security = true;
115
}
116
else
117
	$command_line = false;
118
119
// Load this now just because we can.
120
require_once($upgrade_path . '/Settings.php');
121
122
// We don't use "-utf8" anymore...  Tweak the entry that may have been loaded by Settings.php
123
if (isset($language))
124
	$language = str_ireplace('-utf8', '', $language);
125
126
// Are we logged in?
127
if (isset($upgradeData))
128
{
129
	$upcontext['user'] = json_decode(base64_decode($upgradeData), true);
130
131
	// Check for sensible values.
132 View Code Duplication
	if (empty($upcontext['user']['started']) || $upcontext['user']['started'] < time() - 86400)
133
		$upcontext['user']['started'] = time();
134 View Code Duplication
	if (empty($upcontext['user']['updated']) || $upcontext['user']['updated'] < time() - 86400)
135
		$upcontext['user']['updated'] = 0;
136
137
	$upcontext['started'] = $upcontext['user']['started'];
138
	$upcontext['updated'] = $upcontext['user']['updated'];
139
140
	$is_debug = !empty($upcontext['user']['debug']) ? true : false;
141
}
142
143
// Nothing sensible?
144
if (empty($upcontext['updated']))
145
{
146
	$upcontext['started'] = time();
147
	$upcontext['updated'] = 0;
148
	$upcontext['user'] = array(
149
		'id' => 0,
150
		'name' => 'Guest',
151
		'pass' => 0,
152
		'started' => $upcontext['started'],
153
		'updated' => $upcontext['updated'],
154
	);
155
}
156
157
// Load up some essential data...
158
loadEssentialData();
159
160
// Are we going to be mimic'ing SSI at this point?
161
if (isset($_GET['ssi']))
162
{
163
	require_once($sourcedir . '/Errors.php');
164
	require_once($sourcedir . '/Logging.php');
165
	require_once($sourcedir . '/Load.php');
166
	require_once($sourcedir . '/Security.php');
167
	require_once($sourcedir . '/Subs-Package.php');
168
169
	// SMF isn't started up properly, but loadUserSettings calls our cookies.
170 View Code Duplication
	if (!isset($smcFunc['json_encode']))
171
	{
172
		$smcFunc['json_encode'] = 'json_encode';
173
		$smcFunc['json_decode'] = 'smf_json_decode';
174
	}
175
176
	loadUserSettings();
177
	loadPermissions();
178
}
179
180
// Include our helper functions.
181
require_once($sourcedir . '/Subs.php');
182
require_once($sourcedir . '/LogInOut.php');
183
184
// This only exists if we're on SMF ;)
185
if (isset($modSettings['smfVersion']))
186
{
187
	$request = $smcFunc['db_query']('', '
188
		SELECT variable, value
189
		FROM {db_prefix}themes
190
		WHERE id_theme = {int:id_theme}
191
			AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
192
		array(
193
			'id_theme' => 1,
194
			'theme_url' => 'theme_url',
195
			'theme_dir' => 'theme_dir',
196
			'images_url' => 'images_url',
197
			'db_error_skip' => true,
198
		)
199
	);
200 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
201
		$modSettings[$row['variable']] = $row['value'];
202
	$smcFunc['db_free_result']($request);
203
}
204
205
if (!isset($modSettings['theme_url']))
206
{
207
	$modSettings['theme_dir'] = $boarddir . '/Themes/default';
208
	$modSettings['theme_url'] = 'Themes/default';
209
	$modSettings['images_url'] = 'Themes/default/images';
210
}
211
if (!isset($settings['default_theme_url']))
212
	$settings['default_theme_url'] = $modSettings['theme_url'];
213
if (!isset($settings['default_theme_dir']))
214
	$settings['default_theme_dir'] = $modSettings['theme_dir'];
215
216
$upcontext['is_large_forum'] = (empty($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && !empty($modSettings['totalMessages']) && $modSettings['totalMessages'] > 75000;
217
// Default title...
218
$upcontext['page_title'] = 'Updating Your SMF Installation!';
219
220
// Have we got tracking data - if so use it (It will be clean!)
221
if (isset($_GET['data']))
222
{
223
	global $is_debug;
224
225
	$upcontext['upgrade_status'] = json_decode(base64_decode($_GET['data']), true);
226
	$upcontext['current_step'] = $upcontext['upgrade_status']['curstep'];
227
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
228
	$upcontext['rid'] = $upcontext['upgrade_status']['rid'];
229
	$support_js = $upcontext['upgrade_status']['js'];
230
231
	// Only set this if the upgrader status says so.
232
	if (empty($is_debug))
233
		$is_debug = $upcontext['upgrade_status']['debug'];
234
235
	// Load the language.
236
	if (file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'))
237
		require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php');
238
}
239
// Set the defaults.
240
else
241
{
242
	$upcontext['current_step'] = 0;
243
	$upcontext['rid'] = mt_rand(0, 5000);
244
	$upcontext['upgrade_status'] = array(
245
		'curstep' => 0,
246
		'lang' => isset($_GET['lang']) ? $_GET['lang'] : basename($language, '.lng'),
247
		'rid' => $upcontext['rid'],
248
		'pass' => 0,
249
		'debug' => 0,
250
		'js' => 0,
251
	);
252
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
253
}
254
255
// If this isn't the first stage see whether they are logging in and resuming.
256
if ($upcontext['current_step'] != 0 || !empty($upcontext['user']['step']))
257
	checkLogin();
258
259
if ($command_line)
260
	cmdStep0();
261
262
// Don't error if we're using xml.
263
if (isset($_GET['xml']))
264
	$upcontext['return_error'] = true;
265
266
// Loop through all the steps doing each one as required.
267
$upcontext['overall_percent'] = 0;
268
foreach ($upcontext['steps'] as $num => $step)
269
{
270
	if ($num >= $upcontext['current_step'])
271
	{
272
		// The current weight of this step in terms of overall progress.
273
		$upcontext['step_weight'] = $step[3];
274
		// Make sure we reset the skip button.
275
		$upcontext['skip'] = false;
276
277
		// We cannot proceed if we're not logged in.
278
		if ($num != 0 && !$disable_security && $upcontext['user']['pass'] != $upcontext['upgrade_status']['pass'])
279
		{
280
			$upcontext['steps'][0][2]();
281
			break;
282
		}
283
284
		// Call the step and if it returns false that means pause!
285
		if (function_exists($step[2]) && $step[2]() === false)
286
			break;
287
		elseif (function_exists($step[2])) {
288
			//Start each new step with this unset, so the 'normal' template is called first
289
			unset($_GET['xml']);
290
			//Clear out warnings at the start of each step
291
			unset($upcontext['custom_warning']);
292
			$_GET['substep'] = 0;
293
			$upcontext['current_step']++;
294
		}
295
	}
296
	$upcontext['overall_percent'] += $step[3];
297
}
298
299
upgradeExit();
300
301
// Exit the upgrade script.
302
function upgradeExit($fallThrough = false)
303
{
304
	global $upcontext, $upgradeurl, $sourcedir, $command_line, $is_debug;
305
306
	// Save where we are...
307
	if (!empty($upcontext['current_step']) && !empty($upcontext['user']['id']))
308
	{
309
		$upcontext['user']['step'] = $upcontext['current_step'];
310
		$upcontext['user']['substep'] = $_GET['substep'];
311
		$upcontext['user']['updated'] = time();
312
		$upcontext['debug'] = $is_debug;
313
		$upgradeData = base64_encode(json_encode($upcontext['user']));
314
		require_once($sourcedir . '/Subs-Admin.php');
315
		updateSettingsFile(array('upgradeData' => '"' . $upgradeData . '"'));
316
		updateDbLastError(0);
317
	}
318
319
	// Handle the progress of the step, if any.
320
	if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']]))
321
	{
322
		$upcontext['step_progress'] = round($upcontext['step_progress'], 1);
323
		$upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100);
324
	}
325
	$upcontext['overall_percent'] = (int) $upcontext['overall_percent'];
326
327
	// We usually dump our templates out.
328
	if (!$fallThrough)
329
	{
330
		// This should not happen my dear... HELP ME DEVELOPERS!!
331
		if (!empty($command_line))
332
		{
333
			if (function_exists('debug_print_backtrace'))
334
				debug_print_backtrace();
335
336
			echo "\n" . 'Error: Unexpected call to use the ' . (isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '') . ' template. Please copy and paste all the text above and visit the SMF support forum to tell the Developers that they\'ve made a boo boo; they\'ll get you up and running again.';
337
			flush();
338
			die();
339
		}
340
341
		if (!isset($_GET['xml']))
342
			template_upgrade_above();
343
		else
344
		{
345
			header('content-type: text/xml; charset=UTF-8');
346
			// Sadly we need to retain the $_GET data thanks to the old upgrade scripts.
347
			$upcontext['get_data'] = array();
348
			foreach ($_GET as $k => $v)
349
			{
350
				if (substr($k, 0, 3) != 'amp' && !in_array($k, array('xml', 'substep', 'lang', 'data', 'step', 'filecount')))
351
				{
352
					$upcontext['get_data'][$k] = $v;
353
				}
354
			}
355
			template_xml_above();
356
		}
357
358
		// Call the template.
359
		if (isset($upcontext['sub_template']))
360
		{
361
			$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
362
			$upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&amp;substep=' . $_GET['substep'] . '&amp;data=' . base64_encode(json_encode($upcontext['upgrade_status']));
363
364
			// Custom stuff to pass back?
365
			if (!empty($upcontext['query_string']))
366
				$upcontext['form_url'] .= $upcontext['query_string'];
367
368
			// Call the appropriate subtemplate
369
			if (is_callable('template_' . $upcontext['sub_template']))
370
				call_user_func('template_' . $upcontext['sub_template']);
371
			else
372
				die('Upgrade aborted!  Invalid template: template_' . $upcontext['sub_template']);
373
		}
374
375
		// Was there an error?
376
		if (!empty($upcontext['forced_error_message']))
377
			echo $upcontext['forced_error_message'];
378
379
		// Show the footer.
380
		if (!isset($_GET['xml']))
381
			template_upgrade_below();
382
		else
383
			template_xml_below();
384
	}
385
386
387
	if (!empty($command_line) && $is_debug)
388
	{
389
		$active = time() - $upcontext['started'];
390
		$hours = floor($active / 3600);
391
		$minutes = intval(($active / 60) % 60);
392
		$seconds = intval($active % 60);
393
394
		$totalTime = '';
395
		if ($hours > 0)
396
			$totalTime .= $hours . ' hour' . ($hours > 1 ? 's' : '') . ' ';
397
		if ($minutes > 0)
398
			$totalTime .= $minutes . ' minute' . ($minutes > 1 ? 's' : '') . ' ';
399
		if ($seconds > 0)
400
			$totalTime .= $seconds . ' second' . ($seconds > 1 ? 's' : '') . ' ';
401
402
		if (!empty($totalTime))
403
			echo "\n" . 'Upgrade completed in ' . $totalTime . "\n";
404
	}
405
406
	// Bang - gone!
407
	die();
408
}
409
410
// Used to direct the user to another location.
411
function redirectLocation($location, $addForm = true)
412
{
413
	global $upgradeurl, $upcontext, $command_line;
414
415
	// Command line users can't be redirected.
416
	if ($command_line)
417
		upgradeExit(true);
418
419
	// Are we providing the core info?
420
	if ($addForm)
421
	{
422
		$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
423
		$location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])) . $location;
424
	}
425
426
	while (@ob_end_clean());
427
	header('location: ' . strtr($location, array('&amp;' => '&')));
0 ignored issues
show
Security Response Splitting introduced by
'location: ' . strtr($lo... array('&amp;' => '&')) can contain request data and is used in response header context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_GET, and $location is assigned
    in other/upgrade.php on line 423
  2. $location is passed through strtr()
    in other/upgrade.php on line 427

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
428
429
	// Exit - saving status as we go.
430
	upgradeExit(true);
431
}
432
433
// Load all essential data and connect to the DB as this is pre SSI.php
434
function loadEssentialData()
435
{
436
	global $db_server, $db_user, $db_passwd, $db_name, $db_connection, $db_prefix, $db_character_set, $db_type;
437
	global $modSettings, $sourcedir, $smcFunc;
438
439
	error_reporting(E_ALL);
440
	define('SMF', 1);
441
442
	// Start the session.
443
	if (@ini_get('session.save_handler') == 'user')
444
		@ini_set('session.save_handler', 'files');
445
	@session_start();
446
447
	if (empty($smcFunc))
448
		$smcFunc = array();
449
450
	// We need this for authentication and some upgrade code
451
	require_once($sourcedir . '/Subs-Auth.php');
452
	require_once($sourcedir . '/Class-Package.php');
453
454
	$smcFunc['strtolower'] = 'smf_strtolower';
455
456
	// Initialize everything...
457
	initialize_inputs();
458
459
	// Get the database going!
460
	if (empty($db_type) || $db_type == 'mysqli')
461
	{
462
		$db_type = 'mysql';
463
		// If overriding $db_type, need to set its settings.php entry too
464
		$changes = array();
465
		$changes['db_type'] = '\'mysql\'';
466
		require_once($sourcedir . '/Subs-Admin.php');
467
		updateSettingsFile($changes);
468
	}
469
470
	if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
471
	{
472
		require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
473
474
		// Make the connection...
475
		if (empty($db_connection))
476
			$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, array('non_fatal' => true));
477
		else
478
			// If we've returned here, ping/reconnect to be safe
479
			$smcFunc['db_ping']($db_connection);
480
481
		// Oh dear god!!
482
		if ($db_connection === null)
483
			die('Unable to connect to database - please check username and password are correct in Settings.php');
484
485
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
486
			$smcFunc['db_query']('', '
487
			SET NAMES {string:db_character_set}',
488
			array(
489
				'db_error_skip' => true,
490
				'db_character_set' => $db_character_set,
491
			)
492
		);
493
494
		// Load the modSettings data...
495
		$request = $smcFunc['db_query']('', '
496
			SELECT variable, value
497
			FROM {db_prefix}settings',
498
			array(
499
				'db_error_skip' => true,
500
			)
501
		);
502
		$modSettings = array();
503 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
504
			$modSettings[$row['variable']] = $row['value'];
505
		$smcFunc['db_free_result']($request);
506
	}
507
	else
508
	{
509
		return throw_error('Cannot find ' . $sourcedir . '/Subs-Db-' . $db_type . '.php' . '. Please check you have uploaded all source files and have the correct paths set.');
510
	}
511
512
	require_once($sourcedir . '/Subs.php');
513
514
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
515
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
516
	{
517
		require_once($sourcedir . '/QueryString.php');
518
		cleanRequest();
519
	}
520
521
	if (!isset($_GET['substep']))
522
		$_GET['substep'] = 0;
523
}
524
525
function initialize_inputs()
526
{
527
	global $start_time, $db_type;
528
529
	$start_time = time();
530
531
	umask(0);
532
533
	ob_start();
534
535
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
536
	ignore_user_abort(true);
537
538
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
539
	if (isset($_GET['delete']))
540
	{
541
		@unlink(__FILE__);
542
543
		// And the extra little files ;).
544
		@unlink(dirname(__FILE__) . '/upgrade_1-0.sql');
545
		@unlink(dirname(__FILE__) . '/upgrade_1-1.sql');
546
		@unlink(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
547
		@unlink(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
548
		@unlink(dirname(__FILE__) . '/upgrade-helper.php');
549
550
		$dh = opendir(dirname(__FILE__));
551
		while ($file = readdir($dh))
552
		{
553
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
554
				@unlink(dirname(__FILE__) . '/' . $file);
555
		}
556
		closedir($dh);
557
558
		// Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there.
559
		// 1.1 Sources files not in 2.0+
560
		@unlink(dirname(__FILE__) . '/Sources/ModSettings.php');
561
		// 1.1 Templates that don't exist any more (e.g. renamed)
562
		@unlink(dirname(__FILE__) . '/Themes/default/Combat.template.php');
563
		@unlink(dirname(__FILE__) . '/Themes/default/Modlog.template.php');
564
		// 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder
565
		@unlink(dirname(__FILE__) . '/Themes/default/fader.js');
566
		@unlink(dirname(__FILE__) . '/Themes/default/script.js');
567
		@unlink(dirname(__FILE__) . '/Themes/default/spellcheck.js');
568
		@unlink(dirname(__FILE__) . '/Themes/default/xml_board.js');
569
		@unlink(dirname(__FILE__) . '/Themes/default/xml_topic.js');
570
571
		// 2.0 Sources files not in 2.1+
572
		@unlink(dirname(__FILE__) . '/Sources/DumpDatabase.php');
573
		@unlink(dirname(__FILE__) . '/Sources/LockTopic.php');
574
575
		header('location: http://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png');
0 ignored issues
show
Security Response Splitting introduced by
'location: http://' . (i...fault/images/blank.png' can contain request data and is used in response header context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Fetching key HTTP_HOST from $_SERVER
    in other/upgrade.php on line 575

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

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