{"id":4687,"date":"2023-11-12T13:29:03","date_gmt":"2023-11-12T11:29:03","guid":{"rendered":"https:\/\/www.h-hennes.fr\/blog\/?p=4687"},"modified":"2023-11-12T13:29:05","modified_gmt":"2023-11-12T11:29:05","slug":"prestashop-hhmodule-manager-fonctionnement-technique-et-extension","status":"publish","type":"post","link":"https:\/\/www.h-hennes.fr\/blog\/2023\/11\/12\/prestashop-hhmodule-manager-fonctionnement-technique-et-extension\/","title":{"rendered":"Prestashop : Hhmodule manager, fonctionnement technique et extension"},"content":{"rendered":"\n<p>Dans ma s\u00e9rie d&rsquo;articles pr\u00e9c\u00e9dents sur la mise en place de la CI\/CD sur Prestashop, j&rsquo;ai pr\u00e9sent\u00e9 une \u00e9tape qui permets d&rsquo;automatiser le d\u00e9ploiement de changements de modules et de configuration.<br \/>Celle-ci est g\u00e9r\u00e9 via le module <strong>hhmodulesmanager<\/strong>, dont j&rsquo;ai \u00e9galement pr\u00e9sent\u00e9 le fonctionnement basique qui est disponible dans cet article : <a title=\"Prestashop : Comment limiter les interactions manuelles avec le d\u00e9ploiement continu\" href=\"https:\/\/www.h-hennes.fr\/blog\/2023\/11\/06\/prestashop-comment-limiter-les-interactions-manuelles-avec-le-deploiement-continu\/\">Prestashop : Comment limiter les interactions manuelles avec le d\u00e9ploiement continu<\/a><br \/>Ce module g\u00e8re nativement les actions suivantes :<\/p>\n<ul>\n<li>Installation \/ D\u00e9sinstallation \/ Activation \/ D\u00e9sactivation \/ Mise \u00e0 jour de module<\/li>\n<li>Ajout \/ Mise \u00e0 jour \/ Suppression de configuration<\/li>\n<\/ul>\n<p>En revanche avec sa conception il peut servir de base technique pour g\u00e9rer pleins d&rsquo;autres actions, telles que\u00a0 la Cr\u00e9ation\/Modification\/Suppression d&rsquo;entit\u00e9s sp\u00e9cifiques&#8230;<br \/>Nous verrons plus loin comment le mettre en place.<br \/>Tout d\u2019abord voici le fonctionnement technique du module et les notions importantes.<br \/>Pour rappel pour vous t\u00e9l\u00e9charger le module (gratuitement) sur la boutique ici :<br \/>Pensez \u00e0 vous abonnez aux mises \u00e0 jour de celui-ci pour \u00eatre inform\u00e9s de la sortie des derni\u00e8res versions \ud83d\ude42<\/p>\n\n\n\n<p class=\"has-text-align-center has-luminous-vivid-amber-background-color has-background\"><a href=\"https:\/\/shop.h-hennes.fr\/fr\/16-modules-ci-manager.html\" target=\"_blank\" rel=\"noopener\" title=\"\">T\u00e9l\u00e9charger le module complet ( et gratuit ) sur la boutique<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Objet \u00ab\u00a0Change\u00a0\u00bb<\/h2>\n\n\n\n<p>Chaque action effectu\u00e9e dans la back office est retranscrite sous la forme d&rsquo;un objet \u00ab\u00a0Changement\u00a0\u00bb qui est une instance de la classe Change.<br \/>Cette classe est visible dans le dossier src du module.<br \/><br \/>Cet objet \u00e0 les propri\u00e9t\u00e9s suivantes :<\/p>\n<ul>\n<li>id : Identifiant technique du changement<\/li>\n<li>entity : code de l&rsquo;entit\u00e9 qui a eut le changement ( nativement : configuration \/ module )<\/li>\n<li>key : cl\u00e9 qui permets de r\u00e9cup\u00e9rer l&rsquo;entit\u00e9 qui a boug\u00e9 ( ex: nom de la cl\u00e9 de configuration \/ nom du module )<\/li>\n<li>details : Donn\u00e9es du changement encod\u00e9es en json<\/li>\n<\/ul>\n<p>Dans le module natif, ces changements sont cr\u00e9\u00e9s automatiquement lorsqu&rsquo;une action est effectu\u00e9e sur un module ou une configuration.<br \/>Via des hooks sp\u00e9cifiques.<br \/>Dans le cas d&rsquo;une personnalisation il faudra identifier \u00e0 quel moment g\u00e9n\u00e9rer ce changement.<br \/>L&rsquo;ensemble de ces changements sont visibles et list\u00e9s dans l&rsquo;administration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Converters<\/h2>\n\n\n\n<p>Dans le controller de l&rsquo;administration qui permets de visualiser les changements, lorsqu&rsquo;on va demander la cr\u00e9ation d&rsquo;un nouveau fichier d&rsquo;upgrade.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/09\/image-20.png\"><img loading=\"lazy\" decoding=\"async\" width=\"521\" height=\"448\" src=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/09\/image-20.png\" alt=\"\" class=\"wp-image-3487\" srcset=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/09\/image-20.png 521w, https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/09\/image-20-300x258.png 300w\" sizes=\"auto, (max-width: 521px) 100vw, 521px\" \/><\/a><\/figure>\n\n\n\n<p>(Le code de la partie suivante est g\u00e9r\u00e9e dans le fichier <em>src\/Patch\/Generator.php )<\/em> <br \/>Pour chaque changement on va r\u00e9cup\u00e9rer la liste des \u00ab\u00a0Converters\u00a0\u00bb. <br \/>Ces classes sont charg\u00e9es de <strong>convertir un changement<\/strong> ( ie objet Change ), <strong>sous la forme d&rsquo;un tableau repr\u00e9sentatif de ce changement, qui sera ensuite format\u00e9 dans le yml r\u00e9capitulant les mises \u00e0 jours.<br \/><\/strong>Toutes les classes \u00ab\u00a0converters\u00a0\u00bb sont d\u00e9clar\u00e9es via le fichier config\/services.yml<strong>, <\/strong>et doivent impl\u00e9menter l&rsquo;interface \\Hhennes\\ModulesManager\\Converter\\ConverterInterface.<br \/>Plusieurs converters diff\u00e9rents peuvent traiter un m\u00eame changement si n\u00e9cessaire.<br \/>Nous verrons plus bas un exemple concret de sa mise en \u0153uvre.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Upgraders<\/h2>\n\n\n\n<p>A l&rsquo;inverse, lors de l&rsquo;ex\u00e9cution de la mise \u00e0 jour des donn\u00e9es, via la commande console : php bin\/console hhennes:module-manager:manage -v<br \/>Il est n\u00e9cessaire de <strong>retraduire les mises \u00e0 jour d\u00e9crites dans les fichiers yml, en actions \u00e0 r\u00e9aliser pour les appliquer<\/strong>.<br \/>C&rsquo;est ici qu&rsquo;on va aller chercher les classes \u00ab\u00a0Upgraders\u00a0\u00bb, qui sont \u00e9galement d\u00e9clar\u00e9es via le fichier config\/services.yml, et qui doivent impl\u00e9menter l&rsquo;interface \\Hhennes\\ModulesManager\\Upgrader\\UpgraderInterface<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u00c9tendre les fonctionnalit\u00e9s du module dans votre propre module.<\/h2>\n\n\n\n<p>Maintenant que les concepts basiques sont pr\u00e9sent\u00e9s, et ( plus ou moins ) compris, on va passer \u00e0 un cas pratique pour l&rsquo;ilustrer.<br \/>Pour l&rsquo;exemple on souhaite pouvoir g\u00e9rer dans les mises \u00e0 jour automatique les actions suivantes d&rsquo;un objet \u00ab\u00a0Sample\u00a0\u00bb qui \u00e9tends l&rsquo;ObjetModel standard de Prestashop.<br \/>Le module Sample peut \u00eatre t\u00e9l\u00e9charger ici : \u00a0<a href=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/11\/hhsamplemodule.zip\">hhsamplemodule<\/a> ( tir\u00e9 d&rsquo;un ancien tutoriel sur les grilles admin et l&rsquo;objectmodel)<br \/><br \/>Pour commencer on va cr\u00e9er un nouveau module\u00a0 <strong>hhmodulesmanageraddon<\/strong> , avec le code de base suivant :<br \/>Le module utilisera composer via le namespace Hhennes\\ModulesManagerAddon<br \/>Le code du module de base sers \u00e0 r\u00e9pondre \u00e0 la premi\u00e8re probl\u00e9matique qui est de g\u00e9n\u00e9rer un changement.<\/p>\n\n\n\n<pre lang=\"php\">\nuse \\Hhennes\\ModulesManager\\Change;\n\nclass Hhmodulesmanageraddon extends Module\n{\n\n    public function __construct()\n    {\n        $this->name = 'hhmodulesmanageraddon';\n        $this->tab = 'others';\n        $this->version = '0.1.0';\n        $this->author = 'hhennes';\n        $this->dependencies = ['hhmodulesmanager'];\n        $this->bootstrap = true;\n        parent::__construct();\n\n        $this->displayName = $this->l('Module Manager addon');\n        $this->description = $this->l('Module manager addon');\n\n    }\n\n    public function install()\n    {\n        return parent::install()\n            && $this->registerHook(\n                [\n                    'actionHhmodulesmanagerExcludeConfiguration', \/\/Hook sp\u00e9cifique pour ignorer des conf\n                    'actionObjectSampleAddAfter', \/\/Ajout d'un nouvel objet \"Sample\"\n                    'actionObjectSampleUpdateAfter',\/\/Mise \u00e0 jour de l'objet \"Sample\"\n                    'actionObjectSampleDeleteAfter', \/\/Suppression de l'objet \"Sample\"\n                ]\n            );\n    }\n\n    \/**\n     * Ajout de configurations ignor\u00e9es dans le module HHmodulesmanager\n     *\n     * @param array $params\n     * @return void\n     *\/\n    public function hookActionHhmodulesmanagerExcludeConfiguration(array $params): void\n    {\n        $params['configuration'][]= 'PS_CCCJS_VERSION';\n        $params['configuration'][]= 'PS_CCCCSS_VERSION';\n    }\n\n    \/**\n     * Apr\u00e8s la cr\u00e9ation d'un objet sample\n     *\n     * @param array $params\n     * @return void\n     *\/\n    public function hookActionObjectSampleAddAfter(array $params): void\n    {\n        try {\n            $sampleObject = $params['object']; \/\/R\u00e9cup\u00e9ration de l'objet\n            $change = new Change(); \/\/Cr\u00e9ation d'un changement\n            $change->entity = 'Sample'; \/\/Entit\u00e9\n            $change->action = 'add'; \/\/Action\n            $change->key = $sampleObject->code; \/\/Code de l'objet\n            $change->details = json_encode($sampleObject); \/\/D\u00e9tails de l'objet\n            $change->add();\n        } catch (\\PrestaShopException $e) {\n            PrestaShopLogger::addLog(\n                'Unable to register change in ' . __METHOD__,\n                '0',\n                1,\n            );\n        }\n    }\n\n    \/**\n     * Apr\u00e8s la mise \u00e0 jour d'un objet sample\n     *\n     * @param array $params\n     * @return void\n     *\/\n    public function hookActionObjectSampleUpdateAfter(array $params): void\n    {\n        try {\n            $sampleObject = $params['object'];\n            $change = new Change();\n            $change->entity = 'Sample';\n            $change->action = 'update';\n            $change->key = $sampleObject->code;\n            $change->details = json_encode($sampleObject);\n            $change->add();\n        } catch (\\PrestaShopException $e) {\n            PrestaShopLogger::addLog(\n                'Unable to register change in ' . __METHOD__,\n                '0',\n                1,\n            );\n        }\n    }\n\n    \/**\n     * Apr\u00e8s la mise \u00e0 jour d'un objet sample\n     *\n     * @param array $params\n     * @return void\n     *\/\n    public function hookActionObjectSampleDeleteAfter(array $params): void\n    {\n        try {\n            $sampleObject = $params['object'];\n            $change = new Change();\n            $change->entity = 'Sample';\n            $change->action = 'delete';\n            $change->key = $sampleObject->code;\n            $change->details = 'Content removed';\n            $change->add();\n        } catch (\\PrestaShopException $e) {\n            dump($e->getMessage());\n            PrestaShopLogger::addLog(\n                'Unable to register change in ' . __METHOD__,\n                '0',\n                1,\n            );\n        }\n    }\n}\n<\/pre>\n\n\n\n<p>Pour v\u00e9rifier que cela fonctionne, cr\u00e9er, modifier, supprimer des entit\u00e9s \u00ab\u00a0Sample\u00a0\u00bb et des nouveaux changements doivent remonter.<br \/><br \/>Prochain \u00e9l\u00e9ment \u00e0 faire g\u00e9n\u00e9rer le converter, pour convertir notre changement en action dans le yml.<br \/>Pour commencer il faut d\u00e9clarer le nouveau converter au module hhmodulesmanager via le fichier <em>config\/services.yml<\/em> , en y ajoutant le code suivant :<\/p>\n\n\n\n<pre lang=\"yaml\">\nservices:\n  _defaults:\n    public: true\n\n  #Add a custom converter\n  hhennes.modulesmanageraddon.converter.sampleobject: #Nom du service\n    class: Hhennes\\ModulesManagerAddon\\Converter\\SampleObject #Classe de l'objet\n    tags: [ hhennes.modulesmanager.converter ] #Tag \u00e0 impl\u00e9menter pour enregistrer le converter\n<\/pre>\n\n\n\n<p>Le code de la classe du converter sera ensuite le suivant :<\/p>\n\n\n\n<pre lang=\"php\">\n<?php\n\nnamespace Hhennes\\ModulesManagerAddon\\Converter;\n\nuse Hhennes\\ModulesManager\\Change;\nuse Hhennes\\ModulesManager\\Converter\\ConverterInterface;\n\nclass SampleObject implements ConverterInterface\n{\n    public const TYPE = 'Sample';\n\n    \/**\n     * @var array Allowed actions\n     *\/\n    public const ALLOWED_ACTIONS = [\n        'add',\n        'update',\n        'delete'\n    ];\n\n    \/**\n     * @inheritDoc\n     *\/\n    public function canConvert(Change $change): bool\n    {\n        return $change->entity === self::TYPE; \/\/Si l'entit\u00e9 du changement est = \u00e0 \"Sample\" , on peut le convertir\n    }\n\n    \/**\n     * @inheritDoc\n     * Notez bien que le tableau des changements est pass\u00e9 par r\u00e9f\u00e9rence, ce qui permets de travailler sur le m\u00eame tableau \n     * Pour tous les converters, peut importe leur classe. \n     *\/\n    public function convert(Change $change, array &$currentChangesArray): void\n    {\n        \/\/On v\u00e9rifie que l'action du changement est g\u00e9r\u00e9e, sinon on renvoie une erreur\n        if (!in_array($change->action, self::ALLOWED_ACTIONS)) {\n            throw new Exception('Unknow ' . self::TYPE . ' action , allowed values : ' .\n                implode(',', self::ALLOWED_ACTIONS));\n        }\n        \/\/Cr\u00e9ation d'une entr\u00e9e \"Sample\" qui correspond \u00e0 notre objet dans le tableau, si elle n'existe pas encore\n        if (!array_key_exists(self::TYPE, $currentChangesArray)) {\n            $currentChangesArray[self::TYPE] = [];\n        }\n        \/\/Cr\u00e9ation dans ce tableau de l'action \u00e0 effectuer sur cette entit\u00e9 ( add\/update\/delete)\n        if (!array_key_exists($change->action, $currentChangesArray[self::TYPE])) {\n            $currentChangesArray[self::TYPE][$change->action] = [];\n        }\n        \/\/Cr\u00e9ation du d\u00e9tail de l'entit\u00e9 concern\u00e9e sous la forme : code => valeur\n        if (!in_array($change->key, $currentChangesArray[self::TYPE][$change->action])) {\n            $currentChangesArray[self::TYPE][$change->action][$change->key] = $change->details;\n        }\n    }\n}\n<\/pre>\n\n\n\n<p>Pour v\u00e9rifier que cela fonctionne, g\u00e9n\u00e9rer une mise \u00e0 jour concernant des objets samples depuis la liste des changements dans le back office.<br>Si tout est bon on pourrait avoir les informations suivantes, qui correspondent bien \u00e0 l&rsquo;ensemble de nos cas.<\/p>\n\n\n\n<pre lang=\"yaml\">\nSample:\n    add:\n        new_ci: '{\"id\":\"5\",\"name\":\"NouveauCI\",\"code\":\"autre\",\"email\":\"dhjhjh@test.com\",\"title\":{\"1\":\"hrve\",\"2\":\"ffdfddf\"},\"description\":{\"1\":\"fdsfshjhjf\",\"2\":\"dsdsdds\"},\"id_shop_list\":[],\"force_id\":false}'\n        new_ci_1: '{\"id\":\"5\",\"name\":\"NouveauCIBIS\",\"code\":\"autre\",\"email\":\"dhjhjh@test.com\",\"title\":{\"1\":\"hrve\",\"2\":\"ffdfddf\"},\"description\":{\"1\":\"fdsfshjhjf\",\"2\":\"dsdsdds\"},\"id_shop_list\":[],\"force_id\":false}'\n    update:\n        new_ci_2: '{\"id\":\"5\",\"name\":\"NouveauCITER\",\"code\":\"autre\",\"email\":\"dhjhjh@test.com\",\"title\":{\"1\":\"hrve\",\"2\":\"ffdfddf\"},\"description\":{\"1\":\"fdsfshjhjf\",\"2\":\"dsdsdds\"},\"id_shop_list\":[],\"force_id\":false}'\n    delete:\n        new_ci_3: 'Content removed'\n<\/pre>\n\n\n\n<p>C&rsquo;est donc parti pour la derni\u00e8re partie qui va \u00eatre d&rsquo;\u00e9crire notre upgrader.<br \/>Pour commencer il faut \u00e9galement le d\u00e9clarer dans le fichier <em>config\/services.yml<\/em><\/p>\n\n\n\n<pre lang=\"yml\">\n#Add a custom upgrader\n  hhennes.modulesmanageraddon.upgrader.sampleobject:\n    class: Hhennes\\ModulesManagerAddon\\Upgrader\\SampleObject\n    tags: [ hhennes.modulesmanager.upgrader ] #Tag \u00e0 impl\u00e9menter pour enregistrer l'upgrader\n<\/pre>\n\n\n\n<p>Puis ensuite cr\u00e9er l&rsquo;upgrader correspondant.<\/p>\n\n\n\n<pre lang=\"php\">\nnamespace Hhennes\\ModulesManagerAddon\\Upgrader;\n\n\/\/On charge le fichier de la classe\nrequire_once _PS_MODULE_DIR_ . '\/hhsamplemodule\/classes\/Sample.php';\n\nuse Hhennes\\ModulesManager\\Upgrader\\UpgraderInterface;\nuse Hhennes\\ModulesManager\\Upgrader\\UpgraderResultTrait;\nuse Sample;\n\nclass SampleObject implements UpgraderInterface\n{\n    \/**\n     * Use a trait to avoid to define repetitives methods\n     *\/\n    use UpgraderResultTrait;\n\n    \/** @var string Type d'upgrade *\/\n    public const TYPE = 'Sample';\n\n    public function upgrade(array $data): void\n    {\n        \/\/Les donn\u00e9es du yml sont r\u00e9cup\u00e9r\u00e9es sous forme de tableau.\n        \/\/Si pas de cl\u00e9 correspondante \u00e0 notre entit\u00e9 on ne fait rien.\n        if (!array_key_exists(self::TYPE, $data)) {\n            return;\n        }\n        $data = $data[self::TYPE];\n\n        \/\/Ajouts\n        if (array_key_exists('add', $data)) {\n            foreach ($data['add'] as $entityCode => $entityData) {\n                try {\n                    $entityArray = json_decode($entityData, true); \/\/L'objet est stock\u00e9 en json, on le transforme en array pour r\u00e9cup\u00e9rer les infos.\n                    $sample = new Sample();\n                    $sample->code = $entityCode;\n                    $sample->name = $entityArray['name'];\n                    $sample->email = $entityArray['email'];\n                    $sample->title = $entityArray['title'];\n                    $sample->description = $entityArray['description'];\n                    $sample->save();\n                    $this->success[] = 'create sample entity code ' . $entityCode; \/\/Message de succ\u00e8s pour les logs\n                } catch (\\Throwable $e) {\n                    $this->errors[] = 'Unable to create sample entity code ' . $entityCode . ' Error ' . $e->getMessage();\n                }\n            }\n        }\n        \/\/Mises \u00e0 jours\n        if (array_key_exists('update', $data)) {\n            foreach ($data['update'] as $entityCode => $entityData) {\n                try {\n                    $entityArray = json_decode($entityData, true);\n                    $idSample = Sample::getIdByCode($entityCode);\n                    if ($idSample > 0) {\n                        $sample = new Sample($idSample);\n                        $sample->code = $entityCode;\n                        $sample->name = $entityArray['name'];\n                        $sample->email = $entityArray['email'];\n                        $sample->title = $entityArray['title'];\n                        $sample->description = $entityArray['description'];\n                        $sample->save();\n                        $this->success[] = 'Update sample entity code ' . $entityCode;\n                    }\n                } catch (\\Throwable $e) {\n                    $this->errors[] = 'Unable to update sample entity code ' . $entityCode . ' Error ' . $e->getMessage();\n                }\n            }\n        }\n\n        \/\/Suppressions\n        if (array_key_exists('delete', $data)) {\n            foreach ($data['delete'] as $entityCode => $entityData) {\n                try {\n                    $idSample = Sample::getIdByCode($entityCode);\n                    if ($idSample > 0) {\n                        $sample = new Sample($idSample);\n                        $sample->delete();\n                        $this->success[] = 'Delete sample entity code ' . $entityCode;\n                    }\n                } catch (\\Throwable $e) {\n                    $this->errors[] = 'Unable to update sample entity code ' . $entityCode . ' Error ' . $e->getMessage();\n                }\n            }\n        }\n    }\n}\n<\/pre>\n\n\n\n<p>On a \u00e0 pr\u00e9sent tout termin\u00e9, plus qu&rsquo;\u00e0 d\u00e9ployer notre mise \u00e0 jour et \u00e0 voir si elle est bien prise en compte !<br \/>Comme on peut voir ci-dessous, tout se passe comme pr\u00e9vu, et les changements sur nos entit\u00e9s sont bien en place \u00e9galement \ud83d\ude42<br \/><br \/>Un point important \u00e0 noter est que pour aller plus vite :\u00a0 le seul \u00e9l\u00e9ment qui est r\u00e9ellement obligatoire est de d\u00e9finir un upgrader.<br \/>Vous pouvez tout \u00e0 fait saisir directement vos changements \u00e0 la main dans un fichier yml<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/11\/image-2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"846\" height=\"182\" src=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/11\/image-2.png\" alt=\"\" class=\"wp-image-4689\" srcset=\"https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/11\/image-2.png 846w, https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/11\/image-2-300x65.png 300w, https:\/\/www.h-hennes.fr\/blog\/wp-content\/uploads\/2023\/11\/image-2-768x165.png 768w\" sizes=\"auto, (max-width: 846px) 100vw, 846px\" \/><\/a><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Dans ma s\u00e9rie d&rsquo;articles pr\u00e9c\u00e9dents sur la mise en place de la CI\/CD sur Prestashop, j&rsquo;ai pr\u00e9sent\u00e9 une \u00e9tape qui permets d&rsquo;automatiser le d\u00e9ploiement de changements de modules et de configuration.Celle-ci est g\u00e9r\u00e9 via le module hhmodulesmanager, dont j&rsquo;ai \u00e9galement pr\u00e9sent\u00e9 le fonctionnement basique qui est disponible dans cet article : Prestashop : Comment limiter [&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":"set","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":[597,601],"class_list":["post-4687","post","type-post","status-publish","format-standard","hentry","category-prestashop-2","tag-ci-cd","tag-hhmodulesmanager"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts\/4687","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=4687"}],"version-history":[{"count":4,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts\/4687\/revisions"}],"predecessor-version":[{"id":5283,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/posts\/4687\/revisions\/5283"}],"wp:attachment":[{"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/media?parent=4687"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/categories?post=4687"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.h-hennes.fr\/blog\/wp-json\/wp\/v2\/tags?post=4687"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}