cancel
Showing results for 
Search instead for 
Did you mean: 

CMS-Page in Elasticsearch

CMS-Page in Elasticsearch

I have the following question. I have saved all CMS pages in Elasticsearch. It works correctly. There are two indexes in ES, each for a store. All the fields that I have defined are also in the index. I can output them all with CURL -X GET commands and also search in the index and I get the correct results.

If I search like this:

<?php

namespace Vendor\Cms\Model\Page;

use Magento\AdvancedSearch\Model\Client\ClientInterface;
use Magento\Elasticsearch\SearchAdapter\ConnectionManager;
use Psr\Log\LoggerInterface;


class CmsPageSearch
{
    protected ClientInterface $client;

    public function __construct(
        protected ConnectionManager $connectionManager,
        private readonly LoggerInterface $logger
    ) {
        $this->client = $connectionManager->getConnection();
    }

    public function searchCmsPages(string $termToSearch, string $store): array
    {
        $fields = [
            'identifier',
            'title',
            'page_layout',
            'meta_title',
            'meta_keywords',
            'meta_description',
            'content_heading',
            'content'
        ];

       
        $indexPrefix = 'magento2';
        $params = [
            'index' => $indexPrefix . '_cms_page_' . $store,
            'body' => [
                'query' => [
                    'bool' => [
                        'should' => [
                            [
                                'multi_match' => [
                                    'query' => $termToSearch,
                                    'type' => 'best_fields',
                                    'fields' => [
                                        'data.identifier',
                                        'data.title',
                                        'data.page_layout',
                                        'data.meta_title',
                                        'data.meta_keywords',
                                        'data.meta_description',
                                        'data.content_heading',
                                        'data.content'
                                    ],
                                    'analyzer' => 'standard'
                                ],
                            ],
                            [
                                'fuzzy' => [
                                    'data.title' => [
                                        'value' => $termToSearch,
                                        'fuzziness' => 'AUTO',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
            'size' => 5,
            '_source' => ['data.title', 'data.identifier'] 
        ];

        try {
            $response = $this->client->query($params);
            $this->logger->info(print_r($response, true));
            if (!empty($response['hits']['hits'])) {
                foreach ($response['hits']['hits'] as $hit) {
                    $this->logger->info('Gefundene Seite: ' . print_r($hit['_source'], true));
                }
            } else {
                $this->logger->info('Keine Treffer gefunden.');
            }
            return $response['hits']['hits'];
        } catch (\Exception $e) {
            $this->logger->error($e->getMessage());
            return [];
        }
    }
}

then it works too. But it should also work on Magento, like it does with full-text product searches. I tried that:

<?php

namespace Vendor\Cms\Model;

use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface;
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory;
use Magento\Framework\Api\Search\DocumentInterface;
use Magento\Framework\Api\Search\SearchCriteriaBuilder;
use Magento\Framework\Api\FilterBuilder;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Search\Api\SearchInterface;
use Magento\Cms\Model\ResourceModel\Page\CollectionFactory;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Api\Search\SearchResultInterface;
use Psr\Log\LoggerInterface;

class CmsPagesSearch
{

    public function __construct(
        protected SearchCriteriaBuilder $searchCriteriaBuilder,
        protected SearchCriteriaResolverFactory $searchCriteriaResolverFactory,
        protected FilterBuilder $filterBuilder,
        protected SearchInterface $search,
        protected CollectionFactory $cmsCollectionFactory,
        protected SearchResultInterface $searchResult,
        protected StoreManagerInterface $storeManager,
        protected LoggerInterface $logger
    ) {
    }

    /**
     * Retrieve search result from elastic search
     *
     * @param string $query
     * @return DocumentInterface[]
     * @throws NoSuchEntityException
     */
    public function search(string $query): array
    {
        $searchCriteria = $this->getSearchCriteriaResolver($query)->resolve();
       // $this->logger->info('Search Criteria: ' . json_encode($searchCriteria->getData()));
        $this->searchResult =  $this->search->search($searchCriteria);

        return $this->searchResult->getItems();
    }

    /**
     * @throws NoSuchEntityException
     */
    private function getSearchCriteriaResolver($query): SearchCriteriaResolverInterface
    {
        $storeId = $this->storeManager->getStore()->getId();
        $this->filterBuilder->setField('search_term');
        $this->filterBuilder->setValue($query);
        $searchTermFilter = $this->filterBuilder->create();

        $this->filterBuilder->setField('store_id');
        $this->filterBuilder->setValue($storeId);
        $storeIdFilter = $this->filterBuilder->create();

        $this->searchCriteriaBuilder->addFilter($searchTermFilter);
        $this->searchCriteriaBuilder->addFilter($storeIdFilter);

        return $this->searchCriteriaResolverFactory->create(
            [
                'builder' => $this->searchCriteriaBuilder,
                'collection' => $this->cmsCollectionFactory->create(),
                'searchRequestName' => 'cms_search_container',
            ]
        );
    }
}

Here is 

'cms_search_container'

a container that I defined like this 

search_request.xml

<?xml version="1.0"?>
<requests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_request.xsd">
    <request query="cms_search_container" index="elastic_cms_page_fulltext">
        <dimensions>
            <dimension name="scope" value="default"/>
        </dimensions>
        <queries>
            <query xsi:type="boolQuery" name="cms_search_container" boost="1">
                <queryReference clause="should" ref="search" />
                <queryReference clause="must" ref="is_active"/>
            </query>
            <query xsi:type="matchQuery" value="$search_term$" name="search">
                <match field="*"/>
                <match field="title" matchCondition="match"/>
                <match field="content" matchCondition="match"/>
            </query>
            <query xsi:type="filteredQuery" name="is_active">
                <filterReference clause="must" ref="is_active_filter"/>
            </query>
        </queries>
        <filters>
            <filter xsi:type="termFilter" name="is_active_filter" field="is_active" value="1"/>
        </filters>
        <from>0</from>
        <size>10000</size>
    </request>
</requests>

and then

<?php

namespace Vendor\Cms\Controller\Search;

use Vendor\Cms\Model\CmsPagesSearch;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Model\StoreManagerInterface;

class Cmssearch implements HttpGetActionInterface
{

    public function __construct(
        protected JsonFactory $jsonFactory,
        protected CmsPageSearch $cmsPageSearch,
        protected RequestInterface $request,
        protected StoreManagerInterface $storeManager,
        protected CmsPagesSearch $cmsPagesSearch
    ) {
    }

    /**
     * Execute action based on request and return result
     *
     * @return ResultInterface|ResponseInterface
     * @throws NoSuchEntityException
     */
    public function execute(): ResultInterface|ResponseInterface
    {
     
        $query = $this->request->getParam(\Magento\Search\Model\QueryFactory::QUERY_VAR_NAME);
        $searchResults = $this->cmsPagesSearch->search($query);

        $result = $this->jsonFactory->create();
        $data = ['result' => $searchResults];
        return $result->setData($data);
    }
}

'elastic_cms_page_fulltext' -> indexer.xml and mview.xml

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Indexer/etc/indexer.xsd">
    <indexer id="elastic_cms_page_fulltext" view_id="elastic_cms_page_fulltext"
             class="Vendor\Cms\Model\Page\Indexer\Fulltext">
        <title translate="true">Elastic Cms Page Indexing</title>
        <description translate="true">Reindex Elastic cms pages.</description>
    </indexer>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Mview/etc/mview.xsd">

    <view id="elastic_cms_page_fulltext" class="Vendor\Cms\Model\Page\Indexer\Fulltext" group="indexer">
        <subscriptions>
            <table name="cms_page" entity_column="page_id" />
        </subscriptions>
    </view>

</config>

indexer.xml and mview.xml are intended for importing data into ES and have nothing to do with the search. As I already wrote, storing data in ES works.

 

but it doesn't work. I get an empty response

 

What am I doing wrong, what have I forgotten or how can I use ES Search for CMS pages as well as for products? This should also work without the ElasticSuite from Smile. Please do not write links to commercial modules. Let's just discuss how this can be implemented and what I am doing wrong.

 

Many thanks

Vladimir

1 REPLY 1

Re: CMS-Page in Elasticsearch

Hello @mailmirons1551,

 

To integrate Elasticsearch search functionality for CMS pages in Magento 2, and make sure that your implementation works correctly, let's go through your code and configuration to identify potential issues and correct them.

Summary of Your Current Setup

  1. CMS Page Search Implementation: You have implemented a class (CmsPageSearch) that directly queries Elasticsearch using the ClientInterface.
  2. Search Criteria Setup: You also have a CmsPagesSearch class that utilizes Magento's search criteria and filters.
  3. Search Request Configuration: Your search_request.xml defines the queries and filters for the Elasticsearch index.
  4. Controller for Search: Your Cmssearch controller processes the search query and returns results in JSON format.

Areas to Check

  • Check the Search Request: Ensure that your search_request.xml correctly references the search criteria and properly sets the $search_term$ parameter in your matchQuery. It seems like you are using the wildcard * for the match, which may not yield the expected results if the term does not exist in the index. It might be better to restrict it to specific fields like title or content.

Example Update:

 

<query xsi:type="matchQuery" value="$search_term$" name="search">

<match field="title" matchCondition="match"/>

<match field="content" matchCondition="match"/>

</query>

 

  • Ensure Indexing: After adding new CMS pages or updating existing ones, make sure that you reindex the data to Elasticsearch. Use the following command to reindex:
  • bin/magento indexer:reindex
  • Check Filters: In your getSearchCriteriaResolver method, you filter for the store ID and a search term. Make sure the store ID you're filtering matches the one that your CMS pages are indexed under. You should also confirm that is_active is set correctly for your pages in the database.

  • Verify Search Criteria Logic: Make sure that the filters you apply do not unintentionally restrict results. When you create filters in getSearchCriteriaResolver, ensure that they are set up correctly:

 

$this->filterBuilder->setField('is_active');

$this->filterBuilder->setValue(1);

$isActiveFilter = $this->filterBuilder->create();

 

You might want to remove this filter temporarily to see if it’s causing the empty results.

  • Logging for Debugging: Use logging extensively to understand what’s happening during the search process. Add logs to check the generated search criteria and the query sent to Elasticsearch:
$this->logger->info('Search Criteria: ' . json_encode($searchCriteria->getData()));
  • Ensure Correct Mapping in Elasticsearch: Verify that your Elasticsearch index has the correct mappings for the fields being queried. If the field names are incorrect or the types are mismatched, it may lead to no results being returned.

Specifically, check your query structure in search_request.xml, ensure the indexing process is functioning correctly, and use logging to gain insights into what data is being sent and received from Elasticsearch.

 

If the issue will be resolved, Click Kudos & Accept as a Solution.