PageRenderTime 56ms CodeModel.GetById 2ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 1ms

/php/Sources/ManageMaintenance.php

https://github.com/dekoza/openshift-smf-2.0.7
PHP | 1727 lines | 1253 code | 220 blank | 254 comment | 157 complexity | af545e5cfd6d0930acb13c5f8936648d MD5 | raw file

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

   1<?php
   2
   3/**
   4 * Simple Machines Forum (SMF)
   5 *
   6 * @package SMF
   7 * @author Simple Machines http://www.simplemachines.org
   8 * @copyright 2011 Simple Machines
   9 * @license http://www.simplemachines.org/about/smf/license.php BSD
  10 *
  11 * @version 2.0.7
  12 */
  13
  14if (!defined('SMF'))
  15	die('Hacking attempt...');
  16
  17/* /!!!
  18
  19	void ManageMaintenance()
  20		// !!!
  21
  22	void MaintainDatabase()
  23		// !!!
  24
  25	void MaintainMembers()
  26		// !!!
  27
  28	void MaintainTopics()
  29		// !!!
  30
  31	void MaintainCleanCache()
  32		// !!!
  33
  34	void MaintainFindFixErrors()
  35		// !!!
  36
  37	void MaintainEmptyUnimportantLogs()
  38		// !!!
  39
  40	void ConvertUtf8()
  41		- converts the data and database tables to UTF-8 character set.
  42		- requires the admin_forum permission.
  43		- uses the convert_utf8 sub template of the Admin template.
  44		- only works if UTF-8 is not the global character set.
  45		- supports all character sets used by SMF's language files.
  46		- redirects to ?action=admin;area=maintain after finishing.
  47		- is linked from the maintenance screen (if applicable).
  48		- accessed by ?action=admin;area=maintain;sa=database;activity=convertutf8.
  49
  50	void ConvertEntities()
  51		- converts HTML-entities to UTF-8 characters.
  52		- requires the admin_forum permission.
  53		- uses the convert_entities sub template of the Admin template.
  54		- only works if UTF-8 has been set as database and global character set.
  55		- is divided in steps of 10 seconds.
  56		- is linked from the maintenance screen (if applicable).
  57		- accessed by ?action=admin;area=maintain;sa=database;activity=convertentities.
  58
  59	void OptimizeTables()
  60		- optimizes all tables in the database and lists how much was saved.
  61		- requires the admin_forum permission.
  62		- uses the rawdata sub template (built in.)
  63		- shows as the maintain_forum admin area.
  64		- updates the optimize scheduled task such that the tables are not
  65		  automatically optimized again too soon.
  66		- accessed from ?action=admin;area=maintain;sa=database;activity=optimize.
  67
  68	void AdminBoardRecount()
  69		- recounts many forum totals that can be recounted automatically
  70		  without harm.
  71		- requires the admin_forum permission.
  72		- shows the maintain_forum admin area.
  73		- fixes topics with wrong num_replies.
  74		- updates the num_posts and num_topics of all boards.
  75		- recounts instant_messages but not unread_messages.
  76		- repairs messages pointing to boards with topics pointing to
  77		  other boards.
  78		- updates the last message posted in boards and children.
  79		- updates member count, latest member, topic count, and message count.
  80		- redirects back to ?action=admin;area=maintain when complete.
  81		- accessed via ?action=admin;area=maintain;sa=database;activity=recount.
  82
  83	void VersionDetail()
  84		- parses the comment headers in all files for their version information
  85		  and outputs that for some javascript to check with simplemacines.org.
  86		- does not connect directly with simplemachines.org, but rather
  87		  expects the client to.
  88		- requires the admin_forum permission.
  89		- uses the view_versions admin area.
  90		- loads the view_versions sub template (in the Admin template.)
  91		- accessed through ?action=admin;area=maintain;sa=routine;activity=version.
  92
  93	void MaintainReattributePosts()
  94		// !!!
  95
  96	void MaintainDownloadBackup()
  97		// !!!
  98
  99	void MaintainPurgeInactiveMembers()
 100		// !!!
 101
 102	void MaintainRemoveOldPosts(bool do_action = true)
 103		// !!!
 104
 105	mixed MaintainMassMoveTopics()
 106		- Moves topics from one board to another.
 107		- User the not_done template to pause the process.
 108*/
 109
 110// The maintenance access point.
 111function ManageMaintenance()
 112{
 113	global $txt, $modSettings, $scripturl, $context, $options;
 114
 115	// You absolutely must be an admin by here!
 116	isAllowedTo('admin_forum');
 117
 118	// Need something to talk about?
 119	loadLanguage('ManageMaintenance');
 120	loadTemplate('ManageMaintenance');
 121
 122	// This uses admin tabs - as it should!
 123	$context[$context['admin_menu_name']]['tab_data'] = array(
 124		'title' => $txt['maintain_title'],
 125		'description' => $txt['maintain_info'],
 126		'tabs' => array(
 127			'routine' => array(),
 128			'database' => array(),
 129			'members' => array(),
 130			'topics' => array(),
 131		),
 132	);
 133
 134	// So many things you can do - but frankly I won't let you - just these!
 135	$subActions = array(
 136		'routine' => array(
 137			'function' => 'MaintainRoutine',
 138			'template' => 'maintain_routine',
 139			'activities' => array(
 140				'version' => 'VersionDetail',
 141				'repair' => 'MaintainFindFixErrors',
 142				'recount' => 'AdminBoardRecount',
 143				'logs' => 'MaintainEmptyUnimportantLogs',
 144				'cleancache' => 'MaintainCleanCache',
 145			),
 146		),
 147		'database' => array(
 148			'function' => 'MaintainDatabase',
 149			'template' => 'maintain_database',
 150			'activities' => array(
 151				'optimize' => 'OptimizeTables',
 152				'backup' => 'MaintainDownloadBackup',
 153				'convertentities' => 'ConvertEntities',
 154				'convertutf8' => 'ConvertUtf8',
 155			),
 156		),
 157		'members' => array(
 158			'function' => 'MaintainMembers',
 159			'template' => 'maintain_members',
 160			'activities' => array(
 161				'reattribute' => 'MaintainReattributePosts',
 162				'purgeinactive' => 'MaintainPurgeInactiveMembers',
 163			),
 164		),
 165		'topics' => array(
 166			'function' => 'MaintainTopics',
 167			'template' => 'maintain_topics',
 168			'activities' => array(
 169				'massmove' => 'MaintainMassMoveTopics',
 170				'pruneold' => 'MaintainRemoveOldPosts',
 171			),
 172		),
 173		'destroy' => array(
 174			'function' => 'Destroy',
 175			'activities' => array(),
 176		),
 177	);
 178
 179	// Yep, sub-action time!
 180	if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]))
 181		$subAction = $_REQUEST['sa'];
 182	else
 183		$subAction = 'routine';
 184
 185	// Doing something special?
 186	if (isset($_REQUEST['activity']) && isset($subActions[$subAction]['activities'][$_REQUEST['activity']]))
 187		$activity = $_REQUEST['activity'];
 188
 189	// Set a few things.
 190	$context['page_title'] = $txt['maintain_title'];
 191	$context['sub_action'] = $subAction;
 192	$context['sub_template'] = !empty($subActions[$subAction]['template']) ? $subActions[$subAction]['template'] : '';
 193
 194	// Finally fall through to what we are doing.
 195	$subActions[$subAction]['function']();
 196
 197	// Any special activity?
 198	if (isset($activity))
 199		$subActions[$subAction]['activities'][$activity]();
 200
 201	//converted to UTF-8? show a small maintenance info
 202	if (isset($_GET['done']) && $_GET['done'] == 'convertutf8')
 203		$context['maintenance_finished'] = $txt['utf8_title'];
 204}
 205
 206// Supporting function for the database maintenance area.
 207function MaintainDatabase()
 208{
 209	global $context, $db_type, $db_character_set, $modSettings, $smcFunc, $txt;
 210
 211	// Show some conversion options?
 212	$context['convert_utf8'] = $db_type == 'mysql' && (!isset($db_character_set) || $db_character_set !== 'utf8' || empty($modSettings['global_character_set']) || $modSettings['global_character_set'] !== 'UTF-8') && version_compare('4.1.2', preg_replace('~\-.+?$~', '', $smcFunc['db_server_info']())) <= 0;
 213	$context['convert_entities'] = $db_type == 'mysql' && isset($db_character_set, $modSettings['global_character_set']) && $db_character_set === 'utf8' && $modSettings['global_character_set'] === 'UTF-8';
 214
 215	if (isset($_GET['done']) && $_GET['done'] == 'convertutf8')
 216		$context['maintenance_finished'] = $txt['utf8_title'];
 217	if (isset($_GET['done']) && $_GET['done'] == 'convertentities')
 218		$context['maintenance_finished'] = $txt['entity_convert_title'];
 219}
 220
 221// Supporting function for the routine maintenance area.
 222function MaintainRoutine()
 223{
 224	global $context, $txt;
 225
 226	if (isset($_GET['done']) && $_GET['done'] == 'recount')
 227		$context['maintenance_finished'] = $txt['maintain_recount'];
 228}
 229
 230// Supporting function for the members maintenance area.
 231function MaintainMembers()
 232{
 233	global $context, $smcFunc, $txt;
 234
 235	// Get membergroups - for deleting members and the like.
 236	$result = $smcFunc['db_query']('', '
 237		SELECT id_group, group_name
 238		FROM {db_prefix}membergroups',
 239		array(
 240		)
 241	);
 242	$context['membergroups'] = array(
 243		array(
 244			'id' => 0,
 245			'name' => $txt['maintain_members_ungrouped']
 246		),
 247	);
 248	while ($row = $smcFunc['db_fetch_assoc']($result))
 249	{
 250		$context['membergroups'][] = array(
 251			'id' => $row['id_group'],
 252			'name' => $row['group_name']
 253		);
 254	}
 255	$smcFunc['db_free_result']($result);
 256}
 257
 258// Supporting function for the topics maintenance area.
 259function MaintainTopics()
 260{
 261	global $context, $smcFunc, $txt;
 262
 263	// Let's load up the boards in case they are useful.
 264	$result = $smcFunc['db_query']('order_by_board_order', '
 265		SELECT b.id_board, b.name, b.child_level, c.name AS cat_name, c.id_cat
 266		FROM {db_prefix}boards AS b
 267			LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
 268		WHERE {query_see_board}
 269			AND redirect = {string:blank_redirect}',
 270		array(
 271			'blank_redirect' => '',
 272		)
 273	);
 274	$context['categories'] = array();
 275	while ($row = $smcFunc['db_fetch_assoc']($result))
 276	{
 277		if (!isset($context['categories'][$row['id_cat']]))
 278			$context['categories'][$row['id_cat']] = array(
 279				'name' => $row['cat_name'],
 280				'boards' => array()
 281			);
 282
 283		$context['categories'][$row['id_cat']]['boards'][] = array(
 284			'id' => $row['id_board'],
 285			'name' => $row['name'],
 286			'child_level' => $row['child_level']
 287		);
 288	}
 289	$smcFunc['db_free_result']($result);
 290
 291	if (isset($_GET['done']) && $_GET['done'] == 'purgeold')
 292		$context['maintenance_finished'] = $txt['maintain_old'];
 293	elseif (isset($_GET['done']) && $_GET['done'] == 'massmove')
 294		$context['maintenance_finished'] = $txt['move_topics_maintenance'];
 295}
 296
 297// Find and fix all errors.
 298function MaintainFindFixErrors()
 299{
 300	global $sourcedir;
 301
 302	require_once($sourcedir . '/RepairBoards.php');
 303	RepairBoards();
 304}
 305
 306// Wipes the whole cache directory.
 307function MaintainCleanCache()
 308{
 309	global $context, $txt;
 310
 311	// Just wipe the whole cache directory!
 312	clean_cache();
 313
 314	$context['maintenance_finished'] = $txt['maintain_cache'];
 315}
 316
 317// Empties all uninmportant logs
 318function MaintainEmptyUnimportantLogs()
 319{
 320	global $context, $smcFunc, $txt;
 321
 322	checkSession();
 323
 324	// No one's online now.... MUHAHAHAHA :P.
 325	$smcFunc['db_query']('', '
 326		DELETE FROM {db_prefix}log_online');
 327
 328	// Dump the banning logs.
 329	$smcFunc['db_query']('', '
 330		DELETE FROM {db_prefix}log_banned');
 331
 332	// Start id_error back at 0 and dump the error log.
 333	$smcFunc['db_query']('truncate_table', '
 334		TRUNCATE {db_prefix}log_errors');
 335
 336	// Clear out the spam log.
 337	$smcFunc['db_query']('', '
 338		DELETE FROM {db_prefix}log_floodcontrol');
 339
 340	// Clear out the karma actions.
 341	$smcFunc['db_query']('', '
 342		DELETE FROM {db_prefix}log_karma');
 343
 344	// Last but not least, the search logs!
 345	$smcFunc['db_query']('truncate_table', '
 346		TRUNCATE {db_prefix}log_search_topics');
 347
 348	$smcFunc['db_query']('truncate_table', '
 349		TRUNCATE {db_prefix}log_search_messages');
 350
 351	$smcFunc['db_query']('truncate_table', '
 352		TRUNCATE {db_prefix}log_search_results');
 353
 354	updateSettings(array('search_pointer' => 0));
 355
 356	$context['maintenance_finished'] = $txt['maintain_logs'];
 357}
 358
 359// Oh noes!
 360function Destroy()
 361{
 362	global $context;
 363
 364	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 365		<html xmlns="http://www.w3.org/1999/xhtml"', $context['right_to_left'] ? ' dir="rtl"' : '', '><head><title>', $context['forum_name_html_safe'], ' deleted!</title></head>
 366		<body style="background-color: orange; font-family: arial, sans-serif; text-align: center;">
 367		<div style="margin-top: 8%; font-size: 400%; color: black;">Oh my, you killed ', $context['forum_name_html_safe'], '!</div>
 368		<div style="margin-top: 7%; font-size: 500%; color: red;"><strong>You lazy bum!</strong></div>
 369		</body></html>';
 370	obExit(false);
 371}
 372
 373// Convert both data and database tables to UTF-8 character set.
 374function ConvertUtf8()
 375{
 376	global $scripturl, $context, $txt, $language, $db_character_set;
 377	global $modSettings, $user_info, $sourcedir, $smcFunc, $db_prefix;
 378
 379	// Show me your badge!
 380	isAllowedTo('admin_forum');
 381
 382	// The character sets used in SMF's language files with their db equivalent.
 383	$charsets = array(
 384		// Chinese-traditional.
 385		'big5' => 'big5',
 386		// Chinese-simplified.
 387		'gbk' => 'gbk',
 388		// West European.
 389		'ISO-8859-1' => 'latin1',
 390		// Romanian.
 391		'ISO-8859-2' => 'latin2',
 392		// Turkish.
 393		'ISO-8859-9' => 'latin5',
 394		// West European with Euro sign.
 395		'ISO-8859-15' => 'latin9',
 396		// Thai.
 397		'tis-620' => 'tis620',
 398		// Persian, Chinese, etc.
 399		'UTF-8' => 'utf8',
 400		// Russian.
 401		'windows-1251' => 'cp1251',
 402		// Greek.
 403		'windows-1253' => 'utf8',
 404		// Hebrew.
 405		'windows-1255' => 'utf8',
 406		// Arabic.
 407		'windows-1256' => 'cp1256',
 408	);
 409
 410	// Get a list of character sets supported by your MySQL server.
 411	$request = $smcFunc['db_query']('', '
 412		SHOW CHARACTER SET',
 413		array(
 414		)
 415	);
 416	$db_charsets = array();
 417	while ($row = $smcFunc['db_fetch_assoc']($request))
 418		$db_charsets[] = $row['Charset'];
 419
 420	$smcFunc['db_free_result']($request);
 421
 422	// Character sets supported by both MySQL and SMF's language files.
 423	$charsets = array_intersect($charsets, $db_charsets);
 424
 425	// This is for the first screen telling backups is good.
 426	if (!isset($_POST['proceed']))
 427	{
 428		// Character set conversions are only supported as of MySQL 4.1.2.
 429		if (version_compare('4.1.2', preg_replace('~\-.+?$~', '', $smcFunc['db_server_info']())) > 0)
 430			fatal_lang_error('utf8_db_version_too_low');
 431
 432		// Use the messages.body column as indicator for the database charset.
 433		$request = $smcFunc['db_query']('', '
 434			SHOW FULL COLUMNS
 435			FROM {db_prefix}messages
 436			LIKE {string:body_like}',
 437			array(
 438				'body_like' => 'body',
 439			)
 440		);
 441		$column_info = $smcFunc['db_fetch_assoc']($request);
 442		$smcFunc['db_free_result']($request);
 443
 444		// A collation looks like latin1_swedish. We only need the character set.
 445		list($context['database_charset']) = explode('_', $column_info['Collation']);
 446		$context['database_charset'] = in_array($context['database_charset'], $charsets) ? array_search($context['database_charset'], $charsets) : $context['database_charset'];
 447
 448		// No need to convert to UTF-8 if it already is.
 449		if ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8')
 450			fatal_lang_error('utf8_already_utf8');
 451
 452		// Cannot do conversion if using a fulltext index
 453		if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'fulltext')
 454			fatal_lang_error('utf8_cannot_convert_fulltext');
 455
 456		// Grab the character set from the default language file.
 457		loadLanguage('index', $language, true);
 458		$context['charset_detected'] = $txt['lang_character_set'];
 459		$context['charset_about_detected'] = sprintf($txt['utf8_detected_charset'], $language, $context['charset_detected']);
 460
 461		// Go back to your own language.
 462		loadLanguage('index', $user_info['language'], true);
 463
 464		// Show a warning if the character set seems not to be supported.
 465		if (!isset($charsets[strtr(strtolower($context['charset_detected']), array('utf' => 'UTF', 'iso' => 'ISO'))]))
 466		{
 467			$context['charset_warning'] = sprintf($txt['utf8_charset_not_supported'], $txt['lang_character_set']);
 468
 469			// Default to ISO-8859-1.
 470			$context['charset_detected'] = 'ISO-8859-1';
 471		}
 472
 473		$context['charset_list'] = array_keys($charsets);
 474
 475		$context['page_title'] = $txt['utf8_title'];
 476		$context['sub_template'] = 'convert_utf8';
 477		return;
 478	}
 479
 480	// After this point we're starting the conversion. But first: session check.
 481	checkSession();
 482
 483	// Translation table for the character sets not native for MySQL.
 484	$translation_tables = array(
 485		'windows-1255' => array(
 486			'0x81' => '\'\'',		'0x8A' => '\'\'',		'0x8C' => '\'\'',
 487			'0x8D' => '\'\'',		'0x8E' => '\'\'',		'0x8F' => '\'\'',
 488			'0x90' => '\'\'',		'0x9A' => '\'\'',		'0x9C' => '\'\'',
 489			'0x9D' => '\'\'',		'0x9E' => '\'\'',		'0x9F' => '\'\'',
 490			'0xCA' => '\'\'',		'0xD9' => '\'\'',		'0xDA' => '\'\'',
 491			'0xDB' => '\'\'',		'0xDC' => '\'\'',		'0xDD' => '\'\'',
 492			'0xDE' => '\'\'',		'0xDF' => '\'\'',		'0xFB' => '\'\'',
 493			'0xFC' => '\'\'',		'0xFF' => '\'\'',		'0xC2' => '0xFF',
 494			'0x80' => '0xFC',		'0xE2' => '0xFB',		'0xA0' => '0xC2A0',
 495			'0xA1' => '0xC2A1',		'0xA2' => '0xC2A2',		'0xA3' => '0xC2A3',
 496			'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
 497			'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
 498			'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
 499			'0xAF' => '0xC2AF',		'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',
 500			'0xB2' => '0xC2B2',		'0xB3' => '0xC2B3',		'0xB4' => '0xC2B4',
 501			'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
 502			'0xB8' => '0xC2B8',		'0xB9' => '0xC2B9',		'0xBB' => '0xC2BB',
 503			'0xBC' => '0xC2BC',		'0xBD' => '0xC2BD',		'0xBE' => '0xC2BE',
 504			'0xBF' => '0xC2BF',		'0xD7' => '0xD7B3',		'0xD1' => '0xD781',
 505			'0xD4' => '0xD7B0',		'0xD5' => '0xD7B1',		'0xD6' => '0xD7B2',
 506			'0xE0' => '0xD790',		'0xEA' => '0xD79A',		'0xEC' => '0xD79C',
 507			'0xED' => '0xD79D',		'0xEE' => '0xD79E',		'0xEF' => '0xD79F',
 508			'0xF0' => '0xD7A0',		'0xF1' => '0xD7A1',		'0xF2' => '0xD7A2',
 509			'0xF3' => '0xD7A3',		'0xF5' => '0xD7A5',		'0xF6' => '0xD7A6',
 510			'0xF7' => '0xD7A7',		'0xF8' => '0xD7A8',		'0xF9' => '0xD7A9',
 511			'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
 512			'0x86' => '0xE280A0',	'0x87' => '0xE280A1',	'0x89' => '0xE280B0',
 513			'0x8B' => '0xE280B9',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
 514			'0x95' => '0xE280A2',	'0x97' => '0xE28094',	'0x99' => '0xE284A2',
 515			'0xC0' => '0xD6B0',		'0xC1' => '0xD6B1',		'0xC3' => '0xD6B3',
 516			'0xC4' => '0xD6B4',		'0xC5' => '0xD6B5',		'0xC6' => '0xD6B6',
 517			'0xC7' => '0xD6B7',		'0xC8' => '0xD6B8',		'0xC9' => '0xD6B9',
 518			'0xCB' => '0xD6BB',		'0xCC' => '0xD6BC',		'0xCD' => '0xD6BD',
 519			'0xCE' => '0xD6BE',		'0xCF' => '0xD6BF',		'0xD0' => '0xD780',
 520			'0xD2' => '0xD782',		'0xE3' => '0xD793',		'0xE4' => '0xD794',
 521			'0xE5' => '0xD795',		'0xE7' => '0xD797',		'0xE9' => '0xD799',
 522			'0xFD' => '0xE2808E',	'0xFE' => '0xE2808F',	'0x92' => '0xE28099',
 523			'0x83' => '0xC692',		'0xD3' => '0xD783',		'0x88' => '0xCB86',
 524			'0x98' => '0xCB9C',		'0x91' => '0xE28098',	'0x96' => '0xE28093',
 525			'0xBA' => '0xC3B7',		'0x9B' => '0xE280BA',	'0xAA' => '0xC397',
 526			'0xA4' => '0xE282AA',	'0xE1' => '0xD791',		'0xE6' => '0xD796',
 527			'0xE8' => '0xD798',		'0xEB' => '0xD79B',		'0xF4' => '0xD7A4',
 528			'0xFA' => '0xD7AA',		'0xFF' => '0xD6B2',		'0xFC' => '0xE282AC',
 529			'0xFB' => '0xD792',
 530		),
 531		'windows-1253' => array(
 532			'0x81' => '\'\'',			'0x88' => '\'\'',			'0x8A' => '\'\'',
 533			'0x8C' => '\'\'',			'0x8D' => '\'\'',			'0x8E' => '\'\'',
 534			'0x8F' => '\'\'',			'0x90' => '\'\'',			'0x98' => '\'\'',
 535			'0x9A' => '\'\'',			'0x9C' => '\'\'',			'0x9D' => '\'\'',
 536			'0x9E' => '\'\'',			'0x9F' => '\'\'',			'0xAA' => '\'\'',
 537			'0xD2' => '\'\'',			'0xFF' => '\'\'',			'0xCE' => '0xCE9E',
 538			'0xB8' => '0xCE88',		'0xBA' => '0xCE8A',		'0xBC' => '0xCE8C',
 539			'0xBE' => '0xCE8E',		'0xBF' => '0xCE8F',		'0xC0' => '0xCE90',
 540			'0xC8' => '0xCE98',		'0xCA' => '0xCE9A',		'0xCC' => '0xCE9C',
 541			'0xCD' => '0xCE9D',		'0xCF' => '0xCE9F',		'0xDA' => '0xCEAA',
 542			'0xE8' => '0xCEB8',		'0xEA' => '0xCEBA',		'0xEC' => '0xCEBC',
 543			'0xEE' => '0xCEBE',		'0xEF' => '0xCEBF',		'0xC2' => '0xFF',
 544			'0xBD' => '0xC2BD',		'0xED' => '0xCEBD',		'0xB2' => '0xC2B2',
 545			'0xA0' => '0xC2A0',		'0xA3' => '0xC2A3',		'0xA4' => '0xC2A4',
 546			'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
 547			'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
 548			'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
 549			'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',		'0xB3' => '0xC2B3',
 550			'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
 551			'0xBB' => '0xC2BB',		'0xE2' => '0xCEB2',		'0x80' => '0xD2',
 552			'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
 553			'0x86' => '0xE280A0',	'0xA1' => '0xCE85',		'0xA2' => '0xCE86',
 554			'0x87' => '0xE280A1',	'0x89' => '0xE280B0',	'0xB9' => '0xCE89',
 555			'0x8B' => '0xE280B9',	'0x91' => '0xE28098',	'0x99' => '0xE284A2',
 556			'0x92' => '0xE28099',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
 557			'0x95' => '0xE280A2',	'0x96' => '0xE28093',	'0x97' => '0xE28094',
 558			'0x9B' => '0xE280BA',	'0xAF' => '0xE28095',	'0xB4' => '0xCE84',
 559			'0xC1' => '0xCE91',		'0xC3' => '0xCE93',		'0xC4' => '0xCE94',
 560			'0xC5' => '0xCE95',		'0xC6' => '0xCE96',		'0x83' => '0xC692',
 561			'0xC7' => '0xCE97',		'0xC9' => '0xCE99',		'0xCB' => '0xCE9B',
 562			'0xD0' => '0xCEA0',		'0xD1' => '0xCEA1',		'0xD3' => '0xCEA3',
 563			'0xD4' => '0xCEA4',		'0xD5' => '0xCEA5',		'0xD6' => '0xCEA6',
 564			'0xD7' => '0xCEA7',		'0xD8' => '0xCEA8',		'0xD9' => '0xCEA9',
 565			'0xDB' => '0xCEAB',		'0xDC' => '0xCEAC',		'0xDD' => '0xCEAD',
 566			'0xDE' => '0xCEAE',		'0xDF' => '0xCEAF',		'0xE0' => '0xCEB0',
 567			'0xE1' => '0xCEB1',		'0xE3' => '0xCEB3',		'0xE4' => '0xCEB4',
 568			'0xE5' => '0xCEB5',		'0xE6' => '0xCEB6',		'0xE7' => '0xCEB7',
 569			'0xE9' => '0xCEB9',		'0xEB' => '0xCEBB',		'0xF0' => '0xCF80',
 570			'0xF1' => '0xCF81',		'0xF2' => '0xCF82',		'0xF3' => '0xCF83',
 571			'0xF4' => '0xCF84',		'0xF5' => '0xCF85',		'0xF6' => '0xCF86',
 572			'0xF7' => '0xCF87',		'0xF8' => '0xCF88',		'0xF9' => '0xCF89',
 573			'0xFA' => '0xCF8A',		'0xFB' => '0xCF8B',		'0xFC' => '0xCF8C',
 574			'0xFD' => '0xCF8D',		'0xFE' => '0xCF8E',		'0xFF' => '0xCE92',
 575			'0xD2' => '0xE282AC',
 576		),
 577	);
 578
 579	// Make some preparations.
 580	if (isset($translation_tables[$_POST['src_charset']]))
 581	{
 582		$replace = '%field%';
 583		foreach ($translation_tables[$_POST['src_charset']] as $from => $to)
 584			$replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')';
 585	}
 586
 587	// Grab a list of tables.
 588	if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) === 1)
 589		$queryTables = $smcFunc['db_query']('', '
 590			SHOW TABLE STATUS
 591			FROM `' . strtr($match[1], array('`' => '')) . '`
 592			LIKE {string:table_name}',
 593			array(
 594				'table_name' => str_replace('_', '\_', $match[2]) . '%',
 595			)
 596		);
 597	else
 598		$queryTables = $smcFunc['db_query']('', '
 599			SHOW TABLE STATUS
 600			LIKE {string:table_name}',
 601			array(
 602				'table_name' => str_replace('_', '\_', $db_prefix) . '%',
 603			)
 604		);
 605
 606	while ($table_info = $smcFunc['db_fetch_assoc']($queryTables))
 607	{
 608		// Just to make sure it doesn't time out.
 609		if (function_exists('apache_reset_timeout'))
 610			@apache_reset_timeout();
 611
 612		$table_charsets = array();
 613
 614		// Loop through each column.
 615		$queryColumns = $smcFunc['db_query']('', '
 616			SHOW FULL COLUMNS
 617			FROM ' . $table_info['Name'],
 618			array(
 619			)
 620		);
 621		while ($column_info = $smcFunc['db_fetch_assoc']($queryColumns))
 622		{
 623			// Only text'ish columns have a character set and need converting.
 624			if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
 625			{
 626				$collation = empty($column_info['Collation']) || $column_info['Collation'] === 'NULL' ? $table_info['Collation'] : $column_info['Collation'];
 627				if (!empty($collation) && $collation !== 'NULL')
 628				{
 629					list($charset) = explode('_', $collation);
 630
 631					if (!isset($table_charsets[$charset]))
 632						$table_charsets[$charset] = array();
 633
 634					$table_charsets[$charset][] = $column_info;
 635				}
 636			}
 637		}
 638		$smcFunc['db_free_result']($queryColumns);
 639
 640		// Only change the column if the data doesn't match the current charset.
 641		if ((count($table_charsets) === 1 && key($table_charsets) !== $charsets[$_POST['src_charset']]) || count($table_charsets) > 1)
 642		{
 643			$updates_blob = '';
 644			$updates_text = '';
 645			foreach ($table_charsets as $charset => $columns)
 646			{
 647				if ($charset !== $charsets[$_POST['src_charset']])
 648				{
 649					foreach ($columns as $column)
 650					{
 651						$updates_blob .= '
 652							CHANGE COLUMN ' . $column['Field'] . ' ' . $column['Field'] . ' ' . strtr($column['Type'], array('text' => 'blob', 'char' => 'binary')) . ($column['Null'] === 'YES' ? ' NULL' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
 653						$updates_text .= '
 654							CHANGE COLUMN ' . $column['Field'] . ' ' . $column['Field'] . ' ' . $column['Type'] . ' CHARACTER SET ' . $charsets[$_POST['src_charset']] . ($column['Null'] === 'YES' ? '' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
 655					}
 656				}
 657			}
 658
 659			// Change the columns to binary form.
 660			$smcFunc['db_query']('', '
 661				ALTER TABLE {raw:table_name}{raw:updates_blob}',
 662				array(
 663					'table_name' => $table_info['Name'],
 664					'updates_blob' => substr($updates_blob, 0, -1),
 665				)
 666			);
 667
 668			// Convert the character set if MySQL has no native support for it.
 669			if (isset($translation_tables[$_POST['src_charset']]))
 670			{
 671				$update = '';
 672				foreach ($table_charsets as $charset => $columns)
 673					foreach ($columns as $column)
 674						$update .= '
 675							' . $column['Field'] . ' = ' . strtr($replace, array('%field%' => $column['Field'])) . ',';
 676
 677				$smcFunc['db_query']('', '
 678					UPDATE {raw:table_name}
 679					SET {raw:updates}',
 680					array(
 681						'table_name' => $table_info['Name'],
 682						'updates' => substr($update, 0, -1),
 683					)
 684				);
 685			}
 686
 687			// Change the columns back, but with the proper character set.
 688			$smcFunc['db_query']('', '
 689				ALTER TABLE {raw:table_name}{raw:updates_text}',
 690				array(
 691					'table_name' => $table_info['Name'],
 692					'updates_text' => substr($updates_text, 0, -1),
 693				)
 694			);
 695		}
 696
 697		// Now do the actual conversion (if still needed).
 698		if ($charsets[$_POST['src_charset']] !== 'utf8')
 699			$smcFunc['db_query']('', '
 700				ALTER TABLE {raw:table_name}
 701				CONVERT TO CHARACTER SET utf8',
 702				array(
 703					'table_name' => $table_info['Name'],
 704				)
 705			);
 706	}
 707	$smcFunc['db_free_result']($queryTables);
 708
 709	// Let the settings know we have a new character set.
 710	updateSettings(array('global_character_set' => 'UTF-8', 'previousCharacterSet' => (empty($translation_tables[$_POST['src_charset']])) ? $charsets[$_POST['src_charset']] : $translation_tables[$_POST['src_charset']]));
 711
 712	// Store it in Settings.php too because it's needed before db connection.
 713	require_once($sourcedir . '/Subs-Admin.php');
 714	updateSettingsFile(array('db_character_set' => '\'utf8\''));
 715
 716	// The conversion might have messed up some serialized strings. Fix them!
 717	require_once($sourcedir . '/Subs-Charset.php');
 718	fix_serialized_columns();
 719
 720	redirectexit('action=admin;area=maintain;done=convertutf8');
 721}
 722
 723// Convert HTML-entities to their UTF-8 character equivalents.
 724function ConvertEntities()
 725{
 726	global $db_character_set, $modSettings, $context, $sourcedir, $smcFunc;
 727
 728	isAllowedTo('admin_forum');
 729
 730	// Check to see if UTF-8 is currently the default character set.
 731	if ($modSettings['global_character_set'] !== 'UTF-8' || !isset($db_character_set) || $db_character_set !== 'utf8')
 732		fatal_lang_error('entity_convert_only_utf8');
 733
 734	// Some starting values.
 735	$context['table'] = empty($_REQUEST['table']) ? 0 : (int) $_REQUEST['table'];
 736	$context['start'] = empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'];
 737
 738	$context['start_time'] = time();
 739
 740	$context['first_step'] = !isset($_REQUEST[$context['session_var']]);
 741	$context['last_step'] = false;
 742
 743	// The first step is just a text screen with some explanation.
 744	if ($context['first_step'])
 745	{
 746		$context['sub_template'] = 'convert_entities';
 747		return;
 748	}
 749	// Otherwise use the generic "not done" template.
 750	$context['sub_template'] = 'not_done';
 751	$context['continue_post_data'] = '';
 752	$context['continue_countdown'] = 3;
 753
 754	// Now we're actually going to convert...
 755	checkSession('request');
 756
 757	// A list of tables ready for conversion.
 758	$tables = array(
 759		'ban_groups',
 760		'ban_items',
 761		'boards',
 762		'calendar',
 763		'calendar_holidays',
 764		'categories',
 765		'log_errors',
 766		'log_search_subjects',
 767		'membergroups',
 768		'members',
 769		'message_icons',
 770		'messages',
 771		'package_servers',
 772		'personal_messages',
 773		'pm_recipients',
 774		'polls',
 775		'poll_choices',
 776		'smileys',
 777		'themes',
 778	);
 779	$context['num_tables'] = count($tables);
 780
 781	// This function will do the conversion later on.
 782	$entity_replace = create_function('$string', '
 783		$num = substr($string, 0, 1) === \'x\' ? hexdec(substr($string, 1)) : (int) $string;
 784		return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) ? \'\' : ($num < 0x80 ? \'&#\' . $num . \';\' : ($num < 0x800 ? chr(192 | $num >> 6) . chr(128 | $num & 63) : ($num < 0x10000 ? chr(224 | $num >> 12) . chr(128 | $num >> 6 & 63) . chr(128 | $num & 63) : chr(240 | $num >> 18) . chr(128 | $num >> 12 & 63) . chr(128 | $num >> 6 & 63) . chr(128 | $num & 63))));');
 785
 786	// Loop through all tables that need converting.
 787	for (; $context['table'] < $context['num_tables']; $context['table']++)
 788	{
 789		$cur_table = $tables[$context['table']];
 790		$primary_key = '';
 791		// Make sure we keep stuff unique!
 792		$primary_keys = array();
 793
 794		if (function_exists('apache_reset_timeout'))
 795			@apache_reset_timeout();
 796
 797		// Get a list of text columns.
 798		$columns = array();
 799		$request = $smcFunc['db_query']('', '
 800			SHOW FULL COLUMNS
 801			FROM {db_prefix}' . $cur_table,
 802			array(
 803			)
 804		);
 805		while ($column_info = $smcFunc['db_fetch_assoc']($request))
 806			if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
 807				$columns[] = strtolower($column_info['Field']);
 808
 809		// Get the column with the (first) primary key.
 810		$request = $smcFunc['db_query']('', '
 811			SHOW KEYS
 812			FROM {db_prefix}' . $cur_table,
 813			array(
 814			)
 815		);
 816		while ($row = $smcFunc['db_fetch_assoc']($request))
 817		{
 818			if ($row['Key_name'] === 'PRIMARY')
 819			{
 820				if (empty($primary_key) || ($row['Seq_in_index'] == 1 && !in_array(strtolower($row['Column_name']), $columns)))
 821					$primary_key = $row['Column_name'];
 822
 823				$primary_keys[] = $row['Column_name'];
 824			}
 825		}
 826		$smcFunc['db_free_result']($request);
 827
 828		// No primary key, no glory.
 829		// Same for columns. Just to be sure we've work to do!
 830		if (empty($primary_key) || empty($columns))
 831			continue;
 832
 833		// Get the maximum value for the primary key.
 834		$request = $smcFunc['db_query']('', '
 835			SELECT MAX(' . $primary_key . ')
 836			FROM {db_prefix}' . $cur_table,
 837			array(
 838			)
 839		);
 840		list($max_value) = $smcFunc['db_fetch_row']($request);
 841		$smcFunc['db_free_result']($request);
 842
 843		if (empty($max_value))
 844			continue;
 845
 846		while ($context['start'] <= $max_value)
 847		{
 848			// Retrieve a list of rows that has at least one entity to convert.
 849			$request = $smcFunc['db_query']('', '
 850				SELECT {raw:primary_keys}, {raw:columns}
 851				FROM {db_prefix}{raw:cur_table}
 852				WHERE {raw:primary_key} BETWEEN {int:start} AND {int:start} + 499
 853					AND {raw:like_compare}
 854				LIMIT 500',
 855				array(
 856					'primary_keys' => implode(', ', $primary_keys),
 857					'columns' => implode(', ', $columns),
 858					'cur_table' => $cur_table,
 859					'primary_key' => $primary_key,
 860					'start' => $context['start'],
 861					'like_compare' => '(' . implode(' LIKE \'%&#%\' OR ', $columns) . ' LIKE \'%&#%\')',
 862				)
 863			);
 864			while ($row = $smcFunc['db_fetch_assoc']($request))
 865			{
 866				$insertion_variables = array();
 867				$changes = array();
 868				foreach ($row as $column_name => $column_value)
 869					if ($column_name !== $primary_key && strpos($column_value, '&#') !== false)
 870					{
 871						$changes[] = $column_name . ' = {string:changes_' . $column_name . '}';
 872						$insertion_variables['changes_' . $column_name] = preg_replace_callback('~&#(\d{1,7}|x[0-9a-fA-F]{1,6});~', 'fixchar__callback', $column_value);
 873					}
 874
 875				$where = array();
 876				foreach ($primary_keys as $key)
 877				{
 878					$where[] = $key . ' = {string:where_' . $key . '}';
 879					$insertion_variables['where_' . $key] = $row[$key];
 880				}
 881
 882				// Update the row.
 883				if (!empty($changes))
 884					$smcFunc['db_query']('', '
 885						UPDATE {db_prefix}' . $cur_table . '
 886						SET
 887							' . implode(',
 888							', $changes) . '
 889						WHERE ' . implode(' AND ', $where),
 890						$insertion_variables
 891					);
 892			}
 893			$smcFunc['db_free_result']($request);
 894			$context['start'] += 500;
 895
 896			// After ten seconds interrupt.
 897			if (time() - $context['start_time'] > 10)
 898			{
 899				// Calculate an approximation of the percentage done.
 900				$context['continue_percent'] = round(100 * ($context['table'] + ($context['start'] / $max_value)) / $context['num_tables'], 1);
 901				$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;activity=convertentities;table=' . $context['table'] . ';start=' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
 902				return;
 903			}
 904		}
 905		$context['start'] = 0;
 906	}
 907
 908	// Make sure all serialized strings are all right.
 909	require_once($sourcedir . '/Subs-Charset.php');
 910	fix_serialized_columns();
 911
 912	// If we're here, we must be done.
 913	$context['continue_percent'] = 100;
 914	$context['continue_get_data'] = '?action=admin;area=maintain;sa=database;done=convertentities';
 915	$context['last_step'] = true;
 916	$context['continue_countdown'] = -1;
 917}
 918
 919// Optimize the database's tables.
 920function OptimizeTables()
 921{
 922	global $db_type, $db_name, $db_prefix, $txt, $context, $scripturl, $sourcedir, $smcFunc;
 923
 924	isAllowedTo('admin_forum');
 925
 926	checkSession('post');
 927
 928	ignore_user_abort(true);
 929	db_extend();
 930
 931	// Start with no tables optimized.
 932	$opttab = 0;
 933
 934	$context['page_title'] = $txt['database_optimize'];
 935	$context['sub_template'] = 'optimize';
 936
 937	// Only optimize the tables related to this smf install, not all the tables in the db
 938	$real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
 939
 940	// Get a list of tables, as well as how many there are.
 941	$temp_tables = $smcFunc['db_list_tables'](false, $real_prefix . '%');
 942	$tables = array();
 943	foreach ($temp_tables as $table)
 944		$tables[] = array('table_name' => $table);
 945
 946	// If there aren't any tables then I believe that would mean the world has exploded...
 947	$context['num_tables'] = count($tables);
 948	if ($context['num_tables'] == 0)
 949		fatal_error('You appear to be running SMF in a flat file mode... fantastic!', false);
 950
 951	// For each table....
 952	$context['optimized_tables'] = array();
 953	foreach ($tables as $table)
 954	{
 955		// Optimize the table!  We use backticks here because it might be a custom table.
 956		$data_freed = $smcFunc['db_optimize_table']($table['table_name']);
 957
 958		// Optimizing one sqlite table optimizes them all.
 959		if ($db_type == 'sqlite')
 960			break;
 961
 962		if ($data_freed > 0)
 963			$context['optimized_tables'][] = array(
 964				'name' => $table['table_name'],
 965				'data_freed' => $data_freed,
 966			);
 967	}
 968
 969	// Number of tables, etc....
 970	$txt['database_numb_tables'] = sprintf($txt['database_numb_tables'], $context['num_tables']);
 971	$context['num_tables_optimized'] = count($context['optimized_tables']);
 972
 973	// Check that we don't auto optimise again too soon!
 974	require_once($sourcedir . '/ScheduledTasks.php');
 975	CalculateNextTrigger('auto_optimize', true);
 976}
 977
 978// Recount all the important board totals.
 979function AdminBoardRecount()
 980{
 981	global $txt, $context, $scripturl, $modSettings, $sourcedir;
 982	global $time_start, $smcFunc;
 983
 984	isAllowedTo('admin_forum');
 985
 986	checkSession('request');
 987
 988	$context['page_title'] = $txt['not_done_title'];
 989	$context['continue_post_data'] = '';
 990	$context['continue_countdown'] = '3';
 991	$context['sub_template'] = 'not_done';
 992
 993	// Try for as much time as possible.
 994	@set_time_limit(600);
 995
 996	// Step the number of topics at a time so things don't time out...
 997	$request = $smcFunc['db_query']('', '
 998		SELECT MAX(id_topic)
 999		FROM {db_prefix}topics',
1000		array(
1001		)
1002	);
1003	list ($max_topics) = $smcFunc['db_fetch_row']($request);
1004	$smcFunc['db_free_result']($request);
1005
1006	$increment = min(max(50, ceil($max_topics / 4)), 2000);
1007	if (empty($_REQUEST['start']))
1008		$_REQUEST['start'] = 0;
1009
1010	$total_steps = 8;
1011
1012	// Get each topic with a wrong reply count and fix it - let's just do some at a time, though.
1013	if (empty($_REQUEST['step']))
1014	{
1015		$_REQUEST['step'] = 0;
1016
1017		while ($_REQUEST['start'] < $max_topics)
1018		{
1019			// Recount approved messages
1020			$request = $smcFunc['db_query']('', '
1021				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.num_replies) AS num_replies,
1022					CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END AS real_num_replies
1023				FROM {db_prefix}topics AS t
1024					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = {int:is_approved})
1025				WHERE t.id_topic > {int:start}
1026					AND t.id_topic <= {int:max_id}
1027				GROUP BY t.id_topic
1028				HAVING CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END != MAX(t.num_replies)',
1029				array(
1030					'is_approved' => 1,
1031					'start' => $_REQUEST['start'],
1032					'max_id' => $_REQUEST['start'] + $increment,
1033				)
1034			);
1035			while ($row = $smcFunc['db_fetch_assoc']($request))
1036				$smcFunc['db_query']('', '
1037					UPDATE {db_prefix}topics
1038					SET num_replies = {int:num_replies}
1039					WHERE id_topic = {int:id_topic}',
1040					array(
1041						'num_replies' => $row['real_num_replies'],
1042						'id_topic' => $row['id_topic'],
1043					)
1044				);
1045			$smcFunc['db_free_result']($request);
1046
1047			// Recount unapproved messages
1048			$request = $smcFunc['db_query']('', '
1049				SELECT /*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.unapproved_posts) AS unapproved_posts,
1050					COUNT(mu.id_msg) AS real_unapproved_posts
1051				FROM {db_prefix}topics AS t
1052					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = {int:not_approved})
1053				WHERE t.id_topic > {int:start}
1054					AND t.id_topic <= {int:max_id}
1055				GROUP BY t.id_topic
1056				HAVING COUNT(mu.id_msg) != MAX(t.unapproved_posts)',
1057				array(
1058					'not_approved' => 0,
1059					'start' => $_REQUEST['start'],
1060					'max_id' => $_REQUEST['start'] + $increment,
1061				)
1062			);
1063			while ($row = $smcFunc['db_fetch_assoc']($request))
1064				$smcFunc['db_query']('', '
1065					UPDATE {db_prefix}topics
1066					SET unapproved_posts = {int:unapproved_posts}
1067					WHERE id_topic = {int:id_topic}',
1068					array(
1069						'unapproved_posts' => $row['real_unapproved_posts'],
1070						'id_topic' => $row['id_topic'],
1071					)
1072				);
1073			$smcFunc['db_free_result']($request);
1074
1075			$_REQUEST['start'] += $increment;
1076
1077			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1078			{
1079				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=0;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1080				$context['continue_percent'] = round((100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1081
1082				return;
1083			}
1084		}
1085
1086		$_REQUEST['start'] = 0;
1087	}
1088
1089	// Update the post count of each board.
1090	if ($_REQUEST['step'] <= 1)
1091	{
1092		if (empty($_REQUEST['start']))
1093			$smcFunc['db_query']('', '
1094				UPDATE {db_prefix}boards
1095				SET num_posts = {int:num_posts}
1096				WHERE redirect = {string:redirect}',
1097				array(
1098					'num_posts' => 0,
1099					'redirect' => '',
1100				)
1101			);
1102
1103		while ($_REQUEST['start'] < $max_topics)
1104		{
1105			$request = $smcFunc['db_query']('', '
1106				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_num_posts
1107				FROM {db_prefix}messages AS m
1108				WHERE m.id_topic > {int:id_topic_min}
1109					AND m.id_topic <= {int:id_topic_max}
1110					AND m.approved = {int:is_approved}
1111				GROUP BY m.id_board',
1112				array(
1113					'id_topic_min' => $_REQUEST['start'],
1114					'id_topic_max' => $_REQUEST['start'] + $increment,
1115					'is_approved' => 1,
1116				)
1117			);
1118			while ($row = $smcFunc['db_fetch_assoc']($request))
1119				$smcFunc['db_query']('', '
1120					UPDATE {db_prefix}boards
1121					SET num_posts = num_posts + {int:real_num_posts}
1122					WHERE id_board = {int:id_board}',
1123					array(
1124						'id_board' => $row['id_board'],
1125						'real_num_posts' => $row['real_num_posts'],
1126					)
1127				);
1128			$smcFunc['db_free_result']($request);
1129
1130			$_REQUEST['start'] += $increment;
1131
1132			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1133			{
1134				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=1;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1135				$context['continue_percent'] = round((200 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1136
1137				return;
1138			}
1139		}
1140
1141		$_REQUEST['start'] = 0;
1142	}
1143
1144	// Update the topic count of each board.
1145	if ($_REQUEST['step'] <= 2)
1146	{
1147		if (empty($_REQUEST['start']))
1148			$smcFunc['db_query']('', '
1149				UPDATE {db_prefix}boards
1150				SET num_topics = {int:num_topics}',
1151				array(
1152					'num_topics' => 0,
1153				)
1154			);
1155
1156		while ($_REQUEST['start'] < $max_topics)
1157		{
1158			$request = $smcFunc['db_query']('', '
1159				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_num_topics
1160				FROM {db_prefix}topics AS t
1161				WHERE t.approved = {int:is_approved}
1162					AND t.id_topic > {int:id_topic_min}
1163					AND t.id_topic <= {int:id_topic_max}
1164				GROUP BY t.id_board',
1165				array(
1166					'is_approved' => 1,
1167					'id_topic_min' => $_REQUEST['start'],
1168					'id_topic_max' => $_REQUEST['start'] + $increment,
1169				)
1170			);
1171			while ($row = $smcFunc['db_fetch_assoc']($request))
1172				$smcFunc['db_query']('', '
1173					UPDATE {db_prefix}boards
1174					SET num_topics = num_topics + {int:real_num_topics}
1175					WHERE id_board = {int:id_board}',
1176					array(
1177						'id_board' => $row['id_board'],
1178						'real_num_topics' => $row['real_num_topics'],
1179					)
1180				);
1181			$smcFunc['db_free_result']($request);
1182
1183			$_REQUEST['start'] += $increment;
1184
1185			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1186			{
1187				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=2;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1188				$context['continue_percent'] = round((300 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1189
1190				return;
1191			}
1192		}
1193
1194		$_REQUEST['start'] = 0;
1195	}
1196
1197	// Update the unapproved post count of each board.
1198	if ($_REQUEST['step'] <= 3)
1199	{
1200		if (empty($_REQUEST['start']))
1201			$smcFunc['db_query']('', '
1202				UPDATE {db_prefix}boards
1203				SET unapproved_posts = {int:unapproved_posts}',
1204				array(
1205					'unapproved_posts' => 0,
1206				)
1207			);
1208
1209		while ($_REQUEST['start'] < $max_topics)
1210		{
1211			$request = $smcFunc['db_query']('', '
1212				SELECT /*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_unapproved_posts
1213				FROM {db_prefix}messages AS m
1214				WHERE m.id_topic > {int:id_topic_min}
1215					AND m.id_topic <= {int:id_topic_max}
1216					AND m.approved = {int:is_approved}
1217				GROUP BY m.id_board',
1218				array(
1219					'id_topic_min' => $_REQUEST['start'],
1220					'id_topic_max' => $_REQUEST['start'] + $increment,
1221					'is_approved' => 0,
1222				)
1223			);
1224			while ($row = $smcFunc['db_fetch_assoc']($request))
1225				$smcFunc['db_query']('', '
1226					UPDATE {db_prefix}boards
1227					SET unapproved_posts = unapproved_posts + {int:unapproved_posts}
1228					WHERE id_board = {int:id_board}',
1229					array(
1230						'id_board' => $row['id_board'],
1231						'unapproved_posts' => $row['real_unapproved_posts'],
1232					)
1233				);
1234			$smcFunc['db_free_result']($request);
1235
1236			$_REQUEST['start'] += $increment;
1237
1238			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1239			{
1240				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=3;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1241				$context['continue_percent'] = round((400 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1242
1243				return;
1244			}
1245		}
1246
1247		$_REQUEST['start'] = 0;
1248	}
1249
1250	// Update the unapproved topic count of each board.
1251	if ($_REQUEST['step'] <= 4)
1252	{
1253		if (empty($_REQUEST['start']))
1254			$smcFunc['db_query']('', '
1255				UPDATE {db_prefix}boards
1256				SET unapproved_topics = {int:unapproved_topics}',
1257				array(
1258					'unapproved_topics' => 0,
1259				)
1260			);
1261
1262		while ($_REQUEST['start'] < $max_topics)
1263		{
1264			$request = $smcFunc['db_query']('', '
1265				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_unapproved_topics
1266				FROM {db_prefix}topics AS t
1267				WHERE t.approved = {int:is_approved}
1268					AND t.id_topic > {int:id_topic_min}
1269					AND t.id_topic <= {int:id_topic_max}
1270				GROUP BY t.id_board',
1271				array(
1272					'is_approved' => 0,
1273					'id_topic_min' => $_REQUEST['start'],
1274					'id_topic_max' => $_REQUEST['start'] + $increment,
1275				)
1276			);
1277			while ($row = $smcFunc['db_fetch_assoc']($request))
1278				$smcFunc['db_query']('', '
1279					UPDATE {db_prefix}boards
1280					SET unapproved_topics = unapproved_topics + {int:real_unapproved_topics}
1281					WHERE id_board = {int:id_board}',
1282					array(
1283						'id_board' => $row['id_board'],
1284						'real_unapproved_topics' => $row['real_unapproved_topics'],
1285					)
1286				);
1287			$smcFunc['db_free_result']($request);
1288
1289			$_REQUEST['start'] += $increment;
1290
1291			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1292			{
1293				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=4;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1294				$context['continue_percent'] = round((500 + 100 * $_REQUEST['start'] / $max_topics) / $total_steps);
1295
1296				return;
1297			}
1298		}
1299
1300		$_REQUEST['start'] = 0;
1301	}
1302
1303	// Get all members with wrong number of personal messages.
1304	if ($_REQUEST['step'] <= 5)
1305	{
1306		$request = $smcFunc['db_query']('', '
1307			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1308				MAX(mem.instant_messages) AS instant_messages
1309			FROM {db_prefix}members AS mem
1310				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted})
1311			GROUP BY mem.id_member
1312			HAVING COUNT(pmr.id_pm) != MAX(mem.instant_messages)',
1313			array(
1314				'is_not_deleted' => 0,
1315			)
1316		);
1317		while ($row = $smcFunc['db_fetch_assoc']($request))
1318			updateMemberData($row['id_member'], array('instant_messages' => $row['real_num']));
1319		$smcFunc['db_free_result']($request);
1320
1321		$request = $smcFunc['db_query']('', '
1322			SELECT /*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
1323				MAX(mem.unread_messages) AS unread_messages
1324			FROM {db_prefix}members AS mem
1325				LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted} AND pmr.is_read = {int:is_not_read})
1326			GROUP BY mem.id_member
1327			HAVING COUNT(pmr.id_pm) != MAX(mem.unread_messages)',
1328			array(
1329				'is_not_deleted' => 0,
1330				'is_not_read' => 0,
1331			)
1332		);
1333		while ($row = $smcFunc['db_fetch_assoc']($request))
1334			updateMemberData($row['id_member'], array('unread_messages' => $row['real_num']));
1335		$smcFunc['db_free_result']($request);
1336
1337		if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1338		{
1339			$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=0;' . $context['session_var'] . '=' . $context['session_id'];
1340			$context['continue_percent'] = round(700 / $total_steps);
1341
1342			return;
1343		}
1344	}
1345
1346	// Any messages pointing to the wrong board?
1347	if ($_REQUEST['step'] <= 6)
1348	{
1349		while ($_REQUEST['start'] < $modSettings['maxMsgID'])
1350		{
1351			$request = $smcFunc['db_query']('', '
1352				SELECT /*!40001 SQL_NO_CACHE */ t.id_board, m.id_msg
1353				FROM {db_prefix}messages AS m
1354					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.id_board != m.id_board)
1355				WHERE m.id_msg > {int:id_msg_min}
1356					AND m.id_msg <= {int:id_msg_max}',
1357				array(
1358					'id_msg_min' => $_REQUEST['start'],
1359					'id_msg_max' => $_REQUEST['start'] + $increment,
1360				)
1361			);
1362			$boards = array();
1363			while ($row = $smcFunc['db_fetch_assoc']($request))
1364				$boards[$row['id_board']][] = $row['id_msg'];
1365			$smcFunc['db_free_result']($request);
1366
1367			foreach ($boards as $board_id => $messages)
1368				$smcFunc['db_query']('', '
1369					UPDATE {db_prefix}messages
1370					SET id_board = {int:id_board}
1371					WHERE id_msg IN ({array_int:id_msg_array})',
1372					array(
1373						'id_msg_array' => $messages,
1374						'id_board' => $board_id,
1375					)
1376				);
1377
1378			$_REQUEST['start'] += $increment;
1379
1380			if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)) > 3)
1381			{
1382				$context['continue_get_data'] = '?action=admin;area=maintain;sa=routine;activity=recount;step=6;start=' . $_REQUEST['start'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1383				$context['continue_percent'] = round((700 + 100 * $_REQUEST['start'] / $modSettings['maxMsgID']) / $total_steps);
1384
1385				return;
1386			}
1387		}
1388
1389		$_REQUEST['start'] = 0;
1390	}
1391
1392	// Update the latest message of each board.
1393	$request = $smcFunc['db_query']('', '
1394		SELECT m.id_board, MAX(m.id_msg) AS local_last_msg
1395		FROM {db_prefix}messages AS m
1396		WHERE m.approved = {int:is_approved}
1397		GROUP BY m.id_board',
1398		array(
1399			'is_approved' => 1,
1400		)
1401	);
1402	$realBoardCounts = array();
1403	while ($row = $smcFunc['db_fetch_assoc']($request))
1404		$realBoardCounts[$row['id_board']] = $row['local_last_msg'];
1405	$smcFunc['db_free_result']($request);
1406
1407	$request = $smcFunc['db_query']('', '
1408		SELECT /*!40001 SQL_NO_CACHE */ id_board, id_parent, id_last_msg, child_level, id_msg_updated
1409		FROM {db_prefix}boards',
1410		array(
1411		)
1412	);
1413	$resort_me = array();
1414	while ($row = $smcFunc['db_fetch_assoc']($request))
1415	{
1416		$row['local_last_msg'] = isset($realBoardCounts[$row['id_board']]) ? $realBoardCounts[$row['id_board']] : 0;
1417		$resort_me[$row['child_level']][] = $row;
1418	}
1419	$smcFunc['db_free_result']($request);
1420
1421	krsort($resort_me);
1422
1423	$lastModifiedMsg = array();
1424	foreach ($resort_me as $rows)
1425		foreach ($rows as $row)
1426		{
1427			// The latest message is the latest of the current board and its children.
1428			if (isset($lastModifiedMsg[$row['id_board']]))
1429				$curLastModifiedMsg = max($row['local_last_msg'], $lastModifiedMsg[$row['id_board']]);
1430			else
1431				$curLastModifiedMsg = $row['local_last_msg'];
1432
1433			// If what is and what should be the latest message differ, an update is necessary.
1434			if ($row['local_last_msg'] != $row['id_last_msg'] || $curLastModifiedMsg != $row['id_msg_updated'])
1435				$smcFunc['db_query']('', '
1436					UPDATE {db_prefix}boards
1437					SET id_last_msg = {int:id_last_msg}, id_msg_updated = {int:id_msg_updated}
1438					WHERE id_board = {int:id_board}',
1439					array(
1440						'id_last_msg' => $row['local_last_msg'],
1441						'id_msg_updated' => $curLastModifiedMsg,
1442						'id_board' => $row['id_board'],
1443					)
1444				);
1445
1446			// Parent boards inherit the latest modified message of their children.
1447			if (isset($lastModifiedMsg[$row['id_parent']]))
1448				$lastModifiedMsg[$row['id_parent']] = max($row['local_last_msg'], $lastModifiedMsg[$row['id_parent']]);
1449			else
1450				$lastModifiedMsg[$row['id_parent']] = $row['local_last_msg'];
1451		}
1452
1453	// Update all the basic statistics.
1454	updateStats('member');
1455	updateStats('m…

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