Remplacer / surcharger un script sans modifier le script d'origne - Une méthode plus efficace?

Bonjour,

Pour appliquer des adaptations à un contexte d’utilisation particulier (épicerie coopérative tenue par des bénévoles), j’ai besoin de surcharger des scripts en utilisant un module externe.

Comme il n’y pas de fonction override je passe par un hook à l’intérieur de la page que je veux surcharger. Le but est de ne pas modifier les scripts de Dolibarr pour faciliter les mise à jour.

Je prend le premier hook de la page à surcharger et je recharge la page avec un header si possible ou bien avec js si le header est déjà envoyé. Je transmet les paramètres GET et si nécessaire, POST

En général il faut passer par doActions mais cela peut varier (voir le 1er hook qui sera toujours lancé) :

if ($parameters['context'] == 'pricesuppliercard:globalcard:main' && $parameters['modulevalcoop']!= 1) {
            // On est sur la page de la fiche produit
            // Rediriger vers page personnalisée
            // Transmettre les paramètres $_GET
            $query = http_build_query($_GET);
            // Rediriger vers la page personnalisée avec les paramètres GET
            header("Location: " . DOL_URL_ROOT . "/custom/valcoop/product/price_suppliers.php" . ($query ? "?$query" : ""));
            exit;            
        }

S’il faut récuperer aussi les paramètres POST :

// Remplacer la page reception/card.php
        // Ici il faut aussi récupérer les paramètres POST pour les passer à la page de destination
        if ($parameters['context'] == 'receptioncard:globalcard:main' && $parameters['modulevalcoop'] != 1) {
            // Construit la query string à partir de $_GET
            $query = http_build_query($_GET);
            $url = DOL_URL_ROOT . "/custom/valcoop/reception/card.php" . ($query ? "?$query" : "");
        
            // Génère le formulaire caché avec tous les paramètres POST
            echo '<form id="redirectForm" method="POST" action="' . htmlspecialchars($url) . '">';
            foreach ($_POST as $key => $value) {
                if (is_array($value)) {
                    foreach ($value as $v) {
                        echo '<input type="hidden" name="' . htmlspecialchars($key) . '[]" value="' . htmlspecialchars($v) . '">';
                    }
                } else {
                    echo '<input type="hidden" name="' . htmlspecialchars($key) . '" value="' . htmlspecialchars($value) . '">';
                }
            }
            echo '</form>';
            // envoie le formulaire en js avec le POST et le GET
            // de manière à surcharger la page reception/card.php avec les paramètres GET et POST
            echo '<script>document.getElementById("redirectForm").submit();</script>';
            exit;
        }

Si le header est déjà envoyé (pages index.php), il faut utiliser js :

// Redirige vers la page personnalisée avec tous les paramètres GET
            // Comme la page d'origine a déjà lancé un header, on utilise un script JS pour rediriger (par exemple si un écho ou un print a été fait avant)           
            echo '<script>window.location.href="'.DOL_URL_ROOT.'/custom/valcoop/index_module.php?'.$query.'";</script>';
            // Au cas ou le JS ne fonctionne pas, on met un meta refresh et un lien
            echo '<noscript><meta http-equiv="refresh" content="0;url='.DOL_URL_ROOT.'/custom/valcoop/product/index.php'.$query.'"></noscript>';
            echo '<a href="'.DOL_URL_ROOT.'/custom/valcoop/index_module.php">Cliquez ici si vous n\'êtes pas redirigé</a>';
            exit;

Dans la page modifiée je conserve les appels hooks mais je met un drapeau dans l’appel concerné pour éviter la récurence à l’infini :

$parameters = array('socid' => $socid,'modulevalcoop' => 1);

J’ai des remarques et des questions à ce sujet :

  • Est-ce que cette méthode est correcte ? Cela a l’air de fonctionner.
  • Ne serait-il pas plus simple d’incorporer un hook prévu à cet effet dans main.inc ? Du type « beforepage ». En écrivant cela je me rend compte qu’il y a peut-être un hook dans main.inc à utiliser pour éviter de commencer à charger la page à surcharger. Il faut que je regarde cela.
  • Je voulais savoir s’il y a une méthode plus directe de surcharge d’une page. Et au besoin, la rajouter :
  • Un peu comme dans Joomla : on peut simplement mettre sa page dans son thème en utilisant la même arborescence que dans le coeur et le tour est joué : joomla charge cette page de préférence à la page du coeur.
  • Et du coup cela serait valable aussi pour surcharger une classe (la méthode que j’ai trouvée est moins directe).

