diff --git a/CHANGELOG.md b/CHANGELOG.md index f002db03..5fc0fcb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,24 @@ -## 0.1.0-beta (2017-02-01) +## 3.0.0 +1) Ajax filtering implemented +2) methods and property visibility updated to "protected" (to allow easier preferences). -Features: -- Supported Magento versions 100.1.*|100.0.*, \ No newline at end of file +BC breaks: +1) Template src/view/frontend/templates/product/navigation/view.phtml moved to src/view/frontend/templates/layer/view.phtml +as that is the template it replaces +2) Complete overhaul of js components, if you have any changes in those you will need to redo them. +3) Overhauled slider template: src/view/frontend/templates/product/layered/slider.phtml the javascript part has been moved to a separate js component, namely +src/view/frontend/web/js/navigation-slider.js +4) Removed deprecated methods from src/Block/LayeredNavigation/RenderLayered/SliderRenderer.php +5) All data-mage-init statements now go through model: src/Model/NavigationConfig.php this class will resolve the correct js components based on your configuration +This means yet more bc breaks on your template overrides (if any) +6) References to "Zend\Http\Request as HttpRequest" have been removed, we now depend on the concrete magento request object. This is because of the move from zend to laminas. +7) Added methods +```php +public function getClearUrl(MagentoHttpRequest $request, array $activeFilterItems): string; +public function buildFilterUrl(MagentoHttpRequest $request, array $filters = []): string; +``` +To interface UrlInterface to facilitate ajax filtering + +Furthermore: various code style fixes, simplifications and general "cleanup". + + diff --git a/src/Block/Catalog/Product/ProductList/Featured.php b/src/Block/Catalog/Product/ProductList/Featured.php index e471f62c..76bd2280 100644 --- a/src/Block/Catalog/Product/ProductList/Featured.php +++ b/src/Block/Catalog/Product/ProductList/Featured.php @@ -35,22 +35,22 @@ class Featured extends ListProduct /** * @var RecommendationsContext */ - private $recommendationsContext; + protected $recommendationsContext; /** * @var Config */ - private $config; + protected $config; /** * @var TemplateFinder */ - private $templateFinder; + protected $templateFinder; /** * @var PreparePostDataFactory */ - private $preparePostDataFactory; + protected $preparePostDataFactory; /** * Featured constructor. diff --git a/src/Block/Catalog/Product/ProductList/Toolbar/Plugin.php b/src/Block/Catalog/Product/ProductList/Toolbar/Plugin.php index 69d75c55..cf00e3b6 100644 --- a/src/Block/Catalog/Product/ProductList/Toolbar/Plugin.php +++ b/src/Block/Catalog/Product/ProductList/Toolbar/Plugin.php @@ -59,4 +59,21 @@ public function aroundGetAvailableOrders(Toolbar $subject, Closure $proceed) } return $result; } -} \ No newline at end of file + + /** + * @param Toolbar $subject + * @param string $result + * @return false|string + */ + public function afterGetWidgetOptionsJson(Toolbar $subject, string $result) + { + if (!$this->config->isAjaxFilters()) { + return $result; + } + + $options = json_decode($result, true); + $options['productListToolbarForm']['ajaxFilters'] = true; + + return json_encode($options); + } +} diff --git a/src/Block/LayeredNavigation/Navigation/Plugin.php b/src/Block/LayeredNavigation/Navigation/Plugin.php deleted file mode 100644 index 13323a87..00000000 --- a/src/Block/LayeredNavigation/Navigation/Plugin.php +++ /dev/null @@ -1,37 +0,0 @@ -config = $config; - } - - /** - * @param Navigation $block - * @param $result - * @return mixed - */ - public function afterGetFilters(Navigation $block, $result) - { - $block->setData('form_filters', $this->config->getUseFormFilters()); - return $result; - } -} \ No newline at end of file diff --git a/src/Block/LayeredNavigation/Navigation/State.php b/src/Block/LayeredNavigation/Navigation/State.php new file mode 100644 index 00000000..90c6ddc5 --- /dev/null +++ b/src/Block/LayeredNavigation/Navigation/State.php @@ -0,0 +1,112 @@ +config = $config; + $this->url = $url; + $this->updateTemplate($currentContext); + } + + /** + * Use our template if applicable + * If you want to change this behaviour use a plugin on afterGetTemplate + * + * @param CurrentContext $currentContext + */ + protected function updateTemplate(CurrentContext $currentContext) + { + if ($this->config->getUseDefaultLinkRenderer()) { + return; + } + + $searchEnabled = $this->config->isSearchEnabled(); + $navigationEnabled = $this->config->isLayeredEnabled(); + + $isSearch = $currentContext->getRequest() instanceof ProductSearchRequest; + $isNavigation = !$isSearch; + + if ($isSearch && $searchEnabled) { + $this->_template = 'Emico_Tweakwise::layer/state.phtml'; + } + + if ($isNavigation && $navigationEnabled) { + $this->_template = 'Emico_Tweakwise::layer/state.phtml'; + } + } + + /** + * @return string + */ + public function getClearUrl() + { + if (!$this->config->isLayeredEnabled()) { + return parent::getClearUrl(); + } + + return $this->url->getClearUrl($this->getActiveFilters()); + } + + /** + * @param Item $item + * @return string|void + */ + public function getActiveFilterCssId(Item $item) + { + $facetSettings = $item->getFilter()->getFacet()->getFacetSettings(); + if ($facetSettings->getSelectionType() === SettingsType::SELECTION_TYPE_SLIDER) { + return 'slider-' . $facetSettings->getUrlKey(); + } + + return spl_object_hash($item); + } +} diff --git a/src/Block/LayeredNavigation/Navigation/State/Plugin.php b/src/Block/LayeredNavigation/Navigation/State/Plugin.php deleted file mode 100644 index eaea4875..00000000 --- a/src/Block/LayeredNavigation/Navigation/State/Plugin.php +++ /dev/null @@ -1,49 +0,0 @@ -config = $config; - $this->url = $url; - } - - /** - * @param State $subject - * @param Closure $proceed - * @return array - */ - public function aroundGetClearUrl(State $subject, Closure $proceed) - { - if (!$this->config->isLayeredEnabled()) { - return $proceed(); - } - - return $this->url->getClearUrl($subject->getActiveFilters()); - } - -} \ No newline at end of file diff --git a/src/Block/LayeredNavigation/RenderLayered/AnchorRendererTrait.php b/src/Block/LayeredNavigation/RenderLayered/AnchorRendererTrait.php index 0ddbe23a..e6a8bf1c 100644 --- a/src/Block/LayeredNavigation/RenderLayered/AnchorRendererTrait.php +++ b/src/Block/LayeredNavigation/RenderLayered/AnchorRendererTrait.php @@ -6,8 +6,8 @@ namespace Emico\Tweakwise\Block\LayeredNavigation\RenderLayered; -use Emico\Tweakwise\Model\Seo\FilterHelper; use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item; +use Emico\Tweakwise\Model\Seo\FilterHelper; trait AnchorRendererTrait { @@ -52,4 +52,4 @@ protected function getItemUrl(Item $item) { return $this->escapeHtml($item->getUrl()); } -} \ No newline at end of file +} diff --git a/src/Block/LayeredNavigation/RenderLayered/DefaultRenderer.php b/src/Block/LayeredNavigation/RenderLayered/DefaultRenderer.php index 476d3af7..20c73ccc 100644 --- a/src/Block/LayeredNavigation/RenderLayered/DefaultRenderer.php +++ b/src/Block/LayeredNavigation/RenderLayered/DefaultRenderer.php @@ -12,6 +12,7 @@ use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item; use Emico\Tweakwise\Model\Client\Type\FacetType\SettingsType; use Emico\Tweakwise\Model\Config; +use Emico\Tweakwise\Model\NavigationConfig; use Emico\Tweakwise\Model\Seo\FilterHelper; use Magento\Framework\View\Element\Template; use Magento\Framework\Serialize\Serializer\Json; @@ -40,21 +41,34 @@ class DefaultRenderer extends Template */ protected $config; + /** + * @var NavigationConfig + */ + protected $navigationConfig; + /** * Constructor * * @param Template\Context $context * @param Config $config + * @param NavigationConfig $navigationConfig * @param FilterHelper $filterHelper * @param Json $jsonSerializer * @param array $data */ - public function __construct(Template\Context $context, Config $config, FilterHelper $filterHelper, Json $jsonSerializer, array $data = []) - { + public function __construct( + Template\Context $context, + Config $config, + NavigationConfig $navigationConfig, + FilterHelper $filterHelper, + Json $jsonSerializer, + array $data = [] + ) { parent::__construct($context, $data); $this->config = $config; $this->filterHelper = $filterHelper; $this->jsonSerializer = $jsonSerializer; + $this->navigationConfig = $navigationConfig; } /** @@ -90,20 +104,27 @@ public function getItems() return $items; } + /** + * @return string + */ + public function getJsSortConfig() + { + return $this->navigationConfig->getJsSortConfig($this->hasAlternateSortOrder()); + } + /** * @return boolean */ public function hasAlternateSortOrder() { - $filter = function (Item $item) - { + $filter = function (Item $item) { return $item->getAlternateSortOrder() !== null; }; $items = $this->getItems(); - $itemsWIthAlternateSortOrder = array_filter($items, $filter); + $itemsWithAlternateSortOrder = array_filter($items, $filter); - return \count($items) === \count($itemsWIthAlternateSortOrder); + return \count($items) === \count($itemsWithAlternateSortOrder); } /** @@ -165,23 +186,6 @@ public function shouldDisplayProductCountOnLayer() return $this->getFacetSettings()->getIsNumberOfResultVisible(); } - /** - * @return string - */ - public function getCssId() - { - return spl_object_hash($this); - } - - /** - * @param Item $item - * @return string - */ - public function getCssItemId(Item $item) - { - return spl_object_hash($item); - } - /** * @return bool */ @@ -206,23 +210,6 @@ public function getItemPostfix() return $this->escapeHtml($this->getFacetSettings()->getPostfix()); } - /** - * @return string - */ - public function getJsNavigationConfig(): string - { - $navigationOptions = ['hasAlternateSort' => $this->hasAlternateSortOrder()]; - return $this->config->getJsNavigationConfig($navigationOptions); - } - - /** - * @return Config - */ - public function getJsUseFormFilters() - { - return $this->config->getJsUseFormFilters(); - } - /** * @return string */ @@ -230,4 +217,4 @@ public function getUrlKey() { return $this->getFacetSettings()->getUrlKey(); } -} \ No newline at end of file +} diff --git a/src/Block/LayeredNavigation/RenderLayered/SliderRenderer.php b/src/Block/LayeredNavigation/RenderLayered/SliderRenderer.php index 143f527f..2e62239d 100644 --- a/src/Block/LayeredNavigation/RenderLayered/SliderRenderer.php +++ b/src/Block/LayeredNavigation/RenderLayered/SliderRenderer.php @@ -9,6 +9,7 @@ namespace Emico\Tweakwise\Block\LayeredNavigation\RenderLayered; use Emico\Tweakwise\Model\Config; +use Emico\Tweakwise\Model\NavigationConfig; use Emico\Tweakwise\Model\Seo\FilterHelper; use Magento\Tax\Helper\Data as TaxHelper; use Magento\Framework\Pricing\Helper\Data as PriceHelper; @@ -37,6 +38,7 @@ class SliderRenderer extends DefaultRenderer * @param PriceHelper $priceHelper * @param TaxHelper $taxHelper * @param Config $config + * @param NavigationConfig $navigationConfig * @param FilterHelper $filterHelper * @param Template\Context $context * @param Json $jsonSerializer @@ -46,12 +48,20 @@ public function __construct( PriceHelper $priceHelper, TaxHelper $taxHelper, Config $config, + NavigationConfig $navigationConfig, FilterHelper $filterHelper, Template\Context $context, Json $jsonSerializer, array $data = [] ) { - parent::__construct($context, $config, $filterHelper, $jsonSerializer, $data); + parent::__construct( + $context, + $config, + $navigationConfig, + $filterHelper, + $jsonSerializer, + $data + ); $this->priceHelper = $priceHelper; $this->taxHelper = $taxHelper; } @@ -138,41 +148,24 @@ public function getFilterUrl() } /** - * @deprecated v2.1.8 - * @return float - */ - public function getMaxFloatValue() - { - return $this->getItemValue(3, $this->getCurrentMaxValue()); - } - - /** - * @deprecated v2.1.8 - * @return float - */ - public function getCurrentMaxFloatValue() - { - return $this->getItemValue(1, 99999); - } - - /** - * @deprecated v1.5.0 use getFilterUrl() - * @see SliderRenderer::getFilterUrl() * @return string */ - public function getPriceUrl() + public function getJsSliderConfig(): string { - return $this->getFilterUrl(); + return $this->navigationConfig->getJsSliderConfig($this); } /** - * @deprecated 1.5.0 use renderValue() - * @see SliderRenderer::renderValue() - * @param string $value * @return string */ - public function renderPrice($value) + public function getCssId() { - return $this->renderValue($value); + $anyItem = $this->getItems()[0]; + $urlKey = $anyItem->getFilter() + ->getFacet() + ->getFacetSettings() + ->getUrlKey(); + + return 'slider-' . $urlKey; } } diff --git a/src/Block/LayeredNavigation/RenderLayered/SwatchRenderer.php b/src/Block/LayeredNavigation/RenderLayered/SwatchRenderer.php index c13e35a6..25a71fb4 100644 --- a/src/Block/LayeredNavigation/RenderLayered/SwatchRenderer.php +++ b/src/Block/LayeredNavigation/RenderLayered/SwatchRenderer.php @@ -8,18 +8,19 @@ namespace Emico\Tweakwise\Block\LayeredNavigation\RenderLayered; +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 Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory as EavAttributeFactory; use Magento\Catalog\Model\ResourceModel\Layer\Filter\AttributeFactory; use Magento\Eav\Model\Entity\Attribute; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\View\Element\Template\Context; use Magento\Swatches\Block\LayeredNavigation\RenderLayered; -use Emico\Tweakwise\Model\Catalog\Layer\Filter; use Magento\Swatches\Helper\Data; use Magento\Swatches\Helper\Media; -use Emico\Tweakwise\Model\Config; -use Emico\Tweakwise\Model\Seo\FilterHelper; -use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory as EavAttributeFactory; class SwatchRenderer extends RenderLayered { @@ -55,7 +56,8 @@ class SwatchRenderer extends RenderLayered * @param Data $swatchHelper * @param Media $mediaHelper * @param Config $config - * @param EavAttributeFactory $attributeFactory + * @param EavAttributeFactory $eavAttributeFactory + * @param FilterHelper $filterHelper * @param array $data */ public function __construct( @@ -68,8 +70,7 @@ public function __construct( EavAttributeFactory $eavAttributeFactory, FilterHelper $filterHelper, array $data = [] - ) - { + ) { parent::__construct($context, $eavAttribute, $layerAttribute, $swatchHelper, $mediaHelper, $data); $this->config = $config; $this->eavAttributeFactory = $eavAttributeFactory; @@ -78,7 +79,7 @@ public function __construct( /** * @param Filter $filter - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function setFilter(Filter $filter) { @@ -93,23 +94,6 @@ public function setFilter(Filter $filter) $this->setSwatchFilter($filter); } - /** - * @return bool - */ - public function getUseFormFilters() - { - return $this->config->getUseFormFilters(); - } - - /** - * @param Item $item - * @return string - */ - public function getCssItemId(Item $item) - { - return spl_object_hash($item); - } - /** * @param int $id * @return Item @@ -118,12 +102,4 @@ public function getItemForSwatch($id) { return $this->filter->getItemByOptionId($id); } - - /** - * @return string - */ - public function getJsNavigationConfig() - { - return $this->config->getJsNavigationConfig(); - } -} \ No newline at end of file +} diff --git a/src/Block/TargetRule/Catalog/Product/ProductList/Plugin.php b/src/Block/TargetRule/Catalog/Product/ProductList/Plugin.php index e091ffb5..524e4e37 100644 --- a/src/Block/TargetRule/Catalog/Product/ProductList/Plugin.php +++ b/src/Block/TargetRule/Catalog/Product/ProductList/Plugin.php @@ -46,7 +46,7 @@ abstract class Plugin * @var Context */ protected $context; - + /** * @var TemplateFinder */ @@ -108,7 +108,7 @@ public function aroundGetPositionLimit(AbstractProductList $subject, Closure $pr /** * @param ProductRequest $request */ - private function configureRequest(ProductRequest $request) + protected function configureRequest(ProductRequest $request) { $product = $this->registry->registry('product'); if (!$product instanceof Product) { @@ -122,7 +122,7 @@ private function configureRequest(ProductRequest $request) /** * @return Collection */ - private function getCollection() + protected function getCollection() { if (!$this->collection) { $request = $this->context->getRequest(); @@ -136,4 +136,4 @@ private function getCollection() return $this->collection; } -} \ No newline at end of file +} diff --git a/src/Block/Theme/Html/Pager/Plugin.php b/src/Block/Theme/Html/Pager/Plugin.php index 5a4888d7..e91403e3 100644 --- a/src/Block/Theme/Html/Pager/Plugin.php +++ b/src/Block/Theme/Html/Pager/Plugin.php @@ -18,12 +18,12 @@ class Plugin /** * @var Config */ - private $config; + protected $config; /** * @var UrlModel */ - private $url; + protected $url; /** * Plugin constructor. @@ -56,4 +56,4 @@ public function aroundGetUrl(Pager $subject, Closure $proceed, string $route = ' return $this->url->getUrl($route, $params); } -} \ No newline at end of file +} diff --git a/src/Controller/Ajax/Navigation.php b/src/Controller/Ajax/Navigation.php new file mode 100644 index 00000000..d552d8db --- /dev/null +++ b/src/Controller/Ajax/Navigation.php @@ -0,0 +1,86 @@ +config = $config; + $this->ajaxNavigationResult = $ajaxNavigationResult; + $this->initializerMap = $initializerMap; + } + + /** + * Execute action based on request and return result + * + * Note: Request will be added as operation argument in future + * + * @return ResultInterface|ResponseInterface + * @throws NotFoundException + */ + public function execute() + { + if (!$this->config->isAjaxFilters()) { + throw new NotFoundException(__('Page not found.')); + } + + $request = $this->getRequest(); + $type = $request->getParam('__tw_ajax_type'); + if (!isset($this->initializerMap[$type])) { + throw new \InvalidArgumentException('No ajax navigation result handler found for ' . $type); + } + + $this->initializerMap[$type]->initializeAjaxResult( + $this->ajaxNavigationResult, + $request + ); + + return $this->ajaxNavigationResult; + } +} diff --git a/src/Controller/Router.php b/src/Controller/Router.php index e981bf42..c5ec5865 100644 --- a/src/Controller/Router.php +++ b/src/Controller/Router.php @@ -20,17 +20,17 @@ class Router implements RouterInterface /** * @var ActionFactory */ - private $actionFactory; + protected $actionFactory; /** * @var RouteMatchingInterface */ - private $routeMatchingStrategy; + protected $routeMatchingStrategy; /** * @var UrlStrategyFactory */ - private $urlStrategyFactory; + protected $urlStrategyFactory; /** * Router constructor. @@ -44,7 +44,7 @@ public function __construct(ActionFactory $actionFactory, UrlStrategyFactory $ur } /** - * + * @return RouteMatchingInterface */ protected function getRouteMatchingStrategy(): RouteMatchingInterface { @@ -77,9 +77,6 @@ public function match(RequestInterface $request) return $result; } - return $this->actionFactory->create( - Forward::class, - ['request' => $request] - ); + return $this->actionFactory->create(Forward::class); } -} \ No newline at end of file +} diff --git a/src/Cron/Version.php b/src/Cron/Version.php index 0136c421..e9dacee0 100644 --- a/src/Cron/Version.php +++ b/src/Cron/Version.php @@ -5,8 +5,8 @@ */ namespace Emico\Tweakwise\Cron; -use Magento\Framework\Composer\ComposerInformation; use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\Composer\ComposerInformation; /** * Class Version @@ -55,7 +55,7 @@ public function execute() '%s(%s)', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36 Magento2Tweakwise', $version - ); + ); $this->configWriter->save('tweakwise/general/version', $userAgentString); } -} \ No newline at end of file +} diff --git a/src/Exception/ApiException.php b/src/Exception/ApiException.php index 63bb48f3..59e50142 100644 --- a/src/Exception/ApiException.php +++ b/src/Exception/ApiException.php @@ -8,7 +8,7 @@ namespace Emico\Tweakwise\Exception; -class ApiException extends RuntimeException implements TweakwiseException +class ApiException extends RuntimeException { } diff --git a/src/Model/AjaxNavigationResult.php b/src/Model/AjaxNavigationResult.php new file mode 100644 index 00000000..63695649 --- /dev/null +++ b/src/Model/AjaxNavigationResult.php @@ -0,0 +1,106 @@ +urlModel = $urlModel; + $this->layerResolver = $layerResolver; + $this->serializer = $serializer; + } + + /** + * @param HttpResponseInterface $response + * @return Framework\Controller\AbstractResult|Layout + */ + public function render(HttpResponseInterface $response) + { + $html = $this->getLayout()->getOutput(); + $url = $this->getResponseUrl(); + + $responseData = $this->serializer->serialize(['url' => $url, 'html' => $html]); + $this->translateInline->processResponseBody($responseData, true); + + $response->setHeader('Content-Type', 'application/json', true); + $response->appendBody($responseData); + + return $this; + } + + /** + * @return string + */ + protected function getResponseUrl() + { + $layer = $this->layerResolver->get(); + $activeFilters = $layer->getState()->getFilters(); + return $this->urlModel->getFilterUrl($activeFilters); + } +} diff --git a/src/Model/AjaxResultInitializer/CategoryInitializer.php b/src/Model/AjaxResultInitializer/CategoryInitializer.php new file mode 100644 index 00000000..9d42915d --- /dev/null +++ b/src/Model/AjaxResultInitializer/CategoryInitializer.php @@ -0,0 +1,92 @@ +layerResolver = $layerResolver; + $this->registry = $registry; + $this->categoryRepository = $categoryRepository; + } + + /** + * @inheritDoc + * @throws NoSuchEntityException + */ + public function initializeAjaxResult( + AjaxNavigationResult $ajaxNavigationResult, + RequestInterface $request + ) { + $this->initializeLayer(); + $this->initializeLayout($ajaxNavigationResult); + $this->initializeRegistry($request); + } + + /** + * @param AjaxNavigationResult $ajaxNavigationResult + */ + protected function initializeLayout(AjaxNavigationResult $ajaxNavigationResult) + { + $ajaxNavigationResult->addHandle(self::LAYOUT_HANDLE_CATEGORY); + } + + /** + * Create category layer + */ + protected function initializeLayer() + { + $this->layerResolver->create(Resolver::CATALOG_LAYER_CATEGORY); + } + + /** + * @param RequestInterface $request + * @throws NoSuchEntityException + */ + protected function initializeRegistry(RequestInterface $request) + { + // Register the category, its needed while rendering filters and products + if (!$this->registry->registry('current_category')) { + $categoryId = (int)$request->getParam('__tw_object_id') ?: 2; + $category = $this->categoryRepository->get($categoryId); + $this->registry->register('current_category', $category); + } + } +} diff --git a/src/Model/AjaxResultInitializer/InitializerInterface.php b/src/Model/AjaxResultInitializer/InitializerInterface.php new file mode 100644 index 00000000..0ce64f6f --- /dev/null +++ b/src/Model/AjaxResultInitializer/InitializerInterface.php @@ -0,0 +1,29 @@ +layerResolver = $layerResolver; + } + + /** + * @inheritDoc + */ + public function initializeAjaxResult(AjaxNavigationResult $ajaxNavigationResult, RequestInterface $request) + { + $this->initializeLayer(); + $this->initializeLayout($ajaxNavigationResult); + } + + /** + * @param AjaxNavigationResult $ajaxNavigationResult + */ + protected function initializeLayout(AjaxNavigationResult $ajaxNavigationResult) + { + $ajaxNavigationResult->addHandle(self::LAYOUT_HANDLE_SEARCH); + } + + /** + * Create search Layer + */ + protected function initializeLayer() + { + $this->layerResolver->create(Resolver::CATALOG_LAYER_SEARCH); + } +} diff --git a/src/Model/AttributeSlug.php b/src/Model/AttributeSlug.php index a725e917..95bef6e9 100644 --- a/src/Model/AttributeSlug.php +++ b/src/Model/AttributeSlug.php @@ -6,7 +6,6 @@ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - namespace Emico\Tweakwise\Model; use Emico\Tweakwise\Model\ResourceModel\AttributeSlug as ResourceModel; diff --git a/src/Model/Catalog/Layer/Filter.php b/src/Model/Catalog/Layer/Filter.php index 5b1fed01..51f9cb25 100644 --- a/src/Model/Catalog/Layer/Filter.php +++ b/src/Model/Catalog/Layer/Filter.php @@ -35,57 +35,57 @@ class Filter extends AbstractFilter implements FilterInterface /** * @var string */ - private $requestVar; + protected $requestVar; /** * @var */ - private $items; + protected $items; /** * @var Layer */ - private $layer; + protected $layer; /** * @var Attribute */ - private $attributeModel; + protected $attributeModel; /** * @var int */ - private $storeId; + protected $storeId; /** * @var int */ - private $websiteId; + protected $websiteId; /** * @var FacetType */ - private $facet; + protected $facet; /** * @var ItemFactory */ - private $itemFactory; + protected $itemFactory; /** * @var StoreManager */ - private $storeManager; + protected $storeManager; /** * @var int[] */ - private $optionLabelValueMap; + protected $optionLabelValueMap; /** * @var Item[] */ - private $optionLabelItemMap; + protected $optionLabelItemMap; /** * Filter constructor. @@ -95,6 +95,8 @@ class Filter extends AbstractFilter implements FilterInterface * @param ItemFactory $itemFactory * @param StoreManager $storeManager * @param Attribute|null $attribute + * @noinspection MagicMethodsValidityInspection + * @noinspection PhpMissingParentConstructorInspection */ public function __construct( Layer $layer, @@ -323,7 +325,7 @@ public function getFacet() * @param AttributeType $attributeType * @return Item */ - private function createItem(AttributeType $attributeType) + protected function createItem(AttributeType $attributeType) { $item = $this->itemFactory->create(['filter' => $this, 'attributeType' => $attributeType]); @@ -339,7 +341,7 @@ private function createItem(AttributeType $attributeType) /** * @param Item $item */ - private function findDeepestActiveChildItem(Item $item) + protected function findDeepestActiveChildItem(Item $item) { if (!$item->hasChildren()) { return $item->isActive() ? $item : null; @@ -357,7 +359,7 @@ private function findDeepestActiveChildItem(Item $item) /** * @return $this */ - private function initItems() + protected function initItems() { foreach ($this->facet->getAttributes() as $attribute) { $this->addItem($attribute); @@ -368,7 +370,7 @@ private function initItems() /** * @return int[] */ - private function getOptionLabelValueMap() + protected function getOptionLabelValueMap() { if (!$this->hasAttributeModel()) { return []; @@ -389,7 +391,7 @@ private function getOptionLabelValueMap() /** * @return Item[] */ - private function getOptionLabelItemMap() + protected function getOptionLabelItemMap() { if (!$this->hasAttributeModel()) { return []; @@ -414,7 +416,7 @@ private function getOptionLabelItemMap() public function getOptionIdByLabel($label) { $map = $this->getOptionLabelValueMap(); - return isset($map[$label]) ? $map[$label] : null; + return $map[$label] ?? null; } /** @@ -425,7 +427,7 @@ public function getLabelByOptionId($id) { $map = $this->getOptionLabelValueMap(); $map = array_flip($map); - return isset($map[$id]) ? $map[$id] : null; + return $map[$id] ?? null; } /** @@ -440,7 +442,7 @@ public function getItemByOptionId($optionId) } $map = $this->getOptionLabelItemMap(); - return isset($map[$label]) ? $map[$label] : null; + return $map[$label] ?? null; } /** @@ -488,7 +490,7 @@ public function getUrlKey() * @param Item[] $activeItems * @return Item[] */ - private function combineActiveSliderItems(array $activeItems) + protected function combineActiveSliderItems(array $activeItems) { if (count($activeItems) !== 2) { return $activeItems; diff --git a/src/Model/Catalog/Layer/Filter/Item.php b/src/Model/Catalog/Layer/Filter/Item.php index 31e71f88..614f7e2f 100644 --- a/src/Model/Catalog/Layer/Filter/Item.php +++ b/src/Model/Catalog/Layer/Filter/Item.php @@ -47,6 +47,8 @@ class Item extends MagentoItem * @param Filter $filter * @param AttributeType $attributeType * @param Url $url + * @noinspection MagicMethodsValidityInspection + * @noinspection PhpMissingParentConstructorInspection */ public function __construct(Filter $filter, AttributeType $attributeType, Url $url) { @@ -182,4 +184,12 @@ public function isActive() { return $this->getAttribute()->getIsSelected(); } + + /** + * @return string + */ + public function getCssId() + { + return spl_object_hash($this); + } } diff --git a/src/Model/Catalog/Layer/FilterList/EmptyAttributeList.php b/src/Model/Catalog/Layer/FilterList/EmptyAttributeList.php index bf264257..134c51de 100644 --- a/src/Model/Catalog/Layer/FilterList/EmptyAttributeList.php +++ b/src/Model/Catalog/Layer/FilterList/EmptyAttributeList.php @@ -8,7 +8,6 @@ namespace Emico\Tweakwise\Model\Catalog\Layer\FilterList; - use Magento\Catalog\Model\Layer\FilterableAttributeListInterface; class EmptyAttributeList implements FilterableAttributeListInterface @@ -20,4 +19,4 @@ public function getList() { return []; } -} \ No newline at end of file +} diff --git a/src/Model/Catalog/Layer/FilterList/Tweakwise.php b/src/Model/Catalog/Layer/FilterList/Tweakwise.php index ad0fc229..d85322da 100644 --- a/src/Model/Catalog/Layer/FilterList/Tweakwise.php +++ b/src/Model/Catalog/Layer/FilterList/Tweakwise.php @@ -23,27 +23,27 @@ class Tweakwise /** * @var FilterInterface[] */ - private $filters; + protected $filters; /** * @var FilterFactory */ - private $filterFactory; + protected $filterFactory; /** * @var CurrentContext */ - private $context; + protected $context; /** * @var Config */ - private $config; + protected $config; /** * @var AttributeFactory */ - private $attributeFactory; + protected $attributeFactory; /** * Tweakwise constructor. @@ -84,12 +84,14 @@ public function getFilters(Layer $layer): array protected function initFilters(Layer $layer) { $request = $this->context->getRequest(); - $request->addCategoryFilter($layer->getCurrentCategory()); + if (!$request->hasParameter('tn_cid')) { + $request->addCategoryFilter($layer->getCurrentCategory()); + } $facets = $this->context->getResponse()->getFacets(); $facetAttributeNames = array_map( - function (FacetType $facet) { + static function (FacetType $facet) { return $facet->getFacetSettings()->getAttributename(); }, $facets diff --git a/src/Model/Catalog/Layer/ItemCollectionProvider.php b/src/Model/Catalog/Layer/ItemCollectionProvider.php index 8bcf7849..47c7c0fb 100644 --- a/src/Model/Catalog/Layer/ItemCollectionProvider.php +++ b/src/Model/Catalog/Layer/ItemCollectionProvider.php @@ -9,13 +9,11 @@ namespace Emico\Tweakwise\Model\Catalog\Layer; use Emico\Tweakwise\Exception\TweakwiseException; -use Emico\Tweakwise\Model\Catalog\Product\Collection; use Emico\Tweakwise\Model\Catalog\Product\CollectionFactory; use Emico\Tweakwise\Model\Config; use Emico\TweakwiseExport\Model\Logger; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\Layer\ItemCollectionProviderInterface; -use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; class ItemCollectionProvider implements ItemCollectionProviderInterface { @@ -53,8 +51,13 @@ class ItemCollectionProvider implements ItemCollectionProviderInterface * @param CollectionFactory $collectionFactory * @param NavigationContext $navigationContext */ - public function __construct(Config $config, Logger $log, ItemCollectionProviderInterface $originalProvider, CollectionFactory $collectionFactory, NavigationContext $navigationContext) - { + public function __construct( + Config $config, + Logger $log, + ItemCollectionProviderInterface $originalProvider, + CollectionFactory $collectionFactory, + NavigationContext $navigationContext + ) { $this->config = $config; $this->log = $log; $this->collectionFactory = $collectionFactory; diff --git a/src/Model/Catalog/Layer/NavigationContext/CurrentContext.php b/src/Model/Catalog/Layer/NavigationContext/CurrentContext.php index dd21e7c0..92b9fb94 100644 --- a/src/Model/Catalog/Layer/NavigationContext/CurrentContext.php +++ b/src/Model/Catalog/Layer/NavigationContext/CurrentContext.php @@ -8,7 +8,6 @@ namespace Emico\Tweakwise\Model\Catalog\Layer\NavigationContext; - use Emico\Tweakwise\Exception\RuntimeException; use Emico\Tweakwise\Model\Catalog\Layer\NavigationContext; use Emico\Tweakwise\Model\Client\Request\ProductNavigationRequest; diff --git a/src/Model/Catalog/Layer/Url.php b/src/Model/Catalog/Layer/Url.php index 8af68bae..9aa0a34a 100644 --- a/src/Model/Catalog/Layer/Url.php +++ b/src/Model/Catalog/Layer/Url.php @@ -10,16 +10,16 @@ use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item; use Emico\Tweakwise\Model\Catalog\Layer\Url\CategoryUrlInterface; -use Emico\Tweakwise\Model\Catalog\Layer\Url\Strategy\UrlStrategyFactory; use Emico\Tweakwise\Model\Catalog\Layer\Url\FilterApplierInterface; +use Emico\Tweakwise\Model\Catalog\Layer\Url\Strategy\UrlStrategyFactory; use Emico\Tweakwise\Model\Catalog\Layer\Url\UrlInterface; use Emico\Tweakwise\Model\Client\Request\ProductNavigationRequest; use Emico\Tweakwise\Model\Client\Type\FacetType\SettingsType; +use Emico\TweakwiseExport\Model\Helper as ExportHelper; +use Magento\Catalog\Api\CategoryRepositoryInterface; use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Framework\App\Request\Http as MagentoHttpRequest; use Magento\Framework\Exception\NoSuchEntityException; -use Zend\Http\Request as HttpRequest; -use Magento\Catalog\Api\CategoryRepositoryInterface; -use Emico\TweakwiseExport\Model\Helper as ExportHelper; /** * Class Url will later implement logic to use implementation selected in configuration. @@ -54,7 +54,7 @@ class Url protected $exportHelper; /** - * @var HttpRequest + * @var MagentoHttpRequest */ protected $request; @@ -67,13 +67,13 @@ class Url * Builder constructor. * * @param UrlStrategyFactory $urlStrategyFactory - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param CategoryRepositoryInterface $categoryRepository * @param ExportHelper $exportHelper */ public function __construct( UrlStrategyFactory $urlStrategyFactory, - HttpRequest $request, + MagentoHttpRequest $request, CategoryRepositoryInterface $categoryRepository, ExportHelper $exportHelper ) { @@ -124,7 +124,6 @@ protected function getCategoryUrlStrategy() /** * @param Item $item * @return string - * @throws NoSuchEntityException */ public function getSelectFilter(Item $item): string { @@ -168,6 +167,16 @@ public function getClearUrl(array $activeFilterItems) ->getClearUrl($this->request, $activeFilterItems); } + /** + * @param array $activeFilterItems + * @return string + */ + public function getFilterUrl(array $activeFilterItems) + { + return $this->getUrlStrategy() + ->buildFilterUrl($this->request, $activeFilterItems); + } + /** * @param ProductNavigationRequest $navigationRequest */ @@ -197,4 +206,4 @@ protected function getCategoryFromItem(Item $item): CategoryInterface return $this->categoryRepository->get($categoryId); } -} \ No newline at end of file +} diff --git a/src/Model/Catalog/Layer/Url/CategoryUrlInterface.php b/src/Model/Catalog/Layer/Url/CategoryUrlInterface.php index 5687b313..b3ce6458 100644 --- a/src/Model/Catalog/Layer/Url/CategoryUrlInterface.php +++ b/src/Model/Catalog/Layer/Url/CategoryUrlInterface.php @@ -9,8 +9,7 @@ namespace Emico\Tweakwise\Model\Catalog\Layer\Url; use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item; -use Magento\Catalog\Api\Data\CategoryInterface; -use Zend\Http\Request as HttpRequest; +use Magento\Framework\App\Request\Http as MagentoHttpRequest; /** * Interface UrlInterface implementation should handle both category url's. @@ -18,18 +17,16 @@ interface CategoryUrlInterface { /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item - * @param CategoryInterface $category * @return mixed */ - public function getCategoryFilterSelectUrl(HttpRequest $request, Item $item): string; + public function getCategoryFilterSelectUrl(MagentoHttpRequest $request, Item $item): string; /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item - * @param CategoryInterface $category * @return mixed */ - public function getCategoryFilterRemoveUrl(HttpRequest $request, Item $item): string; + public function getCategoryFilterRemoveUrl(MagentoHttpRequest $request, Item $item): string; } diff --git a/src/Model/Catalog/Layer/Url/FilterApplierInterface.php b/src/Model/Catalog/Layer/Url/FilterApplierInterface.php index 0327771f..cdaae1e9 100644 --- a/src/Model/Catalog/Layer/Url/FilterApplierInterface.php +++ b/src/Model/Catalog/Layer/Url/FilterApplierInterface.php @@ -8,25 +8,20 @@ namespace Emico\Tweakwise\Model\Catalog\Layer\Url; -use Emico\Tweakwise\Model\Catalog\Layer\Filter; -use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item; use Emico\Tweakwise\Model\Client\Request\ProductNavigationRequest; -use Magento\Framework\Api\SortOrder; -use Zend\Http\Request as HttpRequest; +use Magento\Framework\App\Request\Http as MagentoHttpRequest; /** * Interface UrlInterface implementation should handle both category url's and - * - * */ interface FilterApplierInterface { /** * Apply all attribute filters, category filters, sort order, page limit request parameters to navigation request * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param ProductNavigationRequest $navigationRequest * @return $this */ - public function apply(HttpRequest $request, ProductNavigationRequest $navigationRequest): FilterApplierInterface; -} \ No newline at end of file + public function apply(MagentoHttpRequest $request, ProductNavigationRequest $navigationRequest): FilterApplierInterface; +} diff --git a/src/Model/Catalog/Layer/Url/RewriteResolver/CategoryResolver.php b/src/Model/Catalog/Layer/Url/RewriteResolver/CategoryResolver.php index 6b05eb3c..63b81ed8 100644 --- a/src/Model/Catalog/Layer/Url/RewriteResolver/CategoryResolver.php +++ b/src/Model/Catalog/Layer/Url/RewriteResolver/CategoryResolver.php @@ -65,7 +65,7 @@ public function getRewrites(MagentoHttpRequest $request): array $storeId = $this->storeManager->getStore()->getId(); $rewriteFilterData[UrlRewrite::STORE_ID] = $storeId; } catch (NoSuchEntityException $e) { - // No implementation + return []; } return $this->urlFinder->findAllByData($rewriteFilterData); diff --git a/src/Model/Catalog/Layer/Url/RouteMatchingInterface.php b/src/Model/Catalog/Layer/Url/RouteMatchingInterface.php index da6d72aa..42b83708 100644 --- a/src/Model/Catalog/Layer/Url/RouteMatchingInterface.php +++ b/src/Model/Catalog/Layer/Url/RouteMatchingInterface.php @@ -8,7 +8,6 @@ namespace Emico\Tweakwise\Model\Catalog\Layer\Url; - use Magento\Framework\App\ActionInterface; use Magento\Framework\App\Request\Http as MagentoHttpRequest; @@ -19,4 +18,4 @@ interface RouteMatchingInterface * @return bool|ActionInterface */ public function match(MagentoHttpRequest $request); -} \ No newline at end of file +} diff --git a/src/Model/Catalog/Layer/Url/Strategy/FilterSlugManager.php b/src/Model/Catalog/Layer/Url/Strategy/FilterSlugManager.php index 35760566..dc84b0f0 100644 --- a/src/Model/Catalog/Layer/Url/Strategy/FilterSlugManager.php +++ b/src/Model/Catalog/Layer/Url/Strategy/FilterSlugManager.php @@ -29,32 +29,32 @@ class FilterSlugManager /** * @var TranslitUrl */ - private $translitUrl; + protected $translitUrl; /** * @var AttributeSlugRepositoryInterface */ - private $attributeSlugRepository; + protected $attributeSlugRepository; /** * @var AttributeSlugInterfaceFactory */ - private $attributeSlugFactory; + protected $attributeSlugFactory; /** * @var CacheInterface */ - private $cache; + protected $cache; /** * @var array */ - private $lookupTable; + protected $lookupTable; /** * @var SerializerInterface */ - private $serializer; + protected $serializer; /** * @param TranslitUrl $translitUrl @@ -106,7 +106,7 @@ public function getSlugForFilterItem(Item $filterItem): string /** * @param string $slug * @return string - * @throws \Emico\Tweakwise\Exception\UnexpectedValueException + * @throws UnexpectedValueException */ public function getAttributeBySlug(string $slug): string { @@ -151,4 +151,4 @@ protected function loadLookupTable(): array } return $lookupTable; } -} \ No newline at end of file +} diff --git a/src/Model/Catalog/Layer/Url/Strategy/NullStrategy.php b/src/Model/Catalog/Layer/Url/Strategy/NullStrategy.php index c832632d..dfc8cea0 100644 --- a/src/Model/Catalog/Layer/Url/Strategy/NullStrategy.php +++ b/src/Model/Catalog/Layer/Url/Strategy/NullStrategy.php @@ -8,14 +8,12 @@ namespace Emico\Tweakwise\Model\Catalog\Layer\Url\Strategy; - use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item; use Emico\Tweakwise\Model\Catalog\Layer\Url\FilterApplierInterface; use Emico\Tweakwise\Model\Catalog\Layer\Url\RouteMatchingInterface; use Emico\Tweakwise\Model\Catalog\Layer\Url\UrlInterface; use Emico\Tweakwise\Model\Client\Request\ProductNavigationRequest; use Magento\Framework\App\ActionInterface; -use Zend\Http\Request as HttpRequest; use Magento\Framework\App\Request\Http as MagentoHttpRequest; class NullStrategy implements RouteMatchingInterface, UrlInterface, FilterApplierInterface @@ -32,11 +30,11 @@ public function match(MagentoHttpRequest $request) /** * Apply all attribute filters, category filters, sort order, page limit request parameters to navigation request * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param ProductNavigationRequest $navigationRequest * @return $this */ - public function apply(HttpRequest $request, ProductNavigationRequest $navigationRequest): FilterApplierInterface + public function apply(MagentoHttpRequest $request, ProductNavigationRequest $navigationRequest): FilterApplierInterface { return $this; } @@ -44,11 +42,11 @@ public function apply(HttpRequest $request, ProductNavigationRequest $navigation /** * Get url when selecting item * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getAttributeSelectUrl(HttpRequest $request, Item $item): string + public function getAttributeSelectUrl(MagentoHttpRequest $request, Item $item): string { return ''; } @@ -56,21 +54,21 @@ public function getAttributeSelectUrl(HttpRequest $request, Item $item): string /** * Get url when removing item from selecting * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getAttributeRemoveUrl(HttpRequest $request, Item $item): string + public function getAttributeRemoveUrl(MagentoHttpRequest $request, Item $item): string { return ''; } /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getSliderUrl(HttpRequest $request, Item $item): string + public function getSliderUrl(MagentoHttpRequest $request, Item $item): string { return ''; } @@ -78,11 +76,11 @@ public function getSliderUrl(HttpRequest $request, Item $item): string /** * Fetch clear all items from url * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item[] $activeFilterItems * @return string */ - public function getClearUrl(HttpRequest $request, array $activeFilterItems): string + public function getClearUrl(MagentoHttpRequest $request, array $activeFilterItems): string { return ''; } @@ -96,4 +94,12 @@ public function isAllowed(): bool { return true; } -} \ No newline at end of file + + /** + * @inheritDoc + */ + public function buildFilterUrl(MagentoHttpRequest $request, array $filters = []): string + { + return ''; + } +} diff --git a/src/Model/Catalog/Layer/Url/Strategy/PathSlugStrategy.php b/src/Model/Catalog/Layer/Url/Strategy/PathSlugStrategy.php index a003efd5..0eeb7fa8 100644 --- a/src/Model/Catalog/Layer/Url/Strategy/PathSlugStrategy.php +++ b/src/Model/Catalog/Layer/Url/Strategy/PathSlugStrategy.php @@ -12,13 +12,13 @@ use Emico\Tweakwise\Exception\UnexpectedValueException; use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item; use Emico\Tweakwise\Model\Catalog\Layer\NavigationContext\CurrentContext; -use Emico\Tweakwise\Model\Catalog\Layer\Url\RewriteResolver\RewriteResolverInterface; -use Emico\Tweakwise\Model\Catalog\Layer\UrlFactory; use Emico\Tweakwise\Model\Catalog\Layer\Url\CategoryUrlInterface; use Emico\Tweakwise\Model\Catalog\Layer\Url\FilterApplierInterface; +use Emico\Tweakwise\Model\Catalog\Layer\Url\RewriteResolver\RewriteResolverInterface; use Emico\Tweakwise\Model\Catalog\Layer\Url\RouteMatchingInterface; use Emico\Tweakwise\Model\Catalog\Layer\Url\UrlInterface; use Emico\Tweakwise\Model\Catalog\Layer\Url\UrlModel; +use Emico\Tweakwise\Model\Catalog\Layer\UrlFactory; use Emico\Tweakwise\Model\Client\Request\ProductNavigationRequest; use Emico\Tweakwise\Model\Client\Request\ProductSearchRequest; use Emico\Tweakwise\Model\Client\Type\FacetType\SettingsType; @@ -28,22 +28,17 @@ use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\Framework\App\ActionInterface; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; -use Zend\Http\Request as HttpRequest; use Magento\Framework\App\Request\Http as MagentoHttpRequest; use Magento\Framework\UrlInterface as MagentoUrlInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; -class PathSlugStrategy implements UrlInterface, RouteMatchingInterface, FilterApplierInterface, CategoryUrlInterface +class PathSlugStrategy implements + UrlInterface, + RouteMatchingInterface, + FilterApplierInterface, + CategoryUrlInterface { - const REQUEST_FILTER_PATH = 'filter_path'; - - const MEDIA_EXTENSIONS = [ - '.jpg', - '.png', - '.jpeg', - '.webp', - '.gif' - ]; + public const REQUEST_FILTER_PATH = 'filter_path'; /** * @var Resolver @@ -60,6 +55,11 @@ class PathSlugStrategy implements UrlInterface, RouteMatchingInterface, FilterAp */ protected $magentoUrl; + /** + * @var UrlFinderInterface + */ + protected $urlFinder; + /** * @var Item[] */ @@ -95,6 +95,11 @@ class PathSlugStrategy implements UrlInterface, RouteMatchingInterface, FilterAp */ protected $rewriteResolvers; + /** + * @var array + */ + protected $skipMatchExtensions; + /** * Magento constructor. * @@ -107,6 +112,7 @@ class PathSlugStrategy implements UrlInterface, RouteMatchingInterface, FilterAp * @param CurrentContext $currentContext * @param ScopeConfigInterface $scopeConfig * @param RewriteResolverInterface[] $rewriteResolvers + * @param array $skipMatchExtensions */ public function __construct( UrlModel $magentoUrl, @@ -117,7 +123,8 @@ public function __construct( Config $config, CurrentContext $currentContext, ScopeConfigInterface $scopeConfig, - array $rewriteResolvers + array $rewriteResolvers, + array $skipMatchExtensions ) { $this->magentoUrl = $magentoUrl; $this->layerResolver = $layerResolver; @@ -128,16 +135,17 @@ public function __construct( $this->currentContext = $currentContext; $this->scopeConfig = $scopeConfig; $this->rewriteResolvers = $rewriteResolvers; + $this->skipMatchExtensions = $skipMatchExtensions; } /** * Get url when selecting item * - * @param MagentoHttpRequest|HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getAttributeSelectUrl(HttpRequest $request, Item $item): string + public function getAttributeSelectUrl(MagentoHttpRequest $request, Item $item): string { $filters = $this->getActiveFilters(); $filters[] = $item; @@ -148,11 +156,11 @@ public function getAttributeSelectUrl(HttpRequest $request, Item $item): string /** * Get url when removing item from selecting * - * @param MagentoHttpRequest|HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getAttributeRemoveUrl(HttpRequest $request, Item $item): string + public function getAttributeRemoveUrl(MagentoHttpRequest $request, Item $item): string { $filters = $this->getActiveFilters(); /** @@ -169,11 +177,11 @@ public function getAttributeRemoveUrl(HttpRequest $request, Item $item): string } /** - * @param MagentoHttpRequest|HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getSliderUrl(HttpRequest $request, Item $item): string + public function getSliderUrl(MagentoHttpRequest $request, Item $item): string { $filters = $this->getActiveFilters(); /** @@ -195,11 +203,11 @@ public function getSliderUrl(HttpRequest $request, Item $item): string /** * Fetch clear all items from url * - * @param MagentoHttpRequest|HttpRequest $request + * @param MagentoHttpRequest $request * @param Item[] $activeFilterItems * @return string */ - public function getClearUrl(HttpRequest $request, array $activeFilterItems): string + public function getClearUrl(MagentoHttpRequest $request, array $activeFilterItems): string { return $this->buildFilterUrl($request); } @@ -207,18 +215,14 @@ public function getClearUrl(HttpRequest $request, array $activeFilterItems): str /** * Apply all attribute filters, category filters, sort order, page limit request parameters to navigation request * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param ProductNavigationRequest $navigationRequest * @return $this */ - public function apply(HttpRequest $request, ProductNavigationRequest $navigationRequest): FilterApplierInterface + public function apply(MagentoHttpRequest $request, ProductNavigationRequest $navigationRequest): FilterApplierInterface { // Order / pagination etc. is still done with query parameters. Also apply this using the queryParameter strategy $this->queryParameterStrategy->apply($request, $navigationRequest); - - if (!$request instanceof MagentoHttpRequest) { - return $this; - } $filterPath = $request->getParam(self::REQUEST_FILTER_PATH); if (empty($filterPath)) { return $this; @@ -260,10 +264,42 @@ protected function getActiveFilters(): array return $this->activeFilters; } + /** + * @param MagentoHttpRequest $request + * @return string + */ + public function getOriginalUrl(MagentoHttpRequest $request): string + { + if ($twOriginalUrl = $request->getParam('__tw_original_url')) { + // This seems ugly, perhaps there is another way? + $query = []; + // Add page and sort + $page = $request->getParam('p'); + $sort = $request->getParam('product_list_order'); + $limit = $request->getParam('product_list_limit'); + $mode = $request->getParam('product_list_mode'); + if ($page && (int) $page > 1) { + $query['p'] = $page; + } + if ($sort) { + $query['product_list_order'] = $sort; + } + if ($limit) { + $query['product_list_limit'] = $limit; + } + if ($mode) { + $query['product_list_mode'] = $mode; + } + return $this->magentoUrl->getDirectUrl($twOriginalUrl, ['_query' => $query]); + } + + return $this->getCurrentUrl(); + } + /** * @return string */ - public function getCurrentUrl(): string + protected function getCurrentUrl(): string { $params['_current'] = true; $params['_use_rewrite'] = true; @@ -286,7 +322,7 @@ protected function getLayer(): Layer */ public function buildFilterUrl(MagentoHttpRequest $request, array $filters = []): string { - $currentUrl = $this->getCurrentUrl(); + $currentUrl = $this->getOriginalUrl($request); $currentFilterPath = $request->getParam(self::REQUEST_FILTER_PATH); $newFilterPath = $this->buildFilterSlugPath($filters); @@ -299,7 +335,7 @@ public function buildFilterUrl(MagentoHttpRequest $request, array $filters = []) } } else { // Replace filter path in current URL with the new filter combination path - $url = str_replace($currentFilterPath, $newFilterPath, $currentUrl); + $url = str_replace($currentFilterPath, $newFilterPath, $currentUrl); } $categoryUrlSuffix = $this->scopeConfig->getValue( CategoryUrlPathGenerator::XML_PATH_CATEGORY_URL_SUFFIX, @@ -425,8 +461,8 @@ public function match(MagentoHttpRequest $request) protected function skip(MagentoHttpRequest $request): bool { $requestPath = $request->getPathInfo(); - foreach (self::MEDIA_EXTENSIONS as $fileExtention) { - if (strpos($requestPath, $fileExtention, -\strlen($fileExtention)) !== false) { + foreach ($this->skipMatchExtensions as $fileExtension) { + if (strpos($requestPath, $fileExtension, -\strlen($fileExtension)) !== false) { return true; } } @@ -435,24 +471,24 @@ protected function skip(MagentoHttpRequest $request): bool } /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return mixed */ public function getCategoryFilterSelectUrl( - HttpRequest $request, + MagentoHttpRequest $request, Item $item ): string { return $this->queryParameterStrategy->getCategoryFilterSelectUrl($request, $item); } /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return mixed */ public function getCategoryFilterRemoveUrl( - HttpRequest $request, + MagentoHttpRequest $request, Item $item ): string { return $this->queryParameterStrategy->getCategoryFilterRemoveUrl($request, $item); @@ -471,7 +507,6 @@ public function isAllowed(): bool return true; } - return !$context->getRequest() instanceof ProductSearchRequest - && !$this->config->getUseFormFilters(); + return !$context->getRequest() instanceof ProductSearchRequest; } } diff --git a/src/Model/Catalog/Layer/Url/Strategy/QueryParameterStrategy.php b/src/Model/Catalog/Layer/Url/Strategy/QueryParameterStrategy.php index 0540c920..9d9cb96f 100644 --- a/src/Model/Catalog/Layer/Url/Strategy/QueryParameterStrategy.php +++ b/src/Model/Catalog/Layer/Url/Strategy/QueryParameterStrategy.php @@ -15,13 +15,12 @@ use Emico\Tweakwise\Model\Client\Request\ProductNavigationRequest; use Emico\Tweakwise\Model\Catalog\Layer\Url\UrlModel; use Emico\Tweakwise\Model\Client\Request\ProductSearchRequest; -use Emico\Tweakwise\Model\Config; use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Framework\App\Request\Http as MagentoHttpRequest; use Magento\Catalog\Model\Category; -use Zend\Http\Request as HttpRequest; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Catalog\Api\CategoryRepositoryInterface; use Emico\TweakwiseExport\Model\Helper as ExportHelper; -use Magento\Catalog\Model\Layer\Resolver; class QueryParameterStrategy implements UrlInterface, FilterApplierInterface, CategoryUrlInterface @@ -61,17 +60,17 @@ class QueryParameterStrategy implements UrlInterface, FilterApplierInterface, Ca /** * @var CategoryRepositoryInterface */ - private $categoryRepository; + protected $categoryRepository; /** * @var ExportHelper */ - private $exportHelper; + protected $exportHelper; /** * @var UrlModel */ - private $url; + protected $url; /** * Magento constructor. @@ -93,7 +92,7 @@ public function __construct( /** * {@inheritdoc} */ - public function getClearUrl(HttpRequest $request, array $activeFilterItems): string + public function getClearUrl(MagentoHttpRequest $request, array $activeFilterItems): string { $query = []; /** @var Item $item */ @@ -104,30 +103,35 @@ public function getClearUrl(HttpRequest $request, array $activeFilterItems): str $query[$urlKey] = $filter->getCleanValue(); } - return $this->getCurrentQueryUrl($query); + return $this->getCurrentQueryUrl($request, $query); } /** + * @param MagentoHttpRequest $request * @param array $query * @return string */ - protected function getCurrentQueryUrl(array $query) + protected function getCurrentQueryUrl(MagentoHttpRequest $request, array $query) { $params['_current'] = true; $params['_use_rewrite'] = true; $params['_query'] = $query; $params['_escape'] = false; + + if ($originalUrl = $request->getQuery('__tw_original_url')) { + return $this->url->getDirectUrl($originalUrl, $params); + } return $this->url->getUrl('*/*/*', $params); } /** * Fetch current selected values * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string[]|string|null */ - protected function getRequestValues(HttpRequest $request, Item $item) + protected function getRequestValues(MagentoHttpRequest $request, Item $item) { $filter = $item->getFilter(); $settings = $filter @@ -158,7 +162,7 @@ protected function getRequestValues(HttpRequest $request, Item $item) /** * {@inheritdoc} */ - public function getCategoryFilterSelectUrl(HttpRequest $request, Item $item): string + public function getCategoryFilterSelectUrl(MagentoHttpRequest $request, Item $item): string { $category = $this->getCategoryFromItem($item); if (!$this->getSearch($request)) { @@ -180,25 +184,25 @@ public function getCategoryFilterSelectUrl(HttpRequest $request, Item $item): st $value = implode(self::CATEGORY_TREE_SEPARATOR, array_reverse($value)); $query = [$urlKey => $value]; - return $this->getCurrentQueryUrl($query); + return $this->getCurrentQueryUrl($request, $query); } /** * {@inheritdoc} */ - public function getCategoryFilterRemoveUrl(HttpRequest $request, Item $item): string + public function getCategoryFilterRemoveUrl(MagentoHttpRequest $request, Item $item): string { $filter = $item->getFilter(); $urlKey = $filter->getUrlKey(); $query = [$urlKey => $filter->getCleanValue()]; - return $this->getCurrentQueryUrl($query); + return $this->getCurrentQueryUrl($request, $query); } /** * {@inheritdoc} */ - public function getAttributeSelectUrl(HttpRequest $request, Item $item): string + public function getAttributeSelectUrl(MagentoHttpRequest $request, Item $item): string { $settings = $item ->getFilter() @@ -220,13 +224,24 @@ public function getAttributeSelectUrl(HttpRequest $request, Item $item): string $query = [$urlKey => $value]; } - return $this->getCurrentQueryUrl($query); + return $this->getCurrentQueryUrl($request, $query); + } + + /** + * @param MagentoHttpRequest $request + * @param Item[] $filters + * @return string + */ + public function buildFilterUrl(MagentoHttpRequest $request, array $filters = []): string + { + $attributeFilters = $this->getAttributeFilters($request); + return $this->getCurrentQueryUrl($request, $attributeFilters); } /** * {@inheritdoc} */ - public function getAttributeRemoveUrl(HttpRequest $request, Item $item): string + public function getAttributeRemoveUrl(MagentoHttpRequest $request, Item $item): string { $filter = $item->getFilter(); $settings = $filter->getFacet()->getFacetSettings(); @@ -249,13 +264,13 @@ public function getAttributeRemoveUrl(HttpRequest $request, Item $item): string $query = [$urlKey => $filter->getCleanValue()]; } - return $this->getCurrentQueryUrl($query); + return $this->getCurrentQueryUrl($request, $query); } /** * {@inheritdoc} */ - protected function getCategoryFilters(HttpRequest $request) + protected function getCategoryFilters(MagentoHttpRequest $request) { $categories = $request->getQuery(self::PARAM_CATEGORY); $categories = explode(self::CATEGORY_TREE_SEPARATOR, $categories); @@ -269,7 +284,7 @@ protected function getCategoryFilters(HttpRequest $request) /** * {@inheritdoc} */ - protected function getAttributeFilters(HttpRequest $request) + protected function getAttributeFilters(MagentoHttpRequest $request) { $result = []; foreach ($request->getQuery() as $attribute => $value) { @@ -285,17 +300,17 @@ protected function getAttributeFilters(HttpRequest $request) /** * {@inheritdoc} */ - public function getSliderUrl(HttpRequest $request, Item $item): string + public function getSliderUrl(MagentoHttpRequest $request, Item $item): string { $query = [$item->getFilter()->getUrlKey() => '{{from}}-{{to}}']; - return $this->getCurrentQueryUrl($query); + return $this->getCurrentQueryUrl($request, $query); } /** * {@inheritdoc} */ - public function apply(HttpRequest $request, ProductNavigationRequest $navigationRequest): FilterApplierInterface + public function apply(MagentoHttpRequest $request, ProductNavigationRequest $navigationRequest): FilterApplierInterface { $attributeFilters = $this->getAttributeFilters($request); foreach ($attributeFilters as $attribute => $values) { @@ -338,37 +353,37 @@ public function apply(HttpRequest $request, ProductNavigationRequest $navigation } /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @return string|null */ - protected function getSortOrder(HttpRequest $request) + protected function getSortOrder(MagentoHttpRequest $request) { return $request->getQuery(self::PARAM_ORDER); } /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @return int|null */ - protected function getPage(HttpRequest $request) + protected function getPage(MagentoHttpRequest $request) { return $request->getQuery(self::PARAM_PAGE); } /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @return int|null */ - protected function getLimit(HttpRequest $request) + protected function getLimit(MagentoHttpRequest $request) { return $request->getQuery(self::PARAM_LIMIT); } /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @return string|null */ - protected function getSearch(HttpRequest $request) + protected function getSearch(MagentoHttpRequest $request) { return $request->getQuery(self::PARAM_SEARCH); } @@ -376,7 +391,7 @@ protected function getSearch(HttpRequest $request) /** * @param Item $item * @return CategoryInterface - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws NoSuchEntityException */ protected function getCategoryFromItem(Item $item): CategoryInterface { diff --git a/src/Model/Catalog/Layer/Url/Strategy/UrlStrategyFactory.php b/src/Model/Catalog/Layer/Url/Strategy/UrlStrategyFactory.php index 95a03b83..7c8b8482 100644 --- a/src/Model/Catalog/Layer/Url/Strategy/UrlStrategyFactory.php +++ b/src/Model/Catalog/Layer/Url/Strategy/UrlStrategyFactory.php @@ -8,7 +8,6 @@ namespace Emico\Tweakwise\Model\Catalog\Layer\Url\Strategy; - use Emico\Tweakwise\Model\Catalog\Layer\Url\FilterApplierInterface; use Emico\Tweakwise\Model\Catalog\Layer\Url\RouteMatchingInterface; use Emico\Tweakwise\Model\Catalog\Layer\Url\UrlInterface; @@ -25,12 +24,12 @@ class UrlStrategyFactory /** * @var ObjectManager */ - private $objectManager; + protected $objectManager; /** * @var Config */ - private $config; + protected $config; /** * @param ObjectManagerInterface $objectManager @@ -69,4 +68,4 @@ public function create(string $interface = UrlInterface::class) return $implementation; } -} \ No newline at end of file +} diff --git a/src/Model/Catalog/Layer/Url/UrlInterface.php b/src/Model/Catalog/Layer/Url/UrlInterface.php index 98a099ce..2a97e399 100644 --- a/src/Model/Catalog/Layer/Url/UrlInterface.php +++ b/src/Model/Catalog/Layer/Url/UrlInterface.php @@ -8,11 +8,8 @@ namespace Emico\Tweakwise\Model\Catalog\Layer\Url; -use Emico\Tweakwise\Model\Catalog\Layer\Filter; use Emico\Tweakwise\Model\Catalog\Layer\Filter\Item; -use Emico\Tweakwise\Model\Client\Request\ProductNavigationRequest; -use Magento\Framework\Api\SortOrder; -use Zend\Http\Request as HttpRequest; +use Magento\Framework\App\Request\Http as MagentoHttpRequest; /** * Interface UrlInterface implementation should handle both category url's and @@ -24,36 +21,43 @@ interface UrlInterface /** * Get url when selecting item * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getAttributeSelectUrl(HttpRequest $request, Item $item): string; + public function getAttributeSelectUrl(MagentoHttpRequest $request, Item $item): string; /** * Get url when removing item from selecting * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getAttributeRemoveUrl(HttpRequest $request, Item $item): string; + public function getAttributeRemoveUrl(MagentoHttpRequest $request, Item $item): string; /** - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item $item * @return string */ - public function getSliderUrl(HttpRequest $request, Item $item): string; + public function getSliderUrl(MagentoHttpRequest $request, Item $item): string; /** * Fetch clear all items from url * - * @param HttpRequest $request + * @param MagentoHttpRequest $request * @param Item[] $activeFilterItems * @return string */ - public function getClearUrl(HttpRequest $request, array $activeFilterItems): string; + public function getClearUrl(MagentoHttpRequest $request, array $activeFilterItems): string; + + /** + * @param MagentoHttpRequest $request + * @param array $filters + * @return string + */ + public function buildFilterUrl(MagentoHttpRequest $request, array $filters = []): string; /** * Determine if this UrlInterface is allowed in the current context @@ -61,4 +65,4 @@ public function getClearUrl(HttpRequest $request, array $activeFilterItems): str * @return boolean */ public function isAllowed(): bool; -} \ No newline at end of file +} diff --git a/src/Model/Catalog/Layer/Url/UrlModel.php b/src/Model/Catalog/Layer/Url/UrlModel.php index a34108e5..dccc5f40 100644 --- a/src/Model/Catalog/Layer/Url/UrlModel.php +++ b/src/Model/Catalog/Layer/Url/UrlModel.php @@ -10,16 +10,88 @@ use Emico\Tweakwise\Model\Config; use Emico\Tweakwise\Model\Config\Source\QueryFilterType; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\Route\ConfigInterface; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Session\Generic; +use Magento\Framework\Session\SidResolverInterface; use Magento\Framework\Url as MagentoUrl; +use Magento\Framework\Url\HostChecker; +use Magento\Framework\Url\QueryParamsResolverInterface; +use Magento\Framework\Url\RouteParamsPreprocessorInterface; +use Magento\Framework\Url\RouteParamsResolverFactory; +use Magento\Framework\Url\ScopeResolverInterface; +use Magento\Framework\Url\SecurityInfoInterface; class UrlModel extends MagentoUrl { /** - * @return Config + * @var Config */ - protected function getConfig(): Config - { - return $this->getData('tw_config'); + protected $config; + + /** + * @var array + */ + protected $tweakwiseSystemParams; + + /** + * @param ConfigInterface $routeConfig + * @param RequestInterface $request + * @param SecurityInfoInterface $urlSecurityInfo + * @param ScopeResolverInterface $scopeResolver + * @param Generic $session + * @param SidResolverInterface $sidResolver + * @param RouteParamsResolverFactory $routeParamsResolverFactory + * @param QueryParamsResolverInterface $queryParamsResolver + * @param ScopeConfigInterface $scopeConfig + * @param RouteParamsPreprocessorInterface $routeParamsPreprocessor + * @param string $scopeType + * @param Config $config + * @param array $tweakwiseSystemParams + * @param array $data + * @param HostChecker|null $hostChecker + * @param Json|null $serializer + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + ConfigInterface $routeConfig, + RequestInterface $request, + SecurityInfoInterface $urlSecurityInfo, + ScopeResolverInterface $scopeResolver, + Generic $session, + SidResolverInterface $sidResolver, + RouteParamsResolverFactory $routeParamsResolverFactory, + QueryParamsResolverInterface $queryParamsResolver, + ScopeConfigInterface $scopeConfig, + RouteParamsPreprocessorInterface $routeParamsPreprocessor, + $scopeType, + Config $config, + array $tweakwiseSystemParams = [], + array $data = [], + HostChecker $hostChecker = null, + Json $serializer = null + ) { + parent::__construct( + $routeConfig, + $request, + $urlSecurityInfo, + $scopeResolver, + $session, + $sidResolver, + $routeParamsResolverFactory, + $queryParamsResolver, + $scopeConfig, + $routeParamsPreprocessor, + $scopeType, + $data, + $hostChecker, + $serializer + ); + + $this->config = $config; + $this->tweakwiseSystemParams = $tweakwiseSystemParams; } /** @@ -30,10 +102,6 @@ protected function getConfig(): Config */ protected function _getQuery($escape = false) { - if ($this->getConfig()->getQueryFilterType() === QueryFilterType::TYPE_NONE) { - return parent::_getQuery($escape); - } - $newParams = []; foreach ($this->_queryParamsResolver->getQueryParams() as $param => $value) { if ($this->shouldFilter($param)) { @@ -51,21 +119,26 @@ protected function _getQuery($escape = false) * @param string $param * @return bool */ - private function shouldFilter($param) + protected function shouldFilter($param): bool { - $filterType = $this->getConfig()->getQueryFilterType(); + // First check for our system parameters, which need to be filtered regardless of settings. + if (in_array($param, $this->tweakwiseSystemParams, true)) { + return true; + } + + $filterType = $this->config->getQueryFilterType(); if ($filterType === QueryFilterType::TYPE_NONE) { return false; } if ($filterType === QueryFilterType::TYPE_REGEX) { - return (bool) preg_match('/' . $this->getConfig()->getQueryFilterRegex() . '/', $param); + return (bool) preg_match('/' . $this->config->getQueryFilterRegex() . '/', $param); } if ($filterType === QueryFilterType::TYPE_SPECIFIC) { - return \in_array($param, $this->getConfig()->getQueryFilterArguments(), true); + return \in_array($param, $this->config->getQueryFilterArguments(), true); } return true; } -} \ No newline at end of file +} diff --git a/src/Model/Catalog/Product/Recommendation/Collection.php b/src/Model/Catalog/Product/Recommendation/Collection.php index 210fef24..e6829567 100644 --- a/src/Model/Catalog/Product/Recommendation/Collection.php +++ b/src/Model/Catalog/Product/Recommendation/Collection.php @@ -36,7 +36,7 @@ class Collection extends AbstractCollection /** * @var RecommendationsResponse */ - private $response; + protected $response; /** * {@inheritdoc} @@ -97,4 +97,4 @@ protected function getProductIds() { return $this->response->getProductIds(); } -} \ No newline at end of file +} diff --git a/src/Model/Catalog/Product/Recommendation/Context.php b/src/Model/Catalog/Product/Recommendation/Context.php index d785c9e1..72c62b28 100644 --- a/src/Model/Catalog/Product/Recommendation/Context.php +++ b/src/Model/Catalog/Product/Recommendation/Context.php @@ -14,7 +14,6 @@ use Emico\Tweakwise\Model\Client\Response\RecommendationsResponse; use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\Config as CatalogConfig; -use Magento\Framework\Exception\LocalizedException; class Context { @@ -22,42 +21,42 @@ class Context /** * @var Client */ - private $client; + protected $client; /** * @var RequestFactory */ - private $requestFactory; + protected $requestFactory; /** * @var CollectionFactory */ - private $collectionFactory; + protected $collectionFactory; /** * @var CatalogConfig */ - private $catalogConfig; + protected $catalogConfig; /** * @var Visibility */ - private $visibility; + protected $visibility; /** * @var FeaturedRequest */ - private $request; + protected $request; /** * @var RecommendationsResponse */ - private $response; + protected $response; /** * @var Collection */ - private $collection; + protected $collection; /** * Context constructor. @@ -67,9 +66,13 @@ class Context * @param CatalogConfig $catalogConfig * @param Visibility $visibility */ - public function __construct(Client $client, RequestFactory $requestFactory, CollectionFactory $collectionFactory, - CatalogConfig $catalogConfig, Visibility $visibility) - { + public function __construct( + Client $client, + RequestFactory $requestFactory, + CollectionFactory $collectionFactory, + CatalogConfig $catalogConfig, + Visibility $visibility + ) { $this->client = $client; $this->requestFactory = $requestFactory; $this->collectionFactory = $collectionFactory; @@ -117,7 +120,7 @@ public function getCollection() /** * @param Collection $collection */ - private function prepareCollection(Collection $collection) + protected function prepareCollection(Collection $collection) { $collection->addMinimalPrice() ->addFinalPrice() diff --git a/src/Model/CatalogSearch/Controller/Result/Index/Plugin.php b/src/Model/CatalogSearch/Controller/Result/Index/Plugin.php index 65d8e9d4..899a3cf0 100644 --- a/src/Model/CatalogSearch/Controller/Result/Index/Plugin.php +++ b/src/Model/CatalogSearch/Controller/Result/Index/Plugin.php @@ -8,6 +8,7 @@ use Emico\Tweakwise\Model\Config; use Magento\CatalogSearch\Controller\Result\Index; +use Magento\Search\Model\Query; use Magento\Search\Model\QueryFactory; /** @@ -50,10 +51,10 @@ public function __construct(Config $config, QueryFactory $queryFactory) public function beforeExecute(Index $subject) { if ($this->config->isSearchEnabled()) { - /* @var $query \Magento\Search\Model\Query */ + /* @var Query $query */ $query = $this->queryFactory->get(); // Set redirect to '', so that it does not get executed $query->setRedirect(''); } } -} \ No newline at end of file +} diff --git a/src/Model/Client/Request.php b/src/Model/Client/Request.php index cf25c6b4..231aab89 100644 --- a/src/Model/Client/Request.php +++ b/src/Model/Client/Request.php @@ -196,6 +196,27 @@ public function addCategoryPathFilter(array $categoryIds) return $this; } + /** + * @return string|null + */ + public function getCategoryPathFilter() + { + if (!$categoryPath = $this->getParameter('tn_cid')) { + return null; + } + + if (!is_string($categoryPath)) { + return null; + } + + $magentoIdMapper = function (int $tweakwiseCategoryId) { + return $this->helper->getStoreId($tweakwiseCategoryId); + }; + + $categoryPath = array_map($magentoIdMapper, explode('-', $categoryPath)); + return implode('-', $categoryPath); + } + /** * @return StoreManagerInterface */ @@ -221,4 +242,4 @@ protected function getStoreId() return null; } -} \ No newline at end of file +} diff --git a/src/Model/Client/Request/Recommendations/ProductRequest.php b/src/Model/Client/Request/Recommendations/ProductRequest.php index 37b01d14..8aa33f01 100644 --- a/src/Model/Client/Request/Recommendations/ProductRequest.php +++ b/src/Model/Client/Request/Recommendations/ProductRequest.php @@ -9,9 +9,7 @@ namespace Emico\Tweakwise\Model\Client\Request\Recommendations; use Emico\Tweakwise\Exception\ApiException; -use Emico\TweakwiseExport\Model\Helper; use Magento\Catalog\Model\Product; -use Magento\Store\Model\StoreManager; class ProductRequest extends FeaturedRequest { @@ -20,14 +18,6 @@ class ProductRequest extends FeaturedRequest */ protected $product; - /** - * {@inheritDoc} - */ - public function __construct(Helper $helper, StoreManager $storeManager) - { - parent::__construct($helper, $storeManager); - } - /** * @return Product */ @@ -71,4 +61,4 @@ public function getPathSuffix() return '/' . $productTweakwiseId . parent::getPathSuffix(); } -} \ No newline at end of file +} diff --git a/src/Model/Client/ResponseFactory.php b/src/Model/Client/ResponseFactory.php index e54ea0c8..6bc9ea68 100644 --- a/src/Model/Client/ResponseFactory.php +++ b/src/Model/Client/ResponseFactory.php @@ -18,7 +18,7 @@ class ResponseFactory * * @var ObjectManagerInterface */ - protected $objectManager = null; + protected $objectManager; /** * Factory constructor @@ -47,4 +47,4 @@ public function create(Request $request, array $data) return $response; } -} \ No newline at end of file +} diff --git a/src/Model/Client/Type/FacetType/SettingsType.php b/src/Model/Client/Type/FacetType/SettingsType.php index ebc55187..1784f396 100644 --- a/src/Model/Client/Type/FacetType/SettingsType.php +++ b/src/Model/Client/Type/FacetType/SettingsType.php @@ -78,7 +78,7 @@ public function getIsNumberOfResultVisible() */ public function isPrice() { - return $this->getUrlKey() == 'price'; + return $this->getUrlKey() === 'price'; } /** @@ -201,4 +201,4 @@ public function getCssClass() } return null; } -} \ No newline at end of file +} diff --git a/src/Model/Client/Type/Type.php b/src/Model/Client/Type/Type.php index 429074eb..bd742489 100644 --- a/src/Model/Client/Type/Type.php +++ b/src/Model/Client/Type/Type.php @@ -166,7 +166,7 @@ public function __call($method, $args) } } - throw new BadMethodCallException(sprintf('Invalid method %1::%2', get_class($this), $method)); + throw new BadMethodCallException(sprintf('Invalid method %s::%s', get_class($this), $method)); } /** diff --git a/src/Model/Config.php b/src/Model/Config.php index 2eedcc41..4590cb49 100644 --- a/src/Model/Config.php +++ b/src/Model/Config.php @@ -20,18 +20,18 @@ class Config /** * Recommendation types */ - const RECOMMENDATION_TYPE_UPSELL = 'upsell'; - const RECOMMENDATION_TYPE_CROSSSELL = 'crosssell'; - const RECOMMENDATION_TYPE_FEATURED = 'featured'; + public const RECOMMENDATION_TYPE_UPSELL = 'upsell'; + public const RECOMMENDATION_TYPE_CROSSSELL = 'crosssell'; + public const RECOMMENDATION_TYPE_FEATURED = 'featured'; /** * Attribute names */ - const ATTRIBUTE_FEATURED_TEMPLATE = 'tweakwise_featured_template'; - const ATTRIBUTE_UPSELL_TEMPLATE = 'tweakwise_upsell_template'; - const ATTRIBUTE_UPSELL_GROUP_CODE = 'tweakwise_upsell_group_code'; - const ATTRIBUTE_CROSSSELL_TEMPLATE = 'tweakwise_crosssell_template'; - const ATTRIBUTE_CROSSSELL_GROUP_CODE = 'tweakwise_crosssell_group_code'; + public const ATTRIBUTE_FEATURED_TEMPLATE = 'tweakwise_featured_template'; + public const ATTRIBUTE_UPSELL_TEMPLATE = 'tweakwise_upsell_template'; + public const ATTRIBUTE_UPSELL_GROUP_CODE = 'tweakwise_upsell_group_code'; + public const ATTRIBUTE_CROSSSELL_TEMPLATE = 'tweakwise_crosssell_template'; + public const ATTRIBUTE_CROSSSELL_GROUP_CODE = 'tweakwise_crosssell_group_code'; /** * @var ScopeConfigInterface @@ -70,7 +70,7 @@ public function __construct(ScopeConfigInterface $config, Json $jsonSerializer) */ public function setTweakwiseExceptionThrown($thrown = true) { - $this->tweakwiseExceptionThrown = (bool) $thrown; + $this->tweakwiseExceptionThrown = (bool)$thrown; return $this; } @@ -80,7 +80,7 @@ public function setTweakwiseExceptionThrown($thrown = true) */ public function getGeneralServerUrl(Store $store = null) { - return (string) $this->getStoreConfig('tweakwise/general/server_url', $store); + return (string)$this->getStoreConfig('tweakwise/general/server_url', $store); } /** @@ -89,7 +89,7 @@ public function getGeneralServerUrl(Store $store = null) */ public function getGeneralAuthenticationKey(Store $store = null) { - return (string) $this->getStoreConfig('tweakwise/general/authentication_key', $store); + return (string)$this->getStoreConfig('tweakwise/general/authentication_key', $store); } /** @@ -98,7 +98,7 @@ public function getGeneralAuthenticationKey(Store $store = null) */ public function getTimeout(Store $store = null) { - return (int) $this->getStoreConfig('tweakwise/general/timeout', $store); + return (int)$this->getStoreConfig('tweakwise/general/timeout', $store); } /** @@ -111,7 +111,16 @@ public function isLayeredEnabled(Store $store = null) return false; } - return (bool) $this->getStoreConfig('tweakwise/layered/enabled', $store); + return (bool)$this->getStoreConfig('tweakwise/layered/enabled', $store); + } + + /** + * @param Store|null $store + * @return bool + */ + public function isAjaxFilters(Store $store = null) + { + return (bool)$this->getStoreConfig('tweakwise/layered/ajax_filters', $store); } /** @@ -120,7 +129,7 @@ public function isLayeredEnabled(Store $store = null) */ public function getCategoryAsLink(Store $store = null) { - return (bool) $this->getStoreConfig('tweakwise/layered/category_links', $store); + return (bool)$this->getStoreConfig('tweakwise/layered/category_links', $store); } /** @@ -129,7 +138,7 @@ public function getCategoryAsLink(Store $store = null) */ public function getHideSingleOptions(Store $store = null) { - return (bool) $this->getStoreConfig('tweakwise/layered/hide_single_option', $store); + return (bool)$this->getStoreConfig('tweakwise/layered/hide_single_option', $store); } /** @@ -138,16 +147,16 @@ public function getHideSingleOptions(Store $store = null) */ public function getUseDefaultLinkRenderer(Store $store = null) { - return (bool) $this->getStoreConfig('tweakwise/layered/default_link_renderer', $store); + return (bool)$this->getStoreConfig('tweakwise/layered/default_link_renderer', $store); } /** * @param Store|null $store * @return bool */ - public function getUseFormFilters(Store $store = null) + public function isFormFilters(Store $store = null) { - return (bool) $this->getStoreConfig('tweakwise/layered/form_filters', $store); + return (bool)$this->getStoreConfig('tweakwise/layered/form_filters', $store); } /** @@ -156,7 +165,7 @@ public function getUseFormFilters(Store $store = null) */ public function getQueryFilterType(Store $store = null) { - return (string) $this->getStoreConfig('tweakwise/layered/query_filter_type', $store); + return (string)$this->getStoreConfig('tweakwise/layered/query_filter_type', $store); } /** @@ -182,7 +191,7 @@ public function getQueryFilterArguments(Store $store = null) */ public function getQueryFilterRegex(Store $store = null) { - return (string) $this->getStoreConfig('tweakwise/layered/query_filter_regex', $store); + return (string)$this->getStoreConfig('tweakwise/layered/query_filter_regex', $store); } /** @@ -208,7 +217,7 @@ public function isAutocompleteEnabled(Store $store = null) return false; } - return (bool) $this->getStoreConfig('tweakwise/autocomplete/enabled', $store); + return (bool)$this->getStoreConfig('tweakwise/autocomplete/enabled', $store); } /** @@ -217,7 +226,7 @@ public function isAutocompleteEnabled(Store $store = null) */ public function isAutocompleteProductsEnabled(Store $store = null) { - return (bool) $this->getStoreConfig('tweakwise/autocomplete/show_products', $store); + return (bool)$this->getStoreConfig('tweakwise/autocomplete/show_products', $store); } /** @@ -226,7 +235,7 @@ public function isAutocompleteProductsEnabled(Store $store = null) */ public function isAutocompleteSuggestionsEnabled(Store $store = null) { - return (bool) $this->getStoreConfig('tweakwise/autocomplete/show_suggestions', $store); + return (bool)$this->getStoreConfig('tweakwise/autocomplete/show_suggestions', $store); } /** @@ -235,7 +244,7 @@ public function isAutocompleteSuggestionsEnabled(Store $store = null) */ public function getAutocompleteMaxResults(Store $store = null) { - return (int) $this->getStoreConfig('tweakwise/autocomplete/max_results', $store); + return (int)$this->getStoreConfig('tweakwise/autocomplete/max_results', $store); } /** @@ -244,7 +253,7 @@ public function getAutocompleteMaxResults(Store $store = null) */ public function isAutocompleteStayInCategory(Store $store = null) { - return (bool) $this->getStoreConfig('tweakwise/autocomplete/in_current_category', $store); + return (bool)$this->getStoreConfig('tweakwise/autocomplete/in_current_category', $store); } /** @@ -253,7 +262,7 @@ public function isAutocompleteStayInCategory(Store $store = null) */ public function isSearchEnabled(Store $store = null) { - return (int) $this->getStoreConfig('tweakwise/search/enabled', $store); + return (int)$this->getStoreConfig('tweakwise/search/enabled', $store); } /** @@ -262,7 +271,7 @@ public function isSearchEnabled(Store $store = null) */ public function getSearchTemplateId(Store $store = null) { - return (int) $this->getStoreConfig('tweakwise/search/template', $store); + return (int)$this->getStoreConfig('tweakwise/search/template', $store); } /** @@ -273,7 +282,7 @@ public function getSearchTemplateId(Store $store = null) public function isRecommendationsEnabled($type, Store $store = null) { $this->validateRecommendationType($type); - return (bool) $this->getStoreConfig(sprintf('tweakwise/recommendations/%s_enabled', $type), $store); + return (bool)$this->getStoreConfig(sprintf('tweakwise/recommendations/%s_enabled', $type), $store); } /** @@ -284,7 +293,7 @@ public function isRecommendationsEnabled($type, Store $store = null) public function getRecommendationsTemplate($type, Store $store = null) { $this->validateRecommendationType($type); - return (int) $this->getStoreConfig(sprintf('tweakwise/recommendations/%s_template', $type), $store); + return (int)$this->getStoreConfig(sprintf('tweakwise/recommendations/%s_template', $type), $store); } /** @@ -304,7 +313,7 @@ public function getRecommendationsGroupCode($type, Store $store = null) */ public function getRecommendationsFeaturedLocation(Store $store = null) { - return (string) $this->getStoreConfig('tweakwise/recommendations/featured_location', $store); + return (string)$this->getStoreConfig('tweakwise/recommendations/featured_location', $store); } /** @@ -313,7 +322,7 @@ public function getRecommendationsFeaturedLocation(Store $store = null) */ public function isSeoEnabled(Store $store = null) { - return (bool) $this->getStoreConfig('tweakwise/seo/enabled', $store); + return (bool)$this->getStoreConfig('tweakwise/seo/enabled', $store); } /** @@ -343,7 +352,7 @@ public function getSearchLanguage(Store $store = null) { return $this->getStoreConfig('tweakwise/search/language', $store); } - + /** * @param Store|null $store * @param string $path @@ -383,31 +392,6 @@ protected function validateRecommendationType($type) )); } - /** - * @param array $navigationOptions - * @return string - */ - public function getJsNavigationConfig(array $navigationOptions = []): string - { - return $this->jsonSerializer->serialize([ - 'tweakwiseNavigationFilter' => array_merge( - [ - 'formFilters' => $this->getUseFormFilters(), - 'seoEnabled' => $this->isSeoEnabled() - ], - $navigationOptions - ) - ]); - } - - /** - * @return Config - */ - public function getJsUseFormFilters() - { - return $this->jsonSerializer->serialize($this->getUseFormFilters()); - } - /** * @return string|null */ diff --git a/src/Model/Config/Source/FeaturedLocation.php b/src/Model/Config/Source/FeaturedLocation.php index 575ad468..392dae80 100644 --- a/src/Model/Config/Source/FeaturedLocation.php +++ b/src/Model/Config/Source/FeaturedLocation.php @@ -15,8 +15,8 @@ class FeaturedLocation implements OptionSourceInterface /** * Possible product locations */ - const LOCATION_BEFORE = 'before'; - const LOCATION_AFTER = 'after'; + public const LOCATION_BEFORE = 'before'; + public const LOCATION_AFTER = 'after'; /** * @var array[] @@ -44,4 +44,4 @@ public function toOptionArray() } return $this->options; } -} \ No newline at end of file +} diff --git a/src/Model/Config/Source/QueryFilterType.php b/src/Model/Config/Source/QueryFilterType.php index 5628610c..199b3376 100644 --- a/src/Model/Config/Source/QueryFilterType.php +++ b/src/Model/Config/Source/QueryFilterType.php @@ -15,9 +15,9 @@ class QueryFilterType implements OptionSourceInterface /** * Possible filter types */ - const TYPE_SPECIFIC = 'specific'; - const TYPE_REGEX = 'regex'; - const TYPE_NONE = 'none'; + public const TYPE_SPECIFIC = 'specific'; + public const TYPE_REGEX = 'regex'; + public const TYPE_NONE = 'none'; /** * @var array[] @@ -46,4 +46,4 @@ public function toOptionArray() } return $this->options; } -} \ No newline at end of file +} diff --git a/src/Model/Config/Source/RecommendationOption.php b/src/Model/Config/Source/RecommendationOption.php index 4099f09c..71379fdb 100644 --- a/src/Model/Config/Source/RecommendationOption.php +++ b/src/Model/Config/Source/RecommendationOption.php @@ -19,8 +19,8 @@ class RecommendationOption extends AbstractSource /** * Option to use when expecting a code instead of template id */ - const OPTION_CODE = -1; - const OPTION_EMPTY = null; + public const OPTION_CODE = -1; + public const OPTION_EMPTY = null; /** * @var Client @@ -118,4 +118,4 @@ public function getAllOptions() return $this->options; } -} \ No newline at end of file +} diff --git a/src/Model/Config/Source/UrlStrategy.php b/src/Model/Config/Source/UrlStrategy.php index bc122821..d6783e1b 100644 --- a/src/Model/Config/Source/UrlStrategy.php +++ b/src/Model/Config/Source/UrlStrategy.php @@ -17,8 +17,8 @@ class UrlStrategy implements OptionSourceInterface /** * Possible filter types */ - const STRATEGY_QUERY_PARAM = QueryParameterStrategy::class; - const STRATEGY_PATH_SLUGS = PathSlugStrategy::class; + public const STRATEGY_QUERY_PARAM = QueryParameterStrategy::class; + public const STRATEGY_PATH_SLUGS = PathSlugStrategy::class; /** * @var array[] @@ -46,4 +46,4 @@ public function toOptionArray() } return $this->options; } -} \ No newline at end of file +} diff --git a/src/Model/Config/TemplateFinder.php b/src/Model/Config/TemplateFinder.php index 452ff138..3e1b7193 100644 --- a/src/Model/Config/TemplateFinder.php +++ b/src/Model/Config/TemplateFinder.php @@ -16,7 +16,7 @@ class TemplateFinder /** * @var Config */ - private $config; + protected $config; /** * TemplateFinder constructor. @@ -97,7 +97,7 @@ public function forCategory(Category $category, $type) * @param string $type * @return string */ - private function getAttribute($type) + protected function getAttribute($type) { return sprintf('tweakwise_%s_template', $type); } @@ -106,8 +106,8 @@ private function getAttribute($type) * @param string $type * @return string */ - private function getGroupCodeAttribute($type) + protected function getGroupCodeAttribute($type) { return sprintf('tweakwise_%s_group_code', $type); } -} \ No newline at end of file +} diff --git a/src/Model/FilterFormInputProvider/CategoryInputProvider.php b/src/Model/FilterFormInputProvider/CategoryInputProvider.php new file mode 100644 index 00000000..ff82c359 --- /dev/null +++ b/src/Model/FilterFormInputProvider/CategoryInputProvider.php @@ -0,0 +1,144 @@ +url = $url; + $this->registry = $registry; + $this->config = $config; + $this->storeManager = $storeManager; + $this->categoryRepository = $categoryRepository; + $this->categoryFactory = $categoryFactory; + $this->toolbarInputProvider = $toolbarInputProvider; + } + + /** + * @inheritDoc + */ + public function getFilterFormInput(): array + { + if (!$this->config->isAjaxFilters()) { + return []; + } + + $input = [ + '__tw_ajax_type' => self::TYPE, + '__tw_original_url' => $this->getOriginalUrl(), + '__tw_object_id' => $this->getCategoryId() + ]; + + return array_merge($input, $this->toolbarInputProvider->getFilterFormInput()); + } + + /** + * Public because of plugin options + * + * @return string + */ + public function getOriginalUrl() + { + return str_replace($this->url->getBaseUrl(), '', $this->getCategory()->getUrl()); + } + + /** + * @return int|null + */ + public function getCategoryId() + { + return (int)$this->getCategory()->getId() ?: null; + } + + /** + * @return CategoryInterface|Category + */ + protected function getCategory() + { + if ($currentCategory = $this->registry->registry('current_category')) { + return $currentCategory; + } + + try { + $rootCategory = $this->storeManager->getStore()->getRootCategoryId(); + } catch (NoSuchEntityException $exception) { + $rootCategory = 2; + } + + try { + return $this->categoryRepository->get($rootCategory); + } catch (NoSuchEntityException $exception) { + return $this->categoryFactory->create(); + } + } +} diff --git a/src/Model/FilterFormInputProvider/EmptyInputProvider.php b/src/Model/FilterFormInputProvider/EmptyInputProvider.php new file mode 100644 index 00000000..1db4e419 --- /dev/null +++ b/src/Model/FilterFormInputProvider/EmptyInputProvider.php @@ -0,0 +1,19 @@ + 'value' + * name will be rendered as name attribute in an tag and obviously value will be its value attribute + * + * @return string[] + */ + public function getFilterFormInput(); +} diff --git a/src/Model/FilterFormInputProvider/SearchInputProvider.php b/src/Model/FilterFormInputProvider/SearchInputProvider.php new file mode 100644 index 00000000..7eeb2f85 --- /dev/null +++ b/src/Model/FilterFormInputProvider/SearchInputProvider.php @@ -0,0 +1,99 @@ +config = $config; + $this->currentNavigationContext = $currentNavigationContext; + $this->toolbarInputProvider = $toolbarInputProvider; + } + + /** + * @inheritDoc + */ + public function getFilterFormInput(): array + { + $parameters = [ + 'q' => $this->getSearchTerm() + ]; + + if ($categoryFilter = $this->getCategoryFilter()) { + $parameters[FilterHelper::TWEAKWISE_CATEGORY_FILTER_NAME] = $categoryFilter; + } + + if (!$this->config->isAjaxFilters()) { + return $parameters; + } + + return array_merge( + $parameters, + [ + '__tw_ajax_type' => self::TYPE, + '__tw_original_url' => 'catalogsearch/result/index', + ], + $this->toolbarInputProvider->getFilterFormInput() + ); + } + + /** + * @return string|null + */ + protected function getSearchTerm() + { + return $this->currentNavigationContext + ->getRequest() + ->getParameter('tn_q'); + } + + /** + * @return string|null + */ + protected function getCategoryFilter() + { + return $this->currentNavigationContext + ->getRequest() + ->getCategoryPathFilter(); + } +} diff --git a/src/Model/FilterFormInputProvider/ToolbarInputProvider.php b/src/Model/FilterFormInputProvider/ToolbarInputProvider.php new file mode 100644 index 00000000..dc82b331 --- /dev/null +++ b/src/Model/FilterFormInputProvider/ToolbarInputProvider.php @@ -0,0 +1,55 @@ +request = $request; + } + + /** + * @inheritDoc + */ + public function getFilterFormInput() + { + $input = []; + foreach (self::TOOLBAR_INPUTS as $toolbarInput) { + if ($toolbarInputValue = $this->request->getParam($toolbarInput)) { + $input[$toolbarInput] = $toolbarInputValue; + } + } + + return $input; + } +} diff --git a/src/Model/NavigationConfig.php b/src/Model/NavigationConfig.php new file mode 100644 index 00000000..b22a0fae --- /dev/null +++ b/src/Model/NavigationConfig.php @@ -0,0 +1,189 @@ +config = $config; + $this->jsonSerializer = $jsonSerializer; + $this->url = $url; + $this->currentNavigationContext = $currentNavigationContext; + $this->productMetadata = $productMetadata; + $this->filterFormInputProvider = $filterFormInputProvider; + } + + /** + * @inheritDoc + */ + public function getFilterFormInput(): array + { + return $this->filterFormInputProvider->getFilterFormInput(); + } + + /** + * @return string + */ + public function getJsFormConfig() + { + return $this->jsonSerializer->serialize( + [ + 'tweakwiseNavigationForm' => [ + 'formFilters' => $this->isFormFilters(), + 'ajaxFilters' => $this->isAjaxFilters(), + 'seoEnabled' => $this->config->isSeoEnabled(), + 'ajaxEndpoint' => $this->getAjaxEndPoint(), + 'filterSelector' => '#layered-filter-block', + 'productListSelector' => '.products.wrapper', + 'toolbarSelector' => '.toolbar.toolbar-products' + ], + ] + ); + } + + /** + * @param SliderRenderer $sliderRenderer + * @return string + */ + public function getJsSliderConfig(SliderRenderer $sliderRenderer) + { + $slider = $this->getSliderReference(); + return $this->jsonSerializer->serialize( + [ + $slider => [ + 'ajaxFilters' => $this->isAjaxFilters(), + 'formFilters' => $this->isFormFilters(), + 'filterUrl' => $sliderRenderer->getFilterUrl(), + 'prefix' => "{$sliderRenderer->getItemPrefix()}", + 'postfix' => "{$sliderRenderer->getItemPostfix()}", + 'container' => "#attribute-slider-{$sliderRenderer->getCssId()}", + 'min' => $sliderRenderer->getMinValue(), + 'max' => $sliderRenderer->getMaxValue(), + 'currentMin' => $sliderRenderer->getCurrentMinValue(), + 'currentMax' => $sliderRenderer->getCurrentMaxValue(), + ] + ] + ); + } + + /** + * Return which slider to use, the compat version has the full jquery/ui reference. + * The normal slider definition has jquery-ui-modules/slider, which is only available from 2.3.3 and onwards + * + * @return string + */ + protected function getSliderReference() + { + $mVersion = $this->productMetadata->getVersion(); + if (version_compare($mVersion, '2.3.3', '<')) { + return 'tweakwiseNavigationSliderCompat'; + } + + return 'tweakwiseNavigationSlider'; + } + + /** + * @param bool $hasAlternateSort + */ + public function getJsSortConfig($hasAlternateSort = null) + { + return $this->jsonSerializer->serialize( + [ + 'tweakwiseNavigationSort' => [ + 'hasAlternateSort' => (bool)$hasAlternateSort + ] + ] + ); + } + + /** + * @return bool + */ + public function isFormFilters() + { + return $this->config->isFormFilters(); + } + + /** + * @return bool + */ + public function isAjaxFilters() + { + return $this->config->isAjaxFilters(); + } + + /** + * @return string + */ + protected function getAjaxEndPoint() + { + return $this->url->getUrl('tweakwise/ajax/navigation'); + } +} diff --git a/src/Model/Observer/CatalogLastPageRedirect.php b/src/Model/Observer/CatalogLastPageRedirect.php index adbf708e..6294a088 100644 --- a/src/Model/Observer/CatalogLastPageRedirect.php +++ b/src/Model/Observer/CatalogLastPageRedirect.php @@ -30,7 +30,7 @@ class CatalogLastPageRedirect implements ObserverInterface /** * @var Context */ - private $actionContext; + protected $actionContext; /** * CatalogSearchRedirect constructor. diff --git a/src/Model/Observer/CatalogSearchRedirect.php b/src/Model/Observer/CatalogSearchRedirect.php index 5b4884f0..297e2b54 100644 --- a/src/Model/Observer/CatalogSearchRedirect.php +++ b/src/Model/Observer/CatalogSearchRedirect.php @@ -32,7 +32,7 @@ class CatalogSearchRedirect implements ObserverInterface /** * @var Context */ - private $actionContext; + protected $actionContext; /** * CatalogSearchRedirect constructor. diff --git a/src/Model/Observer/NavigationHtmlOverride.php b/src/Model/Observer/NavigationHtmlOverride.php index d76acd8f..7bb32599 100644 --- a/src/Model/Observer/NavigationHtmlOverride.php +++ b/src/Model/Observer/NavigationHtmlOverride.php @@ -8,26 +8,45 @@ namespace Emico\Tweakwise\Model\Observer; +use Emico\Tweakwise\Model\Catalog\Layer\NavigationContext\CurrentContext; +use Emico\Tweakwise\Model\Client\Request\ProductSearchRequest; use Emico\Tweakwise\Model\Config; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\LayeredNavigation\Block\Navigation; +/** + * Class NavigationHtmlOverride + * @package Emico\Tweakwise\Model\Observer + * + * Change template of the navigation block. + * Changing the template depends on configuration so this could not be done in layout, also since the original definition + * of the block is a virtualType it could not be done in a plugin, hence the observer. + */ class NavigationHtmlOverride implements ObserverInterface { /** * @var Config */ - private $config; + protected $config; + + /** + * @var CurrentContext + */ + protected $currentContext; /** * NavigationHtmlOverride constructor. * * @param Config $config + * @param CurrentContext $currentContext */ - public function __construct(Config $config) - { + public function __construct( + Config $config, + CurrentContext $currentContext + ) { $this->config = $config; + $this->currentContext = $currentContext; } /** @@ -40,14 +59,22 @@ public function execute(Observer $observer) return; } - if (!$this->config->isLayeredEnabled()) { + if ($this->config->getUseDefaultLinkRenderer()) { return; } - if ($this->config->getUseDefaultLinkRenderer()) { - return; + $searchEnabled = $this->config->isSearchEnabled(); + $navigationEnabled = $this->config->isLayeredEnabled(); + + $isSearch = $this->currentContext->getRequest() instanceof ProductSearchRequest; + $isNavigation = !$isSearch; + + if ($isSearch && $searchEnabled) { + $block->setTemplate('Emico_Tweakwise::layer/view.phtml'); } - $block->setTemplate('Emico_Tweakwise::product/navigation/view.phtml'); + if ($isNavigation && $navigationEnabled) { + $block->setTemplate('Emico_Tweakwise::layer/view.phtml'); + } } -} \ No newline at end of file +} diff --git a/src/Model/ResourceModel/AttributeSlug.php b/src/Model/ResourceModel/AttributeSlug.php index ad2c3237..9671249a 100644 --- a/src/Model/ResourceModel/AttributeSlug.php +++ b/src/Model/ResourceModel/AttributeSlug.php @@ -13,11 +13,14 @@ class AttributeSlug extends AbstractDb { - + /** + * Resource initialization + * + * @return void + */ protected function _construct() { $this->_init('tweakwise_attribute_slug', AttributeSlugInterface::ATTRIBUTE); $this->_isPkAutoIncrement = false; } - -} \ No newline at end of file +} diff --git a/src/Model/Seo/FilterHelper.php b/src/Model/Seo/FilterHelper.php index 09f20301..b39a19d4 100644 --- a/src/Model/Seo/FilterHelper.php +++ b/src/Model/Seo/FilterHelper.php @@ -18,7 +18,7 @@ class FilterHelper /** * */ - const TWEAKWISE_CATEGORY_FILTER_NAME = 'categorie'; + public const TWEAKWISE_CATEGORY_FILTER_NAME = 'categorie'; /** * @var Resolver diff --git a/src/Model/Seo/Robots/Plugin.php b/src/Model/Seo/Robots/Plugin.php index 7e41a4f9..d7b9224e 100644 --- a/src/Model/Seo/Robots/Plugin.php +++ b/src/Model/Seo/Robots/Plugin.php @@ -15,12 +15,12 @@ class Plugin /** * @var FilterHelper */ - private $filterHelper; + protected $filterHelper; /** * @var TweakwiseConfig */ - private $tweakwiseConfig; + protected $tweakwiseConfig; /** * Plugin constructor. @@ -57,7 +57,7 @@ public function afterGetRobots(PageConfig $config, $result) /** * @param string $result */ - private function isAlreadyNoIndex(string $result): bool + protected function isAlreadyNoIndex(string $result): bool { return stripos(strtolower($result), 'noindex') !== false; } @@ -65,7 +65,7 @@ private function isAlreadyNoIndex(string $result): bool /** * @param string $oldRobots */ - private function getNewRobots(string $oldRobots): string + protected function getNewRobots(string $oldRobots): string { $follow = explode(',', $oldRobots); $follow = end($follow); @@ -75,8 +75,8 @@ private function getNewRobots(string $oldRobots): string /** * @return bool */ - private function shouldApplyNoindex(): bool + protected function shouldApplyNoindex(): bool { return !$this->filterHelper->shouldPageBeIndexable(); } -} \ No newline at end of file +} diff --git a/src/Model/Swatches/Plugin.php b/src/Model/Swatches/Plugin.php index 261c013c..1165ecad 100644 --- a/src/Model/Swatches/Plugin.php +++ b/src/Model/Swatches/Plugin.php @@ -6,18 +6,24 @@ namespace Emico\Tweakwise\Model\Swatches; -use \Magento\Catalog\Api\Data\ProductInterface as Product; +use Magento\Catalog\Api\Data\ProductInterface as Product; +use Magento\Swatches\Helper\Data; class Plugin { /** + * @param Data $subject + * @param callable $proceed * @param Product $parentProduct * @param array $attributes - * @param callable $proceed * @return */ - public function aroundLoadVariationByFallback($subject, callable $proceed, Product $parentProduct, array $attributes) - { + public function aroundLoadVariationByFallback( + Data $subject, + callable $proceed, + Product $parentProduct, + array $attributes + ) { $hasArrayValues = false; foreach ($attributes as $attribute) { if (\is_array($attribute)) { @@ -40,4 +46,4 @@ public function aroundLoadVariationByFallback($subject, callable $proceed, Produ return $proceed($parentProduct, $newAttributes); } -} \ No newline at end of file +} diff --git a/src/etc/adminhtml/system.xml b/src/etc/adminhtml/system.xml index 64547d75..cebf99b5 100644 --- a/src/etc/adminhtml/system.xml +++ b/src/etc/adminhtml/system.xml @@ -54,13 +54,22 @@ - When filter type is link, checkbox or color (swatches) the default Magento renderer can be used. Filter display settings like: collapsed / collapsible, more / less text, css class and show product count are then ignored. + use this only when you are not planning to use ajax filtering, filter forms or seo options. When filter type is link, checkbox or color (swatches) the default Magento renderer can be used. Filter display settings like: collapsed / collapsible, more / less text, css class and show product count are then ignored. Magento\Config\Model\Config\Source\Yesno 1 - + + + When enabled non category filter results will be fetched via ajax instead of navigation + Magento\Config\Model\Config\Source\Yesno + + 1 + 0 + + + Renders filter links in a form and renders a button with the filters. When this button is clicked all selected filters are applied Magento\Config\Model\Config\Source\Yesno @@ -68,7 +77,7 @@ 0 - + Url's rendered by tweakwise for filter contain all the arguments in the request url. These query arguments can be filtered out with this setting. Emico\Tweakwise\Model\Config\Source\QueryFilterType @@ -76,7 +85,7 @@ 1 - + Specific query arguments to filter out. One argument per line @@ -84,7 +93,7 @@ specific - + Arguments matching regex will be filtered @@ -92,7 +101,7 @@ regex - + Specify how you want the filter URL's to be build Emico\Tweakwise\Model\Config\Source\UrlStrategy diff --git a/src/etc/di.xml b/src/etc/di.xml index 86cb4eee..ef14680a 100644 --- a/src/etc/di.xml +++ b/src/etc/di.xml @@ -9,6 +9,18 @@ --> + + + + + + + + + + + + @@ -18,33 +30,27 @@ Emico\Tweakwise\Model\Catalog\Layer\FilterList\Tweakwise\Proxy - - - + - - - - - - + + @@ -61,6 +67,16 @@ + + + + + Emico\Tweakwise\Model\AjaxResultInitializer\CategoryInitializer + Emico\Tweakwise\Model\AjaxResultInitializer\SearchInitializer + + + + @@ -68,9 +84,9 @@ + - Magento\Search\Model\QueryFactory Emico\Tweakwise\Model\Client\RequestFactory\AutocompleteRequest @@ -84,32 +100,44 @@ - Emico\Tweakwise\Model\Config Emico\Tweakwise\Model\Catalog\Layer\NavigationContext\Search - Magento\Framework\App\Action\Context - Emico\Tweakwise\Model\Config Emico\Tweakwise\Model\Catalog\Layer\NavigationContext\Search - Magento\Framework\App\Action\Context - Emico\Tweakwise\Model\Config Emico\Tweakwise\Model\Catalog\Layer\NavigationContext\Category - Magento\Framework\App\Action\Context - - Emico\Tweakwise\Model\Config + + + __tw_object_id + __tw_original_url + __tw_ajax_type + + + + + + + + category + + + .jpg + .jpeg + .png + .webp + .gif @@ -135,44 +163,31 @@ Magento\Catalog\Model\Layer\Search\FilterableAttributeList - - - Emico\Tweakwise\Model\Client\RequestFactory\ProductSearchRequest - Emico\Tweakwise\Model\Catalog\Layer\FilterList\EmptyAttributeList - - - Emico\Tweakwise\Model\Client Emico\Tweakwise\Model\Client\RequestFactory\Recommendations\ProductRequest - Emico\Tweakwise\Model\Catalog\Product\Recommendation\CollectionFactory - Magento\Catalog\Model\Config - Magento\Catalog\Model\Product\Visibility - Emico\Tweakwise\Model\Client Emico\Tweakwise\Model\Client\RequestFactory\Recommendations\ProductRequest - Emico\Tweakwise\Model\Catalog\Product\Recommendation\CollectionFactory - Magento\Catalog\Model\Config - Magento\Catalog\Model\Product\Visibility - Emico\Tweakwise\Model\Client Emico\Tweakwise\Model\Client\RequestFactory\Recommendations\FeaturedRequest - Emico\Tweakwise\Model\Catalog\Product\Recommendation\CollectionFactory - Magento\Catalog\Model\Config - Magento\Catalog\Model\Product\Visibility - - - - - + + + Emico\Tweakwise\Model\FilterFormInputProvider\CategoryInputProvider + + + + + Emico\Tweakwise\Model\FilterFormInputProvider\SearchInputProvider + + @@ -183,21 +198,18 @@ Emico\Tweakwise\Model\Client\RequestFactory\Catalog\TemplateRequest - Emico\Tweakwise\Model\Client Emico\Tweakwise\Model\Client\RequestFactory\Catalog\SortTemplateRequest - Emico\Tweakwise\Model\Client Emico\Tweakwise\Model\Client\RequestFactory\Catalog\LanguageRequest - Emico\Tweakwise\Model\Client @@ -215,27 +227,16 @@ - Magento\Catalog\Block\Product\Context - Magento\Framework\Data\Helper\PostHelper - Magento\Catalog\Model\Layer\Resolver - Emico\Tweakwise\Model\Config\TemplateFinder - Magento\Catalog\Api\CategoryRepositoryInterface - Magento\Framework\Url\Helper\Data Emico\Tweakwise\Model\Catalog\Product\Recommendation\Context\Featured - Emico\Tweakwise\Model\Config - Emico\Tweakwise\Model\Config - Magento\Framework\Registry Emico\Tweakwise\Model\Catalog\Product\Recommendation\Context\Product\Upsell - Emico\Tweakwise\Model\Config - Magento\Framework\Registry Emico\Tweakwise\Model\Catalog\Product\Recommendation\Context\Product\Related @@ -243,14 +244,12 @@ - Emico\Tweakwise\Model\Catalog\Product\CollectionFactory Magento\CatalogSearch\Model\Layer\Category\ItemCollectionProvider Emico\Tweakwise\Model\Catalog\Layer\NavigationContext\Category - Emico\Tweakwise\Model\Catalog\Product\CollectionFactory Magento\CatalogSearch\Model\Layer\Search\ItemCollectionProvider Emico\Tweakwise\Model\Catalog\Layer\NavigationContext\Search @@ -357,7 +356,4 @@ - - - diff --git a/src/etc/frontend/routes.xml b/src/etc/frontend/routes.xml new file mode 100644 index 00000000..c9c0b927 --- /dev/null +++ b/src/etc/frontend/routes.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/view/frontend/layout/catalog_category_view_type_layered.xml b/src/view/frontend/layout/catalog_category_view_type_layered.xml new file mode 100644 index 00000000..308456ca --- /dev/null +++ b/src/view/frontend/layout/catalog_category_view_type_layered.xml @@ -0,0 +1,16 @@ + + + + + + + Emico\Tweakwise\Model\NavigationConfig\Category + + + + diff --git a/src/view/frontend/layout/catalogsearch_result_index.xml b/src/view/frontend/layout/catalogsearch_result_index.xml new file mode 100644 index 00000000..6e102c6b --- /dev/null +++ b/src/view/frontend/layout/catalogsearch_result_index.xml @@ -0,0 +1,16 @@ + + + + + + + Emico\Tweakwise\Model\NavigationConfig\Search + + + + diff --git a/src/view/frontend/layout/tweakwise_ajax_category.xml b/src/view/frontend/layout/tweakwise_ajax_category.xml new file mode 100644 index 00000000..e6f51485 --- /dev/null +++ b/src/view/frontend/layout/tweakwise_ajax_category.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/src/view/frontend/layout/tweakwise_ajax_search.xml b/src/view/frontend/layout/tweakwise_ajax_search.xml new file mode 100644 index 00000000..2c30ed16 --- /dev/null +++ b/src/view/frontend/layout/tweakwise_ajax_search.xml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/src/view/frontend/requirejs-config.js b/src/view/frontend/requirejs-config.js index 50d56378..0465e20c 100644 --- a/src/view/frontend/requirejs-config.js +++ b/src/view/frontend/requirejs-config.js @@ -2,8 +2,12 @@ var config = { map: { '*': { quickSearch: 'Emico_Tweakwise/js/quick-search', - tweakwiseNavigationFilter: 'Emico_Tweakwise/js/navigation-filter', - tweakwiseNavigationForm: 'Emico_Tweakwise/js/navigation-form' + tweakwiseNavigationForm: 'Emico_Tweakwise/js/navigation-form', + tweakwiseNavigationSort: 'Emico_Tweakwise/js/navigation-sort', + tweakwiseNavigationSlider: 'Emico_Tweakwise/js/navigation-slider', + tweakwiseNavigationSliderCompat: 'Emico_Tweakwise/js/navigation-slider-compat', + productListToolbarForm: 'Emico_Tweakwise/js/toolbar', + jQueryTouchPunch: 'Emico_Tweakwise/js/lib/jquery.ui.touch-punch.min' } } }; diff --git a/src/view/frontend/templates/layer/state.phtml b/src/view/frontend/templates/layer/state.phtml new file mode 100644 index 00000000..e3901c63 --- /dev/null +++ b/src/view/frontend/templates/layer/state.phtml @@ -0,0 +1,38 @@ + + + +getActiveFilters() ?> + +
+ escapeHtml(__('Now Shopping by')) ?> +
    + +
  1. + escapeHtml(__($_filter->getName())) ?> + escapeHtml($block->stripTags($_filter->getLabel())) ?> + escapeHtmlAttr(__($_filter->getName()) . " " . $block->stripTags($_filter->getLabel())); ?> + "> + escapeHtml(__('Remove This Item')) ?> + +
  2. + +
+
+ diff --git a/src/view/frontend/templates/product/navigation/view.phtml b/src/view/frontend/templates/layer/view.phtml similarity index 69% rename from src/view/frontend/templates/product/navigation/view.phtml rename to src/view/frontend/templates/layer/view.phtml index a13f7579..783d5549 100644 --- a/src/view/frontend/templates/product/navigation/view.phtml +++ b/src/view/frontend/templates/layer/view.phtml @@ -7,16 +7,22 @@ */ use Emico\Tweakwise\Model\Catalog\Layer\Filter; +use Emico\Tweakwise\Model\NavigationConfig; +use Magento\LayeredNavigation\Block\Navigation; -/** @var $block \Magento\LayeredNavigation\Block\Navigation */ +/** @var $block Navigation */ if (!$block->canShowBlock()) return; ?> getFilters(); /** @var $filters Filter[] */ ?> -getData('form_filters'); ?> + +getData('tweakwise_navigation_config');?> +getJsFormConfig(); ?> +isFormFilters();?> + 0; ?> -
getLayer()->getState()->getFilters()) ?> @@ -24,7 +30,8 @@ if (!$block->canShowBlock()) return;
- getChildHtml('state')?> +
> + getChildHtml('state');?> getLayer()->getState()->getFilters()): ?>
@@ -32,27 +39,29 @@ if (!$block->canShowBlock()) return;
- +
- - - + getFilterFormInput() as $name => $value):?> + + + isCollapsible()): ?> isDefaultCollapsed() ? 'false' : '[0]'; ?> -
- isDefaultCollapsed() ? ' style="display: none;"' : ''?> +
getName())?> - getTooltip(); ?> - I + i
+ isDefaultCollapsed() ? ' style="display: none;"' : ''?>
> getChildBlock('renderer')->render($filter) ?>
@@ -66,16 +75,14 @@ if (!$block->canShowBlock()) return;
- + - - -
+
-
\ No newline at end of file + diff --git a/src/view/frontend/templates/product/layered/default.phtml b/src/view/frontend/templates/product/layered/default.phtml index b54a247e..81815b40 100644 --- a/src/view/frontend/templates/product/layered/default.phtml +++ b/src/view/frontend/templates/product/layered/default.phtml @@ -11,7 +11,7 @@ use Emico\Tweakwise\Block\LayeredNavigation\RenderLayered\DefaultRenderer; $hasHiddenItems = $block->hasHiddenItems(); $hasAlternateSortOrder = $block->hasAlternateSortOrder(); ?> -
+
    getItems() as $index => $item): ?>
  1. hasAlternateSortOrder(); > renderAnchorHtmlTagAttributes($item);?>> + getCssId();?> showCheckbox()): ?> - isSelected() ? 'checked="checked"' : '')?> value="escapeHtmlAttr($item->getLabel())?>" > -
    +
    - getItemPrefix()?> - renderValue($block->getMinValue())?> - getItemPostfix()?> + + renderValue($minValue)?> + - getItemPrefix()?> - renderValue($block->getMaxValue())?> - getItemPostfix()?> + + renderValue($maxValue)?> +
    -
    +
    - getItemPrefix()?> - renderValue($block->getCurrentMinValue())?> - getItemPostfix()?> + + renderValue($currentMinValue)?> + - getItemPrefix()?> - renderValue($block->getCurrentMaxValue())?> - getItemPostfix()?> + + renderValue($currentMaxValue)?> +
    +
    +
    + + +
    +
    + + +
    + + disabled="disabled" + + id="getCssId()?>" + class="slider-url-value" + name="" + value="" + > +
    - - diff --git a/src/view/frontend/templates/product/layered/swatch.phtml b/src/view/frontend/templates/product/layered/swatch.phtml index 8a0f8cc7..b6646a59 100644 --- a/src/view/frontend/templates/product/layered/swatch.phtml +++ b/src/view/frontend/templates/product/layered/swatch.phtml @@ -12,22 +12,21 @@
    diff --git a/src/view/frontend/web/css/style.less b/src/view/frontend/web/css/style.less index e3a21c9e..4eddd25a 100644 --- a/src/view/frontend/web/css/style.less +++ b/src/view/frontend/web/css/style.less @@ -3,6 +3,42 @@ .current-max-value { float: right; } + + .ui-slider { + width: ~"calc(100% - 20px)"; + margin-left: 4px; + + &-handle { + &:hover { + background: #e67424; + } + } + } + + .slider-inputs { + position: relative; + margin-top: 5px; + overflow: hidden; + + .slider-min-wrapper, + .slider-max-wrapper { + width: 60px; + max-width: 48%; + + label { + font-size: 11px; + font-style: italic; + } + } + + .slider-min-wrapper { + float: left; + } + + .slider-max-wrapper { + float: right; + } + } } .tree-attribute .child-items { @@ -51,6 +87,8 @@ .more-items, .less-items { cursor: pointer; + color: #006bb4; + text-decoration: underline; } .filter-options-title { @@ -61,6 +99,19 @@ content: ''; } + .items .item { + input[type="checkbox"] { + margin-top: 0; + top: 1px; + } + + > a { + label { + cursor: pointer; + } + } + } + .swatch-attribute-options { input[type='checkbox']:checked { + label .swatch-option { @@ -75,21 +126,37 @@ position: relative; cursor: pointer; + > span { + display: block; + width: 18px; + height: 18px; + background: #006bb4; + color: white; + font-family: serif; + line-height: 19px; + text-align: center; + border-radius: 50%; + text-transform: lowercase; + font-style: italic; + } + &:before { content: attr(data-tooltip); box-sizing: border-box; display: block; background: rgba(0, 0, 0, 0.7); color: white; - padding: 3px; + padding: 3px 10px; position: absolute; left: 50%; bottom: 100%; - margin-left: -100px; width: 200px; + text-align: center; + transform: translate(-50%, 0); opacity: 0; transition: 0.25s ease-in-out; text-transform: none; + font-weight: normal; } &:after { @@ -111,7 +178,7 @@ &:hover { &:before { opacity: 1; - bottom: ~"calc(100% + 24px)"; + bottom: ~"calc(100% + 16px)"; } &:after { @@ -121,3 +188,15 @@ } } } + +.filter .filter-current-subtitle, +.filter-title strong { + cursor: pointer; +} + +// Fix for swatch tooltip +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { + .swatch-option-tooltip { + display: none !important; + } +} diff --git a/src/view/frontend/web/js/lib/jquery.ui.touch-punch.min.js b/src/view/frontend/web/js/lib/jquery.ui.touch-punch.min.js new file mode 100644 index 00000000..8c8ceb4d --- /dev/null +++ b/src/view/frontend/web/js/lib/jquery.ui.touch-punch.min.js @@ -0,0 +1,11 @@ +/*! + * jQuery UI Touch Punch 0.2.3 + * + * Copyright 2011–2014, Dave Furfero + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Depends: + * jquery.ui.widget.js + * jquery.ui.mouse.js + */ +!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); diff --git a/src/view/frontend/web/js/navigation-filter.js b/src/view/frontend/web/js/navigation-filter.js deleted file mode 100644 index 8d1e12a2..00000000 --- a/src/view/frontend/web/js/navigation-filter.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved - * - * @copyright Copyright (c) 2017-2017 Tweakwise.com B.V. (https://www.tweakwise.com) - * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) - */ - -define(['jquery', 'jquery/ui'], function($) { - $.widget('tweakwise.navigationFilter', { - _hookEvents: function() { - this.element.on('click', '.more-items', this._handleMoreItemsLink.bind(this)); - this.element.on('click', '.less-items', this._handleLessItemsLink.bind(this)); - if (!this.options.hasOwnProperty('formFilters') || !this.options.formFilters) { - this.element.on('click', '.item input[type="checkbox"]', this._handleCheckboxClick.bind(this)); - this.element.on('click', '.js-swatch-link', this._handleSwatchClick.bind(this)); - } - }, - - _handleMoreItemsLink: function() { - this._sortItems('alternate-sort'); - this.element.find('.default-hidden').show(); - this.element.find('.more-items').hide(); - - return false; - }, - - _handleLessItemsLink: function() { - this._sortItems('original-sort'); - this.element.find('.default-hidden').hide(); - this.element.find('.more-items').show(); - - return false; - }, - - _sortItems: function (type) { - if (!this.options.hasAlternateSort) { - return; - } - - let list = this.element.find('.items'); - list.children('.item').sort(function (a, b) { - return $(a).data(type) - $(b).data(type); - }).appendTo(list); - }, - - _handleCheckboxClick: function(event) { - var a = $(event.currentTarget).closest('a'); - var href = this._findHref(a); - if (href) { - window.location.href = href; - return false; - } - }, - - _handleSwatchClick: function(event) { - event.preventDefault(); - this._handleCheckboxClick(event); - }, - - _findHref: function (aElement) { - var href = aElement.attr('href'); - if (this.options.hasOwnProperty('seoEnabled') && this.options.seoEnabled) { - var seoHref = aElement.data('seo-href'); - return seoHref ? seoHref : href; - } - - return href; - }, - - _create: function() { - this._hookEvents(); - return this._superApply(arguments); - } - }); - - return $.tweakwise.navigationFilter; -}); \ No newline at end of file diff --git a/src/view/frontend/web/js/navigation-form.js b/src/view/frontend/web/js/navigation-form.js index 5a6b7411..0e6cad9c 100644 --- a/src/view/frontend/web/js/navigation-form.js +++ b/src/view/frontend/web/js/navigation-form.js @@ -1,112 +1,321 @@ /** * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved * - * @copyright Copyright (c) 2018-2018 Tweakwise.com B.V. (https://www.tweakwise.com) + * @copyright Copyright (c) 2017-2017 Tweakwise.com B.V. (https://www.tweakwise.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -define(['jquery', 'jquery/ui'], function($) { +define([ + 'jquery', +], function ($) { $.widget('tweakwise.navigationForm', { - _hookEvents: function() { - if (this.options.hasOwnProperty('formFilters') && this.options.formFilters === "1") { - this.element.on('submit', this._handleFilterButtonClick.bind(this)); - } + + options: { + ajaxFilters: false, + formFilters: false, + seoEnabled: false, + ajaxEndpoint: '/tweakwise/ajax/navigation', + filterSelector: '#layered-filter-block', + productListSelector: '.products.wrapper', + toolbarSelector: '.toolbar.toolbar-products', + isLoading: false, }, - _handleFilterButtonClick: function(event) { - event.preventDefault(); + currentXhr: null, - let values = jQuery(this.element).serialize(); - let sliderValues = this._getSliderUrlParameters(); - let searchValue = this._getSearchParam(); - let catValue = this._getCatParam(); + _create: function () { + this._hookEvents(); + return this._superApply(arguments); + }, - let url = '?'; - if (searchValue) { - url = url + searchValue; + /** + * Bind filter events, these are filter click and filter remove + * + * @private + */ + _hookEvents: function () { + if (this.options.ajaxFilters) { + this._bindPopChangeHandler() } + this._bindFilterClickEvents(); + this._bindFilterRemoveEvents(); + }, - if (values && url !== '?') { - url = url + '&' + values; - } else if (values) { - url = url + values; + /** + * Bind filter click events + * + * @private + */ + _bindFilterClickEvents: function () { + if (this.options.formFilters) { + this.element.on('click', '.js-btn-filter', this._getFilterClickHandler().bind(this)); + } else { + this.element.on('change', this._getFilterClickHandler().bind(this)); } + }, + + /** + * Filter remove events are only relevant for ajax filtering. If ajaxFilters is false then we just navigate + * to the url specified in the a. + * + * @private + */ + _bindFilterRemoveEvents: function () { + if (this.options.ajaxFilters) { + this.element.on('click', 'a.remove', this._ajaxClearHandler.bind(this)); + } + }, - if (catValue && url !== '?') { - url = url + '&' + catValue; - } else if (catValue) { - url = url + catValue; + /** + * + * @private + */ + _bindPopChangeHandler: function () { + window.onpopstate = function (event) { + if (event.state && event.state.html) { + this._updateBlocks(event.state.html); + } + }.bind(this); + }, + + /** + * Should return the handler for the filter event, depends on config options. + * Supported options are ajax filtering and form filters and any combination of those options. + * Note that the ajaxHandler also handles the case ajax enabled AND form filters enabled + * + * @returns {tweakwise.navigationFilterAjax._ajaxHandler|tweakwise.navigationFilterAjax._defaultHandler} + * @private + */ + _getFilterClickHandler: function () { + if (this.options.ajaxFilters) { + return this._ajaxHandler; } - if (sliderValues && url !== '?') { - url = url + '&' + sliderValues; - } else if (sliderValues) { - url = url + sliderValues; + if (this.options.formFilters) { + return this._formFilterHandler; } - if (url !== '?') { - window.location = url; + return this._defaultHandler + }, + + /** + * Serialize the form element but skip unwanted inputs + * + * @returns {*} + * @private + */ + _getFilterParameters: function () { + return this.element.find(':not(.js-skip-submit)').serialize(); + }, + + // ------- Default filter handling (i.e. no ajax and no filter form) + /** + * Navigate to the selected filter url + * + * @param event + * @returns {boolean} + * @private + */ + _defaultHandler: function (event) { + var a = $(event.target).closest('a'); + var href = this._findHref(a); + if (href) { + window.location.href = href; + return false; } }, - _getSearchParam: function() { - let q = this._getQParam(); - let searchParam = {}; - if (q) { - searchParam['q'] = q; - return jQuery.param(searchParam); + /** + * Should return the url to navigate to + * + * @param aElement + * @returns {*} + * @private + */ + _findHref: function (aElement) { + var href = aElement.attr('href'); + if (this.options.seoEnabled) { + var seoHref = aElement.data('seo-href'); + return seoHref ? seoHref : href; } - return ''; + return href; }, - _getQParam: function() { - let matches = window.location.search.match(/(\?|&)q\=([^&]*)/); - if (matches && matches[2]) { - let trimmedMatch = matches[2].replace(/\+/g, ' '), - searchVal = jQuery('#search').val(); + /** + * This provides a means to disable ajax filtering. + * If you dont want ajax filtering for certain filters add a data-no-ajax attribute. + * + * @param event + * @returns {boolean} + * @private + */ + _allowAjax: function (event) { + var a = $(event.target).closest('a'); + return !a.data('no-ajax'); + }, - if (searchVal === trimmedMatch) { - return decodeURIComponent(trimmedMatch); - } + // ------- End of default filter handling + + // ------- Handling for ajax filtering (i.e. only ajax filtering) + /** + * Handle Ajax request for new content + * + * @param event + * @private + */ + _ajaxHandler: function (event) { + event.preventDefault(); - return decodeURIComponent(matches[2]); + if (this.currentXhr) { + this.currentXhr.abort(); } - return ''; + if (!this._allowAjax(event)) { + this._defaultHandler(event); + return; + } + + this._startLoader(); + this.currentXhr = $.ajax({ + url: this.options.ajaxEndpoint, + data: this._getFilterParameters(), + cache: true, + success: function (response) { + this._updateBlocks(response.html); + this._updateState(response); + }.bind(this), + error: function (jqXHR, errorStatus) { + if (errorStatus !== 'abort') { + // Something went wrong, try to navigate to the selected filter + this._defaultHandler(event); + } + }.bind(this), + complete: function () { + this._stopLoader(); + }.bind(this) + }); }, - _getCatParam: function () { - let matches = window.location.search.match(/(\?|&)categorie\=([^&]*)/); - if (matches && matches[2]) { - let catParam = {categorie: matches[2]}; - return jQuery.param(catParam); + /** + * Handle filter clear events + * + * @param event + * @private + */ + _ajaxClearHandler: function (event) { + var filterId = '#' + $(event.target).data('js-filter-id'); + var filter = this.element.find(filterId); + if (filter && filter.length) { + event.preventDefault(); + filter = $(filter); + // Set filter disabled so that it will not be submitted when change is triggered + filter.attr('disabled', true); + if (this.options.formFilters) { + // Simulate click so that the form will be submitted + this.element.find('.js-btn-filter').first().trigger('click'); + } else { + filter.trigger('change'); + } + } + }, + + /** + * Update all relevant html with response data, trigger contentUpdated to 'trigger' data-mage-init + * @param htmlResponse + * @private + */ + _updateBlocks: function (htmlResponse) { + var filterSelector = this.options.filterSelector; + var productListSelector = this.options.productListSelector; + var toolbarSelector = this.options.toolbarSelector; + + var wrapper = document.createElement('div'); + wrapper.innerHTML = htmlResponse; + var parsedHtml = $(wrapper); + + var newFiltersHtml = parsedHtml.find(filterSelector).html(); + var newProductListHtml = parsedHtml.find(productListSelector).html(); + var newToolbar = parsedHtml.find(toolbarSelector); + // Toolbar is included twice in the response + // We use this first().get(0) construction to access outerHTML + // The reason for this is that we need to replace the entire toolbar because otherwise + // the data-mage-init attribute is no longer available on the toolbar and hence the toolbar + // would not be initialized when $('body').trigger('contentUpdated'); is called + var newToolbarFirst = newToolbar.first().get(0); + var newToolbarLast = newToolbar.last().get(0); + + if (newFiltersHtml) { + $(filterSelector).html(newFiltersHtml); + } + + var toolbar = $(toolbarSelector); + /* + The product list comes after the toolbar. + We use this construction as there could be more product lists on the page + and we dont want to replace them all + */ + if (newProductListHtml) { + toolbar + .siblings(productListSelector) + .html(newProductListHtml); + } + if (newToolbarFirst) { + toolbar + .first() + .replaceWith(newToolbarFirst.outerHTML); + } + if (newToolbarLast) { + toolbar + .last() + .replaceWith(newToolbarLast.outerHTML); } - return ''; + $('body').trigger('contentUpdated'); }, - _getSliderUrlParameters: function() { - let query = {}; - jQuery('.slider-attribute').each(function(i, slider) { - slider = jQuery(slider); - let key = slider.data('url-key'); - let min = slider.data('min'); - let max = slider.data('max'); - let rangeMin = slider.data('range-min'); - let rangeMax = slider.data('range-max'); - if ((min && max) && (rangeMin !== min || rangeMax !== max)) { - query[key] = min + '-' + max; - } - }); + /** + * + * @param response + * @private + */ + _updateState: function (response) { + window.history.pushState({html: response.html}, '', response.url); + }, - return jQuery.param(query); + /** + * Start loader targeting body relevant for ajax filtering + * @private + */ + _startLoader: function () { + $(this.options.productListSelector).trigger('processStart'); + this.options.isLoading = true; }, - _create: function() { - this._hookEvents(); - return this._superApply(arguments); + /** + * Stop Loader targeting body relevant for ajax filtering + * @private + */ + _stopLoader: function () { + $(this.options.productListSelector).trigger('processStop'); + this.options.isLoading = false; + }, + // ------- End of handling for ajax filtering + + // ------- Handling for form filters. + // ------- Note that is only used when ajax is not enabled and form filters is enabled + /** + * This just handles the filter button click + * + * @private + */ + _formFilterHandler: function () { + var filterUrl = this._getFilterParameters(); + if (filterUrl) { + window.location = '?' + filterUrl; + } } + // ------- End of handling for form filters }); return $.tweakwise.navigationForm; diff --git a/src/view/frontend/web/js/navigation-slider-compat.js b/src/view/frontend/web/js/navigation-slider-compat.js new file mode 100644 index 00000000..3347a869 --- /dev/null +++ b/src/view/frontend/web/js/navigation-slider-compat.js @@ -0,0 +1,196 @@ +/** + * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved + * + * @copyright Copyright (c) (https://www.tweakwise.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +define([ + 'jquery', + 'jquery/ui', + 'jQueryTouchPunch', + 'domReady!' +], function($) { + $.widget('tweakwise.navigationSlider', { + + options: { + filterUrl: '', + prefix: '', + postfix: '', + container: '', + min: 0, + max: 99999999, + currentMin: 0, + currentMax: 99999999, + formFilters: false, + ajaxFilters: false, + }, + + /** + * + * @returns {*} + * @private + */ + _create: function() { + this._createSlider(); + this._bindInputChangeEvents(); + return this._superApply(arguments); + }, + + /** + * Register the correct handler depending on configuration + * @private + */ + _createSlider: function() { + $(this.options.container).find('.slider').slider(this._getSliderConfig()); + }, + + /** + * + * @returns {{min: number, max: number, slide: *, values: [tweakwise.navigationSlider._getSliderConfig.options.currentMin, tweakwise.navigationSlider._getSliderConfig.options.currentMax], change: *, range: boolean}} + * @private + */ + _getSliderConfig: function() { + return { + range: true, + min: this.options.min, + max: this.options.max, + values: [ + this.options.currentMin, this.options.currentMax + ], + + slide: function (event, ui) { + var container = $(this.options.container); + var minValue = ui.values[0]; + var maxValue = ui.values[1]; + container.find('.current-min-value').html(this._labelFormat(minValue)); + container.find('.current-max-value').html(this._labelFormat(maxValue)); + container.find('input.slider-min').val(minValue); + container.find('input.slider-max').val(maxValue); + + var sliderUrlValue = minValue + '-' + maxValue; + var sliderUrlInput = container.find('input.slider-url-value'); + sliderUrlInput.val(sliderUrlValue); + + this._updateSliderDisabledAttribute(sliderUrlInput, sliderUrlValue); + }.bind(this), + + change: this._getChangeHandler() + } + }, + + /** + * Bind handling for manual input + * + * @private + */ + _bindInputChangeEvents: function() { + var sliderContainer = $(this.options.container); + sliderContainer.on('change', '.slider-min', this._updateSliderUrlInput.bind(this)); + sliderContainer.on('change', '.slider-max', this._updateSliderUrlInput.bind(this)); + }, + + /** + * Fire slider change event + * + * @private + */ + _updateSliderUrlInput: function () { + var sliderContainer = $(this.options.container); + var sliderUrlInput = sliderContainer.find('.slider-url-value'); + var minValue = sliderContainer.find('.slider-min').val(); + var maxValue = sliderContainer.find('.slider-max').val(); + var inputValue = minValue + '-' + maxValue; + sliderUrlInput.val(inputValue); + this._updateSliderDisabledAttribute(sliderUrlInput, inputValue) + }, + + /** + * + * @param sliderUrlInput + * @param inputValue + * @private + */ + _updateSliderDisabledAttribute: function (sliderUrlInput, inputValue) { + if (inputValue === sliderUrlInput.data('disabled-input')) { + sliderUrlInput.attr('disabled', true); + } else { + sliderUrlInput.removeAttr('disabled'); + } + }, + + /** + * This determines the "slide" handler depending on configuration + * + * @returns {*} + * @private + */ + _getChangeHandler: function () { + if (this.options.formFilters) { + return this.formFilterChange.bind(this); + } + + if (this.options.ajaxFilters) { + return this.ajaxChange.bind(this); + } + + return this.defaultChange.bind(this); + }, + + /** + * Standard navigation, i.e. no ajax or formfilter options + * + * @param event + * @param ui + */ + defaultChange: function (event, ui) { + var min = ui.values[0]; + var max = ui.values[1]; + var url = this.options.filterUrl; + + url = url.replace(encodeURI('{{from}}'), min); + url = url.replace('{{from}}', min); + url = url.replace(encodeURI('{{to}}'), max); + url = url.replace('{{to}}', max); + window.location.href = url; + }, + + /** + * Ajax navigation, no formfilters + * + * @param event + * @param ui + */ + ajaxChange: function (event, ui) { + this.formFilterChange(event, ui); + $(this.element).closest('form').trigger('change'); + }, + + /** + * Used when form filters is set to true, just update the values, navigation is handled by the filter button + * + * @param event + * @param ui + */ + formFilterChange: function (event, ui) { + var min = ui.values[0]; + var max = ui.values[1]; + var sliderContainer = $(this.options.container); + sliderContainer.data('min', min); + sliderContainer.data('max', max); + }, + + /** + * Format slider label + * + * @param value + * @returns {string} + * @private + */ + _labelFormat: function (value) { + return this.options.prefix + value + this.options.postfix; + } + }); + + return $.tweakwise.navigationSlider; +}); diff --git a/src/view/frontend/web/js/navigation-slider.js b/src/view/frontend/web/js/navigation-slider.js new file mode 100644 index 00000000..c15fcab8 --- /dev/null +++ b/src/view/frontend/web/js/navigation-slider.js @@ -0,0 +1,196 @@ +/** + * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved + * + * @copyright Copyright (c) (https://www.tweakwise.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +define([ + 'jquery', + 'jquery-ui-modules/slider', + 'jQueryTouchPunch', + 'domReady!' +], function ($) { + $.widget('tweakwise.navigationSlider', { + + options: { + filterUrl: '', + prefix: '', + postfix: '', + container: '', + min: 0, + max: 99999999, + currentMin: 0, + currentMax: 99999999, + formFilters: false, + ajaxFilters: false, + }, + + /** + * + * @returns {*} + * @private + */ + _create: function () { + this._createSlider(); + this._bindInputChangeEvents(); + return this._superApply(arguments); + }, + + /** + * Register the correct handler depending on configuration + * @private + */ + _createSlider: function () { + $(this.options.container).find('.slider').slider(this._getSliderConfig()); + }, + + /** + * + * @returns {{min: number, max: number, slide: *, values: [tweakwise.navigationSlider._getSliderConfig.options.currentMin, tweakwise.navigationSlider._getSliderConfig.options.currentMax], change: *, range: boolean}} + * @private + */ + _getSliderConfig: function () { + return { + range: true, + min: this.options.min, + max: this.options.max, + values: [ + this.options.currentMin, this.options.currentMax + ], + + slide: function (event, ui) { + var container = $(this.options.container); + var minValue = ui.values[0]; + var maxValue = ui.values[1]; + container.find('.current-min-value').html(this._labelFormat(minValue)); + container.find('.current-max-value').html(this._labelFormat(maxValue)); + container.find('input.slider-min').val(minValue); + container.find('input.slider-max').val(maxValue); + + var sliderUrlValue = minValue + '-' + maxValue; + var sliderUrlInput = container.find('input.slider-url-value'); + sliderUrlInput.val(sliderUrlValue); + + this._updateSliderDisabledAttribute(sliderUrlInput, sliderUrlValue); + }.bind(this), + + change: this._getChangeHandler() + } + }, + + /** + * Bind handling for manual input + * + * @private + */ + _bindInputChangeEvents: function () { + var sliderContainer = $(this.options.container); + sliderContainer.on('change', '.slider-min', this._updateSliderUrlInput.bind(this)); + sliderContainer.on('change', '.slider-max', this._updateSliderUrlInput.bind(this)); + }, + + /** + * Fire slider change event + * + * @private + */ + _updateSliderUrlInput: function () { + var sliderContainer = $(this.options.container); + var sliderUrlInput = sliderContainer.find('.slider-url-value'); + var minValue = sliderContainer.find('.slider-min').val(); + var maxValue = sliderContainer.find('.slider-max').val(); + var inputValue = minValue + '-' + maxValue; + sliderUrlInput.val(inputValue); + this._updateSliderDisabledAttribute(sliderUrlInput, inputValue) + }, + + /** + * + * @param sliderUrlInput + * @param inputValue + * @private + */ + _updateSliderDisabledAttribute: function (sliderUrlInput, inputValue) { + if (inputValue === sliderUrlInput.data('disabled-input')) { + sliderUrlInput.attr('disabled', true); + } else { + sliderUrlInput.removeAttr('disabled'); + } + }, + + /** + * This determines the "slide" handler depending on configuration + * + * @returns {*} + * @private + */ + _getChangeHandler: function () { + if (this.options.formFilters) { + return this.formFilterChange.bind(this); + } + + if (this.options.ajaxFilters) { + return this.ajaxChange.bind(this); + } + + return this.defaultChange.bind(this); + }, + + /** + * Standard navigation, i.e. no ajax or formfilter options + * + * @param event + * @param ui + */ + defaultChange: function (event, ui) { + var min = ui.values[0]; + var max = ui.values[1]; + var url = this.options.filterUrl; + + url = url.replace(encodeURI('{{from}}'), min); + url = url.replace('{{from}}', min); + url = url.replace(encodeURI('{{to}}'), max); + url = url.replace('{{to}}', max); + window.location.href = url; + }, + + /** + * Ajax navigation, no formfilters + * + * @param event + * @param ui + */ + ajaxChange: function (event, ui) { + this.formFilterChange(event, ui); + $(this.element).closest('form').trigger('change'); + }, + + /** + * Used when form filters is set to true, just update the values, navigation is handled by the filter button + * + * @param event + * @param ui + */ + formFilterChange: function (event, ui) { + var min = ui.values[0]; + var max = ui.values[1]; + var sliderContainer = $(this.options.container); + sliderContainer.data('min', min); + sliderContainer.data('max', max); + }, + + /** + * Format slider label + * + * @param value + * @returns {string} + * @private + */ + _labelFormat: function (value) { + return this.options.prefix + value + this.options.postfix; + } + }); + + return $.tweakwise.navigationSlider; +}); diff --git a/src/view/frontend/web/js/navigation-sort.js b/src/view/frontend/web/js/navigation-sort.js new file mode 100644 index 00000000..20df69b5 --- /dev/null +++ b/src/view/frontend/web/js/navigation-sort.js @@ -0,0 +1,79 @@ +/** + * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved + * + * @copyright Copyright (c) 2017-2017 Tweakwise.com B.V. (https://www.tweakwise.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +define([ + 'jquery' +], function ($) { + $.widget('tweakwise.navigationSort', { + + options: { + hasAlternateSort: null, + }, + + _create: function () { + this._hookEvents(); + return this._superApply(arguments); + }, + + /** + * Bind more and less items click handlers + * + * @private + */ + _hookEvents: function () { + this.element.on('click', '.more-items', this._handleMoreItemsLink.bind(this)); + this.element.on('click', '.less-items', this._handleLessItemsLink.bind(this)); + }, + + /** + * Sort items depending on alternate sort (this comes from tweakwise api) and expand filter list + * + * @returns {boolean} + * @private + */ + _handleMoreItemsLink: function () { + this._sortItems('alternate-sort'); + this.element.find('.default-hidden').show(); + this.element.find('.more-items').hide(); + + return false; + }, + + /** + * Sort items depending on alternate sort (this comes from tweakwise api) and abbreviate filter list + * + * @returns {boolean} + * @private + */ + _handleLessItemsLink: function () { + this._sortItems('original-sort'); + this.element.find('.default-hidden').hide(); + this.element.find('.more-items').show(); + + return false; + }, + + /** + * Sort items based on alternate sort (if available) + * + * @param type + * @private + */ + _sortItems: function (type) { + if (!this.options.hasAlternateSort) { + return; + } + + var list = this.element.find('.items'); + list.children('.item').sort(function (a, b) { + return $(a).data(type) - $(b).data(type); + }).appendTo(list); + }, + }); + + return $.tweakwise.navigationSort; +}); diff --git a/src/view/frontend/web/js/quick-search.js b/src/view/frontend/web/js/quick-search.js index 60b5ffff..f28a9c3f 100644 --- a/src/view/frontend/web/js/quick-search.js +++ b/src/view/frontend/web/js/quick-search.js @@ -8,10 +8,10 @@ define([ 'jquery', 'Magento_Search/form-mini' -], function($, quickSearch){ +], function ($, quickSearch) { $.widget('tweakwise.quickSearch', quickSearch, { - getSelectedProductUrl: function() { + getSelectedProductUrl: function () { if (!this.responseList.selected) { return null; } @@ -19,29 +19,26 @@ define([ return this.responseList.selected.data('url'); }, - _create: function() { - $(this.options.formSelector).on('submit', function(event) { + _create: function () { + $(this.options.formSelector).on('submit', function (event) { if (this.getSelectedProductUrl()) { event.preventDefault(); } }.bind(this)); var templateId = '#autocomplete-item-template'; - this.options.template = templateId; this.options.url = $(templateId).data('url'); return this._superApply(arguments); }, - _onSubmit: function (e) { + _onSubmit: function () { var url = this.getSelectedProductUrl(); if (!url) { return this._superApply(arguments); } - if (url !== null) { - window.location.href = url; - } + window.location.href = url; }, _onPropertyChange: function () { @@ -49,11 +46,11 @@ define([ clearTimeout(this.searchDelayTimeout); } - this.searchDelayTimeout = setTimeout(function() { + this.searchDelayTimeout = setTimeout(function () { quickSearch.prototype._onPropertyChange.apply(this); }.bind(this), 200); } }); return $.tweakwise.quickSearch; -}); \ No newline at end of file +}); diff --git a/src/view/frontend/web/js/toolbar.js b/src/view/frontend/web/js/toolbar.js new file mode 100644 index 00000000..e936fd98 --- /dev/null +++ b/src/view/frontend/web/js/toolbar.js @@ -0,0 +1,74 @@ +/** + * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved + * + * @copyright Copyright (c) 2017-2017 Tweakwise.com B.V. (https://www.tweakwise.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +define([ + 'jquery', + 'Magento_Catalog/js/product/list/toolbar' +], function ($, productListToolbarForm) { + $.widget('tweakwise.productListToolbarForm', productListToolbarForm, { + + options: { + ajaxFilters: false, + pagerItemSelector: '.pages li.item', + filterFormSelector: '#facet-filter-form' + }, + + /** @inheritdoc */ + _create: function () { + var options = this.options; + var element = this.element; + this._bind(element.find(options.modeControl), options.mode, options.modeDefault); + this._bind(element.find(options.directionControl), options.direction, options.directionDefault); + this._bind(element.find(options.orderControl), options.order, options.orderDefault); + this._bind(element.find(options.limitControl), options.limit, options.limitDefault); + if (options.ajaxFilters) { + $(element).on('click', options.pagerItemSelector, this.handlePagerClick.bind(this)); + } + }, + + handlePagerClick: function (event) { + event.preventDefault(); + var anchor = $(event.target).closest('a'); + var page = anchor.attr('href') || ''; + var pageValueRegex = '[?&]p=(\\\d?)'; + var pageValue = new RegExp(pageValueRegex).exec(page); + if (pageValue) { + pageValue = pageValue[1]; + this.changeUrl('p', pageValue, pageValue) + } + + return false; + }, + + /** + * @param {String} paramName + * @param {*} paramValue + * @param {*} defaultValue + */ + changeUrl: function (paramName, paramValue, defaultValue) { + if (!this.options.ajaxFilters) { + return this._super(paramName, paramValue, defaultValue); + } + + var form = $(this.options.filterFormSelector); + var input = form.find('input[name=' + paramName + ']'); + if (!input.length) { + input = document.createElement('input'); + input.name = paramName; + input = $(input); + form.append(input); + input.hide(); + } + + input.attr('value', paramValue); + form.trigger('change'); + } + + }); + + return $.tweakwise.productListToolbarForm; +});