1.4 1.6 1.7.5 1.7.6 1.7.7 1.7.8 8.0 8.1 +
N'hésitez pas à me le signaler si nécessaire via le formulaire de contact.
Cette article inaugure une nouvelle série d’articles à venir sur comment étendre les entités Prestashop sans surcharges.
C’est connu depuis très longtemps qu’il est déconseillé d’utiliser les surcharges pour ajouter des champs, pour autant il n’y avait pas forcément d’autres solutions, c’est de moins en moins vrai 🙂
Ce tutoriel est uniquement compatible avec les versions de Prestashop supérieures à la 1.7.6 ( Lorsque la page de gestion admin est passée sous symfony )
Mais le hook essentiel filterCategoryContent est présent depuis la version 1.7.1
La même logique avec une autre gestion dans l’administration est donc possible.
Pour l’exemple nous allons donc créer un module hh_categoryfields qui va ajouter 3 champs aux catégories :
- Code erp , type texte commun à toutes les langues et tous les sites
- Description SEO , type texte avec possibilité de le traduire pour chaque langue installée dans chaque boutique
- Image : Image commune à toutes les langues et tous les sites.
Ajout des champs dans le formulaire d’édition admin
Pour l’ajout des champs dans le formulaire d’administration j’ai déjà détaillé le concept de fonctionnement dans l’article : https://www.h-hennes.fr/blog/2019/08/05/prestashop-1-7-ajouter-des-champs-dans-un-formulaire-dadministration/
Le module sera donc greffé sur les hooks suivants :
- hookActionCategoryFormBuilderModifier
- hookActionAfterCreateCategoryFormHandler
- hookActionAfterUpdateCategoryFormHandler
Le code complet sera disponible en fin d’article
Gestion des champs
Ces champs seront gérés via une classe Spécifique qui hérite de la classe ObjectModel de Prestashop ( j’ai choisi de faire simple et ne pas faire ce développement via symfony )
Dont voici donc son code qui est très basique.
J’ai uniquement ajouté des fonctions pour gérer l’installation / désinstallation en créant les tables mysql et les dossiers nécessaires au bon fonctionnement du module
<?php use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; if (!defined('_PS_VERSION_')) { exit; } class CategoryFields extends ObjectModel { /** @var int Object id */ public $id; /** @var int Identifiant de la catégorie prestashop */ public $id_category; /** @var string Code erp de la catégorie */ public $code; /** @var string image additionnelle */ public $image_field; /** @var string Champ langue */ public $description_seo; public static $definition = [ 'table' => 'hh_category_field', 'primary' => 'id_category_extra', 'multilang' => true, 'multilang_shop' => true, 'fields' => [ 'id_category' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'length' => 10], 'code' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'length' => 50], 'image_field' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'length' => 255], 'description_seo' => ['type' => self::TYPE_HTML, 'validate' => 'isCleanHtml', 'lang' => true] ] ]; /** * Récupération de l'identifiant de l'entité via l'identifiant de catégorie * @param int $id_category * @return false|string|null */ public static function getIdByCategoryId(int $id_category) { return Db::getInstance()->getValue( (new DbQuery()) ->select(self::$definition['primary']) ->from(self::$definition['table']) ->where('id_category=' . $id_category) ); } /** * Installation du modèle * A ajouter dans l'installation du module */ public static function installSql(): bool { try { //Création de la table avec les champs communs $createTable = Db::getInstance()->execute( "CREATE TABLE IF NOT EXISTS `"._DB_PREFIX_."hh_category_field`( `id_category_extra` int(10) NOT NULL AUTO_INCREMENT, `id_category` INT(10) NOT NULL, `code` VARCHAR (50), `image_field` VARCHAR (50), PRIMARY KEY (`id_category_extra`) ) ENGINE=InnoDB DEFAULT CHARSET=UTF8;" ); //Création de la table des langues $createTableLang = Db::getInstance()->execute( "CREATE TABLE IF NOT EXISTS `"._DB_PREFIX_."hh_category_field_lang`( `id_category_extra` int(10) NOT NULL AUTO_INCREMENT, `id_shop` INT(10) NOT NULL DEFAULT '1', `id_lang` INT(10) NOT NULL, `description_seo` TEXT, PRIMARY KEY (`id_category_extra`,`id_shop`,`id_lang`) ) ENGINE=InnoDB DEFAULT CHARSET=UTF8;" ); } catch (PrestaShopException $e) { return false; } return $createTable && $createTableLang; } /** * Suppression des tables du modules * @return bool */ public static function uninstallSql() { return Db::getInstance()->execute("DROP TABLE IF EXISTS "._DB_PREFIX_."hh_category_field") && Db::getInstance()->execute("DROP TABLE IF EXISTS "._DB_PREFIX_."hh_category_field_lang"); } /** * Création du dossier pour envoyer les images du modules * ( Dans le dossier img de prestashop ) * @return bool */ public static function createImageDirectory(): bool { $fileSystem = new Filesystem(); $imageDir = _PS_IMG_DIR_ . 'modules/hh_categoryfields/'; if (!$fileSystem->exists($imageDir)) { try { $fileSystem->mkdir($imageDir); } catch (IOException $e) { return false; } } return true; } /** * Suppression du dossier pour envoyer les images du modules * ( Dans le dossier img de prestashop ) * @return bool */ public static function removeImageDirectory(): bool { $fileSystem = new Filesystem(); $imageDir = _PS_IMG_DIR_ . 'modules/hh_categoryfields/'; if (!$fileSystem->exists($imageDir)) { try { $fileSystem->remove($imageDir); } catch (IOException $e) { return false; } } return true; } } |
Affichage des informations en front
L’assignation des informations sur la page des catégories en front est gérée via le hook filterCategoryContent.
Celui-ci permets de rendre disponible les informations dans l’objet catégorie sur le front office.
Voici un code simplifié par rapport à celui du module pour voir le concept
/** * Ajout de contenu de catégorie sans surcharge * @param array $params * @return array */ public function hookFilterCategoryContent(array $params) { //Ajout des valeur dans le tableau "object" qui correspond à la catégorie $params['object']['code'] = 'Code'; $params['object']['description_seo'] = 'Description seo'; return [ 'object' => $params['object'] ]; } |
Les valeurs définies sont ensuite directement disponible dans le template front de la catégorie. themes/yourtheme/templates/catalog/listing/category.tpl
On peut donc par exemple rajouter le code suivant qui se chargera d’afficher nos nouvelles propriétés :
{* On rajoute tous nos champs spéciaux dans le footer de la catégorie pour l'exemple Mais l'avantage du hook filterCategoryContent est que le contenu est disponible partout sur la page dans n'importe quel block *} {block name='product_list_bottom'} {*Contenu du parent*} {include file='catalog/_partials/products-bottom.tpl' listing=$listing}{$category|dump} {if isset($category.code)}{/block}Code Erp : {$category.code}
{/if} {if isset($category.description_seo)}{$category.description_seo}{/if} {if isset($category.image_field)} {/if}
Limites et restrictions
L’approche sans les surcharges ne permets malheureusement pas encore de gérer tous les cas.
Il n’est pas encore possible de rendre ces champs disponibles dans la liste des sous-catégories, ni dans l’api de prestashop.
Code complet
Voici le code complet de la classe de gestion du module.
La classe de gestion des champs est disponible plus haut.
if (!defined('_PS_VERSION_')) { exit; } require_once __DIR__ . '/classes/CategoryFields.php'; class Hh_categoryfields extends Module { public function __construct() { $this->name = 'hh_categoryfields'; $this->tab = 'others'; $this->version = '0.1.0'; $this->author = 'hhennes'; $this->bootstrap = true; parent::__construct(); $this->displayName = $this->l('Hh Category Fields'); $this->description = $this->l('POC : Add category fields without override'); } /** * Installation du module * @return bool */ public function install() { if (!parent::install() || !$this->registerHook([ 'actionCategoryFormBuilderModifier', 'actionAfterCreateCategoryFormHandler', 'actionAfterUpdateCategoryFormHandler', 'filterCategoryContent', ]) || !CategoryFields::installSql() || !CategoryFields::createImageDirectory() ) { return false; } return true; } /** * Désinstallation du module * @return bool */ public function uninstall() { if ( !parent::uninstall() || !CategoryFields::uninstallSql() || !CategoryFields::removeImageDirectory() ) { return false; } return true; } /** * Ajout de contenu de catégorie sans surcharge * @param array $params * @return array */ public function hookFilterCategoryContent(array $params) { $additional = $this->getCustomCategoryFields($params['object']['id']); if (count($additional)) { $params['object'] = array_merge($params['object'], $additional); return [ 'object' => $params['object'] ]; } } /** * Récupération des informations spécifiques de la catégorie * * @param int $id_category * @return array * @throws PrestaShopDatabaseException * @throws PrestaShopException */ protected function getCustomCategoryFields(int $id_category): array { $return = []; $idCategoryField = CategoryFields::getIdByCategoryId($id_category); if ($idCategoryField) { $categoryField = new CategoryFields($idCategoryField,$this->context->language->id); $presenter = new \PrestaShop\PrestaShop\Adapter\Presenter\Object\ObjectPresenter(); $return = $presenter->present($categoryField); $return['image_field'] = $this->getUploadUrl().$return['image_field']; //suppression des champs techniques unset($return['id_category']); unset($return['id']); } return $return; } /** * Modification du formulaire de la catégorie * @param array $params */ public function hookActionCategoryFormBuilderModifier(array $params) { //Pour l'envoi du fichier : regarder ici : https://devdocs.prestashop.com/1.7/modules/sample-modules/extending-sf-form-with-upload-image-field/#introduction try { //Récupération des informations des champs custom $customFieldsValues = $this->getCustomFieldsValue($params['id']); $locales = $this->get('prestashop.adapter.legacy.context')->getLanguages(); /** @var \Symfony\Component\Form\FormBuilder $formBuilder */ $formBuilder = $params['form_builder']; //Ajout des champs dans le formulaire d'édition des catégories //Champ standard $formBuilder->add( $this->name . '_code', \Symfony\Component\Form\Extension\Core\Type\TextType::class, [ 'label' => $this->l('Erp Code'), 'required' => false, 'constraints' => [ new \Symfony\Component\Validator\Constraints\Length([ 'max' => 20, 'maxMessage' => $this->l('Max caracters allowed : 20'), ]), ], 'data' => $customFieldsValues['code'], 'help' => $this->name . ' :' . $this->l('This is the erp code') ] ) //Champs langue ( qui gère le multi-shop ) ->add( $this->name . '_description_seo', \PrestaShopBundle\Form\Admin\Type\TranslatableType::class, [ 'locales' => $locales, 'type' => \Symfony\Component\Form\Extension\Core\Type\TextareaType::class, 'label' => $this->l('Description Seo'), 'required' => false, 'data' => $customFieldsValues['description_seo'], 'help' => $this->name . ' :' . $this->l('Seo description for categories'), ] ) //Champ image ->add('image_file_upload', \Symfony\Component\Form\Extension\Core\Type\FileType::class, [ 'label' => $this->l('Additional image'), 'required' => false, 'help' => $this->name . ' :' . $this->l('Additional image') ]); //Dans le cas ou l'image est définie //fonctionnement très basique checkbox pour la supprimer $categoryDatas = $this->getCustomFieldsValue($params['id']); if (!empty($categoryDatas['image_field'])) { $formBuilder ->add($this->name . '_delete_current_image', \Symfony\Component\Form\Extension\Core\Type\CheckboxType::class, [ 'label' => $this->l('Delete Current Image ?'), 'required' => false, 'help' => sprintf( $this->l('current file %s'), $this->getUploadUrl() . $categoryDatas['image_field'] ), ]); } $formBuilder->setData($params['data']); } catch ( Exception $e){ $this->log('Error :'.$e->getMessage()); $this->log('Error string :'.$e->getTraceAsString()); } } /** * Action effectuée après la création d'une catégorie * @param array $params * @return void */ public function hookActionAfterCreateCategoryFormHandler(array $params): void { $this->updateData($params['id'], $params['form_data']); } /** * Action effectuée après la mise à jour d'une catégorie * @param array $params * @return void */ public function hookActionAfterUpdateCategoryFormHandler(array $params): void { $this->updateData($params['id'], $params['form_data']); } /** * Récupération des informations spécifique de l'objet * @param int $id_category * @return array */ protected function getCustomFieldsValue(int $id_category): array { try { $idCategoryField = CategoryFields::getIdByCategoryId($id_category); $categoryField = new CategoryFields($idCategoryField); return [ 'code' => $categoryField->code, 'description_seo' => $categoryField->description_seo, 'image_field' => $categoryField->image_field ]; } catch (PrestaShopException $e) { $this->log($e->getMessage()); return [ 'code' => '', 'description_seo' => [], 'image_field' => '' ]; } } /** * Fonction qui va effectuer la mise à jour * @param int $id_category * @param array $data * @return void */ protected function updateData(int $id_category, array $data): void { try { $idCategoryField = CategoryFields::getIdByCategoryId($id_category); $categoryField = new CategoryFields($idCategoryField); $categoryField->id_category = $id_category; foreach ($data as $key => $value) { if (strpos($key, $this->name) !== false) { $objectKey = str_replace($this->name . '_', '', $key); $categoryField->$objectKey = $value; } } /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $uploadedFile */ $uploadedFile = $data['image_file_upload']; //Gestion de la suppression de l'image if (array_key_exists($this->name . '_delete_current_image', $data) && $data[$this->name . '_delete_current_image'] == 1 && !$uploadedFile instanceof \Symfony\Component\HttpFoundation\File\UploadedFile ) { $this->deleteCurrentImage($categoryField->image_field); unset($data[$this->name . '_image_field']); $categoryField->image_field = ''; } //Gestion de l'envoi de l'image ( simplifiée ) //@todo : Tester sur les versions 1.7.6 / 1.7.7 / 1.7.8 if ($uploadedFile instanceof \Symfony\Component\HttpFoundation\File\UploadedFile) { if ($uploadedFile->isValid()) { $this->uploadFile($uploadedFile); $categoryField->image_field = $uploadedFile->getClientOriginalName(); } } $categoryField->save(); } catch (Exception $e) { $this->log($e->getMessage()); } } /** * Fonction (très basique et sans vérif ) d'envoi du fichier * * @param \Symfony\Component\HttpFoundation\File\UploadedFile $uploadedFile * @return bool */ protected function uploadFile(\Symfony\Component\HttpFoundation\File\UploadedFile $uploadedFile): bool { try { //Bonne pratique : on gère l'envoi des images dans le dossier "img" de prestashop et pas dans le module $uploadDir = $this->getUploadDir(); $uploadedFile->move($uploadDir, $uploadedFile->getClientOriginalName()); } catch (Exception $e) { return false; } return true; } /** * Suppression d'une image existante * @param string $imageName * @return bool */ protected function deleteCurrentImage(string $imageName): bool { if (is_file($this->getUploadDir() . $imageName)) { return unlink($this->getUploadDir() . $imageName); } return true; } /** * Récupération du dossier d'envoi des images * @return string */ protected function getUploadDir(): string { return _PS_IMG_DIR_ . 'modules/' . $this->name . '/'; } /** * Récupération de l'url des images * @return string */ protected function getUploadUrl(): string { $uriPath = 'img/modules/' . $this->name . '/'; return $this->context->link->getBaseLink() . $uriPath; } /** * Fonction basique de log * @param string $message * @return void */ protected function log($message): void { file_put_contents( dirname(__FILE__) . 'debug.log', date('Y-m-d H:i:s') . $message . "\n", FILE_APPEND ); } } |
Bonjour,
Petit correction sur la fonction updateData().
En l’état l’image sera toujours supprimé avec array_key_exists(), il suffit d’ajouter une autre condition pour éviter la suppression systématique de l’image insérée tel que:
if (array_key_exists($this->name . ‘_delete_current_image’, $data) && $data[$this->name . ‘_delete_current_image’] == true)
Alex
Bonjour Alex,
Vous avez tout à fait raison.
Merci de votre vigilance, j’ai corrigé ce point 🙂
Cordialement,
Hervé
Bonjour Hervé,
Tout d’abord, merci pour vos tutos.
Sur le même logique j’ai crée un champ bottom description pour rejouter du contenu en bas de page catégorie.
Ca fonctionne très bien avec cette méthode pour un champ texte basique mais je n’arrive pas a le générer avec éditeur de texte WYSIWYG comme avant
avec ‘type’ => PrestaShopBundle\Form\Admin\Type\FormattedTextareaType::class
Le champ apparait mais l’enregistrement ne fonctionne pas.
De plus je me retrouve avec les contraitre de nombre de caractère dans le contenu du textarea.
Savez-vous si il y a une différence sur la déclaration du type de champ ?
Merci d’avance.
Bonjour,
Dans mon cas tout fonctionne correctement si j’utilise la syntaxe suivante pour le champ :
//Ajout champs avec wysywyg
->add($this->name . '_description_seo', \PrestaShopBundle\Form\Admin\Type\TranslateType::class, [
'label' => $this->l('Description Seo'),
'type' => \PrestaShopBundle\Form\Admin\Type\FormattedTextareaType::class,
'locales' => $locales,
'hideTabs' => false,
'required' => false,
'data' => $customFieldsValues['description_seo'] ?? [],
'options' => [
'constraints' => [
new \PrestaShop\PrestaShop\Core\ConstraintValidator\Constraints\CleanHtml([
'message' => $this->l('Seo description for categories'),
]),
],
],
])
Avec du recul il serait plus logique de mettre ce type de champ dans l’article d’ailleurs.
Cordialement,
Hervé
Bonjour Hervé,
Merci pour votre réponse. ça fonctionnent parfaitement.
J’ai une question concernant la partie produit.
J’aimerai sur le même principe rajouter plusieurs champs texte a la fichier produit.
Pensez-vous que cette méthode est applicable au produit ?
Merci d’avance.
Cordialement.
Olivier.
Bonjour Olivier,
Sur le produit cette partie est moins aboutie.
Je n’ai pas fait cette procédure récemment, il y’a toujours cet article qui fonctionne mais qui date pas mal : https://www.h-hennes.fr/blog/2017/10/19/prestashop-1-7-ajouter-des-champs-produit/
J’ai prévu dans les prochaines semaines d’en faire une nouvelle version actualisée.
Mais en fonction des versions 1.7 de Prestashop les possibilités ne sont pas les mêmes.
Cordialement,
Hervé
Bonjour Hervé,
Merci pour ce code qui est très utile et grâce auquel j’ai pu ajouter une description seo à mes catégorie. J’ai aussi tenté d’ajouté un champ image. Pour ne pas qu’elle soit supprimée, j’ai tenté de remplacer le array_key_exist par le code indiqué dans un commentaire ci-dessus :
if (array_key_exists($this->name . ‘_delete_current_image’, $data) && $data[$this->name . ‘_delete_current_image’] == true)
Or en mettant ce code, le module ne peut plus s’installer, sans que je n’ai d’erreur précise indiqué.
Auriez-vous testé ce code et avez vous une idée de par quoi je peux le remplacer pour qu’il soit fonctionnel svp (prestashop 1.7.8.5) ?
Merci d’avance.
David
Bonjour,
Vérifiez peut être si il y’a une erreur de syntaxe dans votre fichier, car le code copié/collé depuis wordpresss ( la plateforme d’édition ce site ) est des fois faux au niveau des accents.
Cordialement,
Hervé
Bonjour, comment allez-vous ?
J’ai une question
Comment puis-je enregistrer l’image que je sélectionne dans ma base de données ?
public function hookActionCategoryFormBuilderModifier(array $params)
{
$formBuilder = $params[‘form_builder’];
$formBuilder->add(‘image_file_upload’, \Symfony\Component\Form\Extension\Core\Type\FileType::class, [
‘label’ => $this->l(‘Imagen del Icono’),
‘required’ => false,
‘help’ => $this->l(‘Permite añadir iconos a las categorías’),
]);
$formBuilder->setData($params[‘data’]);
}
public function hookActionAfterCreateCategoryFormHandler($params)
{
$this->updateData($params[‘id’],$params[‘form_data’]);
}
public function hookActionAfterUpdateCategoryFormHandler(array $params)
{
$this->updateData($params[‘id’],$params[‘form_data’]);
}
protected function updateData(int $id_category,array $params)
{
$cat = new Category((int)$params[‘id’]);
$cat->icon= $data[‘icon’];
$cat->update();
}
Je pense que la fonction updateDate est fausse.
merci beaucoup
Bonjour Victor,
Désolé pour le délai de réponse.
Si vous regardez dans l’exemple la récupération du nom de l’image est réalisée via le code suivant :
$categoryField->image_field = $uploadedFile->getClientOriginalName();
Ou la variable $uploadFile est une instance de \Symfony\Component\HttpFoundation\File\UploadedFile qui permets à la fois de gérer l’upload et de récupérer le nom du fichier.
Cordialement,
Hervé
Bonjour,
SVP, je veux savoir pourquoi avec la création d’une nouvelle catégorie, les custom fields ne s’affichent pas ??!!
après le sauvegarde de la page, les champs s’affichent !!!!
MERCI
Bonjour Karim,
Est-ce que vous voulez dire que les champs ne s’affichent pas ( visuellement ) ou que les données saisies lors de la création de n’affichent pas ?
Cordialement,
Hervé
Bonjour,
Merci pour ce tuto mais quand je veux installer le module j’ai une erreur 500.
Compile Error: PrestaShop\PrestaShop\Adapter\Module\ModuleDataProvider::main(): Failed opening required
Sur la ligne 6 –> require_once __DIR__ . ‘/classes/CategoryFields.php’;
Je suis en version 1.7.6
Bonjour,
L’erreur est assez explicite 🙂
Il faut créer un fichier CategoryFields.php dans le dossier classes du module ( avec le contenu précisé dans l’article)
Et cela devrait fonctionner.
Cordialement,
Hervé
Bonjour,
Je débute sur la conception de module.
Je vous remercie vivement pour votre contribution et pour votre engouement à nous faire partager vos connaissances.
Cependant, je ne comprends pas comment créer ce module.
Combien faut-il créer de fichier dans le dossier du module ? Deux me semble-t-il mais ce n’est pas très clair. Je le sais car j’ai consulté les commentaires…
Dans le code complet, vous ne faites pas référence à au php. Est-ce normal ?
Bref, est ce qu’il serait possible que vous mettiez les étapes ou même une vidéo ou des copies d’écran ? 🙂
Merci d’avance pour votre aide,
Cordialement,
Hugo
Bonjour Hugo,
Merci pour votre retour.
Effectivement la majorité de mes articles présuppose des connaissances minimum en développement php et en création de modules Prestashop.
Puisque ce sont plutôt des bases techniques pour faire vos propres modules que des modules prêts à l’emploi.
Du coup je ne détaille pas les étapes initiales de la création d’un module Prestashop.
N’hésitez pas à consulter la documentation officielle sur le sujet : https://devdocs.prestashop-project.org/8/modules/creation/tutorial/
Concernant ce tutoriel il y’a effectivement 2 fichiers à créer :
Cordialement,
Hervé
Merci Hervé. Super démonstration trés claire.
Tout marche bien sur PS 8.0.5 J’ai adapté la technique sur les pages CMS avec l’idée de faire un blog.
La technique fonctionne bien sur les hooks ( actionCmsPageFormBuilderModifier & filterCmsContent )
Sauf que ( comme vous l’indiquez ) sur les catégories (par exemple ‘blog’) cela ne marche pas.
C’est dommage car cela permettrait de faire un blog trés simplement en exploitant les pages, tout bêtement et éviter un module de blog.
Savez vous si cette restriction est toujours d’actualité ?
CDT – BK
RE…
Sur 8.0.5,tout va bien sauf quand on veux créer une nouvelle catégorie.
Hh_categoryfields::getCustomFieldsValue(): Argument #1 ($id_category) must be of type int, null given, called in /home/bdpnl/public_html/prestashop.tout-va-bien.fr/modules/hh_categoryfields/hh_categoryfields.php on line 112
J’avais la même erreur sur prestashop 8.1.7.
Pour réparer celui, il faut mettre une condition sur la ligne $customFieldsValues = $this->getCustomFieldsValue($params[‘id’]); dans la function hookActionCategoryFormBuilderModifier()
Pour ma part, j’ai remplacé
$customFieldsValues = $this->getCustomFieldsValue($params[‘id’]);
Par
if($params[‘id’]) { $customFieldsValues = $this->getCustomFieldsValue($params[‘id’]); }
else { $customFieldsValues = false; }
Ca a l’air de fonctionner…
En réponse à ma 1er interv’. J’ai trouvé un hook qui marche pour les catégories de page CMS :
https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/filtercmscategorycontent
Pour faire un blog simple sans ajouter de module inutile votre technique est donc excellente.