Jump to content

[Solved] Search function AND/OR and sorting of the results


rash
 Share

Recommended Posts

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

Link to comment
Share on other sites

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 by kongondo
  • Like 2
Link to comment
Share on other sites

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:

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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 by rash
removed an unneccessary if clause
  • Like 1
Link to comment
Share on other sites

  • 2 years later...

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!

Link to comment
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...