rash Posted January 18, 2017 Posted January 18, 2017 Hi guys, I would like to realize a search function on my site that follows some clear rules: 1 – Search is performed in the two fields 'tite' and 'body'. 2 – If a user searches for 'foo' and 'bar' he can select whether both terms have to be in at least one of the fields (AND) or at least one of the terms has to be in at least one of the fields (OR). 3 – 'foo' should match words like 'food' or 'foolish' as well as 'foo' (LIKE). 4 – Search results where 'foo' or 'bar' are found in the title should be displayed first, followed by the results found in 'body' only. This is what I have: <?php if($input->get->q) { $q = $sanitizer->text($input->get->q); if($q) { $input->whitelist('q', $q); $qs = explode(" ", $q); foreach($qs as $key => $q) : $qs[$key] = $sanitizer->selectorValue($q); endforeach; $selector1 = "title%=".implode("|", $qs).", template=entry, limit=50"; $selector2 = "body%=".implode("|", $qs).", template=entry, limit=50"; // Trying to separate 'important' title matches from 'less important' body matches $matches = $pages->find($selector1); if ($matches->count) < $limit)) $matches->import($pages->find($selector2)); if ($matches->count) { foreach($matches as $m) : // Output matches title & body excerpt endforeach; } else { // Output no matches } } } else { // Output no search term(s) } ?> First problem is regarding rule 2: I don’t know how to do an AND search. As far as I can see, it’s always an OR. Second problem is the order of the search results (rule 4): I split the queries to separate them nicely, but they appear mixed up. Output starts with a few title matches as I would expect, followed by some body matches, then some further title matches appears. I don’t understand how this happens: $matches->import adds array 2 to array 1 without mixing them, isn’t that true? And just in case I will get the job done someday: how could I avoid doubled matches? Matches in body aren’t that interesting anymore, when the terms was already found in the title field. I will appreciate any helping hand – thanks. Ralf
kongondo Posted January 18, 2017 Posted January 18, 2017 (edited) In a hurry, so a quick (and not thoroughly tested) part answer Actually, the 'normal' selector is an AND not an OR (i.e. the comma separated selector). 2a. Both terms in at least one field (@see docs) // both terms in at least one field $sel1 = '(title%=foo, title%=bar), (body%=foo, body%=bar)'; 2b. At least one term in at least one field (@see docs) // at least one term in at least one field $sel2 = 'title|body%=foo|bar'; 3 LIKE (@see docs). Here we use %. See the notes in the docs about the alternative + matching order, etc Edited January 18, 2017 by kongondo 2
Robin S Posted January 19, 2017 Posted January 19, 2017 11 hours ago, rash said: I don’t understand how this happens: $matches->import adds array 2 to array 1 without mixing them, isn’t that true? Not if PageArray 2 contains some of the same pages as PageArray 1. If you want to maintain the sort order make sure the PageArray you are importing has only unique pages not in the other PageArray. For example: $pa2->removeItems($pa1); $pa1->import($pa2); Regarding the %= operator, note that if there are multiple words in the search string then the entire string must appear in that exact order in the field(s) being searched. See this solution for allowing the words in any order/location: 2 1
rash Posted January 19, 2017 Author Posted January 19, 2017 kongondo and Robin S – thank you both a lot. Your explanations sound clear and doable, so I will try how far I can get with the new knowledge this evening. And of course I will post the final solution just in case someone has a need for it.
rash Posted January 19, 2017 Author Posted January 19, 2017 (edited) Okay, the job is done now. The final solution might not be too elegant, but it works exactly the way I want as I described it in the opening post. My search form has two additional selects: scope for setting the fields to search and mode for setting the search mode. kongondo and Robin S: I want to thank you once more – I wouldn't be where I am without your kind help. <?php if ($input->get->search_terms) { // ---- sanitizing text input ------------------------------------------------------- $search_terms = $sanitizer->text($input->get->search_terms); // ---- getting desired search scope, default = title + body ------------------------ if ($input->get->scope) { $scope = $input->get->scope; } else $scope = "t_body"; // ---- getting desired search mode, default = AND ---------------------------------- if ($input->get->mode) { $mode = $input->get->mode; } else $mode = "and"; // ---- template and limit settings -------------------------------------------------- $template = "pg_entry"; $limit = 50; $selector_complete = ", template=".$template.", limit=".$limit; // ---- replace multiple spaces with single space $search_terms = preg_replace("/\s+/", " ", $search_terms); $terms = explode(" ", $search_terms); // ---- both scope options: search title ---------------------------------------- // ---- mode: AND search if ($mode == "and") { $selector_1 = "("; foreach($terms as $term) { $selector_1 .= "title%=".$term.", "; } $selector_1 = substr($selector_1, 0, -2); // delete trailing comma and space $selector_1 .= ")"; } // ---- mode: OR search else { $selector_1 = "title%="; foreach($terms as $term) { $selector_1 .= $term."|"; } $selector_1 = substr($selector_1, 0, -1); // delete trailing pipe } // ---- adding template and limit values $selector_1 .= $selector_complete; // ---- scope: t_body, adding 2nd selector for body ----------------------------- if ($scope == "t_body") { // ---- mode: AND search if ($mode == "and") { $selector_2 = "("; foreach($terms as $term) { $selector_2 .= "body%=".$term.", "; } // ---- delete trailing comma and space $selector_2 = substr($selector_2, 0, -2); $selector_2 .= ")"; } // ---- mode: OR search else { $selector_2 = "body%="; foreach($terms as $term) { $selector_2 .= $term."|"; } // ---- delete trailing pipe $selector_2 = substr($selector_2, 0, -1); } // ---- adding template and limit values $selector_2 .= $selector_complete; } // ++++ matches for selector 1 (title) ++++++++++++++++++++++++++++++++++++++++++ $matches = $pages->find($selector_1); // ++++ scope t_body && limit not reached yet: matches for selector 2 (body) ++++ if ($matches->count < $limit && $scope == "t_body") { $body_matches = $pages->find($selector_2); } else { $body_matches = ""; } // --- matches found in body if ($body_matches) { // ---- remove duplicates already in title matches $body_matches->removeItems($matches); // ---- add body_matches to matches $matches->import($body_matches); } // ++++ Output search result ++++++++++++++++++++++++++++++++++++++++++++++++++++ if ($matches->count) { foreach($matches as $match) { // Output matches } } else { // Output no matches } } else { // Output no search term(s) } ?> Edited January 20, 2017 by rash removed an unneccessary if clause 1
dandeckr Posted May 23, 2019 Posted May 23, 2019 Hello rash, I know this may be a long shot, but I am trying to solve the same problem ? I was wondering if you had the form code that posts to this search script? Even a screen shot of the form would be most helpful! Thanks in advance!
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now