{"id":2350,"date":"2021-11-02T20:29:20","date_gmt":"2021-11-02T18:29:20","guid":{"rendered":"https:\/\/www.h-hennes.fr\/blog\/?p=2350"},"modified":"2021-12-22T17:50:57","modified_gmt":"2021-12-22T15:50:57","slug":"prestashop-ajouter-des-champs-a-une-categorie","status":"publish","type":"post","link":"https:\/\/www.h-hennes.fr\/blog\/2021\/11\/02\/prestashop-ajouter-des-champs-a-une-categorie\/","title":{"rendered":"Prestashop : Ajouter des champs \u00e0 une cat\u00e9gorie"},"content":{"rendered":"\n<p>Cette article inaugure une nouvelle s\u00e9rie d&rsquo;articles \u00e0 venir sur comment \u00e9tendre les entit\u00e9s Prestashop sans surcharges.<br>C&rsquo;est connu depuis tr\u00e8s longtemps qu&rsquo;il est d\u00e9conseill\u00e9 d&rsquo;utiliser les surcharges pour ajouter des champs, pour autant il n&rsquo;y avait pas forc\u00e9ment d&rsquo;autres solutions, c&rsquo;est de moins en moins vrai \ud83d\ude42<\/p>\n<p>Ce tutoriel est uniquement compatible avec les versions de Prestashop sup\u00e9rieures \u00e0 la 1.7.6 ( Lorsque la page de gestion admin est pass\u00e9e sous symfony )<br>Mais le hook essentiel <strong>filterCategoryContent <\/strong>est pr\u00e9sent depuis la version 1.7.1<br>La m\u00eame logique avec une autre gestion dans l&rsquo;administration est donc possible.<\/p>\n<p>Pour l&rsquo;exemple nous allons donc cr\u00e9er un module <strong>hh_categoryfields<\/strong> qui va ajouter 3 champs aux cat\u00e9gories :<\/p>\n<ul>\n<li>Code erp , type texte commun \u00e0 toutes les langues et tous les sites<\/li>\n<li>Description SEO , type texte avec possibilit\u00e9 de le traduire pour chaque langue install\u00e9e dans chaque boutique<\/li>\n<li>Image : Image commune \u00e0 toutes les langues et tous les sites.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Ajout des champs dans le formulaire d&rsquo;\u00e9dition admin<\/h3>\n\n\n\n<p>Pour l&rsquo;ajout des champs dans le formulaire d&rsquo;administration j&rsquo;ai d\u00e9j\u00e0 d\u00e9taill\u00e9 le concept de fonctionnement dans l&rsquo;article : <a href=\"https:\/\/www.h-hennes.fr\/blog\/2019\/08\/05\/prestashop-1-7-ajouter-des-champs-dans-un-formulaire-dadministration\/\" target=\"_blank\" rel=\"noopener\">https:\/\/www.h-hennes.fr\/blog\/2019\/08\/05\/prestashop-1-7-ajouter-des-champs-dans-un-formulaire-dadministration\/<\/a><\/p>\n<p>Le module sera donc greff\u00e9 sur les hooks suivants :<\/p>\n<ul>\n<li>hookAction<strong>Category<\/strong>FormBuilderModifier<\/li>\n<li>hookActionAfterCreate<strong>Category<\/strong>FormHandler<\/li>\n<li>hookActionAfterUpdate<strong>Category<\/strong>FormHandler<\/li>\n<\/ul>\n<p>Le code complet sera disponible en fin d&rsquo;article<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Gestion des champs<\/h3>\n\n\n\n<p>Ces champs seront g\u00e9r\u00e9s via une classe Sp\u00e9cifique qui h\u00e9rite de la classe ObjectModel de Prestashop ( j&rsquo;ai choisi de faire simple et ne pas faire ce d\u00e9veloppement via symfony )<br>Dont voici donc son code qui est tr\u00e8s basique.<br>J&rsquo;ai uniquement ajout\u00e9 des fonctions&nbsp; pour g\u00e9rer l&rsquo;installation \/ d\u00e9sinstallation en cr\u00e9ant les tables mysql et les dossiers n\u00e9cessaires au bon fonctionnement du module<\/p>\n\n\n\n<pre lang=\"php\">\n<?php\nuse Symfony\\Component\\Filesystem\\Exception\\IOException;\nuse Symfony\\Component\\Filesystem\\Filesystem;\n\nif (!defined('_PS_VERSION_')) {\n    exit;\n}\n\nclass CategoryFields extends ObjectModel\n{\n    \/** @var int Object id *\/\n    public $id;\n    \/** @var int Identifiant de la cat\u00e9gorie prestashop *\/\n    public $id_category;\n    \/** @var string Code erp de la cat\u00e9gorie *\/\n    public $code;\n    \/** @var string image additionnelle *\/\n    public $image_field;\n    \/** @var string  Champ langue *\/\n    public $description_seo;\n\n    public static $definition = [\n        'table' => 'hh_category_field',\n        'primary' => 'id_category_extra',\n        'multilang' => true,\n        'multilang_shop' => true,\n        'fields' => [\n            'id_category' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'length' => 10],\n            'code' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'length' => 50],\n            'image_field' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'length' => 255],\n            'description_seo' => ['type' => self::TYPE_HTML, 'validate' => 'isCleanHtml', 'lang' => true]\n        ]\n    ];\n\n    \/**\n     * R\u00e9cup\u00e9ration de l'identifiant de l'entit\u00e9 via l'identifiant de cat\u00e9gorie\n     * @param int $id_category\n     * @return false|string|null\n     *\/\n    public static function getIdByCategoryId(int $id_category)\n    {\n        return Db::getInstance()->getValue(\n            (new DbQuery())\n                ->select(self::$definition['primary'])\n                ->from(self::$definition['table'])\n                ->where('id_category=' . $id_category)\n        );\n    }\n\n    \/**\n     * Installation du mod\u00e8le\n     * A ajouter dans l'installation du module\n     *\/\n    public static function installSql(): bool\n    {\n        try {\n            \/\/Cr\u00e9ation de la table avec les champs communs\n            $createTable = Db::getInstance()->execute(\n                \"CREATE TABLE IF NOT EXISTS `\"._DB_PREFIX_.\"hh_category_field`(\n                `id_category_extra` int(10)  NOT NULL AUTO_INCREMENT,\n                `id_category` INT(10) NOT NULL,\n                `code` VARCHAR (50),\n                `image_field` VARCHAR (50),\n                PRIMARY KEY (`id_category_extra`)\n                ) ENGINE=InnoDB DEFAULT CHARSET=UTF8;\"\n            );\n            \/\/Cr\u00e9ation de la table des langues\n            $createTableLang = Db::getInstance()->execute(\n                \"CREATE TABLE IF NOT EXISTS `\"._DB_PREFIX_.\"hh_category_field_lang`(\n                `id_category_extra` int(10)  NOT NULL AUTO_INCREMENT,\n                `id_shop` INT(10) NOT NULL DEFAULT '1',\n                `id_lang` INT(10) NOT NULL,\n                `description_seo` TEXT,\n                PRIMARY KEY (`id_category_extra`,`id_shop`,`id_lang`)\n                ) ENGINE=InnoDB DEFAULT CHARSET=UTF8;\"\n            );\n        } catch (PrestaShopException $e) {\n            return false;\n        }\n\n        return $createTable && $createTableLang;\n    }\n\n    \/**\n     * Suppression des tables du modules\n     * @return bool\n     *\/\n    public static function uninstallSql()\n    {\n        return Db::getInstance()->execute(\"DROP TABLE IF EXISTS \"._DB_PREFIX_.\"hh_category_field\")\n            && Db::getInstance()->execute(\"DROP TABLE IF EXISTS \"._DB_PREFIX_.\"hh_category_field_lang\");\n    }\n\n    \/**\n     * Cr\u00e9ation du dossier pour envoyer les images du modules\n     * ( Dans le dossier img de prestashop )\n     * @return bool\n     *\/\n    public static function createImageDirectory(): bool\n    {\n        $fileSystem = new Filesystem();\n        $imageDir = _PS_IMG_DIR_ . 'modules\/hh_categoryfields\/';\n        if (!$fileSystem->exists($imageDir)) {\n            try {\n                $fileSystem->mkdir($imageDir);\n            } catch (IOException $e) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    \/**\n     * Suppression du dossier pour envoyer les images du modules\n     * ( Dans le dossier img de prestashop )\n     * @return bool\n     *\/\n    public static function removeImageDirectory(): bool\n    {\n        $fileSystem = new Filesystem();\n        $imageDir = _PS_IMG_DIR_ . 'modules\/hh_categoryfields\/';\n        if (!$fileSystem->exists($imageDir)) {\n            try {\n                $fileSystem->remove($imageDir);\n            } catch (IOException $e) {\n                return false;\n            }\n        }\n        return true;\n    }\n}\n\n\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Affichage des informations en front<\/h3>\n\n\n\n<p>L&rsquo;assignation des informations sur la page des cat\u00e9gories en front est g\u00e9r\u00e9e via le hook <strong>filterCategoryContent<\/strong>.<br>Celui-ci permets de rendre disponible les informations dans l&rsquo;objet cat\u00e9gorie sur le front office.<br>Voici un code simplifi\u00e9 par rapport \u00e0 celui du module pour voir le concept<\/p>\n\n\n\n<pre escaped=\"true\" lang=\"php\">\/**\n     * Ajout de contenu de cat\u00e9gorie sans surcharge\n     * @param array $params\n     * @return array\n     *\/\n    public function hookFilterCategoryContent(array $params)\n    {\n        \/\/Ajout des valeur dans le tableau \"object\" qui correspond \u00e0 la cat\u00e9gorie\n        $params['object']['code'] = 'Code';\n        $params['object']['description_seo'] = 'Description seo';\n        return [\n            'object' =&gt; $params['object']\n        ];\n    }\n\n<\/pre>\n\n\n\n<p>Les valeurs d\u00e9finies sont ensuite directement disponible dans le template front de la cat\u00e9gorie. themes\/<em>yourtheme<\/em>\/templates\/catalog\/listing\/category.tpl<br>On peut donc par exemple rajouter le code suivant qui se chargera d&rsquo;afficher nos nouvelles propri\u00e9t\u00e9s :<\/p>\n\n\n\n<pre language=\"smarty\" escaped=\"true\">{*\n    On rajoute tous nos champs sp\u00e9ciaux dans le footer de la cat\u00e9gorie pour l'exemple\n    Mais l'avantage du hook filterCategoryContent est que le contenu est disponible partout sur la page dans n'importe quel block\n*}\n{block name='product_list_bottom'}\n    {*Contenu du parent*}\n    {include file='catalog\/_partials\/products-bottom.tpl' listing=$listing}\n    <div class=\"extra-content\">\n        {$category|dump}\n        {if isset($category.code)}\n            <p>Code Erp : <strong>{$category.code}<\/strong><\/p>\n        {\/if}\n        {if isset($category.description_seo)}\n            <div class=\"seo-description\">{$category.description_seo}<\/div>\n        {\/if}\n        {if isset($category.image_field)}\n            <img decoding=\"async\" src=\"{$category.image_field}\" alt=\"{$category.name}\">\n        {\/if}\n    <\/div>\n{\/block}\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Limites et restrictions<\/h3>\n\n\n\n<p>L&rsquo;approche sans les surcharges ne permets malheureusement pas encore de g\u00e9rer tous les cas.<br \/>Il n&rsquo;est pas encore possible de rendre ces champs disponibles dans la liste des sous-cat\u00e9gories, ni dans l&rsquo;api de prestashop.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Code complet<\/h3>\n\n\n\n<p>Voici le code complet de la classe de gestion du module.<br>La classe de gestion des champs est disponible plus haut.<\/p>\n\n\n\n<pre escaped=\"true\" lang=\"php\">\nif (!defined('_PS_VERSION_')) {\n    exit;\n}\n\nrequire_once __DIR__ . '\/classes\/CategoryFields.php';\n\n\nclass Hh_categoryfields extends Module\n{\n    public function __construct()\n    {\n        $this-&gt;name = 'hh_categoryfields';\n        $this-&gt;tab = 'others';\n        $this-&gt;version = '0.1.0';\n        $this-&gt;author = 'hhennes';\n        $this-&gt;bootstrap = true;\n        parent::__construct();\n\n        $this-&gt;displayName = $this-&gt;l('Hh Category Fields');\n        $this-&gt;description = $this-&gt;l('POC : Add category fields without override');\n    }\n\n    \/**\n     * Installation du module\n     * @return bool\n     *\/\n    public function install()\n    {\n        if (!parent::install()\n            || !$this-&gt;registerHook([\n                'actionCategoryFormBuilderModifier',\n                'actionAfterCreateCategoryFormHandler',\n                'actionAfterUpdateCategoryFormHandler',\n                'filterCategoryContent',\n            ])\n            || !CategoryFields::installSql()\n            || !CategoryFields::createImageDirectory()\n        ) {\n            return false;\n        }\n        return true;\n    }\n\n    \/**\n     * D\u00e9sinstallation du module\n     * @return bool\n     *\/\n    public function uninstall()\n    {\n        if (\n            !parent::uninstall()\n            || !CategoryFields::uninstallSql()\n            || !CategoryFields::removeImageDirectory()\n        ) {\n            return false;\n        }\n        return true;\n    }\n\n    \/**\n     * Ajout de contenu de cat\u00e9gorie sans surcharge\n     * @param array $params\n     * @return array\n     *\/\n    public function hookFilterCategoryContent(array $params)\n    {\n        $additional = $this-&gt;getCustomCategoryFields($params['object']['id']);\n        if (count($additional)) {\n            $params['object'] = array_merge($params['object'], $additional);\n            return [\n                'object' =&gt; $params['object']\n            ];\n        }\n    }\n\n    \/**\n     * R\u00e9cup\u00e9ration des informations sp\u00e9cifiques de la cat\u00e9gorie\n     *\n     * @param int $id_category\n     * @return array\n     * @throws PrestaShopDatabaseException\n     * @throws PrestaShopException\n     *\/\n    protected function getCustomCategoryFields(int $id_category): array\n    {\n        $return = [];\n        $idCategoryField = CategoryFields::getIdByCategoryId($id_category);\n        if ($idCategoryField) {\n            $categoryField = new CategoryFields($idCategoryField,$this-&gt;context-&gt;language-&gt;id);\n            $presenter = new \\PrestaShop\\PrestaShop\\Adapter\\Presenter\\Object\\ObjectPresenter();\n            $return = $presenter-&gt;present($categoryField);\n            $return['image_field'] = $this-&gt;getUploadUrl().$return['image_field'];\n            \/\/suppression des champs techniques\n            unset($return['id_category']);\n            unset($return['id']);\n        }\n        return $return;\n    }\n\n    \/**\n     * Modification du formulaire de la cat\u00e9gorie\n     * @param array $params\n     *\/\n    public function hookActionCategoryFormBuilderModifier(array $params)\n    {\n        \/\/Pour l'envoi du fichier : regarder ici : https:\/\/devdocs.prestashop.com\/1.7\/modules\/sample-modules\/extending-sf-form-with-upload-image-field\/#introduction\n        try {\n            \/\/R\u00e9cup\u00e9ration des informations des champs custom\n            $customFieldsValues = $this-&gt;getCustomFieldsValue($params['id']);\n            $locales = $this-&gt;get('prestashop.adapter.legacy.context')-&gt;getLanguages();\n\n            \/** @var \\Symfony\\Component\\Form\\FormBuilder $formBuilder *\/\n            $formBuilder = $params['form_builder'];\n\n            \/\/Ajout des champs dans le formulaire d'\u00e9dition des cat\u00e9gories\n\n            \/\/Champ standard\n            $formBuilder-&gt;add(\n                $this-&gt;name . '_code',\n                \\Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType::class,\n                [\n                    'label' =&gt; $this-&gt;l('Erp Code'),\n                    'required' =&gt; false,\n                    'constraints' =&gt; [\n                        new \\Symfony\\Component\\Validator\\Constraints\\Length([\n                            'max' =&gt; 20,\n                            'maxMessage' =&gt; $this-&gt;l('Max caracters allowed : 20'),\n                        ]),\n                    ],\n                    'data' =&gt; $customFieldsValues['code'],\n                    'help' =&gt; $this-&gt;name . ' :' . $this-&gt;l('This is the erp code')\n                ]\n            )\n                \/\/Champs langue ( qui g\u00e8re le multi-shop )\n                -&gt;add(\n                    $this-&gt;name . '_description_seo',\n                    \\PrestaShopBundle\\Form\\Admin\\Type\\TranslatableType::class,\n                    [\n                        'locales' =&gt; $locales,\n                        'type' =&gt; \\Symfony\\Component\\Form\\Extension\\Core\\Type\\TextareaType::class,\n                        'label' =&gt; $this-&gt;l('Description Seo'),\n                        'required' =&gt; false,\n                        'data' =&gt; $customFieldsValues['description_seo'],\n                        'help' =&gt; $this-&gt;name . ' :' . $this-&gt;l('Seo description for categories'),\n                    ]\n                )\n                \/\/Champ image\n                -&gt;add('image_file_upload', \\Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType::class, [\n                    'label' =&gt; $this-&gt;l('Additional image'),\n                    'required' =&gt; false,\n                    'help' =&gt; $this-&gt;name . ' :' . $this-&gt;l('Additional image')\n                ]);\n\n\n            \/\/Dans le cas ou l'image est d\u00e9finie\n            \/\/fonctionnement tr\u00e8s basique checkbox pour la supprimer\n            $categoryDatas = $this-&gt;getCustomFieldsValue($params['id']);\n            if (!empty($categoryDatas['image_field'])) {\n                $formBuilder\n                    -&gt;add($this-&gt;name . '_delete_current_image', \\Symfony\\Component\\Form\\Extension\\Core\\Type\\CheckboxType::class, [\n                        'label' =&gt; $this-&gt;l('Delete Current Image ?'),\n                        'required' =&gt; false,\n                        'help' =&gt; sprintf(\n                            $this-&gt;l('current file %s'),\n                            $this-&gt;getUploadUrl() . $categoryDatas['image_field']\n                        ),\n                    ]);\n            }\n\n            $formBuilder-&gt;setData($params['data']);\n        } catch ( Exception $e){\n            $this-&gt;log('Error :'.$e-&gt;getMessage());\n            $this-&gt;log('Error string :'.$e-&gt;getTraceAsString());\n        }\n    }\n\n    \/**\n     * Action effectu\u00e9e apr\u00e8s la cr\u00e9ation d'une cat\u00e9gorie\n     * @param array $params\n     * @return void\n     *\/\n    public function hookActionAfterCreateCategoryFormHandler(array $params): void\n    {\n        $this-&gt;updateData($params['id'], $params['form_data']);\n    }\n\n    \/**\n     * Action effectu\u00e9e apr\u00e8s la mise \u00e0 jour d'une cat\u00e9gorie\n     * @param array $params\n     * @return void\n     *\/\n    public function hookActionAfterUpdateCategoryFormHandler(array $params): void\n    {\n        $this-&gt;updateData($params['id'], $params['form_data']);\n    }\n\n    \/**\n     * R\u00e9cup\u00e9ration des informations sp\u00e9cifique de l'objet\n     * @param int $id_category\n     * @return array\n     *\/\n    protected function getCustomFieldsValue(int $id_category): array\n    {\n        try {\n            $idCategoryField = CategoryFields::getIdByCategoryId($id_category);\n            $categoryField = new CategoryFields($idCategoryField);\n            return [\n                'code' =&gt; $categoryField-&gt;code,\n                'description_seo' =&gt; $categoryField-&gt;description_seo,\n                'image_field' =&gt; $categoryField-&gt;image_field\n            ];\n        } catch (PrestaShopException $e) {\n            $this-&gt;log($e-&gt;getMessage());\n            return [\n                'code' =&gt; '',\n                'description_seo' =&gt; [],\n                'image_field' =&gt; ''\n            ];\n        }\n    }\n\n    \/**\n     * Fonction qui va effectuer la mise \u00e0 jour\n     * @param int $id_category\n     * @param array $data\n     * @return void\n     *\/\n    protected function updateData(int $id_category, array $data): void\n    {\n        try {\n\n            $idCategoryField = CategoryFields::getIdByCategoryId($id_category);\n            $categoryField = new CategoryFields($idCategoryField);\n            $categoryField-&gt;id_category = $id_category;\n\n            foreach ($data as $key =&gt; $value) {\n                if (strpos($key, $this-&gt;name) !== false) {\n                    $objectKey = str_replace($this-&gt;name . '_', '', $key);\n                    $categoryField-&gt;$objectKey = $value;\n                }\n            }\n\n            \/** @var \\Symfony\\Component\\HttpFoundation\\File\\UploadedFile $uploadedFile *\/\n            $uploadedFile = $data['image_file_upload'];\n\n            \/\/Gestion de la suppression de l'image\n            if (array_key_exists($this-&gt;name . '_delete_current_image', $data)\n                &amp;&amp; $data[$this->name . '_delete_current_image'] == 1\n                &amp;&amp; !$uploadedFile instanceof \\Symfony\\Component\\HttpFoundation\\File\\UploadedFile\n            ) {\n                $this-&gt;deleteCurrentImage($categoryField-&gt;image_field);\n                unset($data[$this-&gt;name . '_image_field']);\n                $categoryField-&gt;image_field = '';\n            }\n\n            \/\/Gestion de l'envoi de l'image ( simplifi\u00e9e )\n            \/\/@todo : Tester sur les versions 1.7.6 \/ 1.7.7 \/ 1.7.8\n            if ($uploadedFile instanceof \\Symfony\\Component\\HttpFoundation\\File\\UploadedFile) {\n                if ($uploadedFile-&gt;isValid()) {\n                    $this-&gt;uploadFile($uploadedFile);\n                    $categoryField-&gt;image_field = $uploadedFile-&gt;getClientOriginalName();\n                }\n            }\n            $categoryField-&gt;save();\n        } catch (Exception $e) {\n            $this-&gt;log($e-&gt;getMessage());\n        }\n    }\n\n    \/**\n     * Fonction (tr\u00e8s basique et sans v\u00e9rif ) d'envoi du fichier\n     *\n     * @param \\Symfony\\Component\\HttpFoundation\\File\\UploadedFile $uploadedFile\n     * @return bool\n     *\/\n    protected function uploadFile(\\Symfony\\Component\\HttpFoundation\\File\\UploadedFile $uploadedFile): bool\n    {\n        try {\n            \/\/Bonne pratique : on g\u00e8re l'envoi des images dans le dossier \"img\" de prestashop et pas dans le module\n            $uploadDir = $this-&gt;getUploadDir();\n            $uploadedFile-&gt;move($uploadDir, $uploadedFile-&gt;getClientOriginalName());\n        } catch (Exception $e) {\n            return false;\n        }\n        return true;\n    }\n\n    \/**\n     * Suppression d'une image existante\n     * @param string $imageName\n     * @return bool\n     *\/\n    protected function deleteCurrentImage(string $imageName): bool\n    {\n        if (is_file($this-&gt;getUploadDir() . $imageName)) {\n            return unlink($this-&gt;getUploadDir() . $imageName);\n        }\n\n        return true;\n    }\n\n    \/**\n     * R\u00e9cup\u00e9ration du dossier d'envoi des images\n     * @return string\n     *\/\n    protected function getUploadDir(): string\n    {\n        return _PS_IMG_DIR_ . 'modules\/' . $this-&gt;name . '\/';\n    }\n\n    \/**\n     * R\u00e9cup\u00e9ration de l'url des images\n     * @return string\n     *\/\n    protected function getUploadUrl(): string\n    {\n        $uriPath = 'img\/modules\/' . $this-&gt;name . '\/';\n        return $this-&gt;context-&gt;link-&gt;getBaseLink() . $uriPath;\n    }\n\n    \/**\n     * Fonction basique de log\n     * @param string $message\n     * @return void\n     *\/\n    protected function log($message): void\n    {\n        file_put_contents(\n            dirname(__FILE__) . 'debug.log',\n            date('Y-m-d H:i:s') . $message . \"\\n\",\n            FILE_APPEND\n        );\n    }\n}\n\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Cette article inaugure une nouvelle s\u00e9rie d&rsquo;articles \u00e0 venir sur comment \u00e9tendre les entit\u00e9s Prestashop sans surcharges.C&rsquo;est connu depuis tr\u00e8s longtemps qu&rsquo;il est d\u00e9conseill\u00e9 d&rsquo;utiliser les surcharges pour ajouter des champs, pour autant il n&rsquo;y avait pas forc\u00e9ment d&rsquo;autres solutions, c&rsquo;est de moins en moins vrai \ud83d\ude42 Ce tutoriel est uniquement compatible avec les versions [&hellip;]<\/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":[424,104],"class_list":["post-2350","post","type-post","status-publish","format-standard","hentry","category-prestashop-2","tag-categories","tag-prestashop","prestashop-1-4","prestashop-1-6","prestashop-1-7-5","prestashop-1-7-6","prestashop-1-7-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\/2350","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=2350"}],"version-history":[{"count":5,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts\/2350\/revisions"}],"predecessor-version":[{"id":2372,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts\/2350\/revisions\/2372"}],"wp:attachment":[{"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/media?parent=2350"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/categories?post=2350"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/tags?post=2350"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}