{"id":1814,"date":"2018-08-08T13:32:56","date_gmt":"2018-08-08T11:32:56","guid":{"rendered":"https:\/\/www.h-hennes.fr\/blog\/?p=1814"},"modified":"2018-11-30T17:09:35","modified_gmt":"2018-11-30T15:09:35","slug":"prestashop-1-7-ajouter-une-navigation-a-facettes-dans-les-listings","status":"publish","type":"post","link":"https:\/\/www.h-hennes.fr\/blog\/2018\/08\/08\/prestashop-1-7-ajouter-une-navigation-a-facettes-dans-les-listings\/","title":{"rendered":"Prestashop 1.7 : Ajouter une navigation \u00e0 facettes dans les listings"},"content":{"rendered":"<p>La gestion des listings \u00e0 compl\u00e9tement \u00e9t\u00e9 r\u00e9\u00e9crite sur Prestashop 1.7 , et la bonne nouvelle et qu&rsquo;ils g\u00e8rent maintenant nativement la navigation \u00e0 facettes ( Tout du moins la partie front \ud83d\ude09 )<br \/>\nNous allons voir ensemble comment mettre en place une <strong>navigation \u00e0 facette basique sur la page des nouveaux produits.<\/strong><\/p>\n<p><strong>Sommaire :<\/strong><\/p>\n<ol>\n<li><a href=\"#affichage\">Fonctionnement g\u00e9n\u00e9ral<\/a><\/li>\n<li><a href=\"#affichage\">Affichage de facettes<\/a><\/li>\n<li><a href=\"#tri\">Tri et gestion des facettes<\/a><\/li>\n<\/ol>\n<p>Cet article est plus dans une d\u00e9marche d&rsquo;explication du fonctionnement, que dans la r\u00e9alisation d&rsquo;un module purement fonctionnel, et la r\u00e9alisation est assez chronophage, dans la majorit\u00e9 des cas il sera pr\u00e9f\u00e9rable de passer par un module qui fera cela.<br \/>\n( Le module de navigation \u00e0 facette de Prestashop le permets uniquement sur les cat\u00e9gories )<\/p>\n<p><a name=\"general\"><\/a><span style=\"text-decoration: underline;\"><strong>Fonctionnement g\u00e9n\u00e9ral<\/strong><\/span><\/p>\n<p>Le premier point essentiel \u00e0 noter et qu&rsquo;il n&rsquo;est pas n\u00e9cessaire de faire de surcharge, tout peut \u00eatre g\u00e9r\u00e9 via un module \ud83d\ude42<\/p>\n<p>Avant de voir ce qu&rsquo;il faut coder, il est important de comprendre comment fonctionnent les pages de listing sur prestashop 1.7<br \/>\nDans mon exemple on va utiliser le controller <strong>NewProductsController<\/strong> ( <em>controllers\/front\/listing\/NewProductsController.php<\/em> ) qui g\u00e8re les nouveaux produits.<\/p>\n<p>Lors de son initialisation il appelle la fonction <strong>doProductSearch<\/strong> de la classe parente ProductListingFrontController<\/p>\n<pre lang=\"php\" escaped=\"true\">    public function init()\r\n    {\r\n        parent::init();\r\n        $this-&gt;doProductSearch('catalog\/listing\/new-products');\r\n    }\r\n<\/pre>\n<p>Celle-ci appelle la fonction <strong>getProductSearchVariables<\/strong> ( j&rsquo;omets volontairement le cas getAjaxProductSearchVariables qui est parlant puisqu&rsquo;il rajoute des information sp\u00e9cifiques uniquement lors d&rsquo;un appel ajax )<\/p>\n<p>Le code est un peu long, mais toute la logique des controller listing sous Prestashop est dans cette fonction :<br \/>\nJe laisse les commentaires de base en anglais qui sont plut\u00f4t clairs.<\/p>\n<pre lang=\"php\" escaped=\"true\">     \/**\r\n     * This returns all template variables needed for rendering\r\n     * the product list, the facets, the pagination and the sort orders.\r\n     *\r\n     * @return array variables ready for templating\r\n     *\/\r\n    protected function getProductSearchVariables()\r\n    {\r\n        \/*\r\n         * To render the page we need to find something (a ProductSearchProviderInterface)\r\n         * that knows how to query products.\r\n         *\/\r\n \r\n        \/\/ the search provider will need a context (language, shop...) to do its job\r\n        $context = $this-&gt;getProductSearchContext();\r\n \r\n        \/\/ the controller generates the query...\r\n        $query = $this-&gt;getProductSearchQuery();\r\n \r\n        \/\/ ...modules decide if they can handle it (first one that can is used)\r\n        \/\/ Dans notre cas c'est ICI que le module devra \u00eatre appell\u00e9\r\n        $provider = $this-&gt;getProductSearchProviderFromModules($query);\r\n \r\n        \/\/ if no module wants to do the query, then the core feature is used\r\n        if (null === $provider) {\r\n            $provider = $this-&gt;getDefaultProductSearchProvider();\r\n        }\r\n \r\n        $resultsPerPage = (int) Tools::getValue('resultsPerPage');\r\n        if ($resultsPerPage &amp;lt;= 0 || $resultsPerPage &gt; 36) {\r\n            $resultsPerPage = Configuration::get('PS_PRODUCTS_PER_PAGE');\r\n        }\r\n \r\n        \/\/ we need to set a few parameters from back-end preferences\r\n        $query\r\n            -&gt;setResultsPerPage($resultsPerPage)\r\n            -&gt;setPage(max((int) Tools::getValue('page'), 1))\r\n        ;\r\n \r\n        \/\/ set the sort order if provided in the URL\r\n        if (($encodedSortOrder = Tools::getValue('order'))) {\r\n            $query-&gt;setSortOrder(SortOrder::newFromString(\r\n                $encodedSortOrder\r\n            ));\r\n        }\r\n \r\n        \/\/ get the parameters containing the encoded facets from the URL\r\n        $encodedFacets = Tools::getValue('q');\r\n \r\n        \/*\r\n         * The controller is agnostic of facets.\r\n         * It's up to the search module to use \/define them.\r\n         *\r\n         * Facets are encoded in the \"q\" URL parameter, which is passed\r\n         * to the search provider through the query's \"$encodedFacets\" property.\r\n         *\/\r\n \r\n        $query-&gt;setEncodedFacets($encodedFacets);\r\n \r\n        \/\/ We're ready to run the actual query!\r\n \r\n        $result = $provider-&gt;runQuery(\r\n            $context,\r\n            $query\r\n        );\r\n \r\n        \/\/ sort order is useful for template,\r\n        \/\/ add it if undefined - it should be the same one\r\n        \/\/ as for the query anyway\r\n        if (!$result-&gt;getCurrentSortOrder()) {\r\n            $result-&gt;setCurrentSortOrder($query-&gt;getSortOrder());\r\n        }\r\n \r\n        \/\/ prepare the products\r\n        $products = $this-&gt;prepareMultipleProductsForTemplate(\r\n            $result-&gt;getProducts()\r\n        );\r\n \r\n        \/\/ render the facets\r\n        if ($provider instanceof FacetsRendererInterface) {\r\n            \/\/ with the provider if it wants to\r\n            $rendered_facets = $provider-&gt;renderFacets(\r\n                $context,\r\n                $result\r\n            );\r\n            $rendered_active_filters = $provider-&gt;renderActiveFilters(\r\n                $context,\r\n                $result\r\n            );\r\n        } else {\r\n            \/\/ with the core\r\n            $rendered_facets = $this-&gt;renderFacets(\r\n                $result\r\n            );\r\n            $rendered_active_filters = $this-&gt;renderActiveFilters(\r\n                $result\r\n            );\r\n        }\r\n \r\n        $pagination = $this-&gt;getTemplateVarPagination(\r\n            $query,\r\n            $result\r\n        );\r\n \r\n        \/\/ prepare the sort orders\r\n        \/\/ note that, again, the product controller is sort-orders\r\n        \/\/ agnostic\r\n        \/\/ a module can easily add specific sort orders that it needs\r\n        \/\/ to support (e.g. sort by \"energy efficiency\")\r\n        $sort_orders = $this-&gt;getTemplateVarSortOrders(\r\n            $result-&gt;getAvailableSortOrders(),\r\n            $query-&gt;getSortOrder()-&gt;toString()\r\n        );\r\n \r\n        $sort_selected = false;\r\n        if (!empty($sort_orders)) {\r\n            foreach ($sort_orders as $order) {\r\n                if (isset($order['current']) &amp;amp;&amp;amp; true === $order['current']) {\r\n                    $sort_selected = $order['label'];\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n \r\n        $searchVariables = array(\r\n            'label' =&gt; $this-&gt;getListingLabel(),\r\n            'products' =&gt; $products,\r\n            'sort_orders' =&gt; $sort_orders,\r\n            'sort_selected' =&gt; $sort_selected,\r\n            'pagination' =&gt; $pagination,\r\n            'rendered_facets' =&gt; $rendered_facets,\r\n            'rendered_active_filters' =&gt; $rendered_active_filters,\r\n            'js_enabled' =&gt; $this-&gt;ajax,\r\n            'current_url' =&gt; $this-&gt;updateQueryString(array(\r\n                'q' =&gt; $result-&gt;getEncodedFacets(),\r\n            )),\r\n        );\r\n \r\n        Hook::exec('filterProductSearch', array('searchVariables' =&gt; &amp;amp;$searchVariables));\r\n        Hook::exec('actionProductSearchAfter', $searchVariables);\r\n \r\n        return $searchVariables;\r\n    }<\/pre>\n<p>En raccourci dans cette fonction notre module pourra renvoyer une classe de provider sp\u00e9cifique gr\u00e2ce \u00e0 cet appel :<\/p>\n<pre lang=\"php\" escaped=\"true\"> $provider = $this-&gt;getProductSearchProviderFromModules($query);<\/pre>\n<p>Ensuite c&rsquo;est la fonction<\/p>\n<pre lang=\"php\" escaped=\"true\">\/\/ We're ready to run the actual query! \r\n$result = $provider-&gt;runQuery( $context, $query ); \r\n<\/pre>\n<p>qui va appeller la fonction runQuery du module en lui passant le contexte et la requ\u00eate de recherche.<\/p>\n<p>La suite de l&rsquo;affichage ( facettes , tris, et pagination ) seront g\u00e9r\u00e9s automatiquement \u00e0 partir des informations retourn\u00e9es par le provider de notre module.<\/p>\n<p>&amp;nbsp<\/p>\n<p><a name=\"affichage\"><\/a><strong>Cr\u00e9ation du module et affichage de facettes<\/strong><\/p>\n<p>Maintenant que la base est comprise nous pouvons passer \u00e0 la cr\u00e9ation du module, celui-ci s&rsquo;appellera <strong>hh_facetedSearch<\/strong><\/p>\n<p>Voici le code de base du module<\/p>\n<pre lang=\"php\" escaped=\"true\">require_once dirname(__FILE__).'\/classes\/Hh_facetedSearchProductSearchProvider.php';\r\nclass Hh_FacetedSearch extends Module\r\n{\r\n \r\n    public function __construct()\r\n    {\r\n \r\n        $this-&gt;author = 'hhennes';\r\n        $this-&gt;name = 'hh_facetedsearch';\r\n        $this-&gt;tab = 'test';\r\n        $this-&gt;version = '0.1.0';\r\n        $this-&gt;bootstrap = true;\r\n        parent::__construct();\r\n \r\n        $this-&gt;displayName = $this-&gt;l('HH Faceted Search');\r\n        $this-&gt;description = $this-&gt;l('HH Sample Facets Implementation');\r\n \r\n \r\n    }\r\n \r\n    \/**\r\n     * Module installation.\r\n     *\r\n     * @return bool Success of the installation\r\n     *\/\r\n    public function install()\r\n    {\r\n        return parent::install()\r\n            &amp;amp;&amp;amp; $this-&gt;registerHook('productSearchProvider');\r\n    }\r\n \r\n    \/**\r\n     * Dans ce hook on intercepte la requ\u00eate des nouveaux produits pour y ajouter des facettes\r\n     * @param $params\r\n     * @return Hh_facetedSearchProductSearchProvider\r\n     *\/\r\n    public function hookProductSearchProvider($params)\r\n    {\r\n        $query = $params['query'];\r\n        if ($query-&gt;getQueryType() == 'new-products') {\r\n            return new Hh_facetedSearchProductSearchProvider($this);\r\n        }\r\n    }\r\n}<\/pre>\n<p>Le point essentiel est qu&rsquo;il doit \u00eatre greff\u00e9 sur le hook <strong>productSearchProvider <\/strong>et qu&rsquo;il doit retourner une classe qui impl\u00e9mente l&rsquo;interface <em>PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchProviderInterface<\/em><br \/>\nNous cr\u00e9erons le fichier dans <em>classes\/Hh_facetedSearchProductSearchProvider.php<\/em><\/p>\n<p>Dans cette \u00e9tape nous allons uniquement afficher des facettes ce qui est g\u00e9r\u00e9 nativement par le controller prestashop.<br \/>\nVoici \u00e0 pr\u00e9sent le code du Product Search Provider, avec les commentaires explicatifs pour faire cela :<\/p>\n<pre lang=\"php\" escaped=\"true\">\r\n\r\n\/\/Use pour la recherche standard \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchProviderInterface; \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchContext; \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchQuery; \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchResult; \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\SortOrderFactory; \r\n\r\n\/\/Use pour les facettes \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\FacetCollection; #Collection de facettes \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\Facet; #Classe de la facette \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\Filter; #Classe des filtres \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\URLFragmentSerializer; #Pour transformer l'url \r\n\r\n\/\/Provider par d\u00e9faut \r\nuse PrestaShop\\PrestaShop\\Adapter\\NewProducts\\NewProductsProductSearchProvider; \r\n\r\nclass Hh_facetedSearchProductSearchProvider implements ProductSearchProviderInterface { \r\n\r\nprivate $module; \r\nprivate $sortOrderFactory; \r\n\r\n\/** \r\n* Instanciation de la classe \r\n* @param Hh_FacetedSearch $module \r\n*\/ \r\npublic function __construct( Hh_FacetedSearch $module ) { \r\n $this-&gt;module = $module;\r\n \/\/R\u00e9cup\u00e9ration des tris disponibles par d\u00e9faut\r\n $this-&gt;sortOrderFactory = new SortOrderFactory($this-&gt;module-&gt;getTranslator());\r\n }\r\n\r\n \/**\r\n * @param ProductSearchContext $context\r\n * @param ProductSearchQuery $query\r\n * @return ProductSearchResult\r\n *\/\r\n public function runQuery(\r\n ProductSearchContext $context,\r\n ProductSearchQuery $query\r\n )\r\n {\r\n\r\n \/\/R\u00e9cup\u00e9ration des produits ( aucun changement par rapport au listing des nouveaux produits )\r\n if (!$products = $this-&gt;getProductsOrCount($context, $query, 'products')) {\r\n $products = array();\r\n }\r\n\r\n $count = $this-&gt;getProductsOrCount($context, $query, 'count');\r\n\r\n \/**\r\n * Gestion du r\u00e9sulat\r\n * Envoi de la productSearchResult\r\n *\/\r\n $results = new ProductSearchResult();\r\n\r\n\r\n if (!empty($products)) {\r\n\r\n \/\/D\u00e9finition des r\u00e9sultats des produits\r\n $results\r\n -&gt;setTotalProductsCount($count)\r\n -&gt;setProducts($products);\r\n \/\/D\u00e9finition des tris disponibles ( Utilisation de ceux par d\u00e9faut )\r\n $results-&gt;setAvailableSortOrders(\r\n $this-&gt;sortOrderFactory-&gt;getDefaultSortOrders()\r\n\r\n );\r\n\r\n \/\/R\u00e9cup\u00e9ration des filtres actifs , il sont situ\u00e9s dans l'url sous la forme q=filter-1|filtre-2 ect..\r\n $activeFilters = explode('|',$query-&gt;getEncodedFacets());\r\n \/\/D\u00e9finition des facettes disponibles ( c'est ici qu'on va d\u00e9finir nos facettes )\r\n $results-&gt;setFacetCollection(\r\n $this-&gt;getSampleFacets($activeFilters)\r\n );\r\n\r\n \/\/D\u00e9finition des facettes actuellement utilis\u00e9es\r\n $results-&gt;setEncodedFacets(\r\n $query-&gt;getEncodedFacets()\r\n\r\n );\r\n\r\n }\r\n return $results;\r\n\r\n }\r\n\r\n \r\n\r\n \/**\r\n * R\u00e9cup\u00e9ration des produits et du d\u00e9compte\r\n * Dans cette partie on reprends le fonctionnement du controller des nouveaux produits\r\n * \r\n * @param ProductSearchContext $context\r\n * @param ProductSearchQuery $query\r\n * @param type $type\r\n * @return type\r\n *\/\r\n\r\n private function getProductsOrCount(\r\n ProductSearchContext $context,\r\n ProductSearchQuery $query,\r\n $type = 'products'\r\n ) {\r\n\r\n return Product::getNewProducts(\r\n $context-&gt;getIdLang(),\r\n $query-&gt;getPage(),\r\n $query-&gt;getResultsPerPage(),\r\n $type !== 'products',\r\n $query-&gt;getSortOrder()-&gt;toLegacyOrderBy(),\r\n $query-&gt;getSortOrder()-&gt;toLegacyOrderWay()\r\n );\r\n \r\n }\r\n\r\n \r\n\r\n \/**\r\n * Fonction d'explication sur comment afficher des facettes\r\n * @return FacetCollection\r\n *\/\r\n protected function getSampleFacets($activeFilters)\r\n\r\n { \r\n\r\n \/\/Gestion des filtres actifs\r\n $activeFiltersQueryString ='';\r\n $activeFiltersQueryString .= implode('|',$activeFilters);\r\n\r\n\r\n \/\/Cr\u00e9ation d'une collection de facettes\r\n $collection = new FacetCollection();\r\n\r\n \/\/Cr\u00e9ation d'une facette\r\n $facet = new Facet();\r\n $facet-&gt;setLabel('Facette 1')\r\n -&gt;setType('custom')\r\n -&gt;setDisplayed(true) \/\/Flag pour afficher ou nom la facette\r\n -&gt;setWidgetType('checkbox') \/\/Type de widget\r\n -&gt;setMultipleSelectionAllowed(true); \/\/D\u00e9fini si on peut cocher plusieurs variantes\r\n\r\n\r\n \/\/Ajout de filtres \u00e0 cette facette\r\n $encodedFactetsUrl1 = $activeFiltersQueryString != '' ? $activeFiltersQueryString.\"|test-1\": \"test-1\";\r\n\r\n $filter1 = new Filter();\r\n $filter1-&gt;setLabel('filtre 1') \/\/Libell\u00e9 du filtre\r\n -&gt;setDisplayed(true) \/\/Flag pour afficher ou nom le filtre\r\n -&gt;setActive(in_array(\"test-1\",$activeFilters) ? true : false ) \/\/D\u00e9finition si le filtre est actif ou non\r\n -&gt;setType('test') \/\/ Type du filtre\r\n -&gt;setValue('2') \/\/Valeur du filtre\r\n -&gt;setNextEncodedFacets($encodedFactetsUrl1) \/\/Url pour afficher la filtre\r\n -&gt;setMagnitude(1); \/\/Nombre de r\u00e9sultats du filtre\r\n\r\n \/\/Ajout du filtre \u00e0 la facette\r\n $facet-&gt;addFilter($filter1); \r\n\r\n $encodedFactetsUrl2 = $activeFiltersQueryString != '' ? $activeFiltersQueryString.\"|test-2\": \"test-2\";\r\n\r\n \/\/Idem pour un 2\u00e8me filtre\r\n $filter2 = new Filter();\r\n $filter2-&gt;setLabel('filtre 2') \/\/Libell\u00e9 du filtre\r\n -&gt;setDisplayed(true) \/\/Flag pour afficher ou nom le filtre\r\n -&gt;setActive(in_array(\"test-2\",$activeFilters) ? true : false ) \/\/D\u00e9finition si le filtre est actif ou non\r\n -&gt;setType('test') \/\/ Type du filtre\r\n -&gt;setValue('2') \/\/Valeur du filtre\r\n -&gt;setNextEncodedFacets($encodedFactetsUrl2) \/\/Url pour afficher la filtre\r\n -&gt;setMagnitude(3); \/\/Nombre de r\u00e9sultats du filtre\r\n \/\/Ajout du filtre \u00e0 la facette\r\n $facet-&gt;addFilter($filter2); \r\n\r\n \r\n \/\/Ajout de la facette \u00e0 la collection\r\n $collection-&gt;addFacet($facet);\r\n\r\n \/\/Renvoi de la collection de facette\r\n return $collection;\r\n }\r\n \r\n \/**\r\n * Provider de recherche par d\u00e9faut\r\n * @return NewProductsProductSearchProvider\r\n *\/\r\n protected function getDefaultProductSearchProvider()\r\n {\r\n return new NewProductsProductSearchProvider(\r\n Context::getContext()-&gt;getTranslator()\r\n );\r\n }\r\n}\r\n<\/pre>\n<p>La s\u00e9lection \/ d\u00e9selection est fonctionnelle lorsqu&rsquo;on clique sur les diff\u00e9rents \u00e9l\u00e9ments et tous les param\u00e8tres sont bien pass\u00e9s.<br \/>\nNotre navigation \u00e0 facette est en place ( sans prise en compte des donn\u00e9es )<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1818\" src=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2018\/08\/facettes.jpg\" alt=\"Navigation facettes\" width=\"941\" height=\"672\" srcset=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2018\/08\/facettes.jpg 941w, https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2018\/08\/facettes-300x214.jpg 300w, https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2018\/08\/facettes-768x548.jpg 768w\" sizes=\"auto, (max-width: 941px) 100vw, 941px\" \/><\/p>\n<p><a name=\"tri\"><\/a><strong>Tri et gestion des facettes<\/strong><\/p>\n<p>Maintenant que la navigation \u00e0 facette est fonctionnelle la prochaine \u00e9tape est de renvoyer uniquement les facettes et les produits disponibles pour la s\u00e9lection demand\u00e9es.<br \/>\nPour cette partie nous allons donc rajouter un filtre sur les cat\u00e9gories par d\u00e9faut des produits.<\/p>\n<p>Pour cela nous allons devoir rajouter 2 nouvelles fonction dans notre module :<\/p>\n<ul>\n<li>getNewProducts =&gt; Reprends la fonction Product::getNewProducts et lui appliquera les filtres s\u00e9lectionn\u00e9s<\/li>\n<li>getNewProductsCategoryFilters =&gt; Renverra la liste des filtres disponibles pour la s\u00e9l\u00e9ection des produits<\/li>\n<\/ul>\n<p>Voici le code complet du module fonctionnel qui permets de filtrer par les cat\u00e9gories par d\u00e9faut du produit.<br \/>\nFichier <em>classes\/Hh_facetedSearchProductSearchProvider.php<\/em><\/p>\n<pre lang=\"php\" escaped=\"true\">\u00a0\/\/Use pour la recherche standard \r\n use PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchProviderInterface; \r\n use PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchContext; \r\n use PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchQuery; \r\n use PrestaShop\\PrestaShop\\Core\\Product\\Search\\ProductSearchResult; \r\n use PrestaShop\\PrestaShop\\Core\\Product\\Search\\SortOrderFactory; \/\/Provider par d\u00e9faut \r\n use PrestaShop\\PrestaShop\\Adapter\\NewProducts\\NewProductsProductSearchProvider; \r\n \r\n class Hh_facetedSearchProductSearchProvider implements ProductSearchProviderInterface { \r\n \r\n private $module; \r\n private $sortOrderFactory; \r\n \r\n public function __construct( Hh_FacetedSearch $module ) { \r\n\t\t$this-&gt;module = $module;\r\n        $this-&gt;sortOrderFactory = new SortOrderFactory($this-&gt;module-&gt;getTranslator());\r\n    }\r\n \r\n    \/**\r\n     * @param ProductSearchContext $context\r\n     * @param ProductSearchQuery $query\r\n     * @return ProductSearchResult\r\n     *\/\r\n    public function runQuery(\r\n        ProductSearchContext $context,\r\n        ProductSearchQuery $query\r\n    )\r\n    {\r\n \r\n        \/\/R\u00e9cup\u00e9ration des filtres actifs\r\n        $activeFilters = explode('|',$query-&gt;getEncodedFacets());\r\n \r\n        \/\/R\u00e9cup\u00e9ration des r\u00e9sultats initiaux ( page des nouveaux produits )\r\n        if (!$products = $this-&gt;getProductsOrCount($context, $query, 'products',$activeFilters)) {\r\n            $products = array();\r\n        }\r\n        $count = $this-&gt;getProductsOrCount($context, $query, 'count',$activeFilters);\r\n \r\n \r\n        \/\/R\u00e9cup\u00e9ration des filtres de cat\u00e9gories dispo pour la s\u00e9lection de produits\r\n        $categoryfilters = $this-&gt;module-&gt;getNewProductsCategoryFilters($products,$activeFilters);\r\n \r\n        \/**\r\n         * Derni\u00e8re Etape : Gestion du r\u00e9sulat\r\n         * Envoi de la productSearchResult\r\n         *\/\r\n        $results = new ProductSearchResult();\r\n \r\n        \/\/D\u00e9finition des r\u00e9sultats des produits\r\n        $results\r\n            -&gt;setTotalProductsCount($count)\r\n            -&gt;setProducts($products);\r\n \r\n        \/\/D\u00e9finition des tris disponibles\r\n        $results-&gt;setAvailableSortOrders(\r\n            $this-&gt;sortOrderFactory-&gt;getDefaultSortOrders()\r\n        );\r\n \r\n        \/\/D\u00e9finition des facettes disponibles\r\n        if ( sizeof($categoryfilters-&gt;getFacets())){\r\n            $results-&gt;setFacetCollection(\r\n                $categoryfilters \/\/C'est ici qu'on assigne les filtres de notre fonction\r\n            );\r\n        }\r\n \r\n        \/\/D\u00e9finition des facettes utilis\u00e9es\r\n        $results-&gt;setEncodedFacets(\r\n          $query-&gt;getEncodedFacets()\r\n        );\r\n \r\n        return $results;\r\n \r\n    }\r\n \r\n    private function getProductsOrCount(\r\n        ProductSearchContext $context,\r\n        ProductSearchQuery $query,\r\n        $type = 'products',\r\n        $activeFilter = array()\r\n    ) {\r\n        \/\/La fonction appell\u00e9e ici est celle du module\r\n        return $this-&gt;module-&gt;getNewProducts(\r\n            $context-&gt;getIdLang(),\r\n            $query-&gt;getPage(),\r\n            $query-&gt;getResultsPerPage(),\r\n            $type !== 'products',\r\n            $query-&gt;getSortOrder()-&gt;toLegacyOrderBy(),\r\n            $query-&gt;getSortOrder()-&gt;toLegacyOrderWay(),\r\n            Context::getContext(),\r\n            $activeFilter \/\/Rajout d'un param\u00e8tre pour passer les filtre s\u00e9lectionn\u00e9s\r\n        );\r\n    }\r\n \r\n    \/**\r\n     * Provider de recherche par d\u00e9faut\r\n     * @return SearchProductSearchProvider\r\n     *\/\r\n    protected function getDefaultProductSearchProvider()\r\n    {\r\n        return new NewProductsProductSearchProvider(\r\n            Context::getContext()-&gt;getTranslator()\r\n        );\r\n    }\r\n \r\n}<\/pre>\n<p>Et le fichier du module. <em>hh_facetedsearch.php<\/em><\/p>\n<pre lang=\"php\" escaped=\"true\"> \r\nrequire_once dirname(__FILE__).'\/classes\/Hh_facetedSearchProductSearchProvider.php'; \r\n\/\/Use pour les facettes \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\FacetCollection; #Collection de facettes \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\Facet; #Classe de la facette \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\Filter; #Classe des filtres \r\nuse PrestaShop\\PrestaShop\\Core\\Product\\Search\\URLFragmentSerializer; #Pour transformer l'url \r\n\r\nclass Hh_FacetedSearch extends Module { \r\n\r\n     public function __construct() { \r\n\t     $this->author = 'hhennes';\r\n        $this->name = 'hh_facetedsearch';\r\n        $this->tab = 'test';\r\n        $this->version = '0.1.0';\r\n        $this->bootstrap = true;\r\n        parent::__construct();\r\n \r\n        $this->displayName = $this->l('HH Faceted Search');\r\n        $this->description = $this->l('HH Sample Facets Implementation');\r\n \r\n \r\n    }\r\n \r\n    \/**\r\n     * Module installation.\r\n     *\r\n     * @return bool Success of the installation\r\n     *\/\r\n    public function install()\r\n    {\r\n        return parent::install()\r\n            && $this->registerHook('productSearchProvider');\r\n    }\r\n \r\n    \/**\r\n     * Dans ce hook on intercepte la requ\u00eate des nouveaux produits pour y ajouter des facettes\r\n     * @param $params\r\n     * @return Hh_facetedSearchProductSearchProvider\r\n     *\/\r\n    public function hookProductSearchProvider($params)\r\n    {\r\n        \/\/dump($this->context->controller);\r\n        $query = $params['query'];\r\n        if ($query->getQueryType() == 'new-products') {\r\n            return new Hh_facetedSearchProductSearchProvider($this);\r\n        }\r\n    }\r\n \r\n    \/**\r\n     * A partir des produits s\u00e9lectionn\u00e9s on d\u00e9duit les filtres de cat\u00e9gories \u00e0 afficher\r\n     * @param array $activeFilters\r\n     * @return FacetCollection\r\n     *\/\r\n    public function getNewProductsCategoryFilters($products , array $activeFilters)\r\n    {\r\n        \/\/R\u00e9cup\u00e9ration des cat\u00e9gories des produits et de leurs filtres\r\n        $categoriesArray = [];\r\n        foreach ( $products as $product )\r\n        {\r\n            if ( !array_key_exists($product['id_category_default'],$categoriesArray)){\r\n                $categoriesArray[$product['id_category_default']] = 1;\r\n            } else {\r\n                $categoriesArray[$product['id_category_default']] = (int)$categoriesArray[$product['id_category_default']]+1;\r\n            }\r\n        }\r\n \r\n        $activeFiltersQueryString ='';\r\n        $activeFiltersQueryString .= implode('|',$activeFilters);\r\n \r\n        \/\/Cr\u00e9ation d'une collection de facettes\r\n        $collection = new FacetCollection();\r\n \r\n        if ( sizeof($categoriesArray)) {\r\n \r\n            \/\/Cr\u00e9ation d'une facette\r\n            $facet = new Facet();\r\n            $facet->setLabel($this->l('Cat\u00e9gories'))\r\n                ->setType('category')\r\n                ->setDisplayed(true)\r\n                ->setWidgetType('checkbox')\r\n                ->setMultipleSelectionAllowed(true);\r\n \r\n            \/\/Cr\u00e9ation des filtres avec les categories disponibles\r\n            foreach ( $categoriesArray as $categoryId => $categoryCount) {\r\n \r\n                $encodedFactetsUrl = $activeFiltersQueryString != '' ? $activeFiltersQueryString.\"|cat-\".$categoryId : \"cat-\".$categoryId;\r\n \r\n                $category = new Category($categoryId,$this->context->language->id);\r\n                $filter = new Filter();\r\n                $filter->setLabel($category->name)\r\n                    ->setDisplayed(true)\r\n                    ->setActive(in_array(\"cat-\".$categoryId,$activeFilters) ? true : false )\r\n                    ->setType('category')\r\n                    ->setValue($categoryId)\r\n                    ->setNextEncodedFacets($encodedFactetsUrl)\r\n                    ->setMagnitude($categoryCount);\r\n \r\n                \/\/Ajout du filtre \u00e0 la facette\r\n                $facet->addFilter($filter);\r\n            }\r\n \r\n            \/\/Ajout de la facette \u00e0 la collection\r\n            $collection->addFacet($facet);\r\n        }\r\n \r\n \r\n        return $collection;\r\n    }\r\n \r\n    \/**\r\n     * Get new products\r\n     * ( Fonction modifi\u00e9e de la classe produit avec gestion des filtres )\r\n     *\r\n     * @param int $id_lang Language id\r\n     * @param int $pageNumber Start from (optional)\r\n     * @param int $nbProducts Number of products to return (optional)\r\n     * @return array New products\r\n     *\/\r\n    public function getNewProducts(\r\n        $id_lang,\r\n        $page_number = 0,\r\n        $nb_products = 10,\r\n        $count = false,\r\n        $order_by = null,\r\n        $order_way = null,\r\n        Context $context = null ,\r\n        $applieds_filters = array() \/\/Nouveau param\u00e8tre de test\r\n    )\r\n    {\r\n        $now = date('Y-m-d') . ' 00:00:00';\r\n        if (!$context) {\r\n            $context = Context::getContext();\r\n        }\r\n \r\n        $front = true;\r\n        if (!in_array($context->controller->controller_type, array('front', 'modulefront'))) {\r\n            $front = false;\r\n        }\r\n \r\n        if ($page_number < 1) {\r\n            $page_number = 1;\r\n        }\r\n        if ($nb_products < 1) { $nb_products = 10; } if (empty($order_by) || $order_by == 'position') { $order_by = 'date_add'; } if (empty($order_way)) { $order_way = 'DESC'; } if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add' || $order_by == 'date_upd') { $order_by_prefix = 'product_shop'; } elseif ($order_by == 'name') { $order_by_prefix = 'pl'; } if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way)) { die(Tools::displayError()); } $sql_groups = ''; if (Group::isFeatureActive()) { $groups = FrontController::getCurrentCustomerGroups(); $sql_groups = ' AND EXISTS(SELECT 1 FROM `'._DB_PREFIX_.'category_product` cp JOIN `'._DB_PREFIX_.'category_group` cg ON (cp.id_category = cg.id_category AND cg.`id_group` '.(count($groups) ? 'IN ('.implode(',', $groups).')' : '= '.(int)Configuration::get('PS_UNIDENTIFIED_GROUP')).') WHERE cp.`id_product` = p.`id_product`)'; } if (strpos($order_by, '.') > 0) {\r\n            $order_by = explode('.', $order_by);\r\n            $order_by_prefix = $order_by[0];\r\n            $order_by = $order_by[1];\r\n        }\r\n \r\n        $nb_days_new_product = (int) Configuration::get('PS_NB_DAYS_NEW_PRODUCT');\r\n \r\n        if ($count) {\r\n \r\n            \/\/Gestion des conditions dans le count\r\n            if ( sizeof($applieds_filters)) {\r\n                $sqlFilters = \"\";\r\n                foreach ($applieds_filters as $applieds_filter) {\r\n \r\n                    if ( $applieds_filter == \"\" ) {\r\n                        continue;\r\n                    }\r\n                    $catId = str_replace('cat-','',$applieds_filter);\r\n                    if ( $catId == 0 ) {\r\n                        continue;\r\n                    }\r\n                    $sqlFilters .= \" INNER JOIN \"._DB_PREFIX_.\"product ps_cat_\".$catId.' ON  p.id_product = ps_cat_'.$catId.'.id_product AND ps_cat_'.$catId.'.id_category_default = '.$catId.' ';\r\n                }\r\n            }\r\n \r\n            $sql = 'SELECT COUNT(p.`id_product`) AS nb\r\n\t\t\t\t\tFROM `'._DB_PREFIX_.'product` p \r\n                    '.$sqlFilters.'\r\n\t\t\t\t\t'.Shop::addSqlAssociation('product', 'p').'\r\n\t\t\t\t\tWHERE product_shop.`active` = 1\r\n\t\t\t\t\tAND product_shop.`date_add` > \"'.date('Y-m-d', strtotime('-'.$nb_days_new_product.' DAY')).'\"\r\n\t\t\t\t\t'.($front ? ' AND product_shop.`visibility` IN (\"both\", \"catalog\")' : '').'\r\n\t\t\t\t\t'.$sql_groups;\r\n \r\n            return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);\r\n        }\r\n \r\n        $sql = new DbQuery();\r\n        $sql->select(\r\n            'p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description`, pl.`description_short`, pl.`link_rewrite`, pl.`meta_description`,\r\n\t\t\tpl.`meta_keywords`, pl.`meta_title`, pl.`name`, pl.`available_now`, pl.`available_later`, image_shop.`id_image` id_image, il.`legend`, m.`name` AS manufacturer_name,\r\n\t\t\t(DATEDIFF(product_shop.`date_add`,\r\n\t\t\t\tDATE_SUB(\r\n\t\t\t\t\t\"'.$now.'\",\r\n\t\t\t\t\tINTERVAL '.$nb_days_new_product.' DAY\r\n\t\t\t\t)\r\n\t\t\t) > 0) as new'\r\n        );\r\n \r\n        $sql->from('product', 'p');\r\n        $sql->join(Shop::addSqlAssociation('product', 'p'));\r\n        $sql->leftJoin('product_lang', 'pl', '\r\n\t\t\tp.`id_product` = pl.`id_product`\r\n\t\t\tAND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl')\r\n        );\r\n        $sql->leftJoin('image_shop', 'image_shop', 'image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop='.(int)$context->shop->id);\r\n        $sql->leftJoin('image_lang', 'il', 'image_shop.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang);\r\n        $sql->leftJoin('manufacturer', 'm', 'm.`id_manufacturer` = p.`id_manufacturer`');\r\n \r\n        \/\/Gestion des filtres ( Pour l'exemple on ne g\u00e8re que des cat\u00e9gories )\r\n        if ( sizeof($applieds_filters)) {\r\n            foreach ($applieds_filters as $applieds_filter) {\r\n \r\n                if ( $applieds_filter == \"\") {\r\n                    continue;\r\n                }\r\n \r\n                $catId = str_replace('cat-','',$applieds_filter);\r\n                if ( $catId == 0 ) {\r\n                    continue;\r\n                }\r\n                $sql->innerJoin('product','ps_cat_'.$catId,'p.id_product = ps_cat_'.$catId.'.id_product AND ps_cat_'.$catId.'.id_category_default = '.$catId);\r\n            }\r\n        }\r\n \r\n        \/\/Fin Gestion des filtres\r\n \r\n        $sql->where('product_shop.`active` = 1');\r\n        if ($front) {\r\n            $sql->where('product_shop.`visibility` IN (\"both\", \"catalog\")');\r\n        }\r\n        $sql->where('product_shop.`date_add` > \"'.date('Y-m-d', strtotime('-'.$nb_days_new_product.' DAY')).'\"');\r\n        if (Group::isFeatureActive()) {\r\n            $groups = FrontController::getCurrentCustomerGroups();\r\n            $sql->where('EXISTS(SELECT 1 FROM `'._DB_PREFIX_.'category_product` cp\r\n\t\t\t\tJOIN `'._DB_PREFIX_.'category_group` cg ON (cp.id_category = cg.id_category AND cg.`id_group` '.(count($groups) ? 'IN ('.implode(',', $groups).')' : '= 1').')\r\n\t\t\t\tWHERE cp.`id_product` = p.`id_product`)');\r\n        }\r\n \r\n        $sql->orderBy((isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').'`'.pSQL($order_by).'` '.pSQL($order_way));\r\n        $sql->limit($nb_products, (int)(($page_number-1) * $nb_products));\r\n \r\n        if (Combination::isFeatureActive()) {\r\n            $sql->select('product_attribute_shop.minimal_quantity AS product_attribute_minimal_quantity, IFNULL(product_attribute_shop.id_product_attribute,0) id_product_attribute');\r\n            $sql->leftJoin('product_attribute_shop', 'product_attribute_shop', 'p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop='.(int)$context->shop->id);\r\n        }\r\n        $sql->join(Product::sqlStock('p', 0));\r\n \r\n        $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);\r\n \r\n        if (!$result) {\r\n            return false;\r\n        }\r\n \r\n        if ($order_by == 'price') {\r\n            Tools::orderbyPrice($result, $order_way);\r\n        }\r\n        $products_ids = array();\r\n        foreach ($result as $row) {\r\n            $products_ids[] = $row['id_product'];\r\n        }\r\n        \/\/ Thus you can avoid one query per product, because there will be only one query for all the products of the cart\r\n        Product::cacheFrontFeatures($products_ids, $id_lang);\r\n        return Product::getProductsProperties((int)$id_lang, $result);\r\n    }\r\n \r\n}\r\n<\/pre>\n<p>Une fois tout cela en place vous devriez avoir une navigation \u00e0 facette sur les cat\u00e9gories des produits en place comme sur la capture ci-dessous.&lt;br<br \/>\nCe principe est applicable \u00e0 toutes les pages prestashop \u00e9tendant les listings.<br \/>\nEt il est \u00e9galement possible de cr\u00e9er des pages de listings personnalis\u00e9es ( j&rsquo;y reviendrais peut \u00eatre dans un article ult\u00e9rieurement )<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1821\" src=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2018\/08\/facettes-final.jpg\" alt=\"Navigation \u00e0 facettes prestashop\" width=\"780\" height=\"541\" srcset=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2018\/08\/facettes-final.jpg 780w, https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2018\/08\/facettes-final-300x208.jpg 300w, https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2018\/08\/facettes-final-768x533.jpg 768w\" sizes=\"auto, (max-width: 780px) 100vw, 780px\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>La gestion des listings \u00e0 compl\u00e9tement \u00e9t\u00e9 r\u00e9\u00e9crite sur Prestashop 1.7 , et la bonne nouvelle et qu&rsquo;ils g\u00e8rent maintenant nativement la navigation \u00e0 facettes ( Tout du moins la partie front \ud83d\ude09 )<br \/>\nNous allons voir ensemble comment mettre en place une <strong>navigation \u00e0 facette basique sur la page des nouveaux produits.<\/strong><\/p>\n<p><strong>Sommaire :<\/strong><\/p>\n<ol>\n<li>Fonctionnement g\u00e9n\u00e9ral<\/li>\n<li>Affichage de facettes<\/li>\n<li>Tri et gestion des facettes<\/li>\n<\/ol>\n<p>Cet article est plus dans une d\u00e9marche d&rsquo;explication du fonctionnement, que dans la r\u00e9alisation d&rsquo;un module purement fonctionnel, ou la r\u00e9alisation sera assez chronophage,<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[245],"tags":[365,526,525,524],"class_list":["post-1814","post","type-post","status-publish","format-standard","hentry","category-prestashop-2","tag-filtres","tag-layered-navigation","tag-navigation-facettes","tag-pretashop-17","prestashop-1-5","prestashop-1-7","prestashop-1-7-8","prestashop-8-0","prestashop-8-1"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts\/1814","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/comments?post=1814"}],"version-history":[{"count":12,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts\/1814\/revisions"}],"predecessor-version":[{"id":1881,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts\/1814\/revisions\/1881"}],"wp:attachment":[{"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/media?parent=1814"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/categories?post=1814"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/tags?post=1814"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}