diff --git a/include/ajax.orgs.php b/include/ajax.orgs.php index 041678c0b1166374afc8715030029cf838695a1c..0cf4f22d6ad391afff7543079da762f14113b314 100644 --- a/include/ajax.orgs.php +++ b/include/ajax.orgs.php @@ -29,6 +29,9 @@ class OrgsAjaxAPI extends AjaxController { $q = $_REQUEST['q']; $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; + if (strlen($q) < 2) + return $this->encode(array()); + $orgs = Organization::objects() ->values_flat('id', 'name') ->limit($limit); @@ -38,7 +41,7 @@ class OrgsAjaxAPI extends AjaxController { $orgs->order_by(new SqlCode('__relevance__'), QuerySet::DESC) ->distinct('id'); - if (!count($orgs) && substr($q, strlen($q)-1) != '*') { + if (!count($orgs) && preg_match('`\w$`u', $q)) { // Do wildcard full-text search $_REQUEST['q'] = $q."*"; return $this->search($type); diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 31457ec4f80497849129f1a5f126ac7d8a4c21fb..35bb513e973f3991c3dc729e4b19f4926f33ab39 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -47,8 +47,9 @@ class TicketsAjaxAPI extends AjaxController { ->limit($limit); $q = $_REQUEST['q']; - // Drop at sign in email addresses - $q = str_replace('@', ' ', $q); + + if (strlen($q) < 2) + return $this->encode(array()); global $ost; $hits = $ost->searcher->find($q, $hits) @@ -66,7 +67,7 @@ class TicketsAjaxAPI extends AjaxController { ->limit($limit) ->union($hits); } - elseif (!count($hits) && $q[strlen($q)-1] != '*') { + elseif (!count($hits) && preg_match('`\w$`u', $q)) { // Do wild-card fulltext search $_REQUEST['q'] = $q.'*'; return $this->lookup(); diff --git a/include/ajax.users.php b/include/ajax.users.php index 0dd6da74b3384f14d16800c6c36ca657220cf7d6..bb94a1528992b9fb991d3f3e399de5dcefcd8bdb 100644 --- a/include/ajax.users.php +++ b/include/ajax.users.php @@ -34,6 +34,9 @@ class UsersAjaxAPI extends AjaxController { $users=array(); $emails=array(); + if (strlen($q) < 2) + return $this->encode(array()); + if (!$type || !strcasecmp($type, 'remote')) { foreach (AuthenticationBackend::searchUsers($q) as $u) { $name = new UsersName(array('first' => $u['first'], 'last' => $u['last'])); @@ -55,7 +58,7 @@ class UsersAjaxAPI extends AjaxController { $users->order_by(new SqlCode('__relevance__'), QuerySet::DESC) ->distinct('id'); - if (!count($emails) && !count($users) && substr($q, strlen($q)-1) != '*') { + if (!count($emails) && !count($users) && preg_match('`\w$`u', $q)) { // Do wildcard full-text search $_REQUEST['q'] = $q."*"; return $this->search($type); diff --git a/include/class.search.php b/include/class.search.php index 6cac6d7e4e9d51ee436589114633cb6fe7770af8..474fa3132d6c6763149c49c74893602c829fe26c 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -306,7 +306,7 @@ class MysqlSearchBackend extends SearchBackend { // Quote things like email addresses function quote($query) { $parts = array(); - if (!preg_match_all('`([^\s"\']+)|"[^"]*"|\'[^\']*\'`', $query, $parts, + if (!preg_match_all('`(?:([^\s"\']+)|"[^"]*"|\'[^\']*\')(\s*)`', $query, $parts, PREG_SET_ORDER)) return $query; @@ -319,21 +319,22 @@ class MysqlSearchBackend extends SearchBackend { $char = strpos($m[1], '"') ? "'" : '"'; $m[0] = $char . $m[0] . $char; } - $results[] = $m[0]; + $results[] = $m[0].$m[2]; } - return implode(' ', $results); + return implode('', $results); } function find($query, QuerySet $criteria, $addRelevance=true) { global $thisstaff; + // MySQL usually doesn't handle words shorter than three letters + // (except with special configuration) + if (strlen($query) < 3) + return $criteria; + $criteria = clone $criteria; $mode = ' IN NATURAL LANGUAGE MODE'; - // If using boolean operators, search in boolean mode. This regex - // will ensure proper placement of operators, whitespace, and quotes - // in an effort to avoid crashing the query at MySQL - $query = $this->quote($query); // According to the MySQL full text boolean mode, this grammar is // assumed: @@ -357,6 +358,10 @@ class MysqlSearchBackend extends SearchBackend { if (preg_match('`(^|\s)["()<>~+-]`u', $query, $T = array()) && preg_match("`^{$BOOLEAN}$`u", $query, $T = array()) ) { + // If using boolean operators, search in boolean mode. This regex + // will ensure proper placement of operators, whitespace, and quotes + // in an effort to avoid crashing the query at MySQL + $query = $this->quote($query); $mode = ' IN BOOLEAN MODE'; } #elseif (count(explode(' ', $query)) == 1) @@ -376,7 +381,7 @@ class MysqlSearchBackend extends SearchBackend { $criteria->extra(array( 'tables' => array( str_replace(array(':', '{}'), array(TABLE_PREFIX, $search), - "(SELECT COALESCE(Z3.`object_id`, Z5.`ticket_id`, Z8.`ticket_id`) as `ticket_id`, {} AS `relevance` FROM `:_search` Z1 LEFT JOIN `:thread_entry` Z2 ON (Z1.`object_type` = 'H' AND Z1.`object_id` = Z2.`id`) LEFT JOIN `:thread` Z3 ON (Z2.`thread_id` = Z3.`id` AND Z3.`object_type` = 'T') LEFT JOIN `:ticket` Z5 ON (Z1.`object_type` = 'T' AND Z1.`object_id` = Z5.`ticket_id`) LEFT JOIN `:user` Z6 ON (Z6.`id` = Z1.`object_id` and Z1.`object_type` = 'U') LEFT JOIN `:organization` Z7 ON (Z7.`id` = Z1.`object_id` AND Z7.`id` = Z6.`org_id` AND Z1.`object_type` = 'O') LEFT JOIN :ticket Z8 ON (Z8.`user_id` = Z6.`id`) WHERE {}) Z1"), + "(SELECT COALESCE(Z3.`object_id`, Z5.`ticket_id`, Z8.`ticket_id`) as `ticket_id`, SUM({}) AS `relevance` FROM `:_search` Z1 LEFT JOIN `:thread_entry` Z2 ON (Z1.`object_type` = 'H' AND Z1.`object_id` = Z2.`id`) LEFT JOIN `:thread` Z3 ON (Z2.`thread_id` = Z3.`id` AND Z3.`object_type` = 'T') LEFT JOIN `:ticket` Z5 ON (Z1.`object_type` = 'T' AND Z1.`object_id` = Z5.`ticket_id`) LEFT JOIN `:user` Z6 ON (Z6.`id` = Z1.`object_id` and Z1.`object_type` = 'U') LEFT JOIN `:organization` Z7 ON (Z7.`id` = Z1.`object_id` AND Z7.`id` = Z6.`org_id` AND Z1.`object_type` = 'O') LEFT JOIN :ticket Z8 ON (Z8.`user_id` = Z6.`id`) WHERE {} GROUP BY `ticket_id`) Z1"), ) )); $criteria->filter(array('ticket_id'=>new SqlCode('Z1.`ticket_id`'))); diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index cf3c5e1d92d1c83fd9b02a9bea18bc12f7e3470e..f37874b55a8916081c924599130f64c863d36f37 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -90,10 +90,10 @@ if ($thisclient->canSeeOrgTickets()) { // Perform basic search if ($settings['keywords']) { - $q = $settings['keywords']; + $q = trim($settings['keywords']); if (is_numeric($q)) { $tickets->filter(array('number__startswith'=>$q)); - } else { //Deep search! + } elseif (strlen($q) > 2) { //Deep search! // Use the search engine to perform the search $tickets = $ost->searcher->find($q, $tickets); } diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 2bdb8ba5ecc81cad256df6d4fdbcbb59441bc442..690f6c2066fefa03c4b2e78b7e39a6919b08f610 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -93,18 +93,22 @@ case 'search': )); } } - elseif ($_REQUEST['query']) { + elseif (isset($_REQUEST['query']) + && ($q = trim($_REQUEST['query'])) + && strlen($q) > 2 + ) { // [Search] click, consider keywords - $__tickets = $ost->searcher->find($_REQUEST['query'], $tickets); - if (!count($__tickets)) { + $__tickets = $ost->searcher->find($q, $tickets); + if (!count($__tickets) && preg_match('`\w$`u', $q)) { // Do wildcard search if no hits - $__tickets = $ost->searcher->find($_REQUEST['query'].'*', $tickets); + $__tickets = $ost->searcher->find($q.'*', $tickets); } - $tickets = $__tickets->distinct('ticket_id'); + $tickets = $__tickets; $has_relevance = true; } if (count($tickets) == 1) { // Redirect to ticket page + Http::redirect('tickets.php?id='.$tickets[0]->getId()); } // Clear sticky search queue unset($_SESSION[$queue_key]);