PageRenderTime 34ms CodeModel.GetById 4ms app.highlight 19ms RepoModel.GetById 2ms app.codeStats 0ms

/db_update.php

https://bitbucket.org/delroth/fluxbb-djangofr
PHP | 1692 lines | 1108 code | 390 blank | 194 comment | 234 complexity | 5ac86980940c13c1592f8b897ff8f15d MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2
   3/**
   4 * Copyright (C) 2008-2010 FluxBB
   5 * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
   6 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
   7 */
   8
   9// The FluxBB version this script updates to
  10define('UPDATE_TO', '1.4.2');
  11
  12define('UPDATE_TO_DB_REVISION', 8);
  13define('UPDATE_TO_SI_REVISION', 1);
  14define('UPDATE_TO_PARSER_REVISION', 1);
  15
  16define('MIN_PHP_VERSION', '4.3.0');
  17define('MIN_MYSQL_VERSION', '4.1.2');
  18define('MIN_PGSQL_VERSION', '7.0.0');
  19define('PUN_SEARCH_MIN_WORD', 3);
  20define('PUN_SEARCH_MAX_WORD', 20);
  21
  22// The MySQL connection character set that was used for FluxBB 1.2 - in 99% of cases this should be detected automatically,
  23// but can be overridden using the below constant if required.
  24//define('FORUM_DEFAULT_CHARSET', 'latin1');
  25
  26
  27// The number of items to process per page view (lower this if the update script times out during UTF-8 conversion)
  28define('PER_PAGE', 300);
  29
  30// Don't set to UTF-8 until after we've found out what the default character set is
  31define('FORUM_NO_SET_NAMES', 1);
  32
  33// Make sure we are running at least MIN_PHP_VERSION
  34if (!function_exists('version_compare') || version_compare(PHP_VERSION, MIN_PHP_VERSION, '<'))
  35	exit('You are running PHP version '.PHP_VERSION.'. FluxBB '.UPDATE_TO.' requires at least PHP '.MIN_PHP_VERSION.' to run properly. You must upgrade your PHP installation before you can continue.');
  36
  37define('PUN_ROOT', './');
  38
  39// Attempt to load the configuration file config.php
  40if (file_exists(PUN_ROOT.'config.php'))
  41	include PUN_ROOT.'config.php';
  42
  43// If we have the 1.3-legacy constant defined, define the proper 1.4 constant so we don't get an incorrect "need to install" message
  44if (defined('FORUM'))
  45	define('PUN', FORUM);
  46
  47// If PUN isn't defined, config.php is missing or corrupt or we are outside the root directory
  48if (!defined('PUN'))
  49	exit('This file must be run from the forum root directory.');
  50
  51// Enable debug mode
  52if (!defined('PUN_DEBUG'))
  53	define('PUN_DEBUG', 1);
  54
  55// Load the functions script
  56require PUN_ROOT.'include/functions.php';
  57
  58// Load UTF-8 functions
  59require PUN_ROOT.'include/utf8/utf8.php';
  60
  61// Strip out "bad" UTF-8 characters
  62forum_remove_bad_characters();
  63
  64// Reverse the effect of register_globals
  65forum_unregister_globals();
  66
  67// Turn on full PHP error reporting
  68error_reporting(E_ALL);
  69
  70// Force POSIX locale (to prevent functions such as strtolower() from messing up UTF-8 strings)
  71setlocale(LC_CTYPE, 'C');
  72
  73// Turn off magic_quotes_runtime
  74if (get_magic_quotes_runtime())
  75	set_magic_quotes_runtime(0);
  76
  77// Strip slashes from GET/POST/COOKIE (if magic_quotes_gpc is enabled)
  78if (get_magic_quotes_gpc())
  79{
  80	function stripslashes_array($array)
  81	{
  82		return is_array($array) ? array_map('stripslashes_array', $array) : stripslashes($array);
  83	}
  84
  85	$_GET = stripslashes_array($_GET);
  86	$_POST = stripslashes_array($_POST);
  87	$_COOKIE = stripslashes_array($_COOKIE);
  88	$_REQUEST = stripslashes_array($_REQUEST);
  89}
  90
  91// If a cookie name is not specified in config.php, we use the default (forum_cookie)
  92if (empty($cookie_name))
  93	$cookie_name = 'pun_cookie';
  94
  95// If the cache directory is not specified, we use the default setting
  96if (!defined('FORUM_CACHE_DIR'))
  97	define('FORUM_CACHE_DIR', PUN_ROOT.'cache/');
  98
  99// Turn off PHP time limit
 100@set_time_limit(0);
 101
 102// Define a few commonly used constants
 103define('PUN_UNVERIFIED', 0);
 104define('PUN_ADMIN', 1);
 105define('PUN_MOD', 2);
 106define('PUN_GUEST', 3);
 107define('PUN_MEMBER', 4);
 108
 109// Load DB abstraction layer and try to connect
 110require PUN_ROOT.'include/dblayer/common_db.php';
 111
 112// Check what the default character set is - since 1.2 didn't specify any we will use whatever the default was (usually latin1)
 113$old_connection_charset = defined('FORUM_DEFAULT_CHARSET') ? FORUM_DEFAULT_CHARSET : $db->get_names();
 114
 115// Set the connection to UTF-8 now
 116$db->set_names('utf8');
 117
 118// Check current version
 119$result = $db->query('SELECT conf_value FROM '.$db->prefix.'config WHERE conf_name=\'o_cur_version\'') or error('Unable to fetch version info.', __FILE__, __LINE__, $db->error());
 120$cur_version = $db->result($result);
 121
 122if (version_compare($cur_version, '1.2', '<'))
 123	exit('Version mismatch. The database \''.$db_name.'\' doesn\'t seem to be running a FluxBB database schema supported by this update script.');
 124
 125// Do some DB type specific checks
 126$mysql = false;
 127switch ($db_type)
 128{
 129	case 'mysql':
 130	case 'mysqli':
 131	case 'mysql_innodb':
 132	case 'mysqli_innodb':
 133		$mysql_info = $db->get_version();
 134		if (version_compare($mysql_info['version'], MIN_MYSQL_VERSION, '<'))
 135			error('You are running MySQL version '.$mysql_info['version'].'. FluxBB '.UPDATE_TO.' requires at least MySQL '.MIN_MYSQL_VERSION.' to run properly. You must upgrade your MySQL installation before you can continue.');
 136
 137		$mysql = true;
 138		break;
 139
 140	case 'pgsql':
 141		$pgsql_info = $db->get_version();
 142		if (version_compare($pgsql_info['version'], MIN_PGSQL_VERSION, '<'))
 143			error('You are running PostgreSQL version '.$pgsql_info['version'].'. FluxBB '.UPDATE_TO.' requires at least PostgreSQL '.MIN_PGSQL_VERSION.' to run properly. You must upgrade your PostgreSQL installation before you can continue.');
 144
 145		break;
 146}
 147
 148// Get the forum config
 149$result = $db->query('SELECT * FROM '.$db->prefix.'config') or error('Unable to fetch config.', __FILE__, __LINE__, $db->error());
 150while ($cur_config_item = $db->fetch_row($result))
 151	$pun_config[$cur_config_item[0]] = $cur_config_item[1];
 152
 153// Check the database revision and the current version
 154if (isset($pun_config['o_database_revision']) && $pun_config['o_database_revision'] >= UPDATE_TO_DB_REVISION &&
 155		isset($pun_config['o_searchindex_revision']) && $pun_config['o_searchindex_revision'] >= UPDATE_TO_SI_REVISION &&
 156		isset($pun_config['o_parser_revision']) && $pun_config['o_parser_revision'] >= UPDATE_TO_PARSER_REVISION &&
 157		version_compare($pun_config['o_cur_version'], UPDATE_TO, '>='))
 158	exit('Your database is already as up-to-date as this script can make it.');
 159
 160$default_style = $pun_config['o_default_style'];
 161if (!file_exists(PUN_ROOT.'style/'.$default_style.'.css'))
 162	$default_style = 'Air';
 163
 164// Start a session, used to queue up errors if duplicate users occur when converting from FluxBB v1.2.
 165session_start();
 166
 167if (!isset($_SESSION['dupe_users']))
 168	$_SESSION['dupe_users'] = array();
 169
 170//
 171// Determines whether $str is UTF-8 encoded or not
 172//
 173function seems_utf8($str)
 174{
 175	$str_len = strlen($str);
 176	for ($i = 0; $i < $str_len; ++$i)
 177	{
 178		if (ord($str[$i]) < 0x80) continue; # 0bbbbbbb
 179		else if ((ord($str[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb
 180		else if ((ord($str[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb
 181		else if ((ord($str[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb
 182		else if ((ord($str[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb
 183		else if ((ord($str[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b
 184		else return false; # Does not match any model
 185
 186		for ($j = 0; $j < $n; ++$j) # n bytes matching 10bbbbbb follow ?
 187		{
 188			if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80))
 189				return false;
 190		}
 191	}
 192
 193	return true;
 194}
 195
 196
 197//
 198// Translates the number from a HTML numeric entity into an UTF-8 character
 199//
 200function dcr2utf8($src)
 201{
 202	$dest = '';
 203	if ($src < 0)
 204		return false;
 205	else if ($src <= 0x007f)
 206		$dest .= chr($src);
 207	else if ($src <= 0x07ff)
 208	{
 209		$dest .= chr(0xc0 | ($src >> 6));
 210		$dest .= chr(0x80 | ($src & 0x003f));
 211	}
 212	else if ($src == 0xFEFF)
 213	{
 214		// nop -- zap the BOM
 215	}
 216	else if ($src >= 0xD800 && $src <= 0xDFFF)
 217	{
 218		// found a surrogate
 219		return false;
 220	}
 221	else if ($src <= 0xffff)
 222	{
 223		$dest .= chr(0xe0 | ($src >> 12));
 224		$dest .= chr(0x80 | (($src >> 6) & 0x003f));
 225		$dest .= chr(0x80 | ($src & 0x003f));
 226	}
 227	else if ($src <= 0x10ffff)
 228	{
 229		$dest .= chr(0xf0 | ($src >> 18));
 230		$dest .= chr(0x80 | (($src >> 12) & 0x3f));
 231		$dest .= chr(0x80 | (($src >> 6) & 0x3f));
 232		$dest .= chr(0x80 | ($src & 0x3f));
 233	}
 234	else
 235	{
 236		// out of range
 237		return false;
 238	}
 239
 240	return $dest;
 241}
 242
 243
 244//
 245// Attempts to convert $str from $old_charset to UTF-8. Also converts HTML entities (including numeric entities) to UTF-8 characters
 246//
 247function convert_to_utf8(&$str, $old_charset)
 248{
 249	if ($str === null || $str == '')
 250		return false;
 251
 252	$save = $str;
 253
 254	// Replace literal entities (for non-UTF-8 compliant html_entity_encode)
 255	if (version_compare(PHP_VERSION, '5.0.0', '<') && $old_charset == 'ISO-8859-1' || $old_charset == 'ISO-8859-15')
 256		$str = html_entity_decode($str, ENT_QUOTES, $old_charset);
 257
 258	if ($old_charset != 'UTF-8' && !seems_utf8($str))
 259	{
 260		if (function_exists('iconv'))
 261			$str = iconv($old_charset == 'ISO-8859-1' ? 'WINDOWS-1252' : 'ISO-8859-1', 'UTF-8', $str);
 262		else if (function_exists('mb_convert_encoding'))
 263			$str = mb_convert_encoding($str, 'UTF-8', $old_charset == 'ISO-8859-1' ? 'WINDOWS-1252' : 'ISO-8859-1');
 264		else if ($old_charset == 'ISO-8859-1')
 265			$str = utf8_encode($str);
 266	}
 267
 268	// Replace literal entities (for UTF-8 compliant html_entity_encode)
 269	if (version_compare(PHP_VERSION, '5.0.0', '>='))
 270		$str = html_entity_decode($str, ENT_QUOTES, 'UTF-8');
 271
 272	// Replace numeric entities
 273	$str = preg_replace_callback('/&#([0-9]+);/', 'utf8_callback_1', $str);
 274	$str = preg_replace_callback('/&#x([a-f0-9]+);/i', 'utf8_callback_2', $str);
 275
 276	// Remove "bad" characters
 277	$str = remove_bad_characters($str);
 278
 279	return ($save != $str);
 280}
 281
 282
 283function utf8_callback_1($matches)
 284{
 285	return dcr2utf8($matches[1]);
 286}
 287
 288
 289function utf8_callback_2($matches)
 290{
 291	return dcr2utf8(hexdec($matches[1]));
 292}
 293
 294
 295//
 296// Alter a table to be utf8. MySQL only
 297// Function based on update_convert_table_utf8() from the Drupal project (http://drupal.org/)
 298//
 299function alter_table_utf8($table)
 300{
 301	global $mysql, $db;
 302	static $types;
 303
 304	if (!$mysql)
 305		return;
 306
 307	if (!isset($types))
 308	{
 309		$types = array(
 310			'char'			=> 'binary',
 311			'varchar'		=> 'varbinary',
 312			'tinytext'		=> 'tinyblob',
 313			'mediumtext'	=> 'mediumblob',
 314			'text'			=> 'blob',
 315			'longtext'		=> 'longblob'
 316		);
 317	}
 318
 319	// Set table default charset to utf8
 320	$db->query('ALTER TABLE '.$table.' CHARACTER SET utf8') or error('Unable to set table character set', __FILE__, __LINE__, $db->error());
 321
 322	// Find out which columns need converting and build SQL statements
 323	$result = $db->query('SHOW FULL COLUMNS FROM '.$table) or error('Unable to fetch column information', __FILE__, __LINE__, $db->error());
 324	while ($cur_column = $db->fetch_assoc($result))
 325	{
 326		if ($cur_column['Collation'] === null)
 327			continue;
 328
 329		list($type) = explode('(', $cur_column['Type']);
 330		if (isset($types[$type]) && strpos($cur_column['Collation'], 'utf8') === false)
 331		{
 332			$allow_null = ($cur_column['Null'] == 'YES');
 333			$collate = (substr($cur_column['Collation'], -3) == 'bin') ? 'utf8_bin' : 'utf8_general_ci';
 334
 335			$db->alter_field($table, $cur_column['Field'], preg_replace('/'.$type.'/i', $types[$type], $cur_column['Type']), $allow_null, $cur_column['Default'], null, true) or error('Unable to alter field to binary', __FILE__, __LINE__, $db->error());
 336			$db->alter_field($table, $cur_column['Field'], $cur_column['Type'].' CHARACTER SET utf8 COLLATE '.$collate, $allow_null, $cur_column['Default'], null, true) or error('Unable to alter field to utf8', __FILE__, __LINE__, $db->error());
 337		}
 338	}
 339}
 340
 341//
 342// Safely converts text type columns into utf8
 343// If finished returns true, otherwise returns $end_at
 344//
 345function convert_table_utf8($table, $callback, $old_charset, $key = null, $start_at = null, $error_callback = null)
 346{
 347	global $mysql, $db, $old_connection_charset;
 348
 349	$finished = true;
 350	$end_at = 0;
 351	if ($mysql)
 352	{
 353		// Only set up the tables if we are doing this in 1 go, or its the first go
 354		if ($start_at === null || $start_at == 0)
 355		{
 356			// Drop any temp table that exists, in-case it's left over from a failed update
 357			$db->drop_table($table.'_utf8', true) or error('Unable to drop left over temp table', __FILE__, __LINE__, $db->error());
 358
 359			// Copy the table
 360			$db->query('CREATE TABLE '.$table.'_utf8 LIKE '.$table) or error('Unable to create new table', __FILE__, __LINE__, $db->error());
 361
 362			// Set table default charset to utf8
 363			alter_table_utf8($table.'_utf8');
 364		}
 365
 366		// Change to the old character set so MySQL doesn't attempt to perform conversion on the data from the old table
 367		$db->set_names($old_connection_charset);
 368
 369		// Move & Convert everything
 370		$result = $db->query('SELECT * FROM '.$table.($start_at === null ? '' : ' WHERE '.$key.'>'.$start_at).' ORDER BY '.$key.' ASC'.($start_at === null ? '' : ' LIMIT '.PER_PAGE), false) or error('Unable to select from old table', __FILE__, __LINE__, $db->error());
 371
 372		// Change back to utf8 mode so we can insert it into the new table
 373		$db->set_names('utf8');
 374
 375		while ($cur_item = $db->fetch_assoc($result))
 376		{
 377			$cur_item = call_user_func($callback, $cur_item, $old_charset);
 378
 379			$temp = array();
 380			foreach ($cur_item as $idx => $value)
 381				$temp[$idx] = $value === null ? 'NULL' : '\''.$db->escape($value).'\'';
 382
 383			$db->query('INSERT INTO '.$table.'_utf8('.implode(',', array_keys($temp)).') VALUES ('.implode(',', array_values($temp)).')') or ($error_callback === null ? error('Unable to insert data to new table', __FILE__, __LINE__, $db->error()) : call_user_func($error_callback, $cur_item));
 384
 385			$end_at = $cur_item[$key];
 386		}
 387
 388		// If we aren't doing this all in 1 go and $end_at has a value (i.e. we have processed at least 1 row), figure out if we have more to do or not
 389		if ($start_at !== null && $end_at > 0)
 390		{
 391			$result = $db->query('SELECT 1 FROM '.$table.' WHERE '.$key.'>'.$end_at.' ORDER BY '.$key.' ASC LIMIT 1') or error('Unable to check for next row', __FILE__, __LINE__, $db->error());
 392			$finished = $db->num_rows($result) == 0;
 393		}
 394
 395		// Only swap the tables if we are doing this in 1 go, or its the last go
 396		if ($finished)
 397		{
 398			// Delete old table
 399			$db->drop_table($table, true) or error('Unable to drop old table', __FILE__, __LINE__, $db->error());
 400
 401			// Rename table
 402			$db->query('ALTER TABLE '.$table.'_utf8 RENAME '.$table) or error('Unable to rename new table', __FILE__, __LINE__, $db->error());
 403
 404			return true;
 405		}
 406
 407		return $end_at;
 408	}
 409	else
 410	{
 411		// Convert everything
 412		$result = $db->query('SELECT * FROM '.$table.($start_at === null ? '' : ' WHERE '.$key.'>'.$start_at).' ORDER BY '.$key.' ASC'.($start_at === null ? '' : ' LIMIT '.PER_PAGE)) or error('Unable to select from table', __FILE__, __LINE__, $db->error());
 413		while ($cur_item = $db->fetch_assoc($result))
 414		{
 415			$cur_item = call_user_func($callback, $cur_item, $old_charset);
 416
 417			$temp = array();
 418			foreach ($cur_item as $idx => $value)
 419				$temp[] = $idx.'='.($value === null ? 'NULL' : '\''.$db->escape($value).'\'');
 420
 421			if (!empty($temp))
 422				$db->query('UPDATE '.$table.' SET '.implode(', ', $temp).' WHERE '.$key.'=\''.$db->escape($cur_item[$key]).'\'') or error('Unable to update data', __FILE__, __LINE__, $db->error());
 423
 424			$end_at = $cur_item[$key];
 425		}
 426
 427		if ($start_at !== null && $end_at > 0)
 428		{
 429			$result = $db->query('SELECT 1 FROM '.$table.' WHERE '.$key.'>'.$end_at.' ORDER BY '.$key.' ASC LIMIT 1') or error('Unable to check for next row', __FILE__, __LINE__, $db->error());
 430			if ($db->num_rows($result) == 0)
 431				return true;
 432
 433			return $end_at;
 434		}
 435
 436		return true;
 437	}
 438}
 439
 440
 441header('Content-type: text/html; charset=utf-8');
 442
 443// Empty all output buffers and stop buffering
 444while (@ob_end_clean());
 445
 446
 447$stage = isset($_GET['stage']) ? $_GET['stage'] : '';
 448$old_charset = isset($_GET['req_old_charset']) ? str_replace('ISO8859', 'ISO-8859', strtoupper($_GET['req_old_charset'])) : 'ISO-8859-1';
 449$start_at = isset($_GET['start_at']) ? intval($_GET['start_at']) : 0;
 450$query_str = '';
 451
 452switch ($stage)
 453{
 454	// Show form
 455	case '':
 456
 457?>
 458<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 459
 460<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
 461<head>
 462<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 463<title>FluxBB Database Update</title>
 464<link rel="stylesheet" type="text/css" href="style/<?php echo $default_style ?>.css" />
 465</head>
 466<body>
 467
 468<div id="pundb_update" class="pun">
 469<div class="top-box"><div><!-- Top Corners --></div></div>
 470<div class="punwrap">
 471
 472<div class="blockform">
 473	<h2><span>FluxBB Update</span></h2>
 474	<div class="box">
 475		<form method="get" action="<?php echo pun_htmlspecialchars($_SERVER['REQUEST_URI']) ?>" onsubmit="this.start.disabled=true">
 476		<input type="hidden" name="stage" value="start" />
 477			<div class="inform">
 478				<div class="forminfo">
 479					<p style="font-size: 1.1em">This script will update your forum database. The update procedure might take anything from a second to hours depending on the speed of the server and the size of the forum database. Don't forget to make a backup of the database before continuing.</p>
 480					<p style="font-size: 1.1em">Did you read the update instructions in the documentation? If not, start there.</p>
 481<?php
 482
 483if (strpos($cur_version, '1.2') === 0)
 484{
 485	if (!function_exists('iconv') && !function_exists('mb_convert_encoding'))
 486	{
 487
 488?>
 489					<p style="font-size: 1.1em"><strong>IMPORTANT!</strong> FluxBB has detected that this PHP environment does not have support for the encoding mechanisms required to do UTF-8 conversion from character sets other than ISO-8859-1. What this means is that if the current character set is not ISO-8859-1, FluxBB won't be able to convert your forum database to UTF-8 and you will have to do it manually. Instructions for doing manual charset conversion can be found in the update instructions.</p>
 490<?php
 491
 492	}
 493
 494?>
 495				</div>
 496			</div>
 497			<div class="inform">
 498				<div class="forminfo">
 499					<p style="font-size: 1.1em"><strong>Enable conversion:</strong> When enabled this update script will, after it has made the required structural changes to the database, convert all text in the database from the current character set to UTF-8. This conversion is required if you're upgrading from version 1.2.</p>
 500					<p style="font-size: 1.1em"><strong>Current character set:</strong> If the primary language in your forum is English, you can leave this at the default value. However, if your forum is non-English, you should enter the character set of the primary language pack used in the forum. <i>Getting this wrong can corrupt your database so don't just guess!</i> Note: This is required even if the old database is UTF-8.</p>
 501				</div>
 502				<fieldset>
 503					<legend>Charset conversion</legend>
 504					<div class="infldset">
 505						<div class="rbox">
 506							<label><input type="checkbox" name="convert_charset" value="1" checked="checked" /><strong>Enable conversion</strong> (perform database charset conversion).<br /></label>
 507						</div>
 508						<label>
 509							<strong>Current character set</strong><br />Accept default for English forums otherwise the character set of the primary language pack.<br />
 510							<input type="text" name="req_old_charset" size="12" maxlength="20" value="<?php echo $old_charset ?>" /><br />
 511						</label>
 512					</div>
 513				</fieldset>
 514<?php
 515
 516}
 517else
 518	echo "\t\t\t\t".'</div>'."\n";
 519
 520?>
 521			</div>
 522			<p class="buttons"><input type="submit" name="start" value="Start update" /></p>
 523		</form>
 524	</div>
 525</div>
 526
 527</div>
 528<div class="end-box"><div><!-- Bottom Corners --></div></div>
 529</div>
 530
 531</body>
 532</html>
 533<?php
 534
 535		break;
 536
 537
 538	// Start by updating the database structure
 539	case 'start':
 540		$query_str = '?stage=preparse_posts';
 541
 542		// If we don't need to update the database, skip this stage
 543		if (isset($pun_config['o_database_revision']) && $pun_config['o_database_revision'] >= UPDATE_TO_DB_REVISION)
 544			break;
 545
 546		// Make all email fields VARCHAR(80)
 547		$db->alter_field('bans', 'email', 'VARCHAR(80)', true) or error('Unable to alter email field', __FILE__, __LINE__, $db->error());
 548		$db->alter_field('posts', 'poster_email', 'VARCHAR(80)', true) or error('Unable to alter poster_email field', __FILE__, __LINE__, $db->error());
 549		$db->alter_field('users', 'email', 'VARCHAR(80)', false, '') or error('Unable to alter email field', __FILE__, __LINE__, $db->error());
 550		$db->alter_field('users', 'jabber', 'VARCHAR(80)', true) or error('Unable to alter jabber field', __FILE__, __LINE__, $db->error());
 551		$db->alter_field('users', 'msn', 'VARCHAR(80)', true) or error('Unable to alter msn field', __FILE__, __LINE__, $db->error());
 552		$db->alter_field('users', 'activate_string', 'VARCHAR(80)', true) or error('Unable to alter activate_string field', __FILE__, __LINE__, $db->error());
 553
 554		// Make all IP fields VARCHAR(39) to support IPv6
 555		$db->alter_field('posts', 'poster_ip', 'VARCHAR(39)', true) or error('Unable to alter poster_ip field', __FILE__, __LINE__, $db->error());
 556		$db->alter_field('users', 'registration_ip', 'VARCHAR(39)', false, '0.0.0.0') or error('Unable to alter registration_ip field', __FILE__, __LINE__, $db->error());
 557
 558		// Make the message field MEDIUMTEXT to allow proper conversion of 65535 character posts to UTF-8
 559		$db->alter_field('posts', 'message', 'MEDIUMTEXT', true) or error('Unable to alter message field', __FILE__, __LINE__, $db->error());
 560
 561		// Add the DST option to the users table
 562		$db->add_field('users', 'dst', 'TINYINT(1)', false, 0, 'timezone') or error('Unable to add dst field', __FILE__, __LINE__, $db->error());
 563
 564		// Add the last_post field to the online table
 565		$db->add_field('online', 'last_post', 'INT(10) UNSIGNED', true, null, null) or error('Unable to add last_post field', __FILE__, __LINE__, $db->error());
 566
 567		// Add the last_search field to the online table
 568		$db->add_field('online', 'last_search', 'INT(10) UNSIGNED', true, null, null) or error('Unable to add last_search field', __FILE__, __LINE__, $db->error());
 569
 570		// Add the last_search column to the users table
 571		$db->add_field('users', 'last_search', 'INT(10) UNSIGNED', true, null, 'last_post') or error('Unable to add last_search field', __FILE__, __LINE__, $db->error());
 572
 573		// Drop use_avatar column from users table
 574		$db->drop_field('users', 'use_avatar') or error('Unable to drop use_avatar field', __FILE__, __LINE__, $db->error());
 575
 576		// Drop save_pass column from users table
 577		$db->drop_field('users', 'save_pass') or error('Unable to drop save_pass field', __FILE__, __LINE__, $db->error());
 578
 579		// Drop g_edit_subjects_interval column from groups table
 580		$db->drop_field('groups', 'g_edit_subjects_interval');
 581
 582		// Add database revision number
 583		if (!array_key_exists('o_database_revision', $pun_config))
 584			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_database_revision\', \'0\')') or error('Unable to insert config value \'o_database_revision\'', __FILE__, __LINE__, $db->error());
 585
 586		// Add search index revision number
 587		if (!array_key_exists('o_searchindex_revision', $pun_config))
 588			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_searchindex_revision\', \'0\')') or error('Unable to insert config value \'o_searchindex_revision\'', __FILE__, __LINE__, $db->error());
 589
 590		// Add parser revision number
 591		if (!array_key_exists('o_parser_revision', $pun_config))
 592			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_parser_revision\', \'0\')') or error('Unable to insert config value \'o_parser_revision\'', __FILE__, __LINE__, $db->error());
 593
 594		// Add default email setting option
 595		if (!array_key_exists('o_default_email_setting', $pun_config))
 596			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_default_email_setting\', \'1\')') or error('Unable to insert config value \'o_default_email_setting\'', __FILE__, __LINE__, $db->error());
 597
 598		// Make sure we have o_additional_navlinks (was added in 1.2.1)
 599		if (!array_key_exists('o_additional_navlinks', $pun_config))
 600			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_additional_navlinks\', \'\')') or error('Unable to insert config value \'o_additional_navlinks\'', __FILE__, __LINE__, $db->error());
 601
 602		// Insert new config option o_topic_views
 603		if (!array_key_exists('o_topic_views', $pun_config))
 604			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_topic_views\', \'1\')') or error('Unable to insert config value \'o_topic_views\'', __FILE__, __LINE__, $db->error());
 605
 606		// Insert new config option o_signatures
 607		if (!array_key_exists('o_signatures', $pun_config))
 608			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_signatures\', \'1\')') or error('Unable to insert config value \'o_signatures\'', __FILE__, __LINE__, $db->error());
 609
 610		// Insert new config option o_smtp_ssl
 611		if (!array_key_exists('o_smtp_ssl', $pun_config))
 612			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_smtp_ssl\', \'0\')') or error('Unable to insert config value \'o_smtp_ssl\'', __FILE__, __LINE__, $db->error());
 613
 614		// Insert new config option o_default_dst
 615		if (!array_key_exists('o_default_dst', $pun_config))
 616			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_default_dst\', \'0\')') or error('Unable to insert config value \'o_default_dst\'', __FILE__, __LINE__, $db->error());
 617
 618		// Insert new config option o_quote_depth
 619		if (!array_key_exists('o_quote_depth', $pun_config))
 620			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_quote_depth\', \'3\')') or error('Unable to insert config value \'o_quote_depth\'', __FILE__, __LINE__, $db->error());
 621
 622		// Insert new config option o_feed_type
 623		if (!array_key_exists('o_feed_type', $pun_config))
 624			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_feed_type\', \'2\')') or error('Unable to insert config value \'o_feed_type\'', __FILE__, __LINE__, $db->error());
 625
 626		// Insert config option o_base_url which was removed in 1.3
 627		if (!array_key_exists('o_base_url', $pun_config))
 628		{
 629			// If it isn't in $pun_config['o_base_url'] it should be in $base_url, but just in-case it isn't we can make a guess at it
 630			if (!isset($base_url))
 631			{
 632				// Make an educated guess regarding base_url
 633				$base_url  = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';	// protocol
 634				$base_url .= preg_replace('/:(80|443)$/', '', $_SERVER['HTTP_HOST']);							// host[:port]
 635				$base_url .= str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));							// path
 636			}
 637
 638			if (substr($base_url, -1) == '/')
 639				$base_url = substr($base_url, 0, -1);
 640
 641			$db->query('INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES (\'o_base_url\', \''.$db->escape($base_url).'\')') or error('Unable to insert config value \'o_quote_depth\'', __FILE__, __LINE__, $db->error());
 642		}
 643
 644		if (strpos($cur_version, '1.2') === 0)
 645		{
 646			// Groups are almost the same as 1.2:
 647			// unverified:	32000 -> 0
 648
 649			$db->query('UPDATE '.$db->prefix.'users SET group_id = 0 WHERE group_id = 32000') or error('Unable to update unverified users', __FILE__, __LINE__, $db->error());
 650		}
 651		else if (strpos($cur_version, '1.3') === 0)
 652		{
 653			// Groups have changed quite a lot from 1.3:
 654			// unverified:	0 -> 0
 655			// admin:		1 -> 1
 656			// mod:			? -> 2
 657			// guest:		2 -> 3
 658			// member:		? -> 4
 659
 660			$result = $db->query('SELECT MAX(g_id) + 1 FROM '.$db->prefix.'groups') or error('Unable to select temp group ID', __FILE__, __LINE__, $db->error());
 661			$temp_id = $db->result($result);
 662
 663			$result = $db->query('SELECT g_id FROM '.$db->prefix.'groups WHERE g_moderator = 1 AND g_id > 1 LIMIT 1') or error('Unable to select moderator group', __FILE__, __LINE__, $db->error());
 664			if ($db->num_rows($result))
 665				$mod_gid = $db->result($result);
 666			else
 667			{
 668				$db->query('INSERT INTO '.$db->prefix.'groups (g_title, g_user_title, g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood) VALUES('."'Moderators', 'Moderator', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0)") or error('Unable to add group', __FILE__, __LINE__, $db->error());
 669				$mod_gid = $db->insert_id();
 670			}
 671
 672			$member_gid = $pun_config['o_default_user_group'];
 673
 674			// move the mod group to a temp place
 675			$db->query('UPDATE '.$db->prefix.'groups SET g_id = '.$temp_id.' WHERE g_id = '.$mod_gid) or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
 676			$db->query('UPDATE '.$db->prefix.'users SET group_id = '.$temp_id.' WHERE group_id = '.$mod_gid) or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
 677			$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = '.$temp_id.' WHERE group_id = '.$mod_gid) or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
 678			if ($member_gid == $mod_gid) $member_gid = $temp_id;
 679
 680			// move whoever is in 3 to a spare slot
 681			$db->query('UPDATE '.$db->prefix.'groups SET g_id = '.$mod_gid.' WHERE g_id = 3') or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
 682			$db->query('UPDATE '.$db->prefix.'users SET group_id = '.$mod_gid.' WHERE group_id = 3') or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
 683			$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = '.$mod_gid.' WHERE group_id = 3') or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
 684			if ($member_gid == 3) $member_gid = $mod_gid;
 685
 686			// move guest to 3
 687			$db->query('UPDATE '.$db->prefix.'groups SET g_id = 3 WHERE g_id = 2') or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
 688			$db->query('UPDATE '.$db->prefix.'users SET group_id = 3 WHERE group_id = 2') or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
 689			$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = 3 WHERE group_id = 2') or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
 690			if ($member_gid == 2) $member_gid = 3;
 691
 692			// move mod group in temp place to 2
 693			$db->query('UPDATE '.$db->prefix.'groups SET g_id = 2 WHERE g_id = '.$temp_id) or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
 694			$db->query('UPDATE '.$db->prefix.'users SET group_id = 2 WHERE group_id = '.$temp_id) or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
 695			$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = 2 WHERE group_id = '.$temp_id) or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
 696			if ($member_gid == $temp_id) $member_gid = 2;
 697
 698			// Only move stuff around if it isn't already in the right place
 699			if ($member_gid != $mod_gid || $member_gid != 4)
 700			{
 701				// move members to temp place
 702				$db->query('UPDATE '.$db->prefix.'groups SET g_id = '.$temp_id.' WHERE g_id = '.$member_gid) or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
 703				$db->query('UPDATE '.$db->prefix.'users SET group_id = '.$temp_id.' WHERE group_id = '.$member_gid) or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
 704				$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = '.$temp_id.' WHERE group_id = '.$member_gid) or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
 705
 706				// move whoever is in 4 to members place
 707				$db->query('UPDATE '.$db->prefix.'groups SET g_id = '.$member_gid.' WHERE g_id = 4') or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
 708				$db->query('UPDATE '.$db->prefix.'users SET group_id = '.$member_gid.' WHERE group_id = 4') or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
 709				$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = '.$member_gid.' WHERE group_id = 4') or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
 710
 711				// move members in temp place to 4
 712				$db->query('UPDATE '.$db->prefix.'groups SET g_id = 4 WHERE g_id = '.$temp_id) or error('Unable to update group ID', __FILE__, __LINE__, $db->error());
 713				$db->query('UPDATE '.$db->prefix.'users SET group_id = 4 WHERE group_id = '.$temp_id) or error('Unable to update users group ID', __FILE__, __LINE__, $db->error());
 714				$db->query('UPDATE '.$db->prefix.'forum_perms SET group_id = 4 WHERE group_id = '.$temp_id) or error('Unable to forum_perms group ID', __FILE__, __LINE__, $db->error());
 715			}
 716
 717			$db->query('UPDATE '.$db->prefix.'config SET conf_value=\''.$member_gid.'\' WHERE conf_name=\'o_default_user_group\'') or error('Unable to update default user group ID', __FILE__, __LINE__, $db->error());
 718		}
 719
 720		// Server time zone is now simply the default time zone
 721		if (!array_key_exists('o_default_timezone', $pun_config))
 722			$db->query('UPDATE '.$db->prefix.'config SET conf_name = \'o_default_timezone\' WHERE conf_name = \'o_server_timezone\'') or error('Unable to update time zone config', __FILE__, __LINE__, $db->error());
 723
 724		// Increase visit timeout to 30 minutes (only if it hasn't been changed from the default)
 725		if (!array_key_exists('o_database_revision', $pun_config) && $pun_config['o_timeout_visit'] == '600')
 726			$db->query('UPDATE '.$db->prefix.'config SET conf_value = \'1800\' WHERE conf_name = \'o_timeout_visit\'') or error('Unable to update visit timeout config', __FILE__, __LINE__, $db->error());
 727
 728		// Remove obsolete g_post_polls permission from groups table
 729		$db->drop_field('groups', 'g_post_polls');
 730
 731		// Make room for multiple moderator groups
 732		if (!$db->field_exists('groups', 'g_moderator'))
 733		{
 734			// Add g_moderator column to groups table
 735			$db->add_field('groups', 'g_moderator', 'TINYINT(1)', false, 0, 'g_user_title') or error('Unable to add g_moderator field', __FILE__, __LINE__, $db->error());
 736
 737			// Give the moderator group moderator privileges
 738			$db->query('UPDATE '.$db->prefix.'groups SET g_moderator = 1 WHERE g_id = 2') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 739		}
 740
 741		// Replace obsolete p_mod_edit_users config setting with new per-group permission
 742		if (array_key_exists('p_mod_edit_users', $pun_config))
 743		{
 744			$db->query('DELETE FROM '.$db->prefix.'config WHERE conf_name = \'p_mod_edit_users\'') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 745
 746			$db->add_field('groups', 'g_mod_edit_users', 'TINYINT(1)', false, 0, 'g_moderator') or error('Unable to add g_mod_edit_users field', __FILE__, __LINE__, $db->error());
 747
 748			$db->query('UPDATE '.$db->prefix.'groups SET g_mod_edit_users = '.$pun_config['p_mod_edit_users'].' WHERE g_moderator = 1') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 749		}
 750
 751		// Replace obsolete p_mod_rename_users config setting with new per-group permission
 752		if (array_key_exists('p_mod_rename_users', $pun_config))
 753		{
 754			$db->query('DELETE FROM '.$db->prefix.'config WHERE conf_name = \'p_mod_rename_users\'') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 755
 756			$db->add_field('groups', 'g_mod_rename_users', 'TINYINT(1)', false, 0, 'g_mod_edit_users') or error('Unable to add g_mod_rename_users field', __FILE__, __LINE__, $db->error());
 757
 758			$db->query('UPDATE '.$db->prefix.'groups SET g_mod_rename_users = '.$pun_config['p_mod_rename_users'].' WHERE g_moderator = 1') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 759		}
 760
 761		// Replace obsolete p_mod_change_passwords config setting with new per-group permission
 762		if (array_key_exists('p_mod_change_passwords', $pun_config))
 763		{
 764			$db->query('DELETE FROM '.$db->prefix.'config WHERE conf_name = \'p_mod_change_passwords\'') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 765
 766			$db->add_field('groups', 'g_mod_change_passwords', 'TINYINT(1)', false, 0, 'g_mod_rename_users') or error('Unable to add g_mod_change_passwords field', __FILE__, __LINE__, $db->error());
 767
 768			$db->query('UPDATE '.$db->prefix.'groups SET g_mod_change_passwords = '.$pun_config['p_mod_change_passwords'].' WHERE g_moderator = 1') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 769		}
 770
 771		// Replace obsolete p_mod_ban_users config setting with new per-group permission
 772		if (array_key_exists('p_mod_ban_users', $pun_config))
 773		{
 774			$db->query('DELETE FROM '.$db->prefix.'config WHERE conf_name = \'p_mod_ban_users\'') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 775
 776			$db->add_field('groups', 'g_mod_ban_users', 'TINYINT(1)', false, 0, 'g_mod_change_passwords') or error('Unable to add g_mod_ban_users field', __FILE__, __LINE__, $db->error());
 777
 778			$db->query('UPDATE '.$db->prefix.'groups SET g_mod_ban_users = '.$pun_config['p_mod_ban_users'].' WHERE g_moderator = 1') or error('Unable to update moderator powers', __FILE__, __LINE__, $db->error());
 779		}
 780
 781		// We need to add a unique index to avoid users having multiple rows in the online table
 782		if (!$db->index_exists('online', 'user_id_ident_idx'))
 783		{
 784			$db->truncate_table('online') or error('Unable to clear online table', __FILE__, __LINE__, $db->error());
 785
 786			if ($mysql)
 787				$db->add_index('online', 'user_id_ident_idx', array('user_id', 'ident(25)'), true) or error('Unable to add user_id_ident_idx index', __FILE__, __LINE__, $db->error());
 788			else
 789				$db->add_index('online', 'user_id_ident_idx', array('user_id', 'ident'), true) or error('Unable to add user_id_ident_idx index', __FILE__, __LINE__, $db->error());
 790		}
 791
 792		// Remove the redundant user_id_idx on the online table
 793		$db->drop_index('online', 'user_id_idx') or error('Unable to drop user_id_idx index', __FILE__, __LINE__, $db->error());
 794
 795		// Add an index to ident on the online table
 796		if ($mysql)
 797			$db->add_index('online', 'ident_idx', array('ident(25)')) or error('Unable to add ident_idx index', __FILE__, __LINE__, $db->error());
 798		else
 799			$db->add_index('online', 'ident_idx', array('ident')) or error('Unable to add ident_idx index', __FILE__, __LINE__, $db->error());
 800
 801		// Add an index to logged in the online table
 802		$db->add_index('online', 'logged_idx', array('logged')) or error('Unable to add logged_idx index', __FILE__, __LINE__, $db->error());
 803
 804		// Add an index to last_post in the topics table
 805		$db->add_index('topics', 'last_post_idx', array('last_post')) or error('Unable to add last_post_idx index', __FILE__, __LINE__, $db->error());
 806
 807		// Add an index to username on the bans table
 808		if ($mysql)
 809			$db->add_index('bans', 'username_idx', array('username(25)')) or error('Unable to add username_idx index', __FILE__, __LINE__, $db->error());
 810		else
 811			$db->add_index('bans', 'username_idx', array('username')) or error('Unable to add username_idx index', __FILE__, __LINE__, $db->error());
 812
 813		// Change the username_idx on users to a unique index of max size 25
 814		$db->drop_index('users', 'username_idx') or error('Unable to drop old username_idx index', __FILE__, __LINE__, $db->error());
 815		$field = $mysql ? 'username(25)' : 'username';
 816
 817		// Attempt to add a unique index. If the user doesn't use a transactional database this can fail due to multiple matching usernames in the
 818		// users table. This is bad, but just giving up if it happens is even worse! If it fails just add a regular non-unique index.
 819		if (!$db->add_index('users', 'username_idx', array($field), true))
 820			$db->add_index('users', 'username_idx', array($field)) or error('Unable to add username_idx field', __FILE__, __LINE__, $db->error());
 821
 822		// Add g_view_users field to groups table
 823		$db->add_field('groups', 'g_view_users', 'TINYINT(1)', false, 1, 'g_read_board') or error('Unable to add g_view_users field', __FILE__, __LINE__, $db->error());
 824
 825		// Add the last_email_sent column to the users table and the g_send_email and
 826		// g_email_flood columns to the groups table
 827		$db->add_field('users', 'last_email_sent', 'INT(10) UNSIGNED', true, null, 'last_search') or error('Unable to add last_email_sent field', __FILE__, __LINE__, $db->error());
 828		$db->add_field('groups', 'g_send_email', 'TINYINT(1)', false, 1, 'g_search_users') or error('Unable to add g_send_email field', __FILE__, __LINE__, $db->error());
 829		$db->add_field('groups', 'g_email_flood', 'SMALLINT(6)', false, 60, 'g_search_flood') or error('Unable to add g_email_flood field', __FILE__, __LINE__, $db->error());
 830
 831		// Set non-default g_send_email and g_flood_email values properly
 832		$db->query('UPDATE '.$db->prefix.'groups SET g_send_email = 0 WHERE g_id = 3') or error('Unable to update group email permissions', __FILE__, __LINE__, $db->error());
 833		$db->query('UPDATE '.$db->prefix.'groups SET g_email_flood = 0 WHERE g_id IN (1,2,3)') or error('Unable to update group email permissions', __FILE__, __LINE__, $db->error());
 834
 835		// Add the auto notify/subscription option to the users table
 836		$db->add_field('users', 'auto_notify', 'TINYINT(1)', false, 0, 'notify_with_post') or error('Unable to add auto_notify field', __FILE__, __LINE__, $db->error());
 837
 838		// Add the first_post_id column to the topics table
 839		if (!$db->field_exists('topics', 'first_post_id'))
 840		{
 841			$db->add_field('topics', 'first_post_id', 'INT(10) UNSIGNED', false, 0, 'posted') or error('Unable to add first_post_id field', __FILE__, __LINE__, $db->error());
 842			$db->add_index('topics', 'first_post_id_idx', array('first_post_id')) or error('Unable to add first_post_id_idx index', __FILE__, __LINE__, $db->error());
 843
 844			// Now that we've added the column and indexed it, we need to give it correct data
 845			$result = $db->query('SELECT MIN(id) AS first_post, topic_id FROM '.$db->prefix.'posts GROUP BY topic_id') or error('Unable to fetch first_post_id', __FILE__, __LINE__, $db->error());
 846
 847			while ($cur_post = $db->fetch_assoc($result))
 848				$db->query('UPDATE '.$db->prefix.'topics SET first_post_id = '.$cur_post['first_post'].' WHERE id = '.$cur_post['topic_id']) or error('Unable to update first_post_id', __FILE__, __LINE__, $db->error());
 849		}
 850
 851		// Move any users with the old unverified status to their new group
 852		$db->query('UPDATE '.$db->prefix.'users SET group_id=0 WHERE group_id=32000') or error('Unable to move unverified users', __FILE__, __LINE__, $db->error());
 853
 854		// Add the ban_creator column to the bans table
 855		$db->add_field('bans', 'ban_creator', 'INT(10) UNSIGNED', false, 0) or error('Unable to add ban_creator field', __FILE__, __LINE__, $db->error());
 856
 857		// Add the time/date format settings to the user table
 858		$db->add_field('users', 'time_format', 'TINYINT(1)', false, 0, 'dst') or error('Unable to add time_format field', __FILE__, __LINE__, $db->error());
 859		$db->add_field('users', 'date_format', 'TINYINT(1)', false, 0, 'dst') or error('Unable to add date_format field', __FILE__, __LINE__, $db->error());
 860
 861		// Change the search_data field to mediumtext
 862		$db->alter_field('search_cache', 'search_data', 'MEDIUMTEXT', true) or error('Unable to alter search_data field', __FILE__, __LINE__, $db->error());
 863
 864		// Incase we had the fulltext search extension installed (1.3-legacy), remove it
 865		$db->drop_index('topics', 'subject_idx') or error('Unable to drop subject_idx index', __FILE__, __LINE__, $db->error());
 866		$db->drop_index('posts', 'message_idx') or error('Unable to drop message_idx index', __FILE__, __LINE__, $db->error());
 867		// Incase we had the fulltext search mod installed (1.2), remove it
 868		$db->drop_index('topics', 'subject_fulltext_search') or error('Unable to drop subject_fulltext_search index', __FILE__, __LINE__, $db->error());
 869		$db->drop_index('posts', 'message_fulltext_search') or error('Unable to drop message_fulltext_search index', __FILE__, __LINE__, $db->error());
 870
 871		// If the search_cache table has been dropped by the fulltext search extension, recreate it
 872		if (!$db->table_exists('search_cache'))
 873		{
 874			$schema = array(
 875				'FIELDS'		=> array(
 876					'id'			=> array(
 877						'datatype'		=> 'INT(10) UNSIGNED',
 878						'allow_null'	=> false,
 879						'default'		=> '0'
 880					),
 881					'ident'			=> array(
 882						'datatype'		=> 'VARCHAR(200)',
 883						'allow_null'	=> false,
 884						'default'		=> '\'\''
 885					),
 886					'search_data'	=> array(
 887						'datatype'		=> 'MEDIUMTEXT',
 888						'allow_null'	=> true
 889					)
 890				),
 891				'PRIMARY KEY'	=> array('id'),
 892				'INDEXES'		=> array(
 893					'ident_idx'	=> array('ident')
 894				)
 895			);
 896
 897			if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'mysql_innodb' || $db_type == 'mysqli_innodb')
 898				$schema['INDEXES']['ident_idx'] = array('ident(8)');
 899
 900			$db->create_table('search_cache', $schema);
 901		}
 902
 903		// If the search_matches table has been dropped by the fulltext search extension, recreate it
 904		if (!$db->table_exists('search_matches'))
 905		{
 906			$schema = array(
 907				'FIELDS'		=> array(
 908					'post_id'		=> array(
 909						'datatype'		=> 'INT(10) UNSIGNED',
 910						'allow_null'	=> false,
 911						'default'		=> '0'
 912					),
 913					'word_id'		=> array(
 914						'datatype'		=> 'INT(10) UNSIGNED',
 915						'allow_null'	=> false,
 916						'default'		=> '0'
 917					),
 918					'subject_match'	=> array(
 919						'datatype'		=> 'TINYINT(1)',
 920						'allow_null'	=> false,
 921						'default'		=> '0'
 922					)
 923				),
 924				'INDEXES'		=> array(
 925					'word_id_idx'	=> array('word_id'),
 926					'post_id_idx'	=> array('post_id')
 927				)
 928			);
 929
 930			$db->create_table('search_matches', $schema);
 931		}
 932
 933		// If the search_words table has been dropped by the fulltext search extension, recreate it
 934		if (!$db->table_exists('search_words'))
 935		{
 936			$schema = array(
 937				'FIELDS'		=> array(
 938					'id'			=> array(
 939						'datatype'		=> 'SERIAL',
 940						'allow_null'	=> false
 941					),
 942					'word'			=> array(
 943						'datatype'		=> 'VARCHAR(20)',
 944						'allow_null'	=> false,
 945						'default'		=> '\'\'',
 946						'collation'		=> 'bin'
 947					)
 948				),
 949				'PRIMARY KEY'	=> array('word'),
 950				'INDEXES'		=> array(
 951					'id_idx'	=> array('id')
 952				)
 953			);
 954
 955			if ($db_type == 'sqlite')
 956			{
 957				$schema['PRIMARY KEY'] = array('id');
 958				$schema['UNIQUE KEYS'] = array('word_idx'	=> array('word'));
 959			}
 960
 961			$db->create_table('search_words', $schema);
 962		}
 963
 964		// Change the default style if the old doesn't exist anymore
 965		if ($pun_config['o_default_style'] != $default_style)
 966			$db->query('UPDATE '.$db->prefix.'config SET conf_value = \''.$db->escape($default_style).'\' WHERE conf_name = \'o_default_style\'') or error('Unable to update default style config', __FILE__, __LINE__, $db->error());
 967
 968		// Should we do charset conversion or not?
 969		if (strpos($cur_version, '1.2') === 0 && isset($_GET['convert_charset']))
 970			$query_str = '?stage=conv_bans&req_old_charset='.$old_charset;
 971
 972		break;
 973
 974
 975	// Convert bans
 976	case 'conv_bans':
 977		$query_str = '?stage=conv_categories&req_old_charset='.$old_charset;
 978
 979		function _conv_bans($cur_item, $old_charset)
 980		{
 981			echo 'Converting ban '.$cur_item['id'].' �<br />'."\n";
 982
 983			convert_to_utf8($cur_item['username'], $old_charset);
 984			convert_to_utf8($cur_item['message'], $old_charset);
 985
 986			return $cur_item;
 987		}
 988
 989		$end_at = convert_table_utf8($db->prefix.'bans', '_conv_bans', $old_charset, 'id', $start_at);
 990
 991		if ($end_at !== true)
 992			$query_str = '?stage=conv_bans&req_old_charset='.$old_charset.'&start_at='.$end_at;
 993
 994		break;
 995
 996
 997	// Convert categories
 998	case 'conv_categories':
 999		$query_str = '?stage=conv_censors&req_old_charset='.$old_charset;
1000
1001		echo 'Converting categories �'."<br />\n";
1002
1003		function _conv_categories($cur_item, $old_charset)
1004		{
1005			convert_to_utf8($cur_item['cat_name'], $old_charset);
1006
1007			return $cur_item;
1008		}
1009
1010		convert_table_utf8($db->prefix.'categories', '_conv_categories', $old_charset, 'id');
1011
1012		break;
1013
1014
1015	// Convert censor words
1016	case 'conv_censors':
1017		$query_str = '?stage=conv_config&req_old_charset='.$old_charset;
1018
1019		echo 'Converting censor words �'."<br />\n";
1020
1021		function _conv_censoring($cur_item, $old_charset)
1022		{
1023			convert_to_utf8($cur_item['search_for'], $old_charset);
1024			convert_to_utf8($cur_item['replace_with'], $old_charset);
1025
1026			return $cur_item;
1027		}
1028
1029		convert_table_utf8($db->prefix.'censoring', '_conv_censoring', $old_charset, 'id');
1030
1031		break;
1032
1033
1034	// Convert config
1035	case 'conv_config':
1036		$query_str = '?stage=conv_forums&req_old_charset='.$old_charset;
1037
1038		echo 'Converting configuration �'."<br />\n";
1039
1040		function _conv_config($cur_item, $old_charset)
1041		{
1042			convert_to_utf8($cur_item['conf_value'], $old_charset);
1043
1044			return $cur_item;
1045		}
1046
1047		convert_table_utf8($db->prefix.'config', '_conv_config', $old_charset, 'conf_name');
1048
1049		break;
1050
1051
1052	// Convert forums
1053	case 'conv_forums':
1054		$query_str = '?stage=conv_perms&req_old_charset='.$old_charset;
1055
1056		echo 'Converting forums �'."<br />\n";
1057
1058		function _conv_forums($cur_item, $old_charset)
1059		{
1060			$moderators = ($cur_item['moderators'] != '') ? unserialize($cur_item['moderators']) : array();
1061			$moderators_utf8 = array();
1062			foreach ($moderators as $mod_username => $mod_user_id)
1063			{
1064				convert_to_utf8($mod_username, $old_charset);
1065				$moderators_utf8[$mod_username] = $mod_user_id;
1066			}
1067
1068			convert_to_utf8($cur_item['forum_name'], $old_charset);
1069			convert_to_utf8($cur_item['forum_desc'], $old_charset);
1070
1071			if (!empty($moderators_utf8))
1072				$cur_item['moderators'] = serialize($moderators_utf8);
1073
1074			return $cur_item;
1075		}
1076
1077		convert_table_utf8($db->prefix.'forums', '_conv_forums', $old_charset, 'id');
1078
1079		break;
1080
1081
1082	// Convert forum permissions
1083	case 'conv_perms':
1084		$query_str = '?stage=conv_groups&req_old_charset='.$old_charset;
1085
1086		alter_table_utf8($db->prefix.'forum_perms');
1087
1088		break;
1089
1090
1091	// Convert groups
1092	case 'conv_groups':
1093		$query_str = '?stage=conv_online&req_old_charset='.$old_charset;
1094
1095		echo 'Converting groups �'."<br />\n";
1096
1097		function _conv_groups($cur_item, $old_charset)
1098		{
1099			convert_to_utf8($cur_item['g_title'], $old_charset);
1100			convert_to_utf8($cur_item['g_user_title'], $old_charset);
1101
1102			return $cur_item;
1103		}
1104
1105		convert_table_utf8($db->prefix.'groups', '_conv_groups', $old_charset, 'g_id');
1106
1107		break;
1108
1109
1110	// Convert online
1111	case 'conv_online':
1112		$query_str = '?stage=conv_posts&req_old_charset='.$old_charset;
1113
1114		// Truncate the table
1115		$db->truncate_table('online') or error('Unable to empty online table', __FILE__, __LINE__, $db->error());
1116
1117		alter_table_utf8($db->prefix.'online');
1118
1119		break;
1120
1121
1122	// Convert posts
1123	ca…

Large files files are truncated, but you can click here to view the full file