SSbits - Home page
Site by Carbon Crayon
Submit a Post >

Snippets - Little bits of code to make you happy

Highlighting Query Text in Search Results


Most search results I have seen with Silverstripe just return the Summary of the page or the FirstSentence.  I have knocked up a quick way to show a sentence from the page that contains the search term and even highlights it for you.

After spending some time writing a small snippet of code to achieve this, Matt Clegg very kindly pointed me at a built in function that does pretty much the same thing.  I have left my implementation down at the bottom of this post for reference and interest but directly below is how to use the built-in functionality.

The method of interest that allows you to get a segment of text from each of your search results and even highlight the matched terms is called  Text::ContextSummary().  It has the following signature:


function ContextSummary($characters = 500, $string = false, $striphtml = true, $highlight = true, $prefix = "... ", $suffix = "...")

You can select how many characters you want to see in the summary, whether to strip out html, whether to highlight matched terms and what should be added to the start and end of the summary.  To use it you simply reference the function in your search results template as follows:

<ul id="SearchResults">
  <% control Results %>
    <a href="$Link">
      <% if MenuTitle %>$MenuTitle<% else %>$Title<% end_if %>
    <% end_if %>   
  <% end_control %>

This function marks up the matched terms with a <span class="highlight"> so you may need to add the following CSS to your stylesheets to get it to work.

span.highlight {
  font-weight: bold;
  background: yellow;

The nice thing about this is that you are free to get a ContextSummary of any of the text fields, whereas my version was a bit hard coded into the Page structure.  On the other hand, my version pulled out a whole sentence containing the matched term.  I think there is room for this ContextSummary function to be improved to make it even more flexible.


Here is my original code...

Add this code to your Page.php file:

   public function SentenceContainingQuery() {
      $query = Controller::curr()->Query;
      $content = $this->obj('Content');
      $sentences = explode( '.', $content->NoHTML());
      if ( count($sentences) ) {
         foreach($sentences as $sentence) {
            if ( strchr($sentence,$query) ) {
               return str_replace($query, "<span class='query-instance'>$query</span>", $sentence) . '.';
      } else {
         $sentence = $content->FirstSentence();
         return str_replace($query, "<span class='query-instance'>$query</span>", $sentence) . '.';

Then in your template you can put something like this:

<ul id="SearchResults">
  <% control Results %>
    <a class="searchResultHeader" href="$Link">
      <% if MenuTitle %>$MenuTitle<% else %>$Title<% end_if %>
    <% if SentenceContainingQuery %>
    <% end_if %>   
  <% end_control %>

And finally, you'll see that the text that matches the query is put in a span with class 'query-instance' so you can make it look nice with a bit of CSS:

span.query-instance {
  font-weight: bold;
  background: yellow;

Et voila!  A rather nice looking search results page.

Special Thanks

Special thanks go to Matt Clegg for their contributions to this post.

  • Matt Clegg
    12/07/2011 12:56pm (6 years ago)

    Controller::curr()->Query is returning a null value ?

    Also Im not sure why you wouldn't just use the function from Text::ContextSummary() ?

  • Pete Bacon Darwin
    12/07/2011 1:09pm (6 years ago)

    Hi Matt,
    I got the Controller::curr()->Query returning a null value when nothing had been entered in the search box.
    I did do a fair bit of searching around for this kind of functionality before I wrote thi and I had not found Text::ContextSummary. From an initial glance it looks like this does do pretty much just what I wanted.
    Thanks for pointing me in the right direction. I think the documentation on this function is sorely lacking. Hopefully this posting and your comment will help raise its profile.
    Thanks again Matt.

  • Matt Clegg
    15/07/2011 12:24am (6 years ago)

    Hi Pete,

    From looking on the SS docs ( it suggests defining; "$this->Query = $form->getSearchQuery();" Although Im not sure if that is right because I thought it would get overwritten by $this->customize() (in the example it seems to be duplicated).

    Is that what you were doing?

  • Pete Bacon Darwin
    15/07/2011 7:32am (6 years ago)

    Hi Matt,
    Yes you are right! I totally forgot that I had this function in the Page_Controller...

    function results($data, $form){
    $data = array(
    'Results' => $form->getResults(),
    'Query' => $form->getSearchQuery(),
    'Title' => 'Search Results'
    $this->Query = $form->getSearchQuery();

    return $this->customise($data)->renderWith(array('Page_results', 'Page'));

    You can see here that I did just cut and paste the code from the tutorial which does set the Query property both directly and through the customize method. Probably an unnecessary duplication.

    Sorry about that! Luckily the ContextSummary version works without this.

  • Mom Bunheng
    08/09/2011 12:01pm (6 years ago)


    Where to put the function ContextSummary as I would like to limit the character in search results.


  • Pete Bacon Darwin
    08/09/2011 12:28pm (6 years ago)

    Hi Mom,
    You use the ContextSummary method in your .ss template as shown above in the first block of grey code.

  • Corry
    01/12/2011 10:29am (5 years ago)

    Mom, I added the size I wanted when using the method in the .ss template - and it worked!

    eg <p>$Content.ContextSummary(250)</p>

  • Adil Aliyev
    28/01/2012 9:49pm (5 years ago)

    I used something like that (from SS forum):

    function MyContextSummary($characters = 500, $string = false, $striphtml = true, $highlight = true) {

    if (!$string)
    $string = $_REQUEST['Search']; // Use the default "Search" request variable (from SearchForm)

    /* * * Prepare Content ** */

    // Replace <br /> in order to get separate words

    $content = str_replace('<br />', ' ', $this->Content);

    // Decoding entities prevents XML validation error

    $content = html_entity_decode($content, ENT_COMPAT, 'UTF-8');

    // Remove HTML tags so we don't have to deal with matching tags

    $text = strip_tags($content);

    // Remove BBCode

    $pattern = '|[[\/\!]*?[^\[\]]*?]|si';

    $replace = '';

    $text = preg_replace($pattern, $replace, $text);

    // Find the search string

    $position = (int) stripos($text, $string);

    // We want the search string to be in the middle of our block to give it some context

    $position = max(0, $position - ($characters / 2));

    // We don't want to start mid-word

    if ($position > 0) {

    $position = max((int) strrpos(substr($text, 0, $position), ' '), (int) strrpos(substr($text, 0, $position), "\n"));

    $summary = substr($text, $position, $characters);

    // We also don't want to end mid-word
    $offset = $characters + $position;
    if ($offset > strlen($text) - 1)
    $offset = strlen($text) - 1;
    $position = min((int) strpos($text, ' ', $offset), (int) strpos($text, "\n", $offset));
    if ($position)
    $summary = $summary . substr($text, $offset, $position - $offset);

    if ($highlight) {
    // Setting the content taht will be inserted before and after the highlighted words
    $before_content = "<span class=\"highlight\">";
    $after_content = "</span>";

    // Save the different search values into an array
    $stringPieces = explode(' ', $string);

    foreach ($stringPieces as $stringPiece) {
    //Setting the start from where $stringPiece will be searched
    $offset = 0;
    // Recursively add before_text and after_text around all found $stringPiece

    while ($position = stripos($summary, $stringPiece, $offset)) {
    $summary =
    substr($summary, 0, $position)
    . $before_content
    . substr($summary, $position, strlen($stringPiece))
    . $after_content
    . substr($summary, $position + strlen($stringPiece), strlen($summary) - ($position + 1));

    $offset = $position + strlen($before_content) + strlen($after_content);

    return trim($summary);

  • RuthAdele
    03/08/2012 1:29am (5 years ago)

    I tried using this technique in a project recently, but I found that it didn't render the returning html. It would output the text <span class="highlight"></span> (And I've noticed the same thing happens on the Silverstripe forums).
    Does anyone know how to make it render the html instead of printing it? I'd really like to use this bit of functionality!

  • Nicola Fontana
    23/05/2015 8:27pm (2 years ago)

    @RuthAdele A bit late but... by default tags are usually XML escaped by SilverStripe. If you want them not escaped at all (as your case seems to imply) you should append the RAW postfix (e.g. $Content.ContextSummary.RAW).

    No idea why this is not the default for $ContextSummary though.

Post a comment ...

You cannot post comments until you have logged in. Login Here.


Site of the Month

Find SSbits on

Top Contributers

Rank Avatar Name
1 article image Aram Balakjian
2 article image wfacewomens
3 article image Daniel Hensby
4 article image Marcus Dalgren
5 article image ryduhartchas
6 article image Hamish Campbell
7 article image Ty Barho
8 article image Martijn van Nieuwenhoven
9 article image Darren-Lee
10 article image Roman Schmid

View full leaderboard