Jump to content
rash

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

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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

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!

Share this post


Link to post
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

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By snobjorn
      I have a website with multiple content types that I want to be accessible through search. I really like the live search on processwire.com, that sorts content types while typing. I tried to find the code to recreate this, with no luck. Does anyone know if this is jquery, specific jquery plugins, json/xml cached files, and what kind of PHP code is used? Any tip that point me in the right direction would be much apperciated.
      The search result listing seems fairly easy to create with sorting through parameters.
    • By Anders
      I want to allow full text search on my site. There is a very nice solution that comes right out of the box:
      $selector = "title|body~=$q, limit=50"; This works, but to make it even better I would want to give higher weight to pages where the search term occurs in the title, than if it just occurs in the body. After all, a page with the title "Wine from France" is probably the best match for the search "france wine". How do I accomplish this in ProcessWire?
      I can see three possible paths, but I am not very fond of any of them:
      Do a direct SQL query, circumventing the API, along these lines. But I would prefer to abstract away the database layout if at all possible. Use something like ElasticSearch, but to be honest that would be to complicated to set up and maintain in the long run. Make multiple lookups, first for matches in the title, then for matches in the body, and merge and sort in PHP. My suspicion is that this would get complicated quite quickly. For instance, how do you deal with a page that has two of the three search terms in the title and the third in the body? Is there a magic option four I should look into? Or are any of the above options better than the others? Any input is welcome!
    • By jds43
      Hello,
      I have a search page loosely based on Skyscrapers where I'm parsing a selector with options 'beds', 'bathrooms', 'size' fields. It is working well until I select 'Any' after I've run a search. This is where no results are returned (/?beds=&bathrooms=&size=&submit=). I want it to reset and show all results.
      I hope this isn't too vague.
       
    • By sww
      Hey there,
      i am trying to add a kind of "intelligent" search.
      Is there any way to ignore punctuation and extra chars.
      e.g.
      When I type "somebody elses" that I still get the result "SOMEBODY ELSE´S CAR, 2005"
      The problem is the extra ´ … if I don't type it I don't get the result.
      So far I am using %=query (which isn't enough, I know)
      So the idea would be something like that:
      $selector = "sanitize(title|text)%=$q, limit=50";
      I know, it's not gonna work like that … just to illustrate.
      Best,
      Stefan
    • By dandeckr
      Hello!
      I'm making my way through my first encounter with ProcessWire, and I'm very near the end of my tasks! I've searched Google, these forums, and for add-ons, but I haven't found any documentation or reference work for implementing boolean searching in the native CMS? Am I missing a thread, add-on, or docs that can point the way? I'm sure I'm not the first to have a need like this? Thanks in advance! 
×
×
  • Create New...