HEX
Server: Apache
System: Linux srv13.cpanelhost.cl 3.10.0-962.3.2.lve1.5.38.el7.x86_64 #1 SMP Thu Jun 18 05:28:41 EDT 2020 x86_64
User: cca63905 (4205)
PHP: 7.3.20
Disabled: NONE
Upload Files
File: /home4/cca63905/www/nueva/modules/ps_facetedsearch/src/Filters/Converter.php
<?php
/**
 * Copyright since 2007 PrestaShop SA and Contributors
 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License 3.0 (AFL-3.0)
 * that is bundled with this package in the file LICENSE.md.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/AFL-3.0
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * @author    PrestaShop SA <contact@prestashop.com>
 * @copyright Since 2007 PrestaShop SA and Contributors
 * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
 */

namespace PrestaShop\Module\FacetedSearch\Filters;

use Category;
use Configuration;
use Context;
use Db;
use Manufacturer;
use PrestaShop\Module\FacetedSearch\Filters;
use PrestaShop\Module\FacetedSearch\URLSerializer;
use PrestaShop\PrestaShop\Core\Product\Search\Facet;
use PrestaShop\PrestaShop\Core\Product\Search\Filter;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery;

class Converter
{
    const WIDGET_TYPE_CHECKBOX = 0;
    const WIDGET_TYPE_RADIO = 1;
    const WIDGET_TYPE_DROPDOWN = 2;
    const WIDGET_TYPE_SLIDER = 3;

    const TYPE_ATTRIBUTE_GROUP = 'id_attribute_group';
    const TYPE_AVAILABILITY = 'availability';
    const TYPE_CATEGORY = 'category';
    const TYPE_CONDITION = 'condition';
    const TYPE_FEATURE = 'id_feature';
    const TYPE_MANUFACTURER = 'manufacturer';
    const TYPE_PRICE = 'price';
    const TYPE_WEIGHT = 'weight';

    const PROPERTY_URL_NAME = 'url_name';
    const PROPERTY_COLOR = 'color';
    const PROPERTY_TEXTURE = 'texture';

    /**
     * @var array
     */
    const RANGE_FILTERS = [self::TYPE_PRICE, self::TYPE_WEIGHT];

    /**
     * @var Context
     */
    protected $context;

    /**
     * @var Db
     */
    protected $database;

    /**
     * @var URLSerializer
     */
    protected $urlSerializer;

    /**
     * @var Filters\DataAccessor
     */
    private $dataAccessor;

    /**
     * @var Filters\Provider
     */
    private $provider;

    public function __construct(
        Context $context,
        Db $database,
        URLSerializer $urlSerializer,
        Filters\DataAccessor $dataAccessor,
        Filters\Provider $provider
    ) {
        $this->context = $context;
        $this->database = $database;
        $this->urlSerializer = $urlSerializer;
        $this->dataAccessor = $dataAccessor;
        $this->provider = $provider;
    }

