Dan Brown

Amended search to not break on non-alpha-num chars

And also fixed exact term matches that contain non-alpha-num chars
Fixes #212
...@@ -160,44 +160,46 @@ class Entity extends Ownable ...@@ -160,44 +160,46 @@ class Entity extends Ownable
160 public function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = []) 160 public function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = [])
161 { 161 {
162 $exactTerms = []; 162 $exactTerms = [];
163 - if (count($terms) === 0) { 163 + $fuzzyTerms = [];
164 - $search = $this; 164 + $search = static::newQuery();
165 - $orderBy = 'updated_at'; 165 + foreach ($terms as $key => $term) {
166 - } else { 166 + $safeTerm = htmlentities($term, ENT_QUOTES);
167 - foreach ($terms as $key => $term) { 167 + $safeTerm = preg_replace('/[+\-><\(\)~*\"@]+/', ' ', $safeTerm);
168 - $term = htmlentities($term, ENT_QUOTES); 168 + if (preg_match('/&quot;.*?&quot;/', $safeTerm) || is_numeric($safeTerm)) {
169 - $term = preg_replace('/[+\-><\(\)~*\"@]+/', ' ', $term); 169 + $safeTerm = preg_replace('/^"(.*?)"$/', '$1', $term);
170 - if (preg_match('/&quot;.*?&quot;/', $term)) { 170 + $exactTerms[] = '%' . $safeTerm . '%';
171 - $term = str_replace('&quot;', '', $term); 171 + } else {
172 - $exactTerms[] = '%' . $term . '%'; 172 + $safeTerm = '' . $safeTerm . '*';
173 - $term = '"' . $term . '"'; 173 + if (trim($safeTerm) !== '*') $fuzzyTerms[] = $safeTerm;
174 - } else {
175 - $term = '' . $term . '*';
176 - }
177 - if ($term !== '*') $terms[$key] = $term;
178 } 174 }
179 - $termString = implode(' ', $terms); 175 + }
176 + $isFuzzy = count($exactTerms) === 0 || count($fuzzyTerms) > 0;
177 +
178 + // Perform fulltext search if relevant terms exist.
179 + if ($isFuzzy) {
180 + $termString = implode(' ', $fuzzyTerms);
180 $fields = implode(',', $fieldsToSearch); 181 $fields = implode(',', $fieldsToSearch);
181 - $search = static::selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]); 182 + $search = $search->selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]);
182 $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); 183 $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]);
184 + }
183 185
184 - // Ensure at least one exact term matches if in search 186 + // Ensure at least one exact term matches if in search
185 - if (count($exactTerms) > 0) { 187 + if (count($exactTerms) > 0) {
186 - $search = $search->where(function ($query) use ($exactTerms, $fieldsToSearch) { 188 + $search = $search->where(function ($query) use ($exactTerms, $fieldsToSearch) {
187 - foreach ($exactTerms as $exactTerm) { 189 + foreach ($exactTerms as $exactTerm) {
188 - foreach ($fieldsToSearch as $field) { 190 + foreach ($fieldsToSearch as $field) {
189 - $query->orWhere($field, 'like', $exactTerm); 191 + $query->orWhere($field, 'like', $exactTerm);
190 - }
191 } 192 }
192 - }); 193 + }
193 - } 194 + });
194 - $orderBy = 'title_relevance'; 195 + }
195 - }; 196 + $orderBy = $isFuzzy ? 'title_relevance' : 'updated_at';
196 197
197 // Add additional where terms 198 // Add additional where terms
198 foreach ($wheres as $whereTerm) { 199 foreach ($wheres as $whereTerm) {
199 $search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]); 200 $search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]);
200 } 201 }
202 +
201 // Load in relations 203 // Load in relations
202 if ($this->isA('page')) { 204 if ($this->isA('page')) {
203 $search = $search->with('book', 'chapter', 'createdBy', 'updatedBy'); 205 $search = $search->with('book', 'chapter', 'createdBy', 'updatedBy');
......