I know this is an old question but i stumbled upon it while searching for an answer so others might find it as well and I wanted to share my solution which is not perfect but works really good for all sorts of fields.
What I wanted was to display the context why the entry was returned when searching for a certain keyphrase. So What I do is, I iterated over all fields of an entry using $entry->getFieldValues()
and then based on what type the field is I chose the next step:
- Is it a text or redactor field -> search for the searchQuery in the string and if it is found append it to the excerpt
- Is it an array -> recursively call the function to iterate over all fields of the array
- Is it of type matrixblock or supertable -> recursively call the function to iterate over all fields
This solution obviously finds more than the search function uses to decided if the entry is part of the result set because I didn't find a way to check if a field is part of the searchindex but it results in a nice excerpt to give users context for their search result.
Full TwigExtension:
<?php
namespace modules\extensions;
use Twig\Extension\AbstractExtension;
class SearchResultExcerpt extends AbstractExtension
{
public function getFunctions(): array
{
return [
new \Twig\TwigFunction("getSearchResultExcerpt", function ($entry, $searchString) {
return $this->getBlockExcerpt($entry, $searchString);
}),
];
}
private function getBlockExcerpt($block, $searchString): string
{
$excerpt = "";
$fields = is_array($block) ? $block : $block->getFieldValues();
foreach ($fields as $field) {
$fieldExcerpt = "";
if (is_string($field)) {
$text = strip_tags($field);
$matches = $this->findMatches($text, $searchString);
if (strlen($matches) > 0) {
$fieldExcerpt .= '<span class="' . array_search($field, $fields) . '">' . $matches . "</span>";
}
} elseif (is_array($field)) {
foreach ($field as $value) {
$fieldExcerpt .= $this->getBlockExcerpt($value, $searchString);
}
} elseif (is_object($field) && str_contains(get_class($field), "redactor")) {
$text = strip_tags($field->getParsedContent());
$matches = $this->findMatches($text, $searchString);
if (strlen($matches)) {
$fieldExcerpt .= '<span class="text-field">' . $matches . "</span>";
}
} elseif (
is_object($field) &&
(str_contains(get_class($field), "MatrixBlockQuery") ||
str_contains(get_class($field), "SuperTableBlockQuery"))
) {
foreach ($field->all() as $block) {
$fieldExcerpt .= $this->getBlockExcerpt($block, $searchString);
}
}
if (strlen($fieldExcerpt) > 0) {
$excerpt .= "<div class='content-field'>" . $fieldExcerpt . "</div>";
}
}
return $excerpt;
}
private function findMatches($text, $searchString): string
{
$text = "\n" . $text . "\n";
$words = join("|", explode(" ", preg_quote($searchString)));
preg_match_all("#((?<=\s).{0,30}(" . $words . ").{0,30}(?=\s))#i", $text, $matches, PREG_SET_ORDER);
$results = [];
foreach ($matches as $line) {
$results[] = htmlspecialchars($line[0], 0, "UTF-8");
}
$result = join(" [...] ", $results);
return preg_replace("#" . $words . "#iu", "<span class=\"highlight\">\$0</span>", $result);
}
}