annotate forum/Sources/ManageSearch.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
Chris@76 12 */
Chris@76 13
Chris@76 14 if (!defined('SMF'))
Chris@76 15 die('Hacking attempt...');
Chris@76 16
Chris@76 17 /* The admin screen to change the search settings.
Chris@76 18
Chris@76 19 void ManageSearch()
Chris@76 20 - main entry point for the admin search settings screen.
Chris@76 21 - called by ?action=admin;area=managesearch.
Chris@76 22 - requires the admin_forum permission.
Chris@76 23 - loads the ManageSearch template.
Chris@76 24 - loads the Search language file.
Chris@76 25 - calls a function based on the given sub-action.
Chris@76 26 - defaults to sub-action 'settings'.
Chris@76 27
Chris@76 28 void EditSearchSettings()
Chris@76 29 - edit some general settings related to the search function.
Chris@76 30 - called by ?action=admin;area=managesearch;sa=settings.
Chris@76 31 - requires the admin_forum permission.
Chris@76 32 - uses the 'modify_settings' sub template of the ManageSearch template.
Chris@76 33
Chris@76 34 void EditWeights()
Chris@76 35 - edit the relative weight of the search factors.
Chris@76 36 - called by ?action=admin;area=managesearch;sa=weights.
Chris@76 37 - requires the admin_forum permission.
Chris@76 38 - uses the 'modify_weights' sub template of the ManageSearch template.
Chris@76 39
Chris@76 40 void EditSearchMethod()
Chris@76 41 - edit the search method and search index used.
Chris@76 42 - called by ?action=admin;area=managesearch;sa=method.
Chris@76 43 - requires the admin_forum permission.
Chris@76 44 - uses the 'select_search_method' sub template of the ManageSearch
Chris@76 45 template.
Chris@76 46 - allows to create and delete a fulltext index on the messages table.
Chris@76 47 - allows to delete a custom index (that CreateMessageIndex() created).
Chris@76 48 - calculates the size of the current search indexes in use.
Chris@76 49
Chris@76 50 void CreateMessageIndex()
Chris@76 51 - create a custom search index for the messages table.
Chris@76 52 - called by ?action=admin;area=managesearch;sa=createmsgindex.
Chris@76 53 - linked from the EditSearchMethod screen.
Chris@76 54 - requires the admin_forum permission.
Chris@76 55 - uses the 'create_index', 'create_index_progress', and
Chris@76 56 'create_index_done' sub templates of the ManageSearch template.
Chris@76 57 - depending on the size of the message table, the process is divided
Chris@76 58 in steps.
Chris@76 59
Chris@76 60 array loadSearchAPIs()
Chris@76 61 - get the installed APIs.
Chris@76 62
Chris@76 63 */
Chris@76 64
Chris@76 65 function ManageSearch()
Chris@76 66 {
Chris@76 67 global $context, $txt, $scripturl;
Chris@76 68
Chris@76 69 isAllowedTo('admin_forum');
Chris@76 70
Chris@76 71 loadLanguage('Search');
Chris@76 72 loadTemplate('ManageSearch');
Chris@76 73
Chris@76 74 db_extend('search');
Chris@76 75
Chris@76 76 $subActions = array(
Chris@76 77 'settings' => 'EditSearchSettings',
Chris@76 78 'weights' => 'EditWeights',
Chris@76 79 'method' => 'EditSearchMethod',
Chris@76 80 'createfulltext' => 'EditSearchMethod',
Chris@76 81 'removecustom' => 'EditSearchMethod',
Chris@76 82 'removefulltext' => 'EditSearchMethod',
Chris@76 83 'createmsgindex' => 'CreateMessageIndex',
Chris@76 84 );
Chris@76 85
Chris@76 86 // Default the sub-action to 'edit search settings'.
Chris@76 87 $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'weights';
Chris@76 88
Chris@76 89 $context['sub_action'] = $_REQUEST['sa'];
Chris@76 90
Chris@76 91 // Create the tabs for the template.
Chris@76 92 $context[$context['admin_menu_name']]['tab_data'] = array(
Chris@76 93 'title' => $txt['manage_search'],
Chris@76 94 'help' => 'search',
Chris@76 95 'description' => $txt['search_settings_desc'],
Chris@76 96 'tabs' => array(
Chris@76 97 'weights' => array(
Chris@76 98 'description' => $txt['search_weights_desc'],
Chris@76 99 ),
Chris@76 100 'method' => array(
Chris@76 101 'description' => $txt['search_method_desc'],
Chris@76 102 ),
Chris@76 103 'settings' => array(
Chris@76 104 'description' => $txt['search_settings_desc'],
Chris@76 105 ),
Chris@76 106 ),
Chris@76 107 );
Chris@76 108
Chris@76 109 // Call the right function for this sub-acton.
Chris@76 110 $subActions[$_REQUEST['sa']]();
Chris@76 111 }
Chris@76 112
Chris@76 113 function EditSearchSettings($return_config = false)
Chris@76 114 {
Chris@76 115 global $txt, $context, $scripturl, $sourcedir, $modSettings;
Chris@76 116
Chris@76 117 // What are we editing anyway?
Chris@76 118 $config_vars = array(
Chris@76 119 // Permission...
Chris@76 120 array('permissions', 'search_posts'),
Chris@76 121 // Some simple settings.
Chris@76 122 array('check', 'simpleSearch'),
Chris@76 123 array('int', 'search_results_per_page'),
Chris@76 124 array('int', 'search_max_results', 'subtext' => $txt['search_max_results_disable']),
Chris@76 125 '',
Chris@76 126 // Some limitations.
Chris@76 127 array('int', 'search_floodcontrol_time', 'subtext' => $txt['search_floodcontrol_time_desc']),
Chris@76 128 );
Chris@76 129
Chris@76 130 // Perhaps the search method wants to add some settings?
Chris@76 131 $modSettings['search_index'] = empty($modSettings['search_index']) ? 'standard' : $modSettings['search_index'];
Chris@76 132 if (file_exists($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php'))
Chris@76 133 {
Chris@76 134 loadClassFile('SearchAPI-' . ucwords($modSettings['search_index']) . '.php');
Chris@76 135
Chris@76 136 $method_call = array($modSettings['search_index'] . '_search', 'searchSettings');
Chris@76 137 if (is_callable($method_call))
Chris@76 138 call_user_func_array($method_call, array(&$config_vars));
Chris@76 139 }
Chris@76 140
Chris@76 141 if ($return_config)
Chris@76 142 return $config_vars;
Chris@76 143
Chris@76 144 $context['page_title'] = $txt['search_settings_title'];
Chris@76 145 $context['sub_template'] = 'show_settings';
Chris@76 146
Chris@76 147 // We'll need this for the settings.
Chris@76 148 require_once($sourcedir . '/ManageServer.php');
Chris@76 149
Chris@76 150 // A form was submitted.
Chris@76 151 if (isset($_REQUEST['save']))
Chris@76 152 {
Chris@76 153 checkSession();
Chris@76 154
Chris@76 155 saveDBSettings($config_vars);
Chris@76 156 redirectexit('action=admin;area=managesearch;sa=settings;' . $context['session_var'] . '=' . $context['session_id']);
Chris@76 157 }
Chris@76 158
Chris@76 159 // Prep the template!
Chris@76 160 $context['post_url'] = $scripturl . '?action=admin;area=managesearch;save;sa=settings';
Chris@76 161 $context['settings_title'] = $txt['search_settings_title'];
Chris@76 162
Chris@76 163 prepareDBSettingContext($config_vars);
Chris@76 164 }
Chris@76 165
Chris@76 166 function EditWeights()
Chris@76 167 {
Chris@76 168 global $txt, $context, $modSettings;
Chris@76 169
Chris@76 170 $context['page_title'] = $txt['search_weights_title'];
Chris@76 171 $context['sub_template'] = 'modify_weights';
Chris@76 172
Chris@76 173 $factors = array(
Chris@76 174 'search_weight_frequency',
Chris@76 175 'search_weight_age',
Chris@76 176 'search_weight_length',
Chris@76 177 'search_weight_subject',
Chris@76 178 'search_weight_first_message',
Chris@76 179 'search_weight_sticky',
Chris@76 180 );
Chris@76 181
Chris@76 182 // A form was submitted.
Chris@76 183 if (isset($_POST['save']))
Chris@76 184 {
Chris@76 185 checkSession();
Chris@76 186
Chris@76 187 $changes = array();
Chris@76 188 foreach ($factors as $factor)
Chris@76 189 $changes[$factor] = (int) $_POST[$factor];
Chris@76 190 updateSettings($changes);
Chris@76 191 }
Chris@76 192
Chris@76 193 $context['relative_weights'] = array('total' => 0);
Chris@76 194 foreach ($factors as $factor)
Chris@76 195 $context['relative_weights']['total'] += isset($modSettings[$factor]) ? $modSettings[$factor] : 0;
Chris@76 196
Chris@76 197 foreach ($factors as $factor)
Chris@76 198 $context['relative_weights'][$factor] = round(100 * (isset($modSettings[$factor]) ? $modSettings[$factor] : 0) / $context['relative_weights']['total'], 1);
Chris@76 199 }
Chris@76 200
Chris@76 201 function EditSearchMethod()
Chris@76 202 {
Chris@76 203 global $txt, $context, $modSettings, $smcFunc, $db_type, $db_prefix;
Chris@76 204
Chris@76 205 $context[$context['admin_menu_name']]['current_subsection'] = 'method';
Chris@76 206 $context['page_title'] = $txt['search_method_title'];
Chris@76 207 $context['sub_template'] = 'select_search_method';
Chris@76 208 $context['supports_fulltext'] = $smcFunc['db_search_support']('fulltext');
Chris@76 209
Chris@76 210 // Load any apis.
Chris@76 211 $context['search_apis'] = loadSearchAPIs();
Chris@76 212
Chris@76 213 // Detect whether a fulltext index is set.
Chris@76 214 if ($context['supports_fulltext'])
Chris@76 215 {
Chris@76 216 $request = $smcFunc['db_query']('', '
Chris@76 217 SHOW INDEX
Chris@76 218 FROM {db_prefix}messages',
Chris@76 219 array(
Chris@76 220 )
Chris@76 221 );
Chris@76 222 $context['fulltext_index'] = '';
Chris@76 223 if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
Chris@76 224 {
Chris@76 225 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 226 if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
Chris@76 227 $context['fulltext_index'][] = $row['Key_name'];
Chris@76 228 $smcFunc['db_free_result']($request);
Chris@76 229
Chris@76 230 if (is_array($context['fulltext_index']))
Chris@76 231 $context['fulltext_index'] = array_unique($context['fulltext_index']);
Chris@76 232 }
Chris@76 233
Chris@76 234 $request = $smcFunc['db_query']('', '
Chris@76 235 SHOW COLUMNS
Chris@76 236 FROM {db_prefix}messages',
Chris@76 237 array(
Chris@76 238 )
Chris@76 239 );
Chris@76 240 if ($request !== false)
Chris@76 241 {
Chris@76 242 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 243 if ($row['Field'] == 'body' && $row['Type'] == 'mediumtext')
Chris@76 244 $context['cannot_create_fulltext'] = true;
Chris@76 245 $smcFunc['db_free_result']($request);
Chris@76 246 }
Chris@76 247
Chris@76 248 if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0)
Chris@76 249 $request = $smcFunc['db_query']('', '
Chris@76 250 SHOW TABLE STATUS
Chris@76 251 FROM {string:database_name}
Chris@76 252 LIKE {string:table_name}',
Chris@76 253 array(
Chris@76 254 'database_name' => '`' . strtr($match[1], array('`' => '')) . '`',
Chris@76 255 'table_name' => str_replace('_', '\_', $match[2]) . 'messages',
Chris@76 256 )
Chris@76 257 );
Chris@76 258 else
Chris@76 259 $request = $smcFunc['db_query']('', '
Chris@76 260 SHOW TABLE STATUS
Chris@76 261 LIKE {string:table_name}',
Chris@76 262 array(
Chris@76 263 'table_name' => str_replace('_', '\_', $db_prefix) . 'messages',
Chris@76 264 )
Chris@76 265 );
Chris@76 266
Chris@76 267 if ($request !== false)
Chris@76 268 {
Chris@76 269 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 270 if ((isset($row['Type']) && strtolower($row['Type']) != 'myisam') || (isset($row['Engine']) && strtolower($row['Engine']) != 'myisam'))
Chris@76 271 $context['cannot_create_fulltext'] = true;
Chris@76 272 $smcFunc['db_free_result']($request);
Chris@76 273 }
Chris@76 274 }
Chris@76 275
Chris@76 276 if (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'createfulltext')
Chris@76 277 {
Chris@76 278 checkSession('get');
Chris@76 279
Chris@76 280 // Make sure it's gone before creating it.
Chris@76 281 $smcFunc['db_query']('', '
Chris@76 282 ALTER TABLE {db_prefix}messages
Chris@76 283 DROP INDEX body',
Chris@76 284 array(
Chris@76 285 'db_error_skip' => true,
Chris@76 286 )
Chris@76 287 );
Chris@76 288
Chris@76 289 $smcFunc['db_query']('', '
Chris@76 290 ALTER TABLE {db_prefix}messages
Chris@76 291 ADD FULLTEXT body (body)',
Chris@76 292 array(
Chris@76 293 )
Chris@76 294 );
Chris@76 295
Chris@76 296 $context['fulltext_index'] = 'body';
Chris@76 297 }
Chris@76 298 elseif (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'removefulltext' && !empty($context['fulltext_index']))
Chris@76 299 {
Chris@76 300 checkSession('get');
Chris@76 301
Chris@76 302 $smcFunc['db_query']('', '
Chris@76 303 ALTER TABLE {db_prefix}messages
Chris@76 304 DROP INDEX ' . implode(',
Chris@76 305 DROP INDEX ', $context['fulltext_index']),
Chris@76 306 array(
Chris@76 307 'db_error_skip' => true,
Chris@76 308 )
Chris@76 309 );
Chris@76 310
Chris@76 311 $context['fulltext_index'] = '';
Chris@76 312
Chris@76 313 // Go back to the default search method.
Chris@76 314 if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'fulltext')
Chris@76 315 updateSettings(array(
Chris@76 316 'search_index' => '',
Chris@76 317 ));
Chris@76 318 }
Chris@76 319 elseif (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'removecustom')
Chris@76 320 {
Chris@76 321 checkSession('get');
Chris@76 322
Chris@76 323 db_extend();
Chris@76 324 $tables = $smcFunc['db_list_tables'](false, $db_prefix . 'log_search_words');
Chris@76 325 if (!empty($tables))
Chris@76 326 {
Chris@76 327 $smcFunc['db_search_query']('drop_words_table', '
Chris@76 328 DROP TABLE {db_prefix}log_search_words',
Chris@76 329 array(
Chris@76 330 )
Chris@76 331 );
Chris@76 332 }
Chris@76 333
Chris@76 334 updateSettings(array(
Chris@76 335 'search_custom_index_config' => '',
Chris@76 336 'search_custom_index_resume' => '',
Chris@76 337 ));
Chris@76 338
Chris@76 339 // Go back to the default search method.
Chris@76 340 if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'custom')
Chris@76 341 updateSettings(array(
Chris@76 342 'search_index' => '',
Chris@76 343 ));
Chris@76 344 }
Chris@76 345 elseif (isset($_POST['save']))
Chris@76 346 {
Chris@76 347 checkSession();
Chris@76 348 updateSettings(array(
Chris@76 349 'search_index' => empty($_POST['search_index']) || (!in_array($_POST['search_index'], array('fulltext', 'custom')) && !isset($context['search_apis'][$_POST['search_index']])) ? '' : $_POST['search_index'],
Chris@76 350 'search_force_index' => isset($_POST['search_force_index']) ? '1' : '0',
Chris@76 351 'search_match_words' => isset($_POST['search_match_words']) ? '1' : '0',
Chris@76 352 ));
Chris@76 353 }
Chris@76 354
Chris@76 355 $context['table_info'] = array(
Chris@76 356 'data_length' => 0,
Chris@76 357 'index_length' => 0,
Chris@76 358 'fulltext_length' => 0,
Chris@76 359 'custom_index_length' => 0,
Chris@76 360 );
Chris@76 361
Chris@76 362 // Get some info about the messages table, to show its size and index size.
Chris@76 363 if ($db_type == 'mysql')
Chris@76 364 {
Chris@76 365 if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0)
Chris@76 366 $request = $smcFunc['db_query']('', '
Chris@76 367 SHOW TABLE STATUS
Chris@76 368 FROM {string:database_name}
Chris@76 369 LIKE {string:table_name}',
Chris@76 370 array(
Chris@76 371 'database_name' => '`' . strtr($match[1], array('`' => '')) . '`',
Chris@76 372 'table_name' => str_replace('_', '\_', $match[2]) . 'messages',
Chris@76 373 )
Chris@76 374 );
Chris@76 375 else
Chris@76 376 $request = $smcFunc['db_query']('', '
Chris@76 377 SHOW TABLE STATUS
Chris@76 378 LIKE {string:table_name}',
Chris@76 379 array(
Chris@76 380 'table_name' => str_replace('_', '\_', $db_prefix) . 'messages',
Chris@76 381 )
Chris@76 382 );
Chris@76 383 if ($request !== false && $smcFunc['db_num_rows']($request) == 1)
Chris@76 384 {
Chris@76 385 // Only do this if the user has permission to execute this query.
Chris@76 386 $row = $smcFunc['db_fetch_assoc']($request);
Chris@76 387 $context['table_info']['data_length'] = $row['Data_length'];
Chris@76 388 $context['table_info']['index_length'] = $row['Index_length'];
Chris@76 389 $context['table_info']['fulltext_length'] = $row['Index_length'];
Chris@76 390 $smcFunc['db_free_result']($request);
Chris@76 391 }
Chris@76 392
Chris@76 393 // Now check the custom index table, if it exists at all.
Chris@76 394 if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0)
Chris@76 395 $request = $smcFunc['db_query']('', '
Chris@76 396 SHOW TABLE STATUS
Chris@76 397 FROM {string:database_name}
Chris@76 398 LIKE {string:table_name}',
Chris@76 399 array(
Chris@76 400 'database_name' => '`' . strtr($match[1], array('`' => '')) . '`',
Chris@76 401 'table_name' => str_replace('_', '\_', $match[2]) . 'log_search_words',
Chris@76 402 )
Chris@76 403 );
Chris@76 404 else
Chris@76 405 $request = $smcFunc['db_query']('', '
Chris@76 406 SHOW TABLE STATUS
Chris@76 407 LIKE {string:table_name}',
Chris@76 408 array(
Chris@76 409 'table_name' => str_replace('_', '\_', $db_prefix) . 'log_search_words',
Chris@76 410 )
Chris@76 411 );
Chris@76 412 if ($request !== false && $smcFunc['db_num_rows']($request) == 1)
Chris@76 413 {
Chris@76 414 // Only do this if the user has permission to execute this query.
Chris@76 415 $row = $smcFunc['db_fetch_assoc']($request);
Chris@76 416 $context['table_info']['index_length'] += $row['Data_length'] + $row['Index_length'];
Chris@76 417 $context['table_info']['custom_index_length'] = $row['Data_length'] + $row['Index_length'];
Chris@76 418 $smcFunc['db_free_result']($request);
Chris@76 419 }
Chris@76 420 }
Chris@76 421 elseif ($db_type == 'postgresql')
Chris@76 422 {
Chris@76 423 // In order to report the sizes correctly we need to perform vacuum (optimize) on the tables we will be using.
Chris@76 424 db_extend();
Chris@76 425 $temp_tables = $smcFunc['db_list_tables']();
Chris@76 426 foreach ($temp_tables as $table)
Chris@76 427 if ($table == $db_prefix. 'messages' || $table == $db_prefix. 'log_search_words')
Chris@76 428 $smcFunc['db_optimize_table']($table);
Chris@76 429
Chris@76 430 // PostGreSql has some hidden sizes.
Chris@76 431 $request = $smcFunc['db_query']('', '
Chris@76 432 SELECT relname, relpages * 8 *1024 AS "KB" FROM pg_class
Chris@76 433 WHERE relname = {string:messages} OR relname = {string:log_search_words}
Chris@76 434 ORDER BY relpages DESC',
Chris@76 435 array(
Chris@76 436 'messages' => $db_prefix. 'messages',
Chris@76 437 'log_search_words' => $db_prefix. 'log_search_words',
Chris@76 438 )
Chris@76 439 );
Chris@76 440
Chris@76 441 if ($request !== false && $smcFunc['db_num_rows']($request) > 0)
Chris@76 442 {
Chris@76 443 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 444 {
Chris@76 445 if ($row['relname'] == $db_prefix . 'messages')
Chris@76 446 {
Chris@76 447 $context['table_info']['data_length'] = (int) $row['KB'];
Chris@76 448 $context['table_info']['index_length'] = (int) $row['KB'];
Chris@76 449 // Doesn't support fulltext
Chris@76 450 $context['table_info']['fulltext_length'] = $txt['not_applicable'];
Chris@76 451 }
Chris@76 452 elseif ($row['relname'] == $db_prefix. 'log_search_words')
Chris@76 453 {
Chris@76 454 $context['table_info']['index_length'] = (int) $row['KB'];
Chris@76 455 $context['table_info']['custom_index_length'] = (int) $row['KB'];
Chris@76 456 }
Chris@76 457 }
Chris@76 458 $smcFunc['db_free_result']($request);
Chris@76 459 }
Chris@76 460 else
Chris@76 461 // Didn't work for some reason...
Chris@76 462 $context['table_info'] = array(
Chris@76 463 'data_length' => $txt['not_applicable'],
Chris@76 464 'index_length' => $txt['not_applicable'],
Chris@76 465 'fulltext_length' => $txt['not_applicable'],
Chris@76 466 'custom_index_length' => $txt['not_applicable'],
Chris@76 467 );
Chris@76 468 }
Chris@76 469 else
Chris@76 470 $context['table_info'] = array(
Chris@76 471 'data_length' => $txt['not_applicable'],
Chris@76 472 'index_length' => $txt['not_applicable'],
Chris@76 473 'fulltext_length' => $txt['not_applicable'],
Chris@76 474 'custom_index_length' => $txt['not_applicable'],
Chris@76 475 );
Chris@76 476
Chris@76 477 // Format the data and index length in kilobytes.
Chris@76 478 foreach ($context['table_info'] as $type => $size)
Chris@76 479 {
Chris@76 480 // If it's not numeric then just break. This database engine doesn't support size.
Chris@76 481 if (!is_numeric($size))
Chris@76 482 break;
Chris@76 483
Chris@76 484 $context['table_info'][$type] = comma_format($context['table_info'][$type] / 1024) . ' ' . $txt['search_method_kilobytes'];
Chris@76 485 }
Chris@76 486
Chris@76 487 $context['custom_index'] = !empty($modSettings['search_custom_index_config']);
Chris@76 488 $context['partial_custom_index'] = !empty($modSettings['search_custom_index_resume']) && empty($modSettings['search_custom_index_config']);
Chris@76 489 $context['double_index'] = !empty($context['fulltext_index']) && $context['custom_index'];
Chris@76 490 }
Chris@76 491
Chris@76 492 function CreateMessageIndex()
Chris@76 493 {
Chris@76 494 global $modSettings, $context, $smcFunc, $db_prefix, $txt;
Chris@76 495
Chris@76 496 // Scotty, we need more time...
Chris@76 497 @set_time_limit(600);
Chris@76 498 if (function_exists('apache_reset_timeout'))
Chris@76 499 @apache_reset_timeout();
Chris@76 500
Chris@76 501 $context[$context['admin_menu_name']]['current_subsection'] = 'method';
Chris@76 502 $context['page_title'] = $txt['search_index_custom'];
Chris@76 503
Chris@76 504 $messages_per_batch = 50;
Chris@76 505
Chris@76 506 $index_properties = array(
Chris@76 507 2 => array(
Chris@76 508 'column_definition' => 'small',
Chris@76 509 'step_size' => 1000000,
Chris@76 510 ),
Chris@76 511 4 => array(
Chris@76 512 'column_definition' => 'medium',
Chris@76 513 'step_size' => 1000000,
Chris@76 514 'max_size' => 16777215,
Chris@76 515 ),
Chris@76 516 5 => array(
Chris@76 517 'column_definition' => 'large',
Chris@76 518 'step_size' => 100000000,
Chris@76 519 'max_size' => 2000000000,
Chris@76 520 ),
Chris@76 521 );
Chris@76 522
Chris@76 523 if (isset($_REQUEST['resume']) && !empty($modSettings['search_custom_index_resume']))
Chris@76 524 {
Chris@76 525 $context['index_settings'] = unserialize($modSettings['search_custom_index_resume']);
Chris@76 526 $context['start'] = (int) $context['index_settings']['resume_at'];
Chris@76 527 unset($context['index_settings']['resume_at']);
Chris@76 528 $context['step'] = 1;
Chris@76 529 }
Chris@76 530 else
Chris@76 531 {
Chris@76 532 $context['index_settings'] = array(
Chris@76 533 'bytes_per_word' => isset($_REQUEST['bytes_per_word']) && isset($index_properties[$_REQUEST['bytes_per_word']]) ? (int) $_REQUEST['bytes_per_word'] : 2,
Chris@76 534 );
Chris@76 535 $context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
Chris@76 536 $context['step'] = isset($_REQUEST['step']) ? (int) $_REQUEST['step'] : 0;
Chris@76 537 }
Chris@76 538
Chris@76 539 if ($context['step'] !== 0)
Chris@76 540 checkSession('request');
Chris@76 541
Chris@76 542 // Step 0: let the user determine how they like their index.
Chris@76 543 if ($context['step'] === 0)
Chris@76 544 {
Chris@76 545 $context['sub_template'] = 'create_index';
Chris@76 546 }
Chris@76 547
Chris@76 548 // Step 1: insert all the words.
Chris@76 549 if ($context['step'] === 1)
Chris@76 550 {
Chris@76 551 $context['sub_template'] = 'create_index_progress';
Chris@76 552
Chris@76 553 if ($context['start'] === 0)
Chris@76 554 {
Chris@76 555 db_extend();
Chris@76 556 $tables = $smcFunc['db_list_tables'](false, $db_prefix . 'log_search_words');
Chris@76 557 if (!empty($tables))
Chris@76 558 {
Chris@76 559 $smcFunc['db_search_query']('drop_words_table', '
Chris@76 560 DROP TABLE {db_prefix}log_search_words',
Chris@76 561 array(
Chris@76 562 )
Chris@76 563 );
Chris@76 564 }
Chris@76 565
Chris@76 566 $smcFunc['db_create_word_search']($index_properties[$context['index_settings']['bytes_per_word']]['column_definition']);
Chris@76 567
Chris@76 568 // Temporarily switch back to not using a search index.
Chris@76 569 if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'custom')
Chris@76 570 updateSettings(array('search_index' => ''));
Chris@76 571
Chris@76 572 // Don't let simultanious processes be updating the search index.
Chris@76 573 if (!empty($modSettings['search_custom_index_config']))
Chris@76 574 updateSettings(array('search_custom_index_config' => ''));
Chris@76 575 }
Chris@76 576
Chris@76 577 $num_messages = array(
Chris@76 578 'done' => 0,
Chris@76 579 'todo' => 0,
Chris@76 580 );
Chris@76 581
Chris@76 582 $request = $smcFunc['db_query']('', '
Chris@76 583 SELECT id_msg >= {int:starting_id} AS todo, COUNT(*) AS num_messages
Chris@76 584 FROM {db_prefix}messages
Chris@76 585 GROUP BY todo',
Chris@76 586 array(
Chris@76 587 'starting_id' => $context['start'],
Chris@76 588 )
Chris@76 589 );
Chris@76 590 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 591 $num_messages[empty($row['todo']) ? 'done' : 'todo'] = $row['num_messages'];
Chris@76 592
Chris@76 593 if (empty($num_messages['todo']))
Chris@76 594 {
Chris@76 595 $context['step'] = 2;
Chris@76 596 $context['percentage'] = 80;
Chris@76 597 $context['start'] = 0;
Chris@76 598 }
Chris@76 599 else
Chris@76 600 {
Chris@76 601 // Number of seconds before the next step.
Chris@76 602 $stop = time() + 3;
Chris@76 603 while (time() < $stop)
Chris@76 604 {
Chris@76 605 $inserts = array();
Chris@76 606 $request = $smcFunc['db_query']('', '
Chris@76 607 SELECT id_msg, body
Chris@76 608 FROM {db_prefix}messages
Chris@76 609 WHERE id_msg BETWEEN {int:starting_id} AND {int:ending_id}
Chris@76 610 LIMIT {int:limit}',
Chris@76 611 array(
Chris@76 612 'starting_id' => $context['start'],
Chris@76 613 'ending_id' => $context['start'] + $messages_per_batch - 1,
Chris@76 614 'limit' => $messages_per_batch,
Chris@76 615 )
Chris@76 616 );
Chris@76 617 $forced_break = false;
Chris@76 618 $number_processed = 0;
Chris@76 619 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 620 {
Chris@76 621 // In theory it's possible for one of these to take friggin ages so add more timeout protection.
Chris@76 622 if ($stop < time())
Chris@76 623 {
Chris@76 624 $forced_break = true;
Chris@76 625 break;
Chris@76 626 }
Chris@76 627
Chris@76 628 $number_processed++;
Chris@76 629 foreach (text2words($row['body'], $context['index_settings']['bytes_per_word'], true) as $id_word)
Chris@76 630 {
Chris@76 631 $inserts[] = array($id_word, $row['id_msg']);
Chris@76 632 }
Chris@76 633 }
Chris@76 634 $num_messages['done'] += $number_processed;
Chris@76 635 $num_messages['todo'] -= $number_processed;
Chris@76 636 $smcFunc['db_free_result']($request);
Chris@76 637
Chris@76 638 $context['start'] += $forced_break ? $number_processed : $messages_per_batch;
Chris@76 639
Chris@76 640 if (!empty($inserts))
Chris@76 641 $smcFunc['db_insert']('ignore',
Chris@76 642 '{db_prefix}log_search_words',
Chris@76 643 array('id_word' => 'int', 'id_msg' => 'int'),
Chris@76 644 $inserts,
Chris@76 645 array('id_word', 'id_msg')
Chris@76 646 );
Chris@76 647 if ($num_messages['todo'] === 0)
Chris@76 648 {
Chris@76 649 $context['step'] = 2;
Chris@76 650 $context['start'] = 0;
Chris@76 651 break;
Chris@76 652 }
Chris@76 653 else
Chris@76 654 updateSettings(array('search_custom_index_resume' => serialize(array_merge($context['index_settings'], array('resume_at' => $context['start'])))));
Chris@76 655 }
Chris@76 656
Chris@76 657 // Since there are still two steps to go, 90% is the maximum here.
Chris@76 658 $context['percentage'] = round($num_messages['done'] / ($num_messages['done'] + $num_messages['todo']), 3) * 80;
Chris@76 659 }
Chris@76 660 }
Chris@76 661
Chris@76 662 // Step 2: removing the words that occur too often and are of no use.
Chris@76 663 elseif ($context['step'] === 2)
Chris@76 664 {
Chris@76 665 if ($context['index_settings']['bytes_per_word'] < 4)
Chris@76 666 $context['step'] = 3;
Chris@76 667 else
Chris@76 668 {
Chris@76 669 $stop_words = $context['start'] === 0 || empty($modSettings['search_stopwords']) ? array() : explode(',', $modSettings['search_stopwords']);
Chris@76 670 $stop = time() + 3;
Chris@76 671 $context['sub_template'] = 'create_index_progress';
Chris@76 672 $max_messages = ceil(60 * $modSettings['totalMessages'] / 100);
Chris@76 673
Chris@76 674 while (time() < $stop)
Chris@76 675 {
Chris@76 676 $request = $smcFunc['db_query']('', '
Chris@76 677 SELECT id_word, COUNT(id_word) AS num_words
Chris@76 678 FROM {db_prefix}log_search_words
Chris@76 679 WHERE id_word BETWEEN {int:starting_id} AND {int:ending_id}
Chris@76 680 GROUP BY id_word
Chris@76 681 HAVING COUNT(id_word) > {int:minimum_messages}',
Chris@76 682 array(
Chris@76 683 'starting_id' => $context['start'],
Chris@76 684 'ending_id' => $context['start'] + $index_properties[$context['index_settings']['bytes_per_word']]['step_size'] - 1,
Chris@76 685 'minimum_messages' => $max_messages,
Chris@76 686 )
Chris@76 687 );
Chris@76 688 while ($row = $smcFunc['db_fetch_assoc']($request))
Chris@76 689 $stop_words[] = $row['id_word'];
Chris@76 690 $smcFunc['db_free_result']($request);
Chris@76 691
Chris@76 692 updateSettings(array('search_stopwords' => implode(',', $stop_words)));
Chris@76 693
Chris@76 694 if (!empty($stop_words))
Chris@76 695 $smcFunc['db_query']('', '
Chris@76 696 DELETE FROM {db_prefix}log_search_words
Chris@76 697 WHERE id_word in ({array_int:stop_words})',
Chris@76 698 array(
Chris@76 699 'stop_words' => $stop_words,
Chris@76 700 )
Chris@76 701 );
Chris@76 702
Chris@76 703 $context['start'] += $index_properties[$context['index_settings']['bytes_per_word']]['step_size'];
Chris@76 704 if ($context['start'] > $index_properties[$context['index_settings']['bytes_per_word']]['max_size'])
Chris@76 705 {
Chris@76 706 $context['step'] = 3;
Chris@76 707 break;
Chris@76 708 }
Chris@76 709 }
Chris@76 710 $context['percentage'] = 80 + round($context['start'] / $index_properties[$context['index_settings']['bytes_per_word']]['max_size'], 3) * 20;
Chris@76 711 }
Chris@76 712 }
Chris@76 713
Chris@76 714 // Step 3: remove words not distinctive enough.
Chris@76 715 if ($context['step'] === 3)
Chris@76 716 {
Chris@76 717 $context['sub_template'] = 'create_index_done';
Chris@76 718
Chris@76 719 updateSettings(array('search_index' => 'custom', 'search_custom_index_config' => serialize($context['index_settings'])));
Chris@76 720 $smcFunc['db_query']('', '
Chris@76 721 DELETE FROM {db_prefix}settings
Chris@76 722 WHERE variable = {string:search_custom_index_resume}',
Chris@76 723 array(
Chris@76 724 'search_custom_index_resume' => 'search_custom_index_resume',
Chris@76 725 )
Chris@76 726 );
Chris@76 727 }
Chris@76 728 }
Chris@76 729
Chris@76 730 // Get the installed APIs.
Chris@76 731 function loadSearchAPIs()
Chris@76 732 {
Chris@76 733 global $sourcedir, $txt;
Chris@76 734
Chris@76 735 $apis = array();
Chris@76 736 if ($dh = opendir($sourcedir))
Chris@76 737 {
Chris@76 738 while (($file = readdir($dh)) !== false)
Chris@76 739 {
Chris@76 740 if (is_file($sourcedir . '/' . $file) && preg_match('~SearchAPI-([A-Za-z\d_]+)\.php~', $file, $matches))
Chris@76 741 {
Chris@76 742 // Check this is definitely a valid API!
Chris@76 743 $fp = fopen($sourcedir . '/' . $file, 'rb');
Chris@76 744 $header = fread($fp, 4096);
Chris@76 745 fclose($fp);
Chris@76 746
Chris@76 747 if (strpos($header, '* SearchAPI-' . $matches[1] . '.php') !== false)
Chris@76 748 {
Chris@76 749 loadClassFile($file);
Chris@76 750
Chris@76 751 $index_name = strtolower($matches[1]);
Chris@76 752 $search_class_name = $index_name . '_search';
Chris@76 753 $searchAPI = new $search_class_name();
Chris@76 754
Chris@76 755 // No Support? NEXT!
Chris@76 756 if (!$searchAPI->is_supported)
Chris@76 757 continue;
Chris@76 758
Chris@76 759 $apis[$index_name] = array(
Chris@76 760 'filename' => $file,
Chris@76 761 'setting_index' => $index_name,
Chris@76 762 'has_template' => in_array($index_name, array('custom', 'fulltext', 'standard')),
Chris@76 763 'label' => $index_name && isset($txt['search_index_' . $index_name]) ? $txt['search_index_' . $index_name] : '',
Chris@76 764 'desc' => $index_name && isset($txt['search_index_' . $index_name . '_desc']) ? $txt['search_index_' . $index_name . '_desc'] : '',
Chris@76 765 );
Chris@76 766 }
Chris@76 767 }
Chris@76 768 }
Chris@76 769 }
Chris@76 770 closedir($dh);
Chris@76 771
Chris@76 772 return $apis;
Chris@76 773 }
Chris@76 774
Chris@76 775 ?>