annotate forum/Sources/ManageMaintenance.php @ 85:6d7b61434be7 website

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