Skip to content
This repository has been archived by the owner on Oct 19, 2022. It is now read-only.

Commit

Permalink
Merge pull request #128 from EmicoEcommerce/issue/126-derived-swatchf…
Browse files Browse the repository at this point in the history
…ilter

Issue/126 derived swatchfilter
  • Loading branch information
edwinljacobs authored Oct 21, 2020
2 parents f71c5e8 + ebe714c commit e603802
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 10 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 3.0.2
Added swatch resolver for derived color filters in tweakwise, this fixes [#126](https://github.com/EmicoEcommerce/Magento2Tweakwise/issues/126)
Possible issues with this: It is unclear from the navigator response which magento attribute (if any) was used to created the derived property,
as such we cannot know which swatches to load. We do a guess based on the swatch labels but this could lead to missing swatches. In order to find a match
for derived swatch with label "Red" in tweakwise magento needs to have a swatch attribute with an option labeled "Red", case sensitive.

## 3.0.1
BugFix: Release 3.0.0 introduced an error where the magento autocomplete template was loaded instead of the tweakwise autocomplete template.

## 3.0.0
1) Ajax filtering implemented
2) methods and property visibility updated to "protected" (to allow easier preferences).
Expand Down
92 changes: 82 additions & 10 deletions src/Block/LayeredNavigation/RenderLayered/SwatchRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@

use Emico\Tweakwise\Model\Catalog\Layer\Filter;
use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item;
use Emico\Tweakwise\Model\Config;
use Emico\Tweakwise\Model\Seo\FilterHelper;
use Emico\Tweakwise\Model\Swatches\SwatchAttributeResolver;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory as EavAttributeFactory;
use Magento\Catalog\Model\ResourceModel\Layer\Filter\AttributeFactory;
use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Eav\Model\Entity\Attribute;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\View\Element\Template\Context;
Expand All @@ -33,11 +34,6 @@ class SwatchRenderer extends RenderLayered
*/
protected $_template = 'Emico_Tweakwise::product/layered/swatch.phtml';

/**
* @var Config
*/
protected $config;

/**
* @var Filter
*/
Expand All @@ -48,16 +44,21 @@ class SwatchRenderer extends RenderLayered
*/
protected $eavAttributeFactory;

/**
* @var SwatchAttributeResolver
*/
protected $swatchAttributeResolver;

/**
* SwatchRenderer constructor.
* @param Context $context
* @param Attribute $eavAttribute
* @param AttributeFactory $layerAttribute
* @param Data $swatchHelper
* @param Media $mediaHelper
* @param Config $config
* @param EavAttributeFactory $eavAttributeFactory
* @param FilterHelper $filterHelper
* @param SwatchAttributeResolver $swatchAttributeResolver
* @param array $data
*/
public function __construct(
Expand All @@ -66,15 +67,22 @@ public function __construct(
AttributeFactory $layerAttribute,
Data $swatchHelper,
Media $mediaHelper,
Config $config,
EavAttributeFactory $eavAttributeFactory,
FilterHelper $filterHelper,
SwatchAttributeResolver $swatchAttributeResolver,
array $data = []
) {
parent::__construct($context, $eavAttribute, $layerAttribute, $swatchHelper, $mediaHelper, $data);
$this->config = $config;
parent::__construct(
$context,
$eavAttribute,
$layerAttribute,
$swatchHelper,
$mediaHelper,
$data
);
$this->eavAttributeFactory = $eavAttributeFactory;
$this->filterHelper = $filterHelper;
$this->swatchAttributeResolver = $swatchAttributeResolver;
}

/**
Expand All @@ -94,6 +102,70 @@ public function setFilter(Filter $filter)
$this->setSwatchFilter($filter);
}

/**
* @return array
*/
public function getSwatchData()
{
if (false === $this->eavAttribute instanceof Attribute) {
throw new \RuntimeException('Magento_Swatches: RenderLayered: Attribute has not been set.');
}

/**
* When this attribute has an id it is an actual magento attribute. If so we can use the parent method to
* get the swatches, otherwise it is a mocked attribute see:
* @see \Emico\Tweakwise\Model\Catalog\Layer\FilterList\Tweakwise line 105
*/
if ($this->eavAttribute->getId()) {
return parent::getSwatchData();
}

// We have a derived swatch filter.
$swatchAttributeData = $this->swatchAttributeResolver->getSwatchData($this->filter);
// There was no attribute to be found
if (!$swatchAttributeData) {
return parent::getSwatchData();
}

/** @var AttributeInterface|Attribute $attribute */
$attribute = $swatchAttributeData['attribute'];
$this->filter->setAttributeModel($attribute);
$optionIds = array_values($swatchAttributeData['options']);
$optionLabels = array_keys($swatchAttributeData['options']);

$filterItems = [];
foreach ($this->filter->getItems() as $item) {
if (!in_array($item->getLabel(), $optionLabels, false)) {
continue;
}

$filterItems[$item->getLabel()] = $item;
}


$attributeOptions = [];
foreach ($attribute->getOptions() as $option) {
if (!in_array($option->getValue(), $optionIds, false)) {
continue;
}

$filterItem = $filterItems[$option->getLabel()] ?? null;
if (!$filterItem) {
continue;
}

$attributeOptions[$option->getValue()] = $this->getOptionViewData($filterItem, $option);
}

return [
'attribute_id' => $attribute->getId(),
'attribute_code' => $attribute->getAttributeCode(),
'attribute_label' => $this->filter->getFacet()->getFacetSettings()->getTitle(),
'options' => $attributeOptions,
'swatches' => $this->swatchHelper->getSwatchesByOptionsId($optionIds),
];
}

/**
* @param int $id
* @return Item
Expand Down
2 changes: 2 additions & 0 deletions src/Model/Catalog/Layer/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ public function getActiveItems()
public function setAttributeModel($attribute)
{
$this->attributeModel = $attribute;
$this->optionLabelValueMap = null;
$this->optionLabelItemMap = null;
return $this;
}

Expand Down
158 changes: 158 additions & 0 deletions src/Model/Swatches/SwatchAttributeResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

/**
* @author : Edwin Jacobs, email: ejacobs@emico.nl.
* @copyright : Copyright Emico B.V. 2020.
*/
namespace Emico\Tweakwise\Model\Swatches;

use Emico\Tweakwise\Model\Catalog\Layer\Filter;
use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item;
use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Eav\Api\AttributeRepositoryInterface;
use Magento\Eav\Model\Entity\Attribute\Source\SourceInterface;
use Magento\Eav\Model\Entity\Attribute\Source\Table;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Swatches\Model\SwatchAttributeCodes;
use Magento\Swatches\Model\SwatchAttributeType;

class SwatchAttributeResolver
{
/**
* @var SwatchAttributeCodes
*/
protected $swatchAttributeCodes;

/**
* @var AttributeRepositoryInterface
*/
protected $attributeRepository;

/**
* @var SearchCriteriaBuilder
*/
protected $searchCriteriaBuilder;

/**
* @var array
*/
protected $swatchMap;

/**
* @var SwatchAttributeType
*/
protected $swatchAttributeTypeHelper;

/**
* SwatchAttributeResolver constructor.
* @param SwatchAttributeCodes $swatchAttributeCodes
* @param AttributeRepositoryInterface $attributeRepository
* @param SearchCriteriaBuilder $searchCriteriaBuilder
* @param SwatchAttributeType $swatchAttributeTypeHelper
*/
public function __construct(
SwatchAttributeCodes $swatchAttributeCodes,
AttributeRepositoryInterface $attributeRepository,
SearchCriteriaBuilder $searchCriteriaBuilder,
SwatchAttributeType $swatchAttributeTypeHelper
) {
$this->swatchAttributeCodes = $swatchAttributeCodes;
$this->attributeRepository = $attributeRepository;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->swatchAttributeTypeHelper = $swatchAttributeTypeHelper;
}

/**
* @param Filter $filter
* @return array
*/
public function getSwatchData(Filter $filter): array
{
// Get a map of filter item labels keyed by label
$labels = array_flip(
array_map(
static function (Item $filterItem) {
return $filterItem->getLabel();
},
$filter->getItems()
)
);
// Get all possible swatches
$swatchMap = $this->getSwatchMap();

$hits = 0;
$targetSwatchAttribute = null;
$resolvedOptions = [];
// try to resolve an attribute code based on the labels given by tweakwise
foreach ($swatchMap as $swatchConfiguration) {
$swatchOptions = $swatchConfiguration['options'];
// Compare the labels from tweakwise with the swatch labels, the more labels match the better
$matchingAttributeOptions = array_intersect_key($swatchOptions, $labels);
if (count($labels) === count($matchingAttributeOptions)) {
// All labels match, we pick this swatch attribute
$targetSwatchAttribute = $swatchConfiguration['attribute'];
$resolvedOptions = $matchingAttributeOptions;
break;
}
if ($matchingAttributeOptions > $hits) {
$hits = $matchingAttributeOptions;
$targetSwatchAttribute = $swatchConfiguration['attribute'];
$resolvedOptions = $matchingAttributeOptions;
}
}

if (!$targetSwatchAttribute || !$resolvedOptions) {
return [];
}

return [
'attribute' => $targetSwatchAttribute,
'options' => $resolvedOptions,
];
}

/**
* @return array
*/
protected function getSwatchMap(): array
{
if ($this->swatchMap !== null) {
return $this->swatchMap;
}

$swatchAttributeCodes = $this->swatchAttributeCodes->getCodes();
$searchCriteria = $this->searchCriteriaBuilder
->addFilter('attribute_code', $swatchAttributeCodes, 'in')
->create();

$swatchAttributes = $this->attributeRepository->getList(
ProductAttributeInterface::ENTITY_TYPE_CODE,
$searchCriteria
);

$this->swatchMap = [];

foreach ($swatchAttributes->getItems() as $swatchAttribute) {
if (!$this->swatchAttributeTypeHelper->isSwatchAttribute($swatchAttribute)) {
// This should not happen however just to be sure
continue;
}

if (!$swatchAttribute->usesSource() || !($source = $swatchAttribute->getSource())) {
// We cannot resolve an attribute without source.
continue;
}
/** @var Table $source */
$options = $source->getAllOptions(true, true);

$optionLabels = array_column($options, 'label');
$optionValues = array_column($options, 'value');
$this->swatchMap[] = [
'attribute' => $swatchAttribute,
'options' => array_filter(array_combine($optionLabels, $optionValues))
];
}

return $this->swatchMap;
}
}

0 comments on commit e603802

Please sign in to comment.