Par exemple dans main.inc, on va vérifier s’il existe un script avec même nom / même arborescence à l’intérieur du dossier « override » du thème actif (cela fonctionne comme cela dans Joomla). Une autre possibilité serait d’aller voir dans les modules en gérant les priorités. Si le fichier existe on charge ce fichier à la place de l’autre. La surcharge est ainsi beaucoup plus facile. J’ai trouvé cela très pratique dans Joomla.

Souvent en effet, on est amené à changer toute la page. Le Hook tel qu’il existe dans Dolibarr est davantage adapté aux changements locaux à l’intérieur d’une page.

Hello
une astuce que j’ai déjà vue employé c’est de remplacer une url par une autre (avec myfields) redirigeant vers une page spécifique

J’ai regardé main.inc.php et une bonne méthode semble être l’utilisation du hook « llx_header ». Comme cela on intervient avant même le chargement de la page. Je l’ai utilisé pour les index, cela évite de rediriger après le chargement et permet d’utiliser header pour rediriger la page.

Je pense que cela pourrait être utilisé pour toutes les pages qu’on veut surcharger.

Mais un override à la manière de Joomla serait encore mieux.

Bonjour,
Je ne comprends pas la finalité de la chose, vous voulez modifier quoi, l’apparence des pages ? Il faut peut-être regarder du côté des thèmes, il y en a 2 par défaut de mémoire, peut-être que vous pouvez partir d’eux pour personnaliser votre aspect visuel…
Si c’est modifier les valeurs/caractéristiques d’un objet, avec un hook on récupère l’objet, on le modifie et on le renvoi.
Si c’est ajouter/modifier/supprimer des boutons, pareil avec les hooks, dans le même principe que l’objet, on récupère modifie puis retourne.
Et pour le problème de boucle il faut que votre hook retourne 0 ou 1 pour savoir s’il remplace complètement le traitement ou pas (de tête je ne me rappelle plus quel retour correspond à quoi…)
Après si vous voulez complètement tout reimplementer la gestion de la page y compris les GET et POST peut être qu’à ce moment là ça sert à rien de reprendre une page native de Dolibarr, mais de faire un module complet (c’était le cas il y a quelques années par exemple, où il y avait le module natif Note de Frais et un module payant plus évolué Note de Frais + qui désactivait le module d’origine et le remplaçait)

2 « J'aime »

that is the question !

1 « J'aime »

Bonjour,

  • Est-ce que cette méthode est correcte ? Cela a l’air de fonctionner.

Concernant la vérification du contexte, fais plutôt un explode(':', $parameters['context']) et tu vérifies si le nom du contexte est dans le tableau.

  • Ne serait-il pas plus simple d’incorporer un hook prévu à cet effet dans main.inc ? Du type « beforepage ». En écrivant cela je me rend compte qu’il y a peut-être un hook dans main.inc à utiliser pour éviter de commencer à charger la page à surcharger. Il faut que je regarde cela.

Je pense qu’il manque un trigger et un hook (et oui, Dolibarr s’enquiquine avec 2 systèmes qui pourtant fonctionnent pareil, mais ont juste un but différent)

  • Je voulais savoir s’il y a une méthode plus directe de surcharge d’une page. Et au besoin, la rajouter :
  • Un peu comme dans Joomla : on peut simplement mettre sa page dans son thème en utilisant la même arborescence que dans le coeur et le tour est joué : joomla charge cette page de préférence à la page du coeur.
  • Et du coup cela serait valable aussi pour surcharger une classe (la méthode que j’ai trouvée est moins directe).

La vue n’est pas séparée du contrôleur dans Dolibarr, c’est plutôt un genre de tricot, où tout est mêlé. Les « thèmes » se résument plus où moins à du CSS.

