PageRenderTime 172ms CodeModel.GetById 94ms app.highlight 66ms RepoModel.GetById 1ms app.codeStats 1ms

/forum/Sources/ManageMaintenance.php

https://github.com/leftnode/nooges.com
PHP | 1858 lines | 1333 code | 239 blank | 286 comment | 168 complexity | 74a2f2e8265b5e0587d59296f59d2903 MD5 | raw file

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

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

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