function shd_search2() { global $context, $smcFunc, $txt, $modSettings, $scripturl, $sourcedir; shd_is_allowed_to('shd_search', 0); if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search']) { fatal_lang_error('loadavg_search_disabled', false); } // No, no, no... this is a bit hard on the server, so don't you go prefetching it! if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') { ob_end_clean(); header('HTTP/1.1 403 Forbidden'); die; } // We will need this. require_once $sourcedir . '/sd_source/Subs-SimpleDeskSearch.php'; loadTemplate('sd_template/SimpleDesk-Search'); $context['page_title'] = $txt['shd_search_results']; $context['linktree'][] = array('name' => $txt['shd_search_results']); $context['search_clauses'] = array('{query_see_ticket}'); $context['search_params'] = array(); // Departments first. $visible_depts = shd_allowed_to('access_helpdesk', false); $using_depts = array(); if (!empty($_POST['search_dept']) && is_array($_POST['search_dept'])) { foreach ($_POST['search_dept'] as $dept) { if ((int) $dept > 0) { $using_depts[] = (int) $dept; } } } if (!empty($using_depts)) { $using_depts = array_intersect($using_depts, $visible_depts); } // No departments? Can't really do a lot, sorry. Bye then. if (empty($using_depts)) { return $context['sub_template'] = 'search_no_results'; } // Is the selected list the same size as the list we can see? If it is, theory says that means we're picking every department we can see and don't need to exclude it. if (count($using_depts) != count($visible_depts)) { $context['search_clauses'][] = 'hdt.id_dept IN ({array_int:visible_depts})'; $context['search_params']['visible_depts'] = $using_depts; // Also, we need to get the department list for displaying, only if we can actually see multiple departments at all. if ($context['shd_multi_dept']) { $query = $smcFunc['db_query']('', ' SELECT id_dept, dept_name FROM {db_prefix}helpdesk_depts WHERE id_dept IN ({array_int:dept_list}) ORDER BY dept_order', array('dept_list' => $using_depts)); $context['search_dept_list'] = array(); while ($row = $smcFunc['db_fetch_assoc']($query)) { $context['search_dept_list'][$row['id_dept']] = $row['dept_name']; } $smcFunc['db_free_result']($query); } } // Ticket urgency $using_urgency = array(); if (!empty($_POST['urgency']) && is_array($_POST['urgency'])) { foreach ($_POST['urgency'] as $urgency) { $urgency = (int) $urgency; if ($urgency >= 0 && $urgency <= 5) { // All the currently defined urgencies $using_urgency[] = $urgency; } } } if (empty($using_urgency)) { return $context['sub_template'] = 'search_no_results'; } else { $using_urgency = array_unique($using_urgency); if (count($using_urgency) < 6) { // We have less than 6 selected urgencies, which means we actually need to filter on them, as opposed to if all 6 are selected when we don't. $context['search_clauses'][] = 'hdt.urgency IN ({array_int:urgency})'; $context['search_params']['urgency'] = $using_urgency; } } // Ticket scope // All empty? If so, bye. if (empty($_POST['scope_open']) && empty($_POST['scope_closed']) && empty($_POST['scope_recycle'])) { return $context['sub_template'] = 'search_no_results'; } elseif (empty($_POST['scope_open']) || empty($_POST['scope_closed']) || empty($_POST['scope_recycle'])) { $status = array(); if (!empty($_POST['scope_open'])) { $status = array_merge($status, array(TICKET_STATUS_NEW, TICKET_STATUS_PENDING_STAFF, TICKET_STATUS_PENDING_USER, TICKET_STATUS_WITH_SUPERVISOR, TICKET_STATUS_ESCALATED)); } if (!empty($_POST['scope_closed'])) { $status = array_merge($status, array(TICKET_STATUS_CLOSED)); } if (!empty($_POST['scope_recycle'])) { $status = array_merge($status, array(TICKET_STATUS_DELETED)); } $context['search_clauses'][] = 'hdt.status IN ({array_int:status})'; $context['search_params']['status'] = $status; // That's ticket level status taken care of. We'll pick up recycled items in non recycled tickets separately since it's only relevant if you're actually searching text. } // Ticket starter $starters = shd_get_named_people('starter'); if (!empty($starters)) { $context['search_clauses'][] = 'hdt.id_member_started IN ({array_int:member_started})'; $context['search_params']['member_started'] = $starters; } // Ticket assigned to $assignees = shd_get_named_people('assignee'); if (!empty($assignees)) { $context['search_clauses'][] = 'hdt.id_member_assigned IN ({array_int:member_assigned})'; $context['search_params']['member_assigned'] = $assignees; } // Lastly, page number. We're doing something different to SMF's normal style here. Long and complicated, but there you go. if (isset($_POST['page'])) { $context['pagenum'] = (int) $_POST['page']; } if (empty($context['pagenum']) || $context['pagenum'] < 1) { $context['pagenum'] = 1; } $number_per_page = 20; // OK, so are there any words? If not, execute this sucker the quick way and get out to the template quick. $context['search_terms'] = !empty($_POST['search']) ? trim($_POST['search']) : ''; // Also, did we select some text but fail to select what it was searching in? If so, kick it out. if (!empty($context['search_terms']) && empty($_POST['search_subjects']) && empty($_POST['search_tickets']) && empty($_POST['search_replies'])) { return $context['sub_template'] = 'search_no_results'; } elseif (!empty($context['search_terms'])) { // We're using search terms, and we need to store the areas we're covering. Only makes sense if we're using terms though. $context['search_params']['areas'] = array(); foreach (array('subjects', 'tickets', 'replies') as $area) { if (!empty($_POST['search_' . $area])) { $context['search_params']['areas'][$area] = true; } } // While we're at it, see if we actually have any words to search for. $tokens = shd_tokeniser($context['search_terms']); $count_tokens = count($tokens); // No actual words? if ($count_tokens == 0) { $context['search_terms'] = ''; unset($context['search_params']['areas']); } } // Spam me not! if (empty($_SESSION['lastsearch'])) { spamProtection('search'); } else { list($temp_clauses, $temp_params, $temp_terms) = unserialize($_SESSION['lastsearch']); if ($temp_clauses != $context['search_clauses'] || $temp_params != $context['search_params'] || $temp_terms != $context['search_terms']) { spamProtection('search'); } } $_SESSION['lastsearch'] = serialize(array($context['search_clauses'], $context['search_params'], $context['search_terms'])); $context['search_params']['start'] = ($context['pagenum'] - 1) * $number_per_page; $context['search_params']['limit'] = $number_per_page; if (empty($context['search_terms'])) { // This is where it starts to get expensive, *sob*. We first have to query to get the number of applicable rows. $query = shd_db_query('', ' SELECT COUNT(id_ticket) FROM {db_prefix}helpdesk_tickets AS hdt WHERE ' . implode(' AND ', $context['search_clauses']) . ' LIMIT 1000', $context['search_params']); list($count) = $smcFunc['db_fetch_row']($query); if ($count == 0) { $smcFunc['db_free_result']($query); return $context['sub_template'] = 'search_no_results'; } // OK, at least one result, awesome. Are we off the end of the list? if ($context['search_params']['start'] > $count) { $context['search_params']['start'] = $count - $count % $number_per_page; $context['pagenum'] = $context['search_params']['start'] / $number_per_page + 1; $context['num_results'] = $count; } $query = shd_db_query('', ' SELECT hdt.id_ticket, hdt.id_dept, hdd.dept_name, hdt.subject, hdt.urgency, hdt.private, hdt.last_updated, hdtr.body, hdtr.smileys_enabled, hdtr.id_member AS id_member, IFNULL(mem.real_name, hdtr.poster_name) AS poster_name, hdtr.poster_time FROM {db_prefix}helpdesk_tickets AS hdt INNER JOIN {db_prefix}helpdesk_ticket_replies AS hdtr ON (hdt.id_first_msg = hdtr.id_msg) INNER JOIN {db_prefix}helpdesk_depts AS hdd ON (hdt.id_dept = hdd.id_dept) LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = hdtr.id_member) WHERE ' . implode(' AND ', $context['search_clauses']) . ' ORDER BY hdt.last_updated DESC LIMIT {int:start}, {int:limit}', $context['search_params']); $context['search_results'] = array(); $page_pos = $context['search_params']['start']; // e.g. 0 on page 1, 10 for page 2, the first item will be page_pos + 1, so ++ it before using it. while ($row = $smcFunc['db_fetch_assoc']($query)) { $row['result'] = ++$page_pos; // Increment first, then use. $row['display_id'] = str_pad($row['id_ticket'], $modSettings['shd_zerofill'], '0', STR_PAD_LEFT); $row['is_ticket'] = true; // If we're here, we're only handling tickets anyway. If we're searching text we will need to know if it was a ticket or reply though. $row['dept_link'] = !$context['shd_multi_dept'] ? '' : '[<a href="' . $scripturl . '?action=helpdesk;sa=main;dept=' . $row['id_dept'] . '">' . $row['dept_name'] . '</a>] '; $context['search_results'][] = $row; } return $context['sub_template'] = 'search_results'; } else { $context['match_all'] = empty($_POST['searchtype']) || $_POST['searchtype'] == 'all'; // Then figure out what terms are being matched. $matches = array('subjects' => array(), 'messages' => array(), 'id_msg' => array()); // Doing subjects. Fetch all the instances that match and begin filtering as we go. if (!empty($context['search_params']['areas']['subjects'])) { $query = shd_db_query('', ' SELECT hdssw.id_word, hdt.id_first_msg FROM {db_prefix}helpdesk_search_subject_words AS hdssw INNER JOIN {db_prefix}helpdesk_tickets AS hdt ON (hdssw.id_ticket = hdt.id_ticket) WHERE {query_see_ticket} AND id_word IN ({array_string:tokens})', array('tokens' => $tokens)); while ($row = $smcFunc['db_fetch_assoc']($query)) { $matches['subjects'][$row['id_first_msg']][$row['id_word']] = true; } $smcFunc['db_free_result']($query); // Now go through and figure out which tickets we're interested in keeping. if ($context['match_all']) { foreach ($matches['subjects'] as $msg => $ticket_words) { if (count($ticket_words) != $count_tokens) { // How many words did we match in this subject? If it isn't the number we're expecting, ditch it. unset($matches['subjects'][$msg]); } } } // Now, we just have a list of tickets to play with. Let's put that together in a master list. foreach ($matches['subjects'] as $msg => $ticket_words) { $matches['id_msg'][$msg] = true; } unset($matches['subjects']); } // Now we get the list of words that apply to tickets and replies. The process is different if we do one or both. Both, first. if (!empty($context['search_params']['areas']['tickets']) && !empty($context['search_params']['areas']['replies'])) { // If we're doing both replies and tickets themselves, we don't have to care too much about the message itself, except for being deleted. $query = shd_db_query('', ' SELECT hdssw.id_word, hdt.id_first_msg FROM {db_prefix}helpdesk_search_subject_words AS hdssw INNER JOIN {db_prefix}helpdesk_tickets AS hdt ON (hdssw.id_ticket = hdt.id_ticket) WHERE {query_see_ticket} AND id_word IN ({array_string:tokens})' . (empty($_POST['scope_recycle']) || !shd_allowed_to('shd_access_recyclebin', 0) ? ' AND hdtr.message_status = {int:not_deleted}' : ''), array('tokens' => $tokens, 'not_deleted' => MSG_STATUS_NORMAL)); while ($row = $smcFunc['db_fetch_assoc']($query)) { $matches['messages'][$row['id_first_msg']][$row['id_word']] = true; } $smcFunc['db_free_result']($query); if ($context['match_all']) { foreach ($matches['messages'] as $msg => $ticket_words) { if (count($ticket_words) != $count_tokens) { // How many words did we match in this subject? If it isn't the number we're expecting, ditch it. unset($matches['messages'][$msg]); } } } // Now, we just have a list of tickets to play with. Let's put that together in a master list. foreach ($matches['messages'] as $msg => $ticket_words) { $matches['id_msg'][$msg] = true; } unset($matches['messages']); } elseif (!empty($context['search_params']['areas']['tickets']) || !empty($context['search_params']['areas']['replies'])) { $query = $smcFunc['db_query']('', ' SELECT hdstw.id_word, hdstw.id_msg FROM {db_prefix}helpdesk_search_ticket_words AS hdstw INNER JOIN {db_prefix}helpdesk_ticket_replies AS hdtr ON (hdstw.id_msg = hdtr.id_msg) INNER JOIN {db_prefix}helpdesk_tickets AS hdt ON (hdtr.id_ticket = hdt.id_ticket) WHERE id_word IN ({array_string:tokens}) AND hdstw.id_msg {raw:operator} hdt.id_first_msg' . (empty($_POST['scope_recycle']) || !shd_allowed_to('shd_access_recyclebin', 0) ? ' AND hdtr.message_status = {int:not_deleted}' : ''), array('tokens' => $tokens, 'not_deleted' => MSG_STATUS_NORMAL, 'operator' => !empty($context['search_params']['areas']['tickets']) ? '=' : '!=')); while ($row = $smcFunc['db_fetch_assoc']($query)) { $matches['messages'][$row['id_msg']][$row['id_word']] = true; } $smcFunc['db_free_result']($query); if ($context['match_all']) { foreach ($matches['messages'] as $ticket => $ticket_words) { if (count($ticket_words) != $count_tokens) { // How many words did we match in this subject? If it isn't the number we're expecting, ditch it. unset($matches['messages'][$ticket]); } } } // Now, we just have a list of tickets to play with. Let's put that together in a master list. foreach ($matches['messages'] as $msg => $ticket_words) { $matches['id_msg'][$ticket] = true; } unset($matches['messages']); } // Aw, no matches? if (empty($matches['id_msg'])) { return $context['sub_template'] = 'search_no_results'; } $context['search_clauses'][] = 'hdtr.id_msg IN ({array_int:msg})'; $context['search_params']['msg'] = array_keys($matches['id_msg']); // How many results are there in total? $query = shd_db_query('', ' SELECT COUNT(*) FROM {db_prefix}helpdesk_tickets AS hdt INNER JOIN {db_prefix}helpdesk_ticket_replies AS hdtr ON (hdtr.id_ticket = hdt.id_ticket) WHERE ' . implode(' AND ', $context['search_clauses']) . ' LIMIT 1000', $context['search_params']); list($count) = $smcFunc['db_fetch_row']($query); if ($count == 0) { $smcFunc['db_free_result']($query); return $context['sub_template'] = 'search_no_results'; } // OK, at least one result, awesome. Are we off the end of the list? if ($context['search_params']['start'] > $count) { $context['search_params']['start'] = $count - $count % $number_per_page; $context['pagenum'] = $context['search_params']['start'] / $number_per_page + 1; $context['num_results'] = $count; } // Get the results for displaying. $query = shd_db_query('', ' SELECT hdt.id_ticket, hdt.id_dept, hdd.dept_name, hdt.subject, hdt.urgency, hdt.private, hdt.last_updated, hdtr.body, hdtr.smileys_enabled, hdtr.id_member AS id_member, IFNULL(mem.real_name, hdtr.poster_name) AS poster_name, hdtr.poster_time, hdt.id_first_msg, hdtr.id_msg FROM {db_prefix}helpdesk_ticket_replies AS hdtr INNER JOIN {db_prefix}helpdesk_tickets AS hdt ON (hdt.id_ticket = hdtr.id_ticket) INNER JOIN {db_prefix}helpdesk_depts AS hdd ON (hdt.id_dept = hdd.id_dept) LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = hdtr.id_member) WHERE ' . implode(' AND ', $context['search_clauses']) . ' ORDER BY hdt.last_updated DESC, hdtr.id_msg DESC LIMIT {int:start}, {int:limit}', $context['search_params']); $context['search_results'] = array(); $page_pos = $context['search_params']['start']; // e.g. 0 on page 1, 10 for page 2, the first item will be page_pos + 1, so ++ it before using it. while ($row = $smcFunc['db_fetch_assoc']($query)) { $row['result'] = ++$page_pos; // Increment first, then use. $row['display_id'] = str_pad($row['id_ticket'], $modSettings['shd_zerofill'], '0', STR_PAD_LEFT); $row['is_ticket'] = $row['id_msg'] == $row['id_first_msg']; // If the message we grabbed is the first message, this is actually a ticket, not a reply to one. $row['dept_link'] = !$context['shd_multi_dept'] ? '' : '[<a href="' . $scripturl . '?action=helpdesk;sa=main;dept=' . $row['id_dept'] . '">' . $row['dept_name'] . '</a>] '; $context['search_results'][] = $row; } return $context['sub_template'] = 'search_results'; } }
function shd_admin_maint_search() { global $context, $txt, $modSettings, $sourcedir, $smcFunc; $context['sub_template'] = 'shd_admin_maint_search'; $context['page_title'] = $txt['shd_admin_maint']; checkSession('request'); // Reset the defaults if they're not set. if (empty($modSettings['shd_search_charset'])) { $modSettings['shd_search_charset'] = '0..9, A..Z, a..z, &, ~'; } $modSettings['shd_search_min_size'] = !empty($modSettings['shd_search_min_size']) ? $modSettings['shd_search_min_size'] : 3; $modSettings['shd_search_max_size'] = !empty($modSettings['shd_search_max_size']) ? $modSettings['shd_search_max_size'] : 8; $modSettings['shd_search_prefix_size'] = !empty($modSettings['shd_search_prefix_size']) ? $modSettings['shd_search_prefix_size'] : 0; // Are we doing some fancy work? if (isset($_REQUEST['rebuild'])) { require_once $sourcedir . '/sd_source/Subs-SimpleDeskSearch.php'; // How many tickets are there? $query = $smcFunc['db_query']('', ' SELECT COUNT(id_ticket) FROM {db_prefix}helpdesk_tickets'); list($total) = $smcFunc['db_fetch_row']($query); // Where are we starting? $start = isset($_POST['start']) ? (int) $_POST['start'] : 0; // Get the ids we need to do. $per_inst = 10; $tickets = array(); $query = $smcFunc['db_query']('', ' SELECT id_ticket, subject FROM {db_prefix}helpdesk_tickets ORDER BY id_ticket ASC LIMIT {int:start}, {int:limit}', array('start' => $start, 'limit' => $per_inst)); while ($row = $smcFunc['db_fetch_assoc']($query)) { $tickets[$row['id_ticket']] = $row['subject']; } $smcFunc['db_free_result']($query); // Nothing to do? if ($start >= $total || empty($tickets)) { // Make sure we flag the index as built, then leave. updateSettings(array('shd_new_search_index' => 0)); redirectexit('action=admin;area=helpdesk_maint;sa=search;rebuilddone;' . $context['session_var'] . '=' . $context['session_id']); } // OK, let's get cracking. First, remove the relevant tickets from the subject index. $smcFunc['db_query']('', ' DELETE FROM {db_prefix}helpdesk_search_subject_words WHERE id_ticket IN ({array_int:tickets})', array('tickets' => array_keys($tickets))); // Now, figure out the new term index for the subjects. $rows_to_insert = array(); foreach ($tickets as $id_ticket => $subject) { $tokens = shd_tokeniser($subject); foreach ($tokens as $token) { $rows_to_insert[] = array($token, $id_ticket); } } // And add to the database. if (!empty($rows_to_insert)) { $smcFunc['db_insert']('replace', '{db_prefix}helpdesk_search_subject_words', array('id_word' => 'string', 'id_ticket' => 'int'), $rows_to_insert, array('id_word', 'id_ticket')); } // Now for the slightly... substantially more expensive part: messages. We query for all the messages in a ticket, then query to // insert all the terms for each message. Expensive since it means a lot of queries but it means we don't risk hitting the query // packet limit which could really break things. Besides, this IS a maintenance area, not something you're going to do that often. foreach ($tickets as $id_ticket => $subject) { $rows_to_insert = array(); $query = $smcFunc['db_query']('', ' SELECT id_msg, body FROM {db_prefix}helpdesk_ticket_replies WHERE id_ticket = {int:ticket}', array('ticket' => $id_ticket)); $msg_list = array(); while ($row = $smcFunc['db_fetch_assoc']($query)) { $msg_list[] = $row['id_msg']; $tokens = shd_tokeniser($row['body']); foreach ($tokens as $token) { $rows_to_insert[] = array($token, $row['id_msg']); } } $smcFunc['db_free_result']($query); // Just before we insert, prune the old stuff. No point querying the message list twice. $smcFunc['db_query']('', ' DELETE FROM {db_prefix}helpdesk_search_ticket_words WHERE id_msg IN ({array_int:msgs})', array('msgs' => $msg_list)); if (!empty($rows_to_insert)) { $smcFunc['db_insert']('replace', '{db_prefix}helpdesk_search_ticket_words', array('id_word' => 'string', 'id_msg' => 'int'), $rows_to_insert, array('id_word', 'id_msg')); } } // Set up for calling back. $start += $per_inst; $pc_done = round($start / $total * 100); if ($pc_done > 100) { $pc_done = 100; } $context['continue_countdown'] = 3; $context['sub_template'] = 'not_done'; $context['continue_percent'] = $pc_done; $context['continue_get_data'] = '?action=admin;area=helpdesk_maint;sa=search;' . $context['session_var'] . '=' . $context['session_id']; $context['continue_post_data'] = '<input type="hidden" name="start" value="' . $start . '" /> <input type="hidden" name="rebuild" value="1" />'; // Make SURE we never mess with the other settings. unset($_REQUEST['save']); } // OK, the template will basically display itself, but in the meantime, do we need to do anything else like save new settings? if (isset($_REQUEST['save'])) { $_POST['shd_search_min_size'] = isset($_POST['shd_search_min_size']) ? (int) $_POST['shd_search_min_size'] : 0; $_POST['shd_search_max_size'] = isset($_POST['shd_search_max_size']) ? (int) $_POST['shd_search_max_size'] : 0; $_POST['shd_search_prefix_size'] = isset($_POST['shd_search_prefix_size']) ? (int) $_POST['shd_search_prefix_size'] : 0; // Force some realistic limits. if ($_POST['shd_search_min_size'] < 3) { $_POST['shd_search_min_size'] = 3; } elseif ($_POST['shd_search_min_size'] > 15) { $_POST['shd_search_min_size'] = 15; } if ($_POST['shd_search_max_size'] < $_POST['shd_search_min_size']) { $_POST['shd_search_max_size'] = $_POST['shd_search_min_size']; } elseif ($_POST['shd_search_max_size'] > 15) { $_POST['shd_search_max_size'] = 15; } if ($_POST['shd_search_prefix_size'] < 0) { $_POST['shd_search_prefix_size'] = 0; } elseif ($_POST['shd_search_prefix_size'] > 0 && $_POST['shd_search_prefix_size'] < $_POST['shd_search_min_size']) { $_POST['shd_search_prefix_size'] = $_POST['shd_search_min_size']; } elseif ($_POST['shd_search_prefix_size'] > $_POST['shd_search_max_size']) { $_POST['shd_search_prefix_size'] = $_POST['shd_search_max_size']; } $normal_regex = shd_return_exclude_regex($modSettings['shd_search_charset']); if (empty($_POST['shd_search_charset'])) { $_POST['shd_search_charset'] = $modSettings['shd_search_charset']; } $post_regex = shd_return_exclude_regex($_POST['shd_search_charset']); if (empty($post_regex)) { $post_regex = $normal_regex; } // Nothing specified? Use what we have, then. foreach (array('shd_search_min_size', 'shd_search_max_size', 'shd_search_prefix_size') as $item) { if ($modSettings[$item] != $_POST[$item]) { $update = true; } } if ($normal_regex != $post_regex) { $update = true; } if (!empty($update)) { updateSettings(array('shd_search_min_size' => $_POST['shd_search_min_size'], 'shd_search_max_size' => $_POST['shd_search_max_size'], 'shd_search_prefix_size' => $_POST['shd_search_prefix_size'], 'shd_search_charset' => $_POST['shd_search_charset'], 'shd_new_search_index' => 1)); } } }