1- Pour effectuer des modifications sur l’interface le plus tôt possible avec un module, le fichier à privilégier est bien /classes/actions_monmodule.class.php, car c’est celui qui est toujours inclus. Mais pour être sûr que ton code est exécuté au plus tôt, et surtout, à tous les coups, il faut le mettre en dehors de la classe.
Dolibarr étant ce qu’il est, la fonction DoActions, en plus d’être exécuté plus tardivement, est manquante selon les pages, les versions, …
Évites d’utiliser __construct(), car la classe est instanciée une multitude de fois.
Le fichier actions_monmodule.class.php est inclus plusieurs fois également, mais moins souvent qu’elle est instanciée, tu fais donc un require_once, en première instruction du fichier, vers le fichiers contenant tes modifs.
Évidemment, pas de variables $context fournie. Mais avec un str_ends_with (ou fonction équivalente pour PHP < 8) et $_SERVER['SCRIPT_NAME'], on arrive à se débrouiller.
Pour les scripts à exécuter une fois que Dolibarr a terminé sa tambouille, même hack, avec un register_shutdown_function.

2- Pour effectuer des actions en base de données (lorsqu’un objet est créé, mis à jour, supprimé, …), on utilise plutôt les triggers. Mais là encore, ce n’est pas fiable à 100%. Il manque pas mal de triggers. Parfois, le fichier n’est même pas chargé. Si le fichier est chargé, et qu’il manque le trigger approprié, je transpose l’astuce n°1 au fichier des triggers. Sinon, là encore, si je n’ai pas le choix, j’utilise le fichier actions_monmodule avec l’astuce n° 1. Mais uniquement si cela ne pose pas de problème que le code ne soit pas exécuté lorsque l’API de Dolibarr est utilisé. Sinon, en dernier recours, je ne vois pas d’autres solutions que les tâches crons.

Le mieux est certainement de faire des pull requests sur github, mais pour un trigger, on a parfois pas le temps d’attendre 3 à 6 ans que la majorité des Dolibariens adoptent la version prérequise pour utiliser le module…

Bonjour,

De notre côté on a arrêté de se compliquer la vie.
La solution adoptée c’est un fork de dolibarr dont on modifie le core comme on veut et selon nos besoins.
C’est nettement plus simple et plus rapide que de déporter ça dans des modules et de se casser la tête avec des hooks pour essayer « de ne pas toucher au core ».
Il faut de toute manière réviser les modules lorsqu’on fait une mise à jour de dolibarr, là on a juste a réapliquer les patchs sur le nouveau core, ça ne demande pas plus de travail (on fait une mise à jour toutes les 6 versions, soit 3 ans étant donné qu’on a autre chose à faire que de suivre le rythme des versions de dolibarr - et on n’a toujours pas compris à quoi sert cette frénésie de sortie des nouvelles versions
).

1 « J'aime »

@AGI : je pense que C3DO a bien répondu à votre remarque, merci à lui. Et il faut écouter ce que dit Beers… J’aurai pu faire pareil mais si un jour je ne m’occupe plus de cette coopérative, ce qui arrivera, ils n’arriveront pas à mettre à jour ou pire ils pourront tout écraser…Et j’ai quand même l’espoir de ne pas avoir à mettre à jour les modules à chaque révision. Mais Beers a raison, les versions deux fois par an, c’est usant. Sans parler des changements de nom de variable ou de méthode. Là, c’est abuser.

Sur le fond : je trouve dommage que mes remarques ne soient pas prise au sérieux par certains, je vois des réponses assez arrogantes et qui me prennent pour un imbécile. Merci à eux ! Les dernières interventions montrent au contraire que le pb est réel. Je pense que la communauté Dolibarr devrait accepter les remarques de l’extérieur. Je suis déçu du ton de certaines réponses, d’autant que j’apporte des pistes et des exemples de code. Le monde complexe des hooks et des triggers de Dolibarr a des avantages mais aussi beaucoup d’inconvénients. Il faut d’abord être un bon connaisseur pour les utiliser. Or quand on commence à utiliser Dolibarr on n’est pas encore un expert par définition, et on vient probablement d’un univers où des solutions simples d’override existent, rapidement applicables et néanmoins robustes.

J’aurais une multitude de remarques sur l’ergonomie et le flux de travail de Dolibarr qui est souvent très long et fastidieux. C’est bien mieux que de nombreux progiciels imbuvables au niveau ergonomie, mais pas au niveau d’une application grand public. J’ai passé trois ans à diviser le nombre de clics par dix, à éviter les changement d’onglet, de modules, à éviter les risques d’erreurs, surtout avec des bénévoles, bref à simplifier l’utilisation. Je proposais juste de partager ce travail.

