PageRenderTime 60ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/db_update.php

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