Jump to content

search with "AND" string


Reid Bramblett
 Share

Recommended Posts

I am building off the default install, and noticed that, in practice, the included search template limits me to a single search term (or an exact phrase of more than one word). How can I get it to search a simple boolean-type string, with an AND or OR?

The template PHP code:

$q = $sanitizer->text($input->get->q);
if($q) { 
$input->whitelist('q', $q); 
$q = $sanitizer->selectorValue($q);
$selector = "title|body|interests~=$q, limit=50";
if($user->isLoggedin()) $selector .= ", has_parent!=2"; 
$matches = $pages->find($selector); 
if($matches->count) {
$content .= "<h2>Found $matches->count pages matching your query:</h2>";
$content .= renderNavPanel($matches);
} else {
$content = "<h2>Sorry, no results were found.</h2>";
}
} else {
$content = "<h2>Please enter a search term in the search box (upper right corner)</h2>";
}

I've tried every permutation of entering the data I can think of (x y; x AND y; x&y; x, y; "x" AND "y"; x + y; etc.; etc.) , but whenever I put in more than one search term, I get zero results—even if each of the words, when searched alone, does produce results.

I even experimented replacing the two $sanitizer types with bool, but that threw ugly errors.

 

About to pull my hair out over this. I am sure there is a simple answer, but as I haven't worked much with any sort of input features (built static sites for years), I cannot seem to crack it. Forgive me is this if Kindergarten-level. Thanks.

Link to comment
Share on other sites

Actually there's no simple answer besides "It depends". The part being responsible for how matches are determined is: "title|body|interests~=$q" or rather "~=", which by the docs means "Contains all the words".  To get other results use an other operator in the selector. Also there's no such thing as "search operators" that you can directly enter into the search field. If you want something like that, you'd need to implement that on your own by parsing them down to different selectors in your code.

About the two sanitizers: These are necessary to secure you're search from potential abuse. The first one does make sure you're getting text without markup, while the second one makes sure the selector value is properly escaped (e.g. quotes and commas), so it will be correctly parsed by the $pages->find() function and won't break the selector. 

  • Like 1
Link to comment
Share on other sites

I have narrowed my problem down to the fact that I can search for two terms if both terms happen to show up in the same field (say, both are in the body), but not if one term lies in one field (say, title) and the other term in a second field (body).

So If I search for "Free" I get all the pages that happen to have the word "Free" in the designated selector fields. If I search "London" I similarly get anything with "London" in those fields.

But since "Free" lives in the interests field, and "London" in the places field, if I search "Free London" I get zero results. 

I suspect that answer lies in LostKobrakai's suggestion that I parse the terms into different selectors, but I have no idea how to do that. (Hey, no shame in admitting ignorance). I fiddled around with the code in Mats' suggested thread, but to no avail. (Again, I admit I was basically poking the code around blindly, putting stuff in different places, changing out operators, etc. and seeing what happened).

Link to comment
Share on other sites


$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)

}

$selector = "title|body|interests*=" . implode("|", $gs) . ", limit=50";

if($user->isLoggedin()) $selector .= ", has_parent!=2";

$matches = $pages->find($selector);

if($matches->count) {

$content .= "<h2>Found $matches->count pages matching your query:</h2>";

$content .= renderNavPanel($matches);

} else {

$content = "<h2>Sorry, no results were found.</h2>";

}

} else {

$content = "<h2>Please enter a search term in the search box (upper right corner)</h2>";

}

  • Like 2
Link to comment
Share on other sites

Corrected code:

$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);
}
$selector = "title|body|interests*=" . implode("|", $qs) . ", limit=50";
if($user->isLoggedin()) $selector .= ", has_parent!=2";
$matches = $pages->find($selector); 
if($matches->count) {
$content .= "<h2>Found $matches->count pages matching your query:</h2>";
$content .= renderNavPanel($matches);
} else {
$content = "<h2>Sorry, no results were found.</h2>";
}
} else {
$content = "<h2>Please enter a search term in the search box (upper right corner)</h2>";
}
  • Like 1
Link to comment
Share on other sites

Yeah, I caught the missing ; and the $gs for $qs. Still couldn't quite get it to work, but in the meantime managed to find another way to get the results I wanted. (One nice thing about PW: You can find many solutions to the same problem.)

Your contributions did help me understand much more about how the search function works, and for that I thank you.

Link to comment
Share on other sites

Sure, LostKobraki. As I said, I ended up abandoning this hack of the search method, which I only investigated as a workaround to get what I really wanted—and my new solution is far more useful and user-friendly on the front end.

The fundamental building blocks of my site are the POIs—the items the user wants to see. Everything else is structural or organizational. (POI is an ugly acronym for the even uglier phrase "Point of Interest," but a useful catchall for a travel site like mine for museums, hotels, cafes, bios, and explanations of how the local buses work.)

So, in a two-step process, I first needed to generate a list of "interests" tags associated with a given POI. Easy.

What befuddled me for a few days was the second step: How a click on an "interest" would generate a page of results of links to all pages using the POI template, containing that interest 'tag,' and located in the same "place" as the original POI. (POIs live in the page tree structure under their given place—London, Paris—since that just makes life easier, even if a database normalizer would howl at me for not making "place" a separate table.)  

My eventual solution used url segments:

In the _main.php (which populates the POI pages):

<?php if ($page->interests) { ?>
<div class='panel-plain'>
<h3>Similar interests:</h3>
<ul><?php
foreach ($page->interests as $i) {
echo "<li><a href='../../interest/$i->name'>{$i->title}</a></li>";
} ?>
</ul>
</div>
<?php } ?>

I then created a (hidden) interest.php to live at the root of the place folder:

<?php
if ($input->urlSegment1) {
$int = $sanitizer->text($input->urlSegment1);
$place = $page->parent->title;
$pois = $pages->find("template=poi, place=$place, interests=$int");
$content .= "<h1>$place: $int</h1>";
$content .= renderNavPanel($pois);
$browserTitle = "$place: $int";
$title = "";
}
else {
$content = $page->body;}

(The renderNavPanel lives in my _func.php and makes up a fancy little playing card–like view for each POI.)

Not only is this more extensible that sending all requests to the (or a) search page, but it is cleaner and more logical for the user and—hopefully—SEO. The url for the results, instead of being site/search.php, is now site/places/london/[interest]/.

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...