J’ai vu de nombreux systèmes mourir parce que pas à l’écoute des besoins provenant de l’extérieur. J’en ai vu d’autres se transformer en quelques années en multinationales et en milliardaires parce qu’à chaque fois que l’utilisateur était agacé par un fonctionnement indésirable, quand il revenait la semaine suivante, le système était déjà corrigé, et cela semaines après semaines. D’où la règle n°1 : Ce n’est pas à l’usage de s’adapter au produit, c’est au produit de s’adapter à l’usage. Idem vis à vis des développeurs qui utilisent le système.

Odoo est souvent choisi par les coopératives de consommateurs ou les petites structures. Et je me demande si finalement je ne vais pas leur recommander de rester sur Odoo ou de le choisir, moi qui m’apprêtai justement à leur proposer Dolibarr avec une version adaptée.

Donc oui, l’override d’un script entier est souvent très pratique et non, les thèmes, les canvas, les hooks et les triggers ne permettent pas toujours une solution adéquate, ou alors des détours assez complexes qui nécessitent une bonne connaissance préalable. Il suffit de lire les forums pour le constater. Par contre le passage par une surcharge de script est très simple à mettre en oeuvre, même par un développeur qui découvre Dolibarr, ce qui facilite son adoption. Et elle est robuste et pas plus compliqué à mettre à jour. Mais pourquoi faire simple quand on peut faire compliqué ? Avec la logique actuelle de Dolibarr on risque de transformer trois ou quatre modifs simples en usine à gaz, il n’y a qu’à lire le forum pour le constater : il faut étudier des dizaines de possibilités, multiplier les hooks pour trois ou quatre modifs, etc. Même les experts se perdent en discussions savantes. Alors qu’un simple override de script et le tour est joué.

Je pense que les deux systèmes sont parfaitement complémentaires. Ceux qui veulent jouer avec des hooks peuvent modifier le comportement d’une partie du script : deux ou trois hooks, ou bien un trigger, un petit bout de canvas, ou bien non… ou on peut choisir aussi de surcharger le tout.

Il y a plein des cas les hooks ne sont pas satisfaisants car on n’intervient pas là ou on voulait, il faut du temps pour étudier la meilleure stratégie (choix du hook, du trigger, du contexte, des paramètres…), il faut ruser. Or la ruse est justement ce qu’il faut éviter. Une bonne modif doit aller au plus simple, au plus robuste et au plus lisible. Par exempe si c’est la méthode d’une classe dont le comportement est pour nous indésirable, je préfère la modifier à cet endroit même plutôt que de chercher quel hook ou quel trigger utiliser pour défaire ou refaire ce qui a été fait. L’override d’une classe permet cela. Et c’est possible dans la plupart des systèmes que j’ai utilisé.

Je ne vois pas bien pourquoi Dolibarr ne pourrait pas implémenter la surcharge de script en plus des hooks, canvas, triggers, thèmes : c’est parfaitement complémentaire et cela laisserait au développeur le choix des méthodes. C’est très accessible, c’est ouvert à ceux qui viennent d’un autre système, c’est tout aussi robuste. Et dans certains cas beaucoup plus efficace.

1 « J'aime »

@c3do : merci pour cette réponse complète, que j’étudierais à fond le moment venu !

Juste pour signaler que dorénavant j’utilise le hook llxheader pour surcharger une page, comme cela on intervient avant que la page soit chargée, c’est pas mal. Et au lieu de mettre un drapeau je teste l’adresse de la page que je veux remplacer pour éviter la récurrence.

Et pour surcharger une classe, la méthode que j’ai donnée fonctionne bien.

Au sujet de cette surcharge de classe : bien sûr qu’on peut utiliser mille autres façons de faire, un trigger par exemple. Mais outre que je préfère agir directement là où le comportement ne me convient pas, plutôt que de modifier après coup (Méthode : non on ne change pas l’état. Trigger : en fait si…) j’ai déjà suffisamment à faire avec cette maj donc je récupère la modif que j’avais faite initialement, sauf si j’ai beaucoup plus simple. Et oui il faut tenir compte de l’historique des modifications. Dolibarr aussi a une histoire, ce qui explique certain caractéristiques actuelles.

@dum77

pour aider à convaincre il faut souvent montrer et laisser ensuite l’idée faire son chemin jusqu’à ce que cette « bonne idée » soit adoptée, c’est une forme « d’inception » …

est-ce que dans votre cas vous pourriez publier l’ensemble de « votre » dolibarr tel qu’il est en prod sans les données bien sûr ? ainsi il serait possible de « voir le résultat global » ?

En fait, le plus gros souci n’est pas qu’il n’y a pas de surcharge possible. Le problème des surcharges, c’est d’avoir à gérer ensuite la compatibilité avec les modules externes. On peut vite se retrouver avec un module qui remplace la surcharge d’un autre. Ce n’est pas une mauvaise idée de donner le choix de surcharger, mais c’est généralement déconseillé de les utiliser.

Le premier souci est le système des triggers et des hooks qui est trop complexe à appréhender pour les débutants, en plus d’être largement incomplet. Ça partait sûrement d’une bonne intention d’avoir 2 systèmes distincts. Si ça touche à la base de données, on utilise les triggers, si c’est de l’interface, c’est un hook.

Le problème c’est qu’il y a la possibilité de faire des petites modifs depuis l’interface, comme modifier seulement un libellé ou une date, et là pas de triggers. Ça passe par des petites fonctions dédiées. Et parfois, pas de hooks non plus, sinon c’est pas drôle… D’où mon système de triggers « hors système », qui passe vérifier la bdd après l’action.

Ensuite, le système des modules est tellement une galère, qu’ils sont obligés de maintenir un « modulebuilder » pour aider les débutants. On est loin de wordpress ou prestashop, où un seul fichier dans un dossier suffit à créer un module. Le descripteur est dans un dossier de dossier et n’est lu qu’à l’installation, on a le fichier des hooks dans un autre dossier avec un nom spécifique, les triggers dans un autre dossier de dossier avec un autre nom plus complexe. De plus, il y a une logique d’organisation différente entre les fichiers des modules core et ceux des modules externes.

Malheureusement, je pense que tout ça peut rebuter les développeurs ou intégrateurs à proposer Dolibarr à leurs clients.

1 « J'aime »

@c3do

La question des incompatibilités entre surcharges est un pb en effet, mais cela existe aussi avec les hooks et les triggers. Dès lors que des modules modifient le comportement interne forcément il peut y avoir des incompatibilités. Mais cela peut se gérer.

Avec certains systèmes comme Joomla le dossier override est dans le thème et comme il n’y en a qu’un qui est activé à la fois… mais cela pose d’autres difficultés. Sur Prestashop il est à la racine et un module peut facilement modifier ou écraser le fichier placé par un autre module, cela devient chaotique. Je serais plutôt pour mettre un override dans les modules, cela évite d’écraser le fichier d’un autre. Il peut y avoir un système de priorité ou quelque chose pour gérer les conflits. Quand un module fait des surcharges il doit bien sûr le documenter, ce qui est généralement le cas.

Le modulebuilder fonctionne bien, c’est pratique, cela évite des tonnes de codages rébarbatif et il y a beaucoup d’indications en commentaire dans le code. Mais elle sont parfois fausses (picto notamment), et là cela devient galère. Merci à tous ceux qui font la doc d’ailleurs, mais malheureusement elle est souvent incomplète ou fausse ce qui ne facilite pas la prise en main. Heureusement il y a maintenant l’IA, mais parfois elle s’y perd elle aussi.

Oui, c’est dommage que les modules internes et externes aient une structure différente. Cela rend plus difficile de les cloner - ce qui serait une autre façon de surcharger, mais là on perd les mises à jour. Et impossible de prendre exemple sur eux.

Oui bien sûr, dès que la version de test pour V21/PHP8.3 sera … testée, je préparerais une version en ligne avec des données exemples, et ainsi qu’un dépôt et un descriptif des modifications.

hmmm … « release early, release often(Release early, release often — Wikipédia) », si votre objectif est vraiment de publier, n’attendez pas que ça soit « parfait » le risque est que ça ne soit jamais le cas :slight_smile: