SimpleSearch: Manipulating Results

Hello,

Could anyone help me manipulate the search results from the SimpleSearch extra?

I’m working on a site where the majority of the pages are “products” and “news stories”. The client would like the search results to be sorted so that all product pages (template 3) would display before the news stories (template 6). Basically I would need to gather the results for each template type and store them in their own arrays. Then combine the products array with the news story array with products being first. The problem is I’m not exactly sure where to begin coding that. Would anyone have advice on how to go about this problem?

I’m assuming wrapping the SimpleSearch snippet would be the best way to go about this but I’m unsure how to re-query the results.

Thanks,
Chris

Hi Cris,

This should be possible (I am assuming that your products and news stories are resources).
Have a look here:
https://docs.modx.com/extras/revo/simplesearch/simplesearch.simplesearch

sortBy and sortDir should do the trick.

Let’s assume that you want to order by template and then by pagetitle.
Your simplesearch call will contain the following attributes:

&sortBy=`template,pagetitle`
&sortDir=`ASC`

I believe this might help you.
Please share your outcome.

Cheers

Hello,

Thanks for the response. That very was close, but it ignores search relevancy. Your solution worked to where the results were sorted with products first. However the the product were sorted incorrectly. I ended up removing pagetitle, hoping the the most relevant products should display first. Instead it sorted the product by resource id.

I’m pretty sure I’ll have to capture the results with two arrays, in the order they typically come in as to maintain the most relevant results. Here is the pseudo code I came up with:

foreach ($results as $result) {
if template:IN' => 3
   $product-array = $result
else-if template:IN' => 6
   $news-array = $result
else
  $other-array = $result

$results = array_merge($product-array, $news-array, $other-array);

return $modx->runSnippet('SimpleSearch', $scriptProperties);

I’m not sure where or how to exactly code that.

Thanks,
Chris

Well, it will depend how you categorize the relevancy. If you are using menuindex field (used for menu position on the resources tree), or if you created a Template Var for that,
Either way you can use on the SimpleSearch filtering on the very same way.

Have a look on the previous documentation for the following properties:
includeTVs
includeTVList
processTVs

This might help you.
Cheers

1 Like

You may be looking for faceted search although that doesn’t quite change the relevancy - it does let you show two different result blocks.

There’s also a fieldPotency property which allows you to add more weight to specific fields. Doesn’t quite let you say “template 4 is more important”, but can be used for some finetuning.

1 Like

jnogueira,
Relevancy to the search query is very important. It seems when sortBy is used, the results will no longer be returned by relevancy or score. Unfortunately I don’t think my solution can be found within the standard property settings.

Mark,
I’ve been experimenting with the faceted search with zero successful results. The issue is that I have limited knowledge of PDO and I’m trying to mix and match different examples I’ve found.

To start things off easy, I was trying to only pull results that have a template id of 3. Could you tell what I’m doing wrong? From what I gather, this is what the script is doing:

  • Starts a new query.
  • Filters for templateid and search query.
  • Counts the number results.
  • Selects desired Fields
  • Set Limits
  • I’m assuming getCollection is pulling in the existing search results.
  • Then a loops checks through the “collection” to find matches.
  • Returns results for “products”

…I’m probably getting some of that confused

ProductFacetHook3

$c = $modx->newQuery('modResource');
$c->where(array(
    'template:IN' => '3',
    'AND:menutitle:LIKE' => '%'.$search.'%',
));
$count = $modx->getCount('modResource',$c);
$c->select(array(
    'modResource.*',
    'id',
    'menutitle',
    'template',
));
$c->limit($limit,$offset);

$products = $modx->getCollection('modResource',$c);

$results = array();
foreach ($products as $product) {
    $results[] = array(
        'menutitle' => $product->get('menutitle'),
        'link' => $product->makeUrl(10,'',array(
            'product' => $product->get('id'),
        )),
        'excerpt' => '',
    );
}

$hook->addFacet('products',$results,$count);
return true;

Here is how I’m calling the posthook on the results page. I wasn’t sure if sisea was needed, so I added multiple variations. Nothing I’ve done has returned results.

[[!SimpleSearch?
&containerTpl=`tplSearchResults`
&tpl=`tplSearchResultItem`
&perPage=`10`
&postHooks=`ProductFacetHook3`
&facetLimit=`5`
]]


<h2>Results</h2>
[[+sisea.results]]
[[+results]]

<h2>Products Results</h2>
[[+sisea.products.results]]
[[+products.results]]

Could we see your SimpleSearch call?

I think your basic process is right. First the search on the products, then the posthook to search news articles, two separate searches and two outputs

Hi Nuan,

Below is how I’m calling SimpleSearch. I wasn’t sure exactly how pull in the posthook results. That is why I’m using multiple methods, but none of them are displaying results.

[[!SimpleSearch?
&containerTpl=`tplSearchResults`
&tpl=`tplSearchResultItem`
&perPage=`10`
&postHooks=`ProductFacetHook3`
&facetLimit=`5`
]]


<h2>Results</h2>
[[+sisea.results]]
[[+results]]

<h2>Products Results</h2>
[[+sisea.products.results]]
[[+products.results]]

Ok, my advice is to strip the code down and start with the example simple search call and tpl. Don’t worry about the hook quite yet, let’s get the first level results. You wouldn’t need to edit the tpls, if the second value isn’t there they won’t appear at all I believe. Try to strip down every part of the calls, so we can get something appearing, then you can add in piece by piece.

If pulling out the hook can get the first level search working, then we know something, but it would be strange for the posthook to mess up the first search.

We now know that, if we can get the first search done, then we can do the second search after. So we just need the first one, with the right relevancy, and hopefully its easy after that.

I am not a coder and can’t help with the coding, but please be aware that the code you have modified is supposed to be the hook search, the second search. So you would not want to have products in that one at all, but rather news.

Note that the end of that snippet of code is

`$hook` `->addFacet(` `'people'` `,` `$results` `,` `$count` `);`

Which is intended to add in that second level of searching.

I think if you can get back to the point where the product search is completed properly, then we can add in this hook and get the code working

Also, for the snippet and the tpl of the search, you can build in error messages to tell you what is firing and what is not. I think in the php its something like, if a value is empty then return an error message. That might even work by putting those messages in the tpl as well

Also what does your tplSearchResultItem tpl have in it? That example is using very simple placeholders, one of which probably should be [[+products]] (from the hook as you have it now)

Couple minor things that stand out in your hook code.

When using an IN condition, provide an array:

$c->where(array(
    'template:IN' => ['3'],
    'AND:menutitle:LIKE' => '%'.$search.'%',
));

(As you’re matching against a single template, you could also simply do 'template' => 3.)

Also I don’t think $product, a modResource instance, has a makeUrl method so that could cause a fatal error, so swap out $product for $modx in the link generation:

        'link' => $modx->makeUrl(10,'',array(
            'product' => $product->get('id'),
        )),

Make sure you call the placeholders uncached as your snippet is uncached as well (which it should be), and the sisea prefix is necessary as far as I know:

[[!SimpleSearch?
&containerTpl=`tplSearchResults`
&tpl=`tplSearchResultItem`
&perPage=`10`
&postHooks=`ProductFacetHook3`
&facetLimit=`5`
]]

<h2>Results</h2>
[[!+sisea.results]]

<h2>Products Results</h2>
[[!+sisea.products.results]]

To make sure things are running as expected, you could add some logging into the hook: $modx->log(modX::LOG_LEVEL_ERROR, 'Hook is running!'); - you could also log the query for testing with $c->prepare(); $modx->log(modX::LOG_LEVEL_ERROR, $c->toSQL());

Sorry, when I said I wasn’t receiving results, I meant results for the posthook. The initial results is pulling correctly. Once I have the correct results displaying in the hook, I’ll hide the first level of results.

tplSearchResults formats the entire list of results and tplSearchResultItem is formatting each result.

Thank you for your help and that is a great idea to use the template for error reporting.

Hi Mark,

Thank you so much for your on going help. When I made the updates I’m receiving the following errors:

...public/core/model/modx/modx.class.php : 1031) `[[+id]]` is not a valid integer and may not be passed to makeUrl()
...public/core/model/modx/modparser.class.php : 1374) Bad link tag `[[~[[+id]]]]` encountered

Here’s the update script:

$c = $modx->newQuery('modResource');
$c->where(array(
    'template:IN' => ['3'],
    'AND:menutitle:LIKE' => '%'.$search.'%',
));
$count = $modx->getCount('modResource',$c);
$c->select(array(
    'modResource.*',
    'id',
    'menutitle',
    'template',
));
$c->limit($limit,$offset);

$products = $modx->getCollection('modResource',$c);

$results = array();
foreach ($products as $product) {
    $results[] = array(
        'menutitle' => $product->get('menutitle'),
        'link' => $modx->makeUrl(10,'',array(
            'product' => $product->get('id'),
        )),
        'excerpt' => '',
    );
}

$hook->addFacet('products',$results,$count);
$modx->log(modX::LOG_LEVEL_ERROR, 'Hook is running!');
return true;

My biggest issue is understanding how the Results array is built in the For loop. I tried removing ‘link’ from the loop, but I still did not receive any results.

The good news is that each error message is assigned to a result. So different queries yield a different number of errors. After some testing, the number of errors actually match the number of results that should be returned. So the beginning of the script seems to be working.

I’m just having an issue packaging them and sending them back to the results page. Do you have any suggestions or is there a way we could dumb down the for loop to make it easier to understand?

Thanks
Chris

Sounds like you’re almost there! :wink:

Make sure that the data provided in the $results array matches the placeholders you’re using in the tplSearchResultItem chunk.

Right now you’re only setting a menutitle, but perhaps you need to add a pagetitle or other values (including id for the link tag), to make it show up properly.

Pls check your tplSearchResultItem, that’s the output for each result, and match the array items with it

Based on the example at the link below, a sample tpl for this is something like this:

1
	<div class="sisea-result">
2
	    <h3><a href="[[+link:is=``:then=`[[~[[+id]]]]`:else=`[[+link]]`]]" title="[[+longtitle]]">[[+pagetitle]]</a></h3>
3
	    <div class="extract">
4
	        <p>[[+extract]]</p>
5
	    </div>
6
	    <!-- /.extract -->
7
	</div>
8
	<!-- /.sisea-result -->

https://www.karbondesigned.co.uk/simplesearch-modx-tutorial-easy-to-follow/

This tpl seems to be where SimpleSearch gets the result data and formats them. I would assume this second level of search will use the same tpl, would assume the specified one is used both times.

1 Like

Thanks again for the advice. I’m VERY CLOSE. The values I’m pulling now match the fields in the tpl. For some reason, the revised data isn’t displaying on the results page. I ran a check that outputs the menutitle to the error log and the correct data is being found.

The placeholder seems to be formatted correctly but none of the calls I’ve tried worked. Am I missing something that defines the posthook?

Define Placeholder:
$hook->addFacet(‘products’,$results,$count);

Here are the variations of calls I’ve tried:
[[!+sisea.products.results]]
[[!+sisea.products]]
[[!+products]]
[[!+products.results]]
[[+sisea.products.results]]
[[+sisea.products]]
[[+products]]
[[+products.results]]

ProductFacetHook3:

$c = $modx->newQuery('modResource');
$c->where(array(
    'template:IN' => ['3'],
    'AND:menutitle:LIKE' => '%'.$search.'%',
));
$count = $modx->getCount('modResource',$c);
$c->select(array(
    'modResource.*',
    'id',
    'menutitle',
    'template',
   // 'ProductImgThumb',
    'pagetitle',
));
$c->limit($limit,$offset);
$products = $modx->getCollection('modResource',$c);

$results = array();
foreach ($products as $product) {
    $results[] = array(
        'id' => $product->get('id'),
        'pagetitle' => $product->get('pagetitle'),
        'menutitle' => $product->get('menutitle'),

        //'ProductImgThumb' => $product->get('ProductImgThumb'),
        'link' => $modx->makeUrl(583,'',array(
            'product' => $product->get('id'),
        )),
        
        'excerpt' => '',
    );
    
    $x = $product->get('menutitle');
    $modx->log(modX::LOG_LEVEL_ERROR, $x);
}

$hook->addFacet('products',$results,$count);
return true;

Results Page:

[[!SimpleSearch?
&containerTpl=`tplSearchResults`
&tpl=`tplSearchResultItem`
&toPlaceholder=`sisea.results`
&perPage=`10`
&includeTVs=`1`
&minChars=`1`
&postHooks=`ProductFacetHook3`
&facetLimit=`5`
]]

<h2>Results</h2>
[[!+sisea.results]]

tplSearchResultItem:

<div class="search-result">
    <a href="[[+link:is=``:then=`[[~[[+id]]]]`:else=`[[+link]]`]]">
        <div class="search-content">
            <div class="search-title">[[+menutitle]]</div>
            <div class="search-cat">[[!SearchCheckCat? &url=`[[+link]]`]]</div>
            <div class="search-desc">[[+extract]]</div>
        </div>
    </a>
</div>

I’ve also tried using the default containerTpl and tpl, but still no dice.

Thanks,
Chris

I wonder if you could just make the array and spit it out with a custom chunk, which would effectively be the same as one that you already use.

Anyway, this is the code from the facet example page, I notice that its got a bit of html around it, while the first results don’t

<h2>People Results ([[+sisea.products.total]])</h2>
<ol>[[+sisea.products.results]]</ol>
 
<a href="[[~123]]?facet=products&search=[[!+sisea.query]]">Get more Products...</a>

Good morning,

sending the data out could be an alternative if this don’t work, but I think I’m missing something small.

Yeah, I’ve tried that too. The issue is data isn’t being returned to the page. containerTpl and tpl should take care of the formatting. I even tried using a custom tpl for the posthook results.

I see you list out the placeholder variations you tried. Based on the docs, your variations with [[+sisea.products.results]] or the un-cached version should be correct.

Looking at the snippet, it looks like there is a resultInfo placeholder being set just before it sets the final placeholders and returns the result. Perhaps try adding [[+sisea.resultInfo]] so that you can see the output.

I think there is an error in the docs example for this. Can you also try setting your &toPlaceholder=`results`, rather than using the `sisea.results`?

The docs page here lists an incorrect format on line 2:
https://docs.modx.com/extras/revo/simplesearch/simplesearch.simplesearch/simplesearch.faceted-search-through-posthooks

Also the new docs appear to have errors as well.

Actual code from line 68 of SimpleSearch: $placeholderPrefix = $modx->getOption(‘placeholderPrefix’,$scriptProperties,‘sisea.’);

As you can see, if you don’t specify a &placeholderPrefix=`myprefix`, the placeholders will automatically have ‘sisea.’ added.

The final results output from SimpleSearch from 163 to 174:

/* output */
$modx->setPlaceholder($placeholderPrefix.'query',$searchString);
$modx->setPlaceholder($placeholderPrefix.'count',$response['total']);
$modx->setPlaceholders($placeholders,$placeholderPrefix);
if (empty($response['results'])) {
    $output = $search->getChunk($noResultsTpl,array(
        'query' => $searchString,
    ));
} else {
    $output = $search->getChunk($containerTpl,$placeholders);
}
return $search->output($output,$toPlaceholder);

Or, as a simple test with your existing templates, see if anything exists at the placeholder [[+sisea.sisea.results]]. I think it is getting prefixed twice.

1 Like

Thanks for the advice! I was excited to try [[+sisea.sisea.results]], but sadly it didn’t yield results. You had great idea to investigate how exactly simplesearch is building the posthook.

Boom! My prefix was coded to “simplesearch.” I tried it out and it is now returning results!
[[!+simplesearch.products.results]]

$placeholderPrefix = $modx->getOption('placeholderPrefix', $scriptProperties, 'simplesearch.');

Now that part is complete, I can finish coding my original issue. Hopefully my next post will be the solution.

2 Likes

First off, I wanted to thank everyone for the help and advice. I would still be lost if it wasn’t for the awesome Modx community!

My issue has been solved and I am now able to stack the products results on top of the news results. The correct code is below, the biggest issue was with the prefix for the facet results. It is best to check the SimpleSearch snippet around line 50-70 for a variable named: $placeholderPrefix. That will tell your the correct prefix to use.

SOLUTION TO STACKING RESULTS

Calls from the search page

[[!SimpleSearch?
&containerTpl=`tplSearchResults`
&tpl=`tplSearchResultItem`
&toPlaceholder=`sisea.results`
&includeTVs=`1`
&andTerms=`0`
&useAllWords=`0`
&searchStyle=`partial`
&perPage=`5`
&minChars=`1`
&postHooks=`ProductNewsFacet`
]]

<h2>Search Results</h2>
[[!+simplesearch.productsnew.results]]
[[!If?
   &subject=`[[!+simplesearch.productsnew.total]]`
   &operator=`EQ`
   &operand=``
   &then=`No Results Found`
   &else=``
]]

Here is the Facet "ProductNewsFacet"
There is probably a way to greatly streamline this code, but hey, it works so happy!
Bonus: I was able to use an inner join to grab a needed template variable.

$searchArraySp = explode(" ", $search);
$searchArrayHy = explode("-", $search);
$count = 0;


$cProd = $modx->newQuery('modResource');
$cNews = $modx->newQuery('modResource');

$cProd->innerJoin('modTemplateVarResource','TemplateVarResources');
$cProd->innerJoin('modTemplateVar','TemplateVar','`TemplateVar`.`id` = `TemplateVarResources`.`tmplvarid` AND `TemplateVar`.`name` = "ProductImgThumb"');

$cProd->where(array(
    array(
        ':modResource.menutitle:LIKE' => $searchArrayHy[0] . ' %',
        'OR:modResource.menutitle:LIKE' => $searchArrayHy[0] . '-%',
        'OR:modResource.menutitle:LIKE' => $searchArraySp[0] . ' %',
        'OR:modResource.menutitle:LIKE' => $searchArraySp[0] . '-%',
        'OR:modResource.menutitle:LIKE' => $search . '-%'
    ),
    array(
        'AND:modResource.template:IN' => ['3']
    ),
));
$cProd->select(array(
    'modResource.*',
    'TemplateVar.name',
    'TemplateVarResources.value',
));


$cNews->where(array(
    array(
        'modResource.content:LIKE' => '%' . $search . '%',
        'OR:modResource.menutitle:LIKE' => '%' . $search . '%'
    ),
    array(
        'AND:modResource.template:IN' => ['6']
    ),
));
$cNews->select(array(
    'modResource.*',
));


$cProd->limit($limit,$offset);
$cNews->limit($limit,$offset);


$products = $modx->getCollection('modResource',$cProd);
$Prodresults = array();
foreach ($products as $product) {
    $id = $product->get('id');
    $Prodresults[] = array(
        'id' => $product->get('id'),
        'pagetitle' => $product->get('pagetitle'),
        'menutitle' => $product->get('menutitle'),
        'ProductImgThumb' => $product->get('value'),
        'link' => $modx->makeUrl($id),
        'extract' => $product->get('description'),
    );
    $count++;
}

$news = $modx->getCollection('modResource',$cNews);
$Newsresults = array();

foreach ($news as $post) {
    $postid = $post->get('id');
    $Newsresults[] = array(
        'id' => $post->get('id'),
        'pagetitle' => $post->get('pagetitle'),
        'menutitle' => $post->get('menutitle'),
        'link' => $modx->makeUrl($postid),
        'extract' => $post->get('description'),
    );
    $count++;
}

$results = array();
$results = array_merge($Prodresults, $Newsresults);

$hook->addFacet('productsnew',$results,$count);
return true;