    public function getFacetsFromFilterBlocks(array $filterBlocks)
    {
        $facets = [];

        foreach ($filterBlocks as $filterBlock) {
            if (empty($filterBlock)) {
                // Empty filter, let's continue
                continue;
            }

            $facet = new Facet();
            $facet
                ->setLabel($filterBlock['name'])
                ->setProperty('filter_show_limit', $filterBlock['filter_show_limit'])
                ->setMultipleSelectionAllowed(true);

            switch ($filterBlock['type']) {
                case self::TYPE_CATEGORY:
                case self::TYPE_CONDITION:
                case self::TYPE_MANUFACTURER:
                case self::TYPE_AVAILABILITY:
                case self::TYPE_ATTRIBUTE_GROUP:
                case self::TYPE_FEATURE:
                    $type = $filterBlock['type'];
                    if ($filterBlock['type'] == self::TYPE_ATTRIBUTE_GROUP) {
                        $type = 'attribute_group';
                        $facet->setProperty(self::TYPE_ATTRIBUTE_GROUP, $filterBlock['id_key']);
                        if (isset($filterBlock['url_name'])) {
                            $facet->setProperty(self::PROPERTY_URL_NAME, $filterBlock['url_name']);
                        }
                    } elseif ($filterBlock['type'] == self::TYPE_FEATURE) {
                        $type = 'feature';
                        $facet->setProperty(self::TYPE_FEATURE, $filterBlock['id_key']);
                        if (isset($filterBlock['url_name'])) {
                            $facet->setProperty(self::PROPERTY_URL_NAME, $filterBlock['url_name']);
                        }
                    }

                    $facet->setType($type);
                    $filters = [];
                    foreach ($filterBlock['values'] as $id => $filterArray) {
                        $filter = new Filter();
                        $filter
                            ->setType($type)
                            ->setLabel($filterArray['name'])
                            ->setMagnitude($filterArray['nbr'])
                            ->setValue($id);

                        if (isset($filterArray['url_name'])) {
                            $filter->setProperty(self::PROPERTY_URL_NAME, $filterArray['url_name']);
                        }

                        if (array_key_exists('checked', $filterArray)) {
                            $filter->setActive($filterArray['checked']);
                        }

                        if (isset($filterArray['color'])) {
                            if ($filterArray['color'] != '') {
                                $filter->setProperty(self::PROPERTY_COLOR, $filterArray['color']);
                            } elseif (file_exists(_PS_COL_IMG_DIR_ . $id . '.jpg')) {
                                $filter->setProperty(self::PROPERTY_TEXTURE, _THEME_COL_DIR_ . $id . '.jpg');
                            }
                        }

                        $filters[] = $filter;
                    }

                    if ((int) $filterBlock['filter_show_limit'] !== 0) {
                        usort($filters, [$this, 'sortFiltersByMagnitude']);
                    }

                    $this->hideZeroValuesAndShowLimit($filters, (int) $filterBlock['filter_show_limit']);

                    if ((int) $filterBlock['filter_show_limit'] !== 0 || $filterBlock['type'] !== self::TYPE_ATTRIBUTE_GROUP) {
                        usort($filters, [$this, 'sortFiltersByLabel']);
                    }

                    // No method available to add all filters
                    foreach ($filters as $filter) {
                        $facet->addFilter($filter);
                    }
                    break;
                case self::TYPE_WEIGHT:
                case self::TYPE_PRICE:
                    $facet
                        ->setType($filterBlock['type'])
                        ->setProperty('min', $filterBlock['min'])
                        ->setProperty('max', $filterBlock['max'])
                        ->setProperty('unit', $filterBlock['unit'])
                        ->setProperty('specifications', $filterBlock['specifications'])
                        ->setMultipleSelectionAllowed(false)
                        ->setProperty('range', true);

                    $filter = new Filter();
                    $filter
                        ->setActive($filterBlock['value'] !== null)
                        ->setType($filterBlock['type'])
                        ->setMagnitude($filterBlock['nbr'])
                        ->setProperty('symbol', $filterBlock['unit'])
                        ->setValue($filterBlock['value']);

                    $facet->addFilter($filter);

                    break;
            }

            switch ((int) $filterBlock['filter_type']) {
                case self::WIDGET_TYPE_CHECKBOX:
                    $facet->setMultipleSelectionAllowed(true);
                    $facet->setWidgetType('checkbox');
                    break;
                case self::WIDGET_TYPE_RADIO:
                    $facet->setMultipleSelectionAllowed(false);
                    $facet->setWidgetType('radio');
                    break;
                case self::WIDGET_TYPE_DROPDOWN:
                    $facet->setMultipleSelectionAllowed(false);
                    $facet->setWidgetType('dropdown');
                    break;
                case self::WIDGET_TYPE_SLIDER:
                    $facet->setMultipleSelectionAllowed(false);
                    $facet->setWidgetType('slider');
                    break;
            }

            $facets[] = $facet;
        }

        return $facets;
    }

    /**
     * This method is responsible of parsing the search filters sent in the query.
     * These filters come from the URL in 99 % of cases.
     *
     * It will unserialize it and convert it to actual unique and valid values that
     * we will later use to construct the database query. All invalid filters in the
     * query (unknown value, deleted in shop etc.) are ignored.
     *
     * Filters that are found (if any) will be later used in initSearch method, along
     * with some predefined ones related the the controller we are on.
     *
     * @param ProductSearchQuery $query
     *
     * @return array
     */
    public function createFacetedSearchFiltersFromQuery(ProductSearchQuery $query)
    {
        $idShop = (int) $this->context->shop->id;
        $idLang = (int) $this->context->language->id;

        // Get category ID from the query or home category as a fallback
        $idCategory = (int) $query->getIdCategory();
        if (empty($idCategory)) {
            $idCategory = (int) Configuration::get('PS_HOME_CATEGORY');
        }

        $searchFilters = [];

        // Get filters configured in module settings for the current query
        $filters = $this->provider->getFiltersForQuery($query, $idShop);

        /*
         * Parses submitted encoded facets from (URL) string into a nice array.
         *
         * Facets are set to the URL with a textual representation. This unfortunately does not
         * work very well, because there could be duplicate values for both facet and filter.
         * For example, if there are two features, feature values or categories with the same name.
         */
        $facetAndFiltersLabels = $this->urlSerializer->unserialize($query->getEncodedFacets());

        // Go through filters that are configured and find out which should be activated,
        // depending on what was provided in the encodedFacets.
        foreach ($filters as $filter) {
            $filterLabel = $this->convertFilterTypeToLabel($filter['type']);

            switch ($filter['type']) {
                case self::TYPE_MANUFACTURER:
                    if (!isset($facetAndFiltersLabels[$filterLabel])) {
                        // No need to filter if no information
                        continue 2;
                    }

                    $manufacturers = Manufacturer::getManufacturers(false, $idLang);
                    $searchFilters[$filter['type']] = [];
                    foreach ($manufacturers as $manufacturer) {
                        if (in_array($manufacturer['name'], $facetAndFiltersLabels[$filterLabel])) {
                            $searchFilters[$filter['type']][$manufacturer['name']] = $manufacturer['id_manufacturer'];
                        }
                    }
                    break;
                case self::TYPE_AVAILABILITY:
                    if (!isset($facetAndFiltersLabels[$filterLabel])) {
                        // No need to filter if no information
                        continue 2;
                    }

                    $quantityArray = [
                        $this->context->getTranslator()->trans(
                            'Not available',
                            [],
                            'Modules.Facetedsearch.Shop'
                        ) => 0,
                        $this->context->getTranslator()->trans(
                            'Available',
                            [],
                            'Modules.Facetedsearch.Shop'
                        ) => 1,
                        $this->context->getTranslator()->trans(
                            'In stock',
                            [],
                            'Modules.Facetedsearch.Shop'
                        ) => 2,
                    ];

                    $searchFilters[$filter['type']] = [];
                    foreach ($quantityArray as $quantityName => $quantityId) {
                        if (isset($facetAndFiltersLabels[$filterLabel]) && in_array($quantityName, $facetAndFiltersLabels[$filterLabel])) {
                            $searchFilters[$filter['type']][] = $quantityId;
                        }
                    }
                    break;
                case self::TYPE_CONDITION:
                    if (!isset($facetAndFiltersLabels[$filterLabel])) {
                        // No need to filter if no information
                        continue 2;
                    }

                    $conditionArray = [
                        $this->context->getTranslator()->trans(
                            'New',
                            [],
                            'Modules.Facetedsearch.Shop'
                        ) => 'new',
                        $this->context->getTranslator()->trans(
                            'Used',
                            [],
                            'Modules.Facetedsearch.Shop'
                        ) => 'used',
                        $this->context->getTranslator()->trans(
                            'Refurbished',
                            [],
                            'Modules.Facetedsearch.Shop'
                        ) => 'refurbished',
                    ];

                    $searchFilters[$filter['type']] = [];
                    foreach ($conditionArray as $conditionName => $conditionId) {
                        if (isset($facetAndFiltersLabels[$filterLabel]) && in_array($conditionName, $facetAndFiltersLabels[$filterLabel])) {
                            $searchFilters[$filter['type']][] = $conditionId;
                        }
                    }
                    break;
                case self::TYPE_FEATURE:
                    $features = $this->dataAccessor->getFeatures($idLang);
                    foreach ($features as $feature) {
                        if ($filter['id_value'] != $feature['id_feature']) {
                            continue;
                        }

                        if (isset($facetAndFiltersLabels[$feature['url_name']])) {
                            $featureValueLabels = $facetAndFiltersLabels[$feature['url_name']];
                        } elseif (isset($facetAndFiltersLabels[$feature['name']])) {
                            $featureValueLabels = $facetAndFiltersLabels[$feature['name']];
                        } else {
                            continue;
                        }

                        $featureValues = $this->dataAccessor->getFeatureValues($feature['id_feature'], $idLang);
                        foreach ($featureValues as $featureValue) {
                            if (in_array($featureValue['url_name'], $featureValueLabels)
                                || in_array($featureValue['value'], $featureValueLabels)
                            ) {
                                $searchFilters['id_feature'][$feature['id_feature']][] = $featureValue['id_feature_value'];
                            }
                        }
                    }
                    break;
                case self::TYPE_ATTRIBUTE_GROUP:
                    $attributesGroup = $this->dataAccessor->getAttributesGroups($idLang);
                    foreach ($attributesGroup as $attributeGroup) {
                        if ($filter['id_value'] != $attributeGroup['id_attribute_group']) {
                            continue;
                        }

                        if (isset($facetAndFiltersLabels[$attributeGroup['url_name']])) {
                            $attributeLabels = $facetAndFiltersLabels[$attributeGroup['url_name']];
                        } elseif (isset($facetAndFiltersLabels[$attributeGroup['attribute_group_name']])) {
                            $attributeLabels = $facetAndFiltersLabels[$attributeGroup['attribute_group_name']];
                        } else {
                            continue;
                        }

                        $attributes = $this->dataAccessor->getAttributes($idLang, $attributeGroup['id_attribute_group']);
                        foreach ($attributes as $attribute) {
                            if (in_array($attribute['url_name'], $attributeLabels)
                                || in_array($attribute['name'], $attributeLabels)
                            ) {
                                $searchFilters['id_attribute_group'][$attributeGroup['id_attribute_group']][] = $attribute['id_attribute'];
                            }
                        }
                    }
                    break;
                case self::TYPE_PRICE:
                case self::TYPE_WEIGHT:
                    if (isset($facetAndFiltersLabels[$filterLabel])) {
                        $filters = $facetAndFiltersLabels[$filterLabel];
                        if (isset($filters[1]) && isset($filters[2])) {
                            $from = $filters[1];
                            $to = $filters[2];
                            $searchFilters[$filter['type']][0] = $from;
                            $searchFilters[$filter['type']][1] = $to;
                        }
                    }
                    break;
                case self::TYPE_CATEGORY:
                    if (isset($facetAndFiltersLabels[$filterLabel])) {
                        foreach ($facetAndFiltersLabels[$filterLabel] as $queryFilter) {
                            /*
                             * This works only for categories that are child of the category we are browsing (or home category).
                             * Categories deeper in the tree will never be found. This could be fixed by providing a unique ID
                             * to the URL.
                             */
                            $categories = Category::searchByNameAndParentCategoryId($idLang, $queryFilter, (int) $idCategory);
                            if ($categories) {
                                $searchFilters[$filter['type']][] = $categories['id_category'];
                            }
                        }
                    }
                    break;
                default:
                    if (isset($facetAndFiltersLabels[$filterLabel])) {
                        foreach ($facetAndFiltersLabels[$filterLabel] as $queryFilter) {
                            $searchFilters[$filter['type']][] = $queryFilter;
                        }
                    }
            }
        }

        // Remove all empty selected filters
        foreach ($searchFilters as $key => $value) {
            switch ($key) {
                case self::TYPE_PRICE:
                case self::TYPE_WEIGHT:
                    if ($value[0] === '' && $value[1] === '') {
                        unset($searchFilters[$key]);
                    }
                    break;
                default:
                    if ($value == '' || $value == []) {
                        unset($searchFilters[$key]);
                    }
                    break;
            }
        }

        return $searchFilters;
    }

    /**
     * Convert filter type to label
     *
     * @param string $filterType
     */
    private function convertFilterTypeToLabel($filterType)
    {
        switch ($filterType) {
            case self::TYPE_PRICE:
                return $this->context->getTranslator()->trans('Price', [], 'Modules.Facetedsearch.Shop');
            case self::TYPE_WEIGHT:
                return $this->context->getTranslator()->trans('Weight', [], 'Modules.Facetedsearch.Shop');
            case self::TYPE_CONDITION:
                return $this->context->getTranslator()->trans('Condition', [], 'Modules.Facetedsearch.Shop');
            case self::TYPE_AVAILABILITY:
                return $this->context->getTranslator()->trans('Availability', [], 'Modules.Facetedsearch.Shop');
            case self::TYPE_MANUFACTURER:
                return $this->context->getTranslator()->trans('Brand', [], 'Modules.Facetedsearch.Shop');
            case self::TYPE_CATEGORY:
                return $this->context->getTranslator()->trans('Categories', [], 'Modules.Facetedsearch.Shop');
            case self::TYPE_FEATURE:
            case self::TYPE_ATTRIBUTE_GROUP:
            default:
                return null;
        }
    }

    /**
     * Hide entries with 0 results
     * Hide depending of show limit parameter
     *
     * @param array $filters
     *
     * @return array
     */
    private function hideZeroValuesAndShowLimit(array $filters, $showLimit)
    {
        $count = 0;
        foreach ($filters as $filter) {
            if ($filter->getMagnitude() === 0
                || ($showLimit > 0 && $count >= $showLimit)
            ) {
                $filter->setDisplayed(false);
                continue;
            }

            ++$count;
        }

        return $filters;
    }

    /**
     * Sort filters by magnitude
     *
     * @param Filter $a
     * @param Filter $b
     *
     * @return int
     */
    private function sortFiltersByMagnitude(Filter $a, Filter $b)
    {
        $aMagnitude = $a->getMagnitude();
        $bMagnitude = $b->getMagnitude();
        if ($aMagnitude == $bMagnitude) {
            // Same magnitude, sort by label
            return $this->sortFiltersByLabel($a, $b);
        }

        return $aMagnitude > $bMagnitude ? -1 : +1;
    }

    /**
     * Sort filters by label
     *
     * @param Filter $a
     * @param Filter $b
     *
     * @return int
     */
    private function sortFiltersByLabel(Filter $a, Filter $b)
    {
        return strnatcasecmp($a->getLabel(), $b->getLabel());
    }
}