<commit_before><?php
/**
 * Shopware 5
 * Copyright (c) shopware AG
 *
 * According to our dual licensing model, this program can be used either
 * under the terms of the GNU Affero General Public License, version 3,
 * or under a proprietary license.
 *
 * The texts of the GNU Affero General Public License with an additional
 * permission and of our proprietary license can be found at and
 * in the LICENSE file you have received along with this program.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * "Shopware" is a registered trademark of shopware AG.
 * The licensing of the program under the AGPLv3 does not imply a
 * trademark license. Therefore any rights, title and interest in
 * our trademarks remain entirely with us.
 */

namespace Shopware\Bundle\SearchBundleES;

use Elasticsearch\Client;
use ONGR\ElasticsearchDSL\Search;
use Shopware\Bundle\ESIndexingBundle\IndexFactoryInterface;
use Shopware\Bundle\ESIndexingBundle\Product\ProductMapping;
use Shopware\Bundle\SearchBundle\Criteria;
use Shopware\Bundle\SearchBundle\CriteriaPartInterface;
use Shopware\Bundle\SearchBundle\FacetInterface;
use Shopware\Bundle\SearchBundle\ProductNumberSearchInterface;
use Shopware\Bundle\SearchBundle\ProductNumberSearchResult;
use Shopware\Bundle\StoreFrontBundle\Struct\Attribute;
use Shopware\Bundle\StoreFrontBundle\Struct\BaseProduct;
use Shopware\Bundle\StoreFrontBundle\Struct\ShopContextInterface;

class ProductNumberSearch implements ProductNumberSearchInterface
{
    /**
     * @var Client
     */
    private $client;

    /**
     * @var HandlerInterface[]
     */
    private $handlers;

    /**
     * @var IndexFactoryInterface
     */
    private $indexFactory;

    /**
     * @param Client $client
     * @param IndexFactoryInterface $indexFactory
     * @param $handlers
     */
    public function __construct(
        Client $client,
        IndexFactoryInterface $indexFactory,
        $handlers
    ) {
        $this->client = $client;
        $this->handlers = $handlers;
        $this->indexFactory = $indexFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function search(Criteria $criteria, ShopContextInterface $context)
    {
        $search = $this->buildSearch($criteria, $context);
        $index  = $this->indexFactory->createShopIndex($context->getShop());

        $data = $this->client->search([
            'index' => $index->getName(),
            'type'  => ProductMapping::TYPE,
            'body'  => $search->toArray()
        ]);

        $products = $this->createProducts($data);

        $result = new ProductNumberSearchResult(
            $products,
            $data['hits']['total'],
            []
        );

        if (isset($data['hits']['max_score'])) {
            $result->addAttribute('elastic_search', new Attribute(['max_score' => $data['hits']['max_score']]));
        }

        foreach ($this->handlers as $handler) {
            if (!($handler instanceof ResultHydratorInterface)) {
                continue;
            }
            $handler->hydrate($data, $result, $criteria, $context);
        }

        $facets = $this->sortFacets($criteria, $result);

        return new ProductNumberSearchResult(
            $products,
            $data['hits']['total'],
            $facets,
            $result->getAttributes()
        );
    }

    /**
     * @param Criteria $criteria
     * @param ShopContextInterface $context
     * @return Search
     */
    private function buildSearch(Criteria $criteria, ShopContextInterface $context)
    {
        $search = new Search();

        $this->addConditions($criteria, $context, $search);
        $this->addCriteriaParts($criteria, $context, $search, $criteria->getSortings());
        $this->addCriteriaParts($criteria, $context, $search, $criteria->getFacets());

        $search->setFrom($criteria->getOffset())
            ->setSize($criteria->getLimit());

        return $search;
    }

    /**
     * @param Criteria $criteria
     * @param ShopContextInterface $context
     * @param Search $search
     * @param CriteriaPartInterface[] $criteriaParts
     */<commit_msg>SW-16633 - Fix elastic search popularity sorting<commit_after><?php
/**
 * Shopware 5
 * Copyright (c) shopware AG
 *
 * According to our dual licensing model, this program can be used either
 * under the terms of the GNU Affero General Public License, version 3,
 * or under a proprietary license.
 *
 * The texts of the GNU Affero General Public License with an additional
 * permission and of our proprietary license can be found at and
 * in the LICENSE file you have received along with this program.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * "Shopware" is a registered trademark of shopware AG.
 * The licensing of the program under the AGPLv3 does not imply a
 * trademark license. Therefore any rights, title and interest in
 * our trademarks remain entirely with us.
 */

namespace Shopware\Bundle\SearchBundleES;

use Elasticsearch\Client;
use ONGR\ElasticsearchDSL\Search;
use ONGR\ElasticsearchDSL\Sort\FieldSort;
use Shopware\Bundle\ESIndexingBundle\IndexFactoryInterface;
use Shopware\Bundle\ESIndexingBundle\Product\ProductMapping;
use Shopware\Bundle\SearchBundle\Criteria;
use Shopware\Bundle\SearchBundle\CriteriaPartInterface;
use Shopware\Bundle\SearchBundle\FacetInterface;
use Shopware\Bundle\SearchBundle\ProductNumberSearchInterface;
use Shopware\Bundle\SearchBundle\ProductNumberSearchResult;
use Shopware\Bundle\StoreFrontBundle\Struct\Attribute;
use Shopware\Bundle\StoreFrontBundle\Struct\BaseProduct;
use Shopware\Bundle\StoreFrontBundle\Struct\ShopContextInterface;

class ProductNumberSearch implements ProductNumberSearchInterface
{
    /**
     * @var Client
     */
    private $client;

    /**
     * @var HandlerInterface[]
     */
    private $handlers;

    /**
     * @var IndexFactoryInterface
     */
    private $indexFactory;

    /**
     * @param Client $client
     * @param IndexFactoryInterface $indexFactory
     * @param $handlers
     */
    public function __construct(
        Client $client,
        IndexFactoryInterface $indexFactory,
        $handlers
    ) {
        $this->client = $client;
        $this->handlers = $handlers;
        $this->indexFactory = $indexFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function search(Criteria $criteria, ShopContextInterface $context)
    {
        $search = $this->buildSearch($criteria, $context);
        $index  = $this->indexFactory->createShopIndex($context->getShop());

        $data = $this->client->search([
            'index' => $index->getName(),
            'type'  => ProductMapping::TYPE,
            'body'  => $search->toArray()
        ]);

        $products = $this->createProducts($data);

        $result = new ProductNumberSearchResult(
            $products,
            $data['hits']['total'],
            []
        );

        if (isset($data['hits']['max_score'])) {
            $result->addAttribute('elastic_search', new Attribute(['max_score' => $data['hits']['max_score']]));
        }

        foreach ($this->handlers as $handler) {
            if (!($handler instanceof ResultHydratorInterface)) {
                continue;
            }
            $handler->hydrate($data, $result, $criteria, $context);
        }

        $facets = $this->sortFacets($criteria, $result);

        return new ProductNumberSearchResult(
            $products,
            $data['hits']['total'],
            $facets,
            $result->getAttributes()
        );
    }

    /**
     * @param Criteria $criteria
     * @param ShopContextInterface $context
     * @return Search
     */
    private function buildSearch(Criteria $criteria, ShopContextInterface $context)
    {
        $search = new Search();

        $this->addConditions($criteria, $context, $search);
        $this->addCriteriaParts($criteria, $context, $search, $criteria->getSortings());
        $this->addCriteriaParts($criteria, $context, $search, $criteria->getFacets());

        if ($criteria->getOffset() !== null) {
            $search->setFrom($criteria->getOffset());
        }
        if ($criteria->getLimit() !== null) {
            $search->setSize($criteria->getLimit());
        }
        $search->addSort(new FieldSort('id', 'ASC'));
        return $search;
    }

    /**
     * @param Criteria $criteria
     * @param ShopContextInterface $context
     * @param Search $search
     * @param CriteriaPartInterface[] $criteriaParts
     */
{'fraction_non_alphanumeric': 0.11567373499382924, 'fraction_numerical': 0.0012341523617188377, 'mean_word_length': 2.90109409190372, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3434344', 'n_tokens_mistral': 2400, 'n_tokens_neox': 2340, 'n_words': 632}