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