vendor/contao/core-bundle/src/Resources/contao/modules/ModuleSearch.php line 83

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. use Contao\CoreBundle\Exception\PageNotFoundException;
  11. use Contao\CoreBundle\File\Metadata;
  12. use Contao\CoreBundle\Security\ContaoCorePermissions;
  13. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  14. /**
  15.  * Front end module "search".
  16.  */
  17. class ModuleSearch extends Module
  18. {
  19.     /**
  20.      * Template
  21.      * @var string
  22.      */
  23.     protected $strTemplate 'mod_search';
  24.     /**
  25.      * Display a wildcard in the back end
  26.      *
  27.      * @return string
  28.      */
  29.     public function generate()
  30.     {
  31.         $request System::getContainer()->get('request_stack')->getCurrentRequest();
  32.         if ($request && System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest($request))
  33.         {
  34.             $objTemplate = new BackendTemplate('be_wildcard');
  35.             $objTemplate->wildcard '### ' $GLOBALS['TL_LANG']['FMD']['search'][0] . ' ###';
  36.             $objTemplate->title $this->headline;
  37.             $objTemplate->id $this->id;
  38.             $objTemplate->link $this->name;
  39.             $objTemplate->href StringUtil::specialcharsUrl(System::getContainer()->get('router')->generate('contao_backend', array('do'=>'themes''table'=>'tl_module''act'=>'edit''id'=>$this->id)));
  40.             return $objTemplate->parse();
  41.         }
  42.         $this->pages StringUtil::deserialize($this->pages);
  43.         return parent::generate();
  44.     }
  45.     /**
  46.      * Generate the module
  47.      */
  48.     protected function compile()
  49.     {
  50.         // Mark the x and y parameter as used (see #4277)
  51.         if (isset($_GET['x']))
  52.         {
  53.             Input::get('x');
  54.             Input::get('y');
  55.         }
  56.         // Trigger the search module from a custom form
  57.         if (!isset($_GET['keywords']) && Input::post('FORM_SUBMIT') == 'tl_search')
  58.         {
  59.             $_GET['keywords'] = Input::post('keywords');
  60.             $_GET['query_type'] = Input::post('query_type');
  61.             $_GET['per_page'] = Input::post('per_page');
  62.         }
  63.         $blnFuzzy $this->fuzzy;
  64.         $strQueryType Input::get('query_type') ?: $this->queryType;
  65.         if (\is_array(Input::get('keywords')))
  66.         {
  67.             throw new BadRequestHttpException('Expected string, got array');
  68.         }
  69.         $strKeywords trim(Input::get('keywords'));
  70.         $this->Template->uniqueId $this->id;
  71.         $this->Template->queryType $strQueryType;
  72.         $this->Template->keyword StringUtil::specialchars($strKeywords);
  73.         $this->Template->keywordLabel $GLOBALS['TL_LANG']['MSC']['keywords'];
  74.         $this->Template->optionsLabel $GLOBALS['TL_LANG']['MSC']['options'];
  75.         $this->Template->search StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['searchLabel']);
  76.         $this->Template->matchAll StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['matchAll']);
  77.         $this->Template->matchAny StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['matchAny']);
  78.         $this->Template->advanced = ($this->searchType == 'advanced');
  79.         // Redirect page
  80.         if (($objTarget $this->objModel->getRelated('jumpTo')) instanceof PageModel)
  81.         {
  82.             /** @var PageModel $objTarget */
  83.             $this->Template->action $objTarget->getFrontendUrl();
  84.         }
  85.         $this->Template->pagination '';
  86.         $this->Template->results '';
  87.         // Execute the search if there are keywords
  88.         if ($strKeywords !== '' && $strKeywords != '*' && !$this->jumpTo)
  89.         {
  90.             // Search pages
  91.             if (!empty($this->pages) && \is_array($this->pages))
  92.             {
  93.                 $arrPages = array();
  94.                 foreach ($this->pages as $intPageId)
  95.                 {
  96.                     $arrPages[] = array($intPageId);
  97.                     $arrPages[] = $this->Database->getChildRecords($intPageId'tl_page');
  98.                 }
  99.                 if (!empty($arrPages))
  100.                 {
  101.                     $arrPages array_merge(...$arrPages);
  102.                 }
  103.                 $arrPages array_unique($arrPages);
  104.             }
  105.             // Website root
  106.             else
  107.             {
  108.                 /** @var PageModel $objPage */
  109.                 global $objPage;
  110.                 $arrPages $this->Database->getChildRecords($objPage->rootId'tl_page');
  111.             }
  112.             // HOOK: add custom logic (see #5223)
  113.             if (isset($GLOBALS['TL_HOOKS']['customizeSearch']) && \is_array($GLOBALS['TL_HOOKS']['customizeSearch']))
  114.             {
  115.                 foreach ($GLOBALS['TL_HOOKS']['customizeSearch'] as $callback)
  116.                 {
  117.                     $this->import($callback[0]);
  118.                     $this->{$callback[0]}->{$callback[1]}($arrPages$strKeywords$strQueryType$blnFuzzy$this);
  119.                 }
  120.             }
  121.             // Return if there are no pages
  122.             if (empty($arrPages) || !\is_array($arrPages))
  123.             {
  124.                 return;
  125.             }
  126.             $query_starttime microtime(true);
  127.             try
  128.             {
  129.                 $objResult Search::query($strKeywords, ($strQueryType == 'or'), $arrPages$blnFuzzy$this->minKeywordLength);
  130.             }
  131.             catch (\Exception $e)
  132.             {
  133.                 System::getContainer()->get('monolog.logger.contao.error')->error('Website search failed: ' $e->getMessage());
  134.                 $objResult = new SearchResult(array());
  135.             }
  136.             $query_endtime microtime(true);
  137.             // Sort out protected pages
  138.             if (Config::get('indexProtected'))
  139.             {
  140.                 $objResult->applyFilter(static function ($v)
  141.                 {
  142.                     return empty($v['protected']) || System::getContainer()->get('security.helper')->isGranted(ContaoCorePermissions::MEMBER_IN_GROUPSStringUtil::deserialize($v['groups'] ?? nulltrue));
  143.                 });
  144.             }
  145.             $count $objResult->getCount();
  146.             $this->Template->count $count;
  147.             $this->Template->page null;
  148.             $this->Template->keywords $strKeywords;
  149.             if ($this->minKeywordLength 0)
  150.             {
  151.                 $this->Template->keywordHint sprintf($GLOBALS['TL_LANG']['MSC']['sKeywordHint'], $this->minKeywordLength);
  152.             }
  153.             // No results
  154.             if ($count 1)
  155.             {
  156.                 $this->Template->header sprintf($GLOBALS['TL_LANG']['MSC']['sEmpty'], $strKeywords);
  157.                 $this->Template->duration System::getFormattedNumber($query_endtime $query_starttime3) . ' ' $GLOBALS['TL_LANG']['MSC']['seconds'];
  158.                 return;
  159.             }
  160.             $from 1;
  161.             $to $count;
  162.             // Pagination
  163.             if ($this->perPage 0)
  164.             {
  165.                 $id 'page_s' $this->id;
  166.                 $page = (int) (Input::get($id) ?? 1);
  167.                 $per_page = (int) Input::get('per_page') ?: $this->perPage;
  168.                 // Do not index or cache the page if the page number is outside the range
  169.                 if ($page || $page max(ceil($count/$per_page), 1))
  170.                 {
  171.                     throw new PageNotFoundException('Page not found: ' Environment::get('uri'));
  172.                 }
  173.                 $from = (($page 1) * $per_page) + 1;
  174.                 $to = (($from $per_page) > $count) ? $count : ($from $per_page 1);
  175.                 // Pagination menu
  176.                 if ($to $count || $from 1)
  177.                 {
  178.                     $objPagination = new Pagination($count$per_pageConfig::get('maxPaginationLinks'), $id);
  179.                     $this->Template->pagination $objPagination->generate("\n  ");
  180.                 }
  181.                 $this->Template->page $page;
  182.             }
  183.             $contextLength 48;
  184.             $totalLength 360;
  185.             $lengths StringUtil::deserialize($this->contextLengthtrue) + array(nullnull);
  186.             if ($lengths[0] > 0)
  187.             {
  188.                 $contextLength $lengths[0];
  189.             }
  190.             if ($lengths[1] > 0)
  191.             {
  192.                 $totalLength $lengths[1];
  193.             }
  194.             $arrResult $objResult->getResults($to-$from+1$from-1);
  195.             // Get the results
  196.             foreach (array_keys($arrResult) as $i)
  197.             {
  198.                 $objTemplate = new FrontendTemplate($this->searchTpl ?: 'search_default');
  199.                 $objTemplate->setData($arrResult[$i]);
  200.                 $objTemplate->href $arrResult[$i]['url'];
  201.                 $objTemplate->link $arrResult[$i]['title'];
  202.                 $objTemplate->url StringUtil::specialchars(urldecode($arrResult[$i]['url']), truetrue);
  203.                 $objTemplate->title StringUtil::specialchars(StringUtil::stripInsertTags($arrResult[$i]['title']));
  204.                 $objTemplate->class = ($i == 'first ' '') . ((empty($arrResult[$i+1])) ? 'last ' '') . (($i == 0) ? 'even' 'odd');
  205.                 $objTemplate->relevance sprintf($GLOBALS['TL_LANG']['MSC']['relevance'], number_format($arrResult[$i]['relevance'] / $arrResult[0]['relevance'] * 1002) . '%');
  206.                 $objTemplate->unit $GLOBALS['TL_LANG']['UNITS'][1];
  207.                 $arrContext = array();
  208.                 $strText StringUtil::stripInsertTags(strtok($arrResult[$i]['text'], "\n"));
  209.                 $arrMatches Search::getMatchVariants(StringUtil::trimsplit(','$arrResult[$i]['matches']), $strText$GLOBALS['TL_LANGUAGE']);
  210.                 // Get the context
  211.                 foreach ($arrMatches as $strWord)
  212.                 {
  213.                     $arrChunks = array();
  214.                     preg_match_all('/(^|(?:\b|^).{0,' $contextLength '}(?:\PL|\p{Hiragana}|\p{Katakana}|\p{Han}|\p{Myanmar}|\p{Khmer}|\p{Lao}|\p{Thai}|\p{Tibetan}))' preg_quote($strWord'/') . '((?:\PL|\p{Hiragana}|\p{Katakana}|\p{Han}|\p{Myanmar}|\p{Khmer}|\p{Lao}|\p{Thai}|\p{Tibetan}).{0,' $contextLength '}(?:\b|$)|$)/ui'$strText$arrChunks);
  215.                     foreach ($arrChunks[0] as $strContext)
  216.                     {
  217.                         $arrContext[] = ' ' $strContext ' ';
  218.                     }
  219.                     // Skip other terms if the total length is already reached
  220.                     if (array_sum(array_map('mb_strlen'$arrContext)) >= $totalLength)
  221.                     {
  222.                         break;
  223.                     }
  224.                 }
  225.                 // Shorten the context and highlight all keywords
  226.                 if (!empty($arrContext))
  227.                 {
  228.                     $objTemplate->context trim(StringUtil::substrHtml(implode('…'$arrContext), $totalLength));
  229.                     $objTemplate->context preg_replace('((?<=^|\PL|\p{Hiragana}|\p{Katakana}|\p{Han}|\p{Myanmar}|\p{Khmer}|\p{Lao}|\p{Thai}|\p{Tibetan})(' implode('|'array_map('preg_quote'$arrMatches)) . ')(?=\PL|\p{Hiragana}|\p{Katakana}|\p{Han}|\p{Myanmar}|\p{Khmer}|\p{Lao}|\p{Thai}|\p{Tibetan}|$))ui''<mark class="highlight">$1</mark>'$objTemplate->context);
  230.                     $objTemplate->hasContext true;
  231.                 }
  232.                 $this->addImageToTemplateFromSearchResult($arrResult[$i], $objTemplate);
  233.                 $this->Template->results .= $objTemplate->parse();
  234.             }
  235.             $this->Template->header vsprintf($GLOBALS['TL_LANG']['MSC']['sResults'], array($from$to$count$strKeywords));
  236.             $this->Template->duration System::getFormattedNumber($query_endtime $query_starttime3) . ' ' $GLOBALS['TL_LANG']['MSC']['seconds'];
  237.         }
  238.     }
  239.     protected function addImageToTemplateFromSearchResult(array $resultTemplate $template): void
  240.     {
  241.         $template->hasImage false;
  242.         if (!isset($result['meta']))
  243.         {
  244.             return;
  245.         }
  246.         $meta json_decode($result['meta'], true);
  247.         foreach ($meta as $v)
  248.         {
  249.             if (!isset($v['https://schema.org/primaryImageOfPage']['contentUrl']))
  250.             {
  251.                 continue;
  252.             }
  253.             $baseUrls array_filter(array(Environment::get('base'), System::getContainer()->get('contao.assets.files_context')->getStaticUrl()));
  254.             $figureBuilder System::getContainer()->get('contao.image.studio')->createFigureBuilder();
  255.             $figureBuilder->fromUrl($v['https://schema.org/primaryImageOfPage']['contentUrl'], $baseUrls);
  256.             $figureMeta = new Metadata(array_filter(array(
  257.                 Metadata::VALUE_CAPTION => $v['https://schema.org/primaryImageOfPage']['caption'] ?? null,
  258.                 Metadata::VALUE_TITLE => $v['https://schema.org/primaryImageOfPage']['name'] ?? null,
  259.                 Metadata::VALUE_ALT => $v['https://schema.org/primaryImageOfPage']['alternateName'] ?? null,
  260.             )));
  261.             $figure $figureBuilder
  262.                 ->setSize($this->imgSize)
  263.                 ->setMetadata($figureMeta)
  264.                 ->setLinkHref($result['url'])
  265.                 ->buildIfResourceExists();
  266.             if (null === $figure)
  267.             {
  268.                 continue;
  269.             }
  270.             $template->hasImage true;
  271.             $template->figure $figure;
  272.             $template->image = (object) $figure->getLegacyTemplateData();
  273.             return;
  274.         }
  275.     }
  276. }
  277. class_alias(ModuleSearch::class, 'ModuleSearch');