thelia book


thelia book

 

THELIA BOOK Théliaddicts Editions Jean-Baptiste Billot Ceci est une présentation „ „ non officielle du moteur de Thélia, destinée aux néophytes comme aux experts en programmation php/Sql qui souhaitent développer des fonctionnali- tés poussées pour leur site ou mieux, des plugins. Elle n?est pas suffisante pour permettre à elle toute seule ce type de développements mais devrait satisfaire un grand nombre de types d'utilisateurs de thélia. Vous pouvez envisager cette étude comme une découverte de l?application et de la „ „ programmation pour les débutants. Pour les néophytes complets en php/Sql des sites com- me http://www.vulgarisation-informatique.com/php.php ou http://www.siteduzero.com sont vivement conseillés avant ou après la lecture du premier chapitre. Si vous êtes utilisateur de Thélia, et satisfait de son mode de fonctionnement simple et „ „ puissant, basé sur son propre langage dynamique de boucles que vous maîtrisez et qui vous suffit, vous pouvez aussi être intéressés par la lecture de ce document : il fourmille de détails utiles sur les fonctionnalités avancées et décrit sans technicité les arcanes de l'application. Si vous êtes Webmaster et concevez ou administrez des sites Thélia pour un client ou „ „ vous-mêmes, vous apprécierez d'en savoir plus sur le programme que vous manipulez. Si vous êtes un développeur aguerri et que vous vous intéressez à Thélia, cet ouvrage „ „ devrait aussi vous plaire pour les nombreux tableaux récapitulatifs et synthèses qui seront autant d'aide-mémoires pour accélérer vos développements. Même si j?ai parfois volontairement simplifié l?explication pour plus de clarté, je suis „ „ moi aussi un débutant : cette étude est donc forcément incomplète, imprécise, imparfaite voire peut-être pas tout à fait juste... Fruit d?une démarche en cours, elle doit être considérée comme telle et non comme l?analyse finalisée du script Thélia par un développeur profes- sionnel. Bonne lecture ottoroots@gmail.com - 2009 1.4 Inside Mode d'emploi, posologie et précautions d'usage Le Marchand Le Web Master Le Développeur THELIA BOOK /*********************************************************************************************/ /* /* Thelia /* /* Copyright (c) Octolys Development /* email : thelia@octolys.fr /* Web : http://www.octolys.fr /* /* This program is free software; you can redistribute it and/or modify /* it under the terms of the GNU General Public License as published by /* the Free Software Foundation; either version 2 of the License, or /* (at your option) any later version. /* /* This program is distributed in the hope that it will be useful, /* but WITHOUT ANY WARRANTY; without even the implied warranty of /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the /* GNU General Public License for more details. /* /* You should have received a copy of the GNU General Public License /* along with this program; if not, write to the Free Software /* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA /* /*********************************************************************************************/ A propos de thélia : Au sujet de cet ouvrage vous pouvez : „ „ - Le télécharger - Le dupliquer - Le stocker - Le partager Et ce dans le cadre de tous types d'activités, personnelles ou commerciales. Au sujet de cet ouvrage vous ne pouvez pas : „ „ - Le modifier - L'imprimer - Le commercialiser Ces restrictions sont mises en place afin de vous inciter à remonter aux auteurs vos remar- ques, vos propositions ainsi que les coquilles. De nouvelles versions seront régulièrement mises à disposition. Table des matières Livre 1 - Le moteur du FO 1. Appréhender Thélia par la théorie : présentation du moteur du FO 12 Présentation 1. générale de Thélia 13 La 1. base de données 14 Les 2. variables de session 15 Le 3. moteur 16 Le 4. site Web 20 Synthèse 5. générale du Front Office Thélia 21 Présentation 2. du moteur Thélia 22 Présentation 3. du fichier moteur.php 30 Le 1. chargement des fonctions et classes (partie I) 30 L?initialisation 2. des variables (parties II et III) 30 Le 3. traitement des actions (partie IV) 33 Le 4. traitement du squelette (partie V) 34 Synthèse 5. de la présentation du fichier moteur.php 34 Notions 4. avancées du moteur : le traitement d?une boucle Thélia 36 Avant 1. la fonction Analyse() : La fonction Inclusions() 37 La 2. fonction Substitutions() 37 La 3. fonction filtres() 38 La 4. fonction preg_replace 38 La 5. boucle while... : les fonctions pre(), boucle_simple(), post() 39 Annexes 5. pour aller plus loin 44 Les 1. principales fonctions php utilisées par Thélia 44 Quelques 2. fonctions génériques du moteur Thélia 45 La 3. variable de session ?navig? 46 Les 4. fonctions boucleXXX du fichier boucles.php 52 2. Appréhender Thélia par la pratique : Développer un plugin (1/2) 54 Présentation 6. des plugin dans Thélia 55 La 1. classe PluginsClassiques 55 Votre 2. nouvelle classe 55 Les 3. points d?entrées pour les plugins dans le moteur. 55 Les 4. méthodes des plugins 58 Le 7. projet : création d?un plugin devis 59 Le 1. fichier inclure_session.php 62 Le 2. fichier Devis.class.php 62 Livre 2 - Le moteur du BO 3. Appréhender Thélia par la théorie : L?administration (Back Office) 74 Présentation 8. générale du BO Thélia 75 Le 9. schéma du BO de Thélia 79 Le 1. fichier auth.php 82 Le 2. fichier pre.php 84 La 10. page produit_modifier.php (BO Thé- lia) 85 Les 11. plugins dans le BO Thélia 86 L?inclusion 1. du fichier Nomduplugin_admin.php IF 86 La 2. fonction modules_fonction() MF 86 La 3. fonction admin_inclure() AI 86 Annexes 12. pour aller plus loin avec Thélia 89 Annexe 1. 1 : Synthèse des points d?entrée pour les plugins 89 Livre 3 - Le catalogue 4. Appréhender Thélia par la théorie : L?organisation du catalogue 94 L?arborescence 13. des rubriques dans Thé- lia 95 Les 1. rubriques dans le BO : les pages parcourir.php et rubrique_ modifier.php 95 La 2. gestion de l?arborescence des rubriques sur le FO : le paramè- tre ?parent? 96 Le 3. paramètre ?pasvide? et la fonction arbreBoucle() 98 Le 14. mécanisme de navigation à travers le catalogue Thélia 101 Structure 1. du site et URL 101 Les 2. redirections temporaires 104 La 3. pagination et la boucle page 106 Le 4. file d?Ariane : la boucle chemin 115 5. Appréhender Thélia par la théorie : Caractéristiques et déclinaisons 116 Définitions 15. des caractéristiques et des décli- naisons dans Thélia 117 La 16. gestion des caractéristiques dans Thé- lia 119 La 1. création d?une caractéristique : caracteristique.php et caracte- ristique_modifier.php 119 Le 2. rattachement d?une caractéristique aux rubriques et aux pro- duits 120 Les 3. boucles liées aux caractéristiques : 124 La 17. gestion des déclinaisons dans Thélia 125 La 1. gestion d?une déclinaison par le marchand 125 La 2. gestion d?une déclinaison pour le client 127 Livre 4 - La commande 6. Appréhender Thélia par la théorie : La parcours d?une commande 136 Les 18. étapes constitutives d?une commande Thélia 137 La 1. partie Front Office d?une commande 137 La 2. partie Back Office de la commande 138 La 3. commande : classe, table, boucle, variable de session 139 Du 19. produit à l?article : l?url #PANIER et l?url adresse.php Etape 1 140 La 20. validation du mode de livraison et de l?adresse de livraison = adresse.html et l?url #URLCMD Etapes 3 et 4 144 La 1. validation de l?adresse de livraison 144 La 2. validation du mode de livraison 144 La 21. gestion des remises dans Thélia = la page commande.html Etape 5 147 La 1. création d?une remise par le marchand 147 L?utilisation 2. d?une remise par le client 148 La 22. validation d?une commande = la va- lidation du moyen de paiement = commande. html et l?url #URLPAYER Etape 6 150 Le 23. paiement = la redirection de l?url #URLPAYER Etape 7 154 Les 24. statuts d?avancement de la comman- de dans le BO Thélia Etape 8 à Etape 13 157 La 25. gestion du stock 158 La 26. gestion du port 161 C 1. alcul des frais de livraison pour une commande ? 161 Le 2. paramétrage du port dans le BO 162 Les 3. frais de port sur le Front Office 163 La 27. gestion de la TVA 169 Gestion 28. de la facture et du bon de livrai- son 170 La 29. gestion des mails dans Thélia 173 Le 1. mail de confirmation de commande 173 Le 2. mail de confirmation d?envoi du colis 175 Annexes 30. : pour aller plus loin avec Thélia 176 La 1. fonction paiement() en détail 176 Bilan 2. sur les valeurs (?) disponibles 182 Le 3. fichier facture.php en détail 184 7. Appréhender Thélia par la pratique : Modifications fonctionnelles 185 Livre 5 - La base de données 8. Appréhender Thélia par la théorie : Présentation de la base de données 188 Survol 31. des concepts théoriques du SGB- DR utilisé par Thélia 189 Principes 32. généraux d?une requête SQL avec PHP 192 Principes 33. généraux sur la manipulation de la base par le moteur Thélia 193 La 1. classe mère : Cnx. 195 La 2. sous-classe intermédiaire : Requete 196 La 3. sous-sous-classe principale : Baseobj 197 Les 4. classes concrètes : synthèse d?une connexion à la base de données dans Thélia 198 Schéma 34. général de la base de données Thélia 202 Livre 6 -Fonctionnalités avancées Livre 7 - Classes, boucles, tables : syn- thèse générale de Thélia Livre 1 Le moteur du Front Office Comprendre Agir Modifier Enrichir Adapter Voyage au centre d?une boucle 1. Appréhender Thélia par la théorie : présentation du moteur du FO Nous voici au début d?une longue étude devant nous amener à la maîtrise du pro- „ „ gramme Thélia, un programme Open Source permettant d?envisager la conception d?un site e-commerce complet comprenant un site Web (le Front Office) pour les client et une inter- face d?administration (le Back Office) pour le marchand. Dans le premier livre nous nous intéresserons exclusivement au Front Office : nous al- „ „ lons décrire comment Thélia par son script principal, motorise le fonctionnement d?une vitrine marchande depuis l?affichage du catalogue jusqu?à l?achat en ligne. Notez que je compte décrire la motorisation de Thélia et non vous expliquer comment concevoir votre vitrine : je suppose que vous êtes déjà utilisateur de Thélia : vous avez déjà conçu un site, vous avez lu le Wiki et êtes allé (souvent) compléter vos connaissances dans le forum à l?aide de la communauté. Je ne reviendrais donc pas sur les fondamentaux fonctionnels : cette étude est essentiellement destinée aux utilisateurs avancés de Thélia. Le premier chapitre (présentation générale de Thélia) de cette première partie est plus „ „ particulièrement consacré à présenter les rudiments de la programmation php et pourra être ignoré par ceux pour qui programmer dans ce langage est chose courante. Les chapitres suivants de cette partie seront l?occasion de détailler le fonctionnement du „ „ moteur de template : Thélia, comme d?autres applications offre en effet l?indéniable avantage de permettre à tout un chacun d?envisager la mise en place d?un site sans autre connaissan- ce que le HTML et le CSS, des langages simples et accessibles. La couche php est prévue à côté de la couche html et le méta langage des boucles Chapitre 1 : Présentation générale de Thélia Chapitre 2 : Présentation du moteur Thélia Chapitre 3 : Présentation du fichier moteur.php Chapitre 4 : Notions avancées : Traitement d?une boucle Thélia par le moteur Chapitre 5 : Annexes pour aller plus loin avec Thélia Sec1:14 Présentation générale de 1. Thélia Je vais à travers cette étude détailler le principe des sites dynamiques programmés en php, le type de sites que vous créez lorsque vous utilisez Thélia. Le dynamisme est indispensable dans le cadre d?une boutique en ligne : contrairement à beaucoup d?autres types de sites In- ternet qui peuvent s?envisager de manière statique (affichage d?une suite de fichiers html), le e-commerce entend recréer un environnement contrôlé par le client sur le Front Office ou le marchand sur le Back Office. Votre boulanger ou votre boucher, comme un serveur php, sa- vent comment répondre aux requêtes de leurs clients grâce à un jeu d?instructions (le manuel du parfait commerçant) : pour le serveur, ce jeu d?instructions est un script, un programme écrit en php : le moteur de Thélia. Dans ce premier chapitre, j?envisage le programme Thélia dans son intégralité : ce pro- gramme, une fois installé sur votre disque local ou chez votre hébergeur, est composé de multiples fichiers stockés dans plusieurs dossiers répartis dans 2 dossiers principaux : une dossier "site" et un dossier "base de données". Le moteur, le template de base et le BO com- posent le dossier "site". Le moteur Thélia est constitué par l?ensemble des fichiers contenus dans les dossiers "fonc- tions" et "classes" de Thélia : grâce aux inclusions (en php : inclure un fichier dans un autre), tous ces fichiers sont assemblés les uns après les autres sur le fichier principal moteur.php lui même inclus sur chaque page du site. (Les fichiers xxx.php du template de base). Le mo- teur a pour but de traiter les actions d?un visiteur : ce traitement diffère selon les interactions de ce dernier sur le site (depuis son arrivée sur la page d?accueil jusqu?à l?achat en ligne) néanmoins toute interaction sera un appel à une url et aboutira à la création d?une page Web, résultat du traitement du moteur sur le squelette de l?url appelée (xxx.html). INTER ACTION INTER ACTION URL URL MOTEUR MOTEUR SQUELETTE SQUELETTE PAGE WEB PAGE WEB ACTION ACTION + action optionnelle Le contexte : Considérons (de manière simpliste) Thélia comme un empilement de 4 couches, 4 modules logiques interconnectés ayant chacun un rôle essentiel dans tous les processus de votre site (de la simple navigation à travers les pages, l?affichage, à l?achat en ligne, l?envoi d?un formulaire...) Le dessin ci-dessous illustre cette manière de voir que nous détaillerons juste après : 15 La base de données 1. La couche la plus basse de l?organisation, la base de données, contient des tables, elles- mêmes contenant tous les éléments "durables" de votre activité : notez que je n?emploie pas le terme "site" mais "activité" : il s?agit de stocker les éléments présentés sur le site bien sûr mais également les éléments générés par l?activité de votre site : ainsi les fiches produits, les fiches clients, les commandes, les rubriques, les contenus.... Chacun de ces types de données bénéficie d?une ou + table(s) dédiée(s) dans la base (soit 50 tables au total à l?ins- tallation : les plugins optionnels sont susceptibles de créer des tables supplémentaires. Vous même, suite à la lecture de ce document...). La structure de la base suit une logique modulaire que nous étudierons en détails plus tard PRODUITDESC PRODUITDESC STOCK STOCK DéclinaisonsS DéclinaisonsS CARACTERISTIQUES CARACTERISTIQUES 16 Instruction Instruction Instruction Instruction Instruction Instruction Instruction Base de données Variables de session Moteur Site web Classe Classe Classe Classe Classe Classe Classe Table Table Table Table Table Table Table $commande $formcli $client $panier $promo Interaction Affichage url : pageX.php squelette : pageX.html page Web : pageX.html Une interaction du visiteur sur le site Web... ... Aboutit à l?affichage d?une page Web Tout le contenu de votre site est stocké dans ces tables mais pas le site lui-même : le dossier Thélia, hébergé sur le serveur http ne fait pas partie de la base : La structure de votre site (les pages .php) et les éléments graphiques mettant en forme „ „ les données pour le visiteur (les squelettes html que vous allez créer ou modifier) : ils sont et doivent rester à la racine de votre site. Le contenu du dossier client (images, PDF, flash) : ce sont des "objets informatiques" „ „ peu adaptés au stockage dans une base de données : ils seront donc entreposés et mis à disposition du serveur dans un dossier dédié du site, le dossier "client". Le moteur devant s?exécuter, il n?est pas non plus une donnée de la base : les fichiers „ „ le constituant sont logiquement placés à la racine de votre site (les dossiers "fonctions" et "classes" pour le moteur du front office que nous détaillons dans cette partie et le dossier "admin" pour le moteur du back office que nous étudierons dans la troisième partie). Et plus concrètement ? La base est un ensemble de fichiers stockés sur votre disque dur (local) ou votre serveur d?hébergement (distant). Ces fichiers ne font pas partie du dossier Thélia contenant votre site, ce qui veut dire qu?ils ne sont pas stockés sur le serveur Web mais sur un autre ser- veur : ils sont entreposés à l?extérieur, sur un serveur spécifique, un serveur Sql : pour une installation en local par exemple sur Xampp votre base de donnée Thélia est ici : C:/Xampp/ mySql/data/Thelia (ou le nom que vous lui aurez donné lors de l?installation). Vous n?inter- viendrez jamais directement sur ces fichiers. Ils sont créés lors de la phase d?installation. La base de données est ensuite "manipulée" lors d?une action sur le BO ou sur le site grâce à des fonctions php du moteur. Hors de Thélia (point de salut), on y accède par une interface spécifique dédiée à la gestion des bases Sql (PhpMyAdmin par exemple). Nous aborderons les bases de données dans une partie future. Les variables de session 2. La seconde couche de Thélia est composée des variables de sessions : si la base de don- nées est constituée d?éléments destinés à être stockés pour durer (imaginons un entrepôt), les variables de session vont contenir au contraire les données destinées à ne durer que le temps de la visite d?un client : imaginez les variables de session comme un caddy, un chariot où le visiteur va stocker l?ensemble des données dont il a besoin pendant sa visite : les pro- duits qu?il désire acheter (le "panier") mais également son identité (pour éviter d?avoir à se ré identifier à chaque nouvelle page) ou encore ses commandes... Lorsqu?il quitte le site, son caddy est vidé (les données que son caddy contenait n?étaient que des copies des données de la base : ces dernières ne disparaissent pas avec le départ du client, heureusement). Et plus concrètement ? Les variables de session sont des espaces de mémoire que le serveur réserve pour un client le temps que dure sa visite : vous n?avez pas à vous soucier de ces variables et de leur contenu : tout est géré par le serveur. Il suffit de les créer puis de les manipuler dans le moteur; le serveur s?occupe du reste. Nous y reviendrons en détail dans une prochaine partie. 17 Le moteur 3. Vient ensuite la couche moteur : il s?agit d?un script php d?environ 7000 lignes : pour une meilleure visibilité, le script n?est pas écrit d?un seul bloc mais segmenté, éparpillé entre une centaine de fichiers et reconstitué à chaque fois qu?un visiteur entre sur votre site puis clique sur des liens ou des boutons. Le but de ce tutoriel est de présenter graphiquement le moteur, complet, reconstitué, c?est-à-dire tel qu?il est lu et exécuté par le serveur afin d?en compren- dre les principaux rouages. Présentation des notions de fonction/méthode, variable/attribut en php Un script php est un ensemble d?instructions (des ordres) que vous codez afin que le serveur effectue des tâches, exactement comme un robot que vous programmeriez. En effet quand nous associons le terme "langage dynamique" à "php", nous indiquons que ce langage nous permet de coder des instructions afin qu?un serveur exécute des tâches (nous verrons les- quelles). Le html n?est pas dynamique : lorsque nous codons du html nous ne sommes pas entrain de programmer un serveur pour qu?il agisse d?une manière ou d?une autre : nous écrivons un fichier que le serveur Web, qui connaît sa tâche et qui n?a donc besoin d?aucune instruction de notre part, enverra à tout navigateur qui appelle l?url associée au fichier (très basique comme comportement). Le moteur php lui, est une suite d?instructions, des ordres à destination d?un serveur... qui cause le php. En php, ces instructions sont appelées des "fonctions" (ou des "méthodes" : voir infra) : il s?agit de toutes les compétences que le serveur devra savoir mettre en oeuvre, en fonction des ordres donnés par le client à travers sa navigation sur le site : manipuler la base de données, les variables de session, afficher un page html contenant des données précises comme le contenu d?une rubrique... Une introduction à la programmation avant d?aller plus loin. Imaginez que vous programmiez un robot à ranger de la vaisselle dans une armoire : Vous pourriez imaginer coder cette séquence : "-1 prendre les assiettes -2 si l?armoire est fermée ouvrir l?armoire -3 ranger les assiettes à côté des autres assiettes" Puis la séquence : "-1 prendre les fourchettes -2 si l?armoire est fermée ouvrir l?armoire -3 ranger les fourchettes à côté des autres fourchettes" Puis la séquence : "-1 prendre les bols -2 si l?armoire est fermée ouvrir l?armoire -3 ranger les bols à côté des autres bols" ... Vous comprenez que s?il faut aussi prévoir les couteaux, les cuillers, les casseroles, les tasses et les sa- ladiers... l?écriture du code va vite devenir pénible. Voilà pourquoi 2 notions fondamentales existent dans toute programmation : Si on utilise une variable (identifiée par un "$" en php) et une fonction pour manipuler cette variable nous simplifions considérablement l?écriture de code : 18 $vaisselle =??; fonction ranger($vaisselle) {-1 prendre $vaisselle -2 si l?armoire est fermée ouvrir armoire -3ranger $vaisselle à côté des autres $vaisselle}; Ainsi, nous parlons à notre robot de la façon suivante : A chaque fois que je vais te désigner une $vaisselle, tu la prends, tu ouvres l?armoire si elle est fer- mée et tu ranges cette $vaisselle à côté des autres $vaisselle. Puis nous pointons du doigt une assiette et lui disons : ceci est une $vaisselle.... ($vaisselle = ?as- siette?;)... Ainsi nous voyons qu?un moteur php est une suite d?instructions pour qu?un serveur exécute des tâches : ces instructions ont besoin de $variables en entrée pour être exécutées. Dans le moteur Thélia : Les $variables seront alimentées non par des assiettes ou des fourchettes mais par „ „ des données issues : 1°) soit de la base de données, 2°) soit d?une variable de session 3°) soit de données renseignées par le visiteur sur le site lorsqu?il inter agit avec lui. - le moteur "démarre" avec une interaction du visiteur : son navigateur appelle votre page d?accueil : ceci est sa toute première interaction, qui va, comme toutes les autres, être sou- mise au moteur. Les instructions de celui-ci sont lues intégralement par le serveur de haut en bas et vont permettre : - d?appeler des données stockées dans la base ou dans une variable de session, - ou au contraire d?envoyer des données vers la base ou vers une variable de session, - de manipuler ces données ainsi que d?autres envoyées par le visiteur sous forme de para- mètres - d?ouvrir une connexion au serveur sécurisé de la banque etc.... - d?afficher tout ou partie de ces données ou le résultat du traitement de ces données. La structure générale du code moteur Toutes les instructions attendues pour le fonctionnement de votre boutique sont donc décri- tes dans le moteur sous la forme de fonctions manipulant des variables : or les ordres à faire exécuter par le serveur seront très différents selon que le visiteur souhaite afficher tous les produits de la sous-rubrique "chaussures pour enfants" ou qu?il souhaite créer un compte, ou qu?il souhaite valider une commande de 3 paires de chaussettes rouges ou qu?il vous envoie un message par mail... Ceci amène deux réflexions : Puisque toutes les instructions sont codées dans le moteur et que quelques processus „ „ seulement sont en cours sur le site (par exemple : validation d?un panier + chargement d?une page) alors nous en concluons qu?à chaque interaction du visiteur sur le site, si toutes les parties du moteur seront lues par le serveur, quelques-une seulement seront exécutées : en rentrant dans le détail des différents fichiers vous saurez maintenant pourquoi il y a tant de structures conditionnelles "if" : le serveur balaye toutes les instructions : "si on te demande 19 de faire ceci ou cela... si telle variable a telle valeur... si tu trouves telle boucle sur le squelette que tu vas traiter..... La majorité seront ignorées car elles sont prévues pour une situation différente : des valeurs de variables différentes, des actions différentes...) Lorsque vous ajoutez un plugin, vous rajoutez de nouveaux fichiers contenant de nou- „ „ velles instructions, ces instructions seront lues et exécutées à des endroits bien précis sur le script moteur (des points d?entrée, nous y reviendrons). Pour autant, le découpage du script moteur en plusieurs fichiers ne suit pas exactement cette logique : ce découpage répond plutôt à une volonté de respecter les standards de pro- grammation en la matière (notamment ce qu?on appelle templating ou moteur de template, ce qu?est Thélia, au même titre que Smarty par exemple). Le découpage s?explique donc de la manière suivante : Le dossier „ „ classes : nous allons voir ce que cache le terme "classe" dans le paragraphe suivant : la programmation orientée objet offre lisibilité et souplesse au code et se caractérise par la notion de classes : celles créées pour et utilisées par le moteur ont été regroupées dans le dossier "classes". Concernant le dossier „ „ fonctions il regroupe : -- Le fichier parseur.php : regroupe les fonctions "syntaxiques" du moteur c?est-à-dire tout ce qui concerne les instructions de traduction des boucles des squelettes (traduction en lan- gage php). -- Le fichier boucles.php : les fonctions du parseur appellerons les fonctions décrites par le fichier boucles pour effectuer le travail. -- Le fichier substitutions.php et le dossier substitutions : de la même façon, la fonctionnalité substitution de Thélia sera traitée syntaxiquement pas les fonctions du fichier substitutions qui feront elles-même appel aux fonctions décrites dans les fichiers du dossier "substitu- tions". -- Le dossier filtres : la fonctionnalité filtres de Thélia sera pareillement interprétée en php grâce aux fonctions de ce dossier. -- Le fichier divers.php : il décrit un ensemble de fonctions génériques utiles et utilisées un peu partout dans Thélia : elles permettent de simplifier le code : elles sont décrites dans ce fichier une fois et appelées quand nécessaire (souvent) tout au long du script moteur. -- Le fichier action.php : décrit les fonctions mises en oeuvre dans le moteur pour traduire les actions du visiteur : nous approfondirons cette notion un peu plus bas. Présentation de la notion de classes en Programmation Orientée Objet Vous avez probablement remarqué sur le premier schéma que j?ai divisé la couche moteur en deux parties : les instructions et les classes. Nous savons à ce stade ce que sont les instructions : des opérations effectuées sur des $variables par le serveur (opérations qui vont permettre de stocker temporairement ou du- rablement des données, valider une commande, mettre à jour des données temporaires ou durables ou simplement modifier la valeur d?une variable et finalement afficher le résultat du traitement opéré par le moteur sous la forme d?une page html.....): À partir de maintenant nous utiliserons la terminologie propre à php et parlerons de fonctions ou de méthodes (si ces fonctions sont définies dans une classe). 20 A ce sujet et pour comprendre ce qui va suivre concernant l?architecture de Thélia, il convient de s?arrêter un instant sur la notion de classes, propre à la programmation par objet : Sans rentrer dans le détail, disons que la POO consiste essentiellement à définir des classes : une classe est un ensemble de variables (encore des variables ! Mais dans une classe, une variable sera appelée un "attribut") et de fonctions (encore des fonctions ! Mais dans une classe, une fonction sera appelée une "méthode"). Puis dans le code php (ici le moteur Thélia) nous pourrons ensuite définir des objets "ratta- chés" à une classe : ces objets sont eux aussi des $variables, mais qui ont une particularité puisque ces variables bénéficient des attributs et méthodes de la classe à laquelle elles sont rattachées (on dit qu?un objet est l?instanciation d?une classe). Tout comme l?introduction des fonctions en php, les classes permettent de gagner énormément de temps de codage et cla- rifient le script en proposant une logique de développement par empilement et mutualisation des lignes de code. Un peu trop théorique ? Essayons avec un exemple : Reprenons l?exemple du robot ménager et enrichissons-le pour comprendre la notion de classes : Dans le premier exemple nous envisagions une fourchette comme une simple variable (rien d?autre que la valeur de la variable ne définissait la fourchette : la fourchette était une $vaisselle dont la valeur était égale à...."fourchette") Maintenant nous définissons une classe "vaisselle" class Vaisselle{ Comme toute classe, la classe "vaisselle" serait constituée de 1°) Une ou plusieurs variables (ici nous en créerons 3) : var $type; var $usage; var $fragile; 2°) Une ou plusieurs fonctions (ici 2 ) function ranger() - nous connaissons déjà cette fonction. function laver() {- Si $fragile = oui, alors 1°) prendre $type, 2°) plonger $type dans eau chaude, 3°) frotter $type avec une éponge..... - Sinon 1°) prendre $type, 2°) mettre $type dans lave-vaisselle.....;} Cette fois-ci la variable $fourchette n?est pas qu?une simple valeur mais un objet composé de plusieurs attributs. On entrevoit d?ores et déjà l?intérêt d?un tel concept. Car en définissant la variable $fourchette, comme un objet de class "vaisselle", nous lui accordons un certain nombre de caractéristiques (les attributs de la classe "vaisselle") et un certain nombre d?actions qui pourront être effectuées sur cet objet (ranger, laver) en fonction des attributs par exemple. Dés lors, partout où nous aurons besoin de faire travailler notre robot sur de la vaisselle nous pouvons imaginer créer différents objets "vaisselle" et très simplement indiquer que ces objets sont une fourchette, une assiette, un couteau, chacun doté de caractéristiques communes mais dont la valeur peut différer. Par exemple $fourchette = new Vaisselle(); // création de l?objet fourchette rattaché à la classe "vaisselle" var $type= "couvert"; // on attribue des valeurs var $usage= "normal"; // aux attributs 21 var $fragile = "non"; // de l?objet En suivant le même raisonnement, les jolis verres en cristal, cadeau de mariage de belle-maman, pourront égale- ment être définis par un objet $vaisselle dont les valeurs des attributs seront cette fois : $verre_cristal = new Vaisselle(); var $type= "verre_cristal"; var $usage = "grandes_occasions"; var $fragile = "oui"; Comprenez l?intérêt de ce mode d?organisation : par exemple si nous souhaitons faire laver la vaisselle à notre robot : voyez comment la fonction laver(), codée dans la classe vaisselle prend en compte l?information selon laquelle la vaisselle manipulée par le robot est fragile ou non (attribut $fragile) : le mode de lavage que choisira le robot dépendra de cet attribut : lavage à la main si la vaisselle est fragile, au lave-vaisselle si elle ne l?est pas. De la même façon nous pouvons envisager de faire ranger la vaisselle par notre robot en indiquant l?armoire de la cuisine pour la vaisselle "normale" et le buffet du salon pour la vaisselle "destinée aux grandes occasions" etc etc. Comme une classe est mutualisée, l?écriture de toutes ces combinaisons (vaisselle fragile et normale, solide et nor- male, fragile et pour grandes occasions etc...) sera grandement simplifiée : toutes ces combinaisons pourront être traitées avec la classe vaisselle. On définira seulement des attributs spécifiques pour chaque objet et le robot saura comment se comporter sur ces objets, en fonction des caractéristiques. Dans Thélia le moteur dispose de 64 classes par défaut permettant de manipuler 64 types d?objets. Les plugins (inclus ou optionnels) rajoutent à chaque fois au moins une classe sup- plémentaire. Et plus concrètement ? Quelques chiffres sur le moteur - Nombre de fichiers* : 92 - Nombre de classes * : 64 - Nombre de fonctions moteur** : plus de 100 - Nombre de ligne de code* : environ 7000 * hors plugins et hors admin ** hors méthodes, hors plugin et hors admin Ces fichiers sont stockés à la racine de votre site dans les dossiers "classe" et "fonctions". Ils composent, avec le template de base et le BO (le dossier "admin"), l?intégralité de l?appli- cation Thélia (le .zip que vous téléchargez pour l?installation) : la base de donnée ne fait pas partie du .zip : elle est crée lors de l?installation. Le site Web 4. La couche site, la plus haute de l?organisation, est aussi la plus évidente à appréhender : cette couche de présentation est du pur html (en effet les navigateurs Web peuvent afficher seulement du html, pas du php) : la dernière action du serveur consistera donc envoyer un fichier 100% html au navigateur du visiteur : le squelette de l?url appelée, débarrassé de ses boucles, remplacées par les données dynamiquement injectées lors du traitement par le mo- 22 teur, données résultant des choix du visiteur (interactions) Enfin n?oubliez pas le tout premier rôle de la couche présentation qu?est votre site Web : donner à votre client la possibilité d?interagir, manipuler le robot qu?est le serveur : bienvenu sur un site dynamique ! Et plus concrètement ? Le site Web, ce sont les différentes pages que votre moteur va générer. Par défaut, la struc- ture du site est organisée par les fichiers .php du template de base (1 ficher .php = 1 type de page). Leur aspect graphique et les boucles sont gérés par les squelettes html associés (1 squelette = 1 type de page) et le fichier style.css. Les différents scripts javascript ou toute autre fonctionnalité d?animation de votre site (flash) que vous souhaiterez rajouter font éga- lement partie du site Web : ces scripts ne sont d?ailleurs pas exécutés par le serveur mais par le navigateur de votre visiteur. Tout ce beau petit monde sera sagement entreposé à la racine du site, en compagnie du (ou dans le) dossier "client" dont nous connaissons déjà la raison d?être : stocker les images, les PDF, les fichiers flash dont vous aurez besoin (la base de données n?étant pas prévue pour le stockage de ce genre de données informatiques). Synthèse générale du Front Office Thélia 5. Grâce à Thélia nous disposons donc de tous les éléments structurels nécessaires à la condui- te d?une activité commerciale : - Un entrepôt (la base de données) stocke les éléments à conserver (fiches produits, réfé- rences clients, les commandes passées, les messages envoyés, les mails de confirmation.... Toutes les données de votre activité). - Une vitrine (le site) présente les produits à vendre, stockés dans l?entrepôt ; joliment déco- rée par vos soins, elle met en valeur vos références et inspire l?acte d?achat au visiteur. Elle dispose également de fonctionnalités pour que le client interagisse avec votre entreprise (des boutons, des liens, des formulaires...) Entre le site et l?entrepôt, un chariot (les variables de session) permet au visiteur de stoc- ker l?ensemble des éléments dont il peut avoir besoin pendant sa déambulation sur le site. Lorsqu?il le quittera, son caddy sera vidé par le serveur et rangé dans la file des caddy, en attendant un autre visiteur. Et bien sûr, votre activité commerciale étant distante et dématérialisée, votre visiteur n?aura pas la possibilité de choisir lui-même un produit, de le placer sur le chariot, de se rendre ensuite dans un autre rayon chercher d?autres produits, pousser son petit chariot entre les allées, vérifier si une référence en vitrine est disponible dans l?entrepôt... Etc... Pas de panique : un robot, le serveur, est là pour effectuer toutes ces actions à sa place (on parle de processus), grâce à un jeu d?instructions qui s?appelle le moteur : ce moteur permet au serveur d?effectuer des tâches sur des objets (instanciés par les classes de Thélia) en lieu et place du visiteur, quand celui-ci le lui ordonne (c?est-à-dire quand il interagit sur le site) Ceci termine la présentation générale de Thélia. Nous allons pouvoir détailler maintenant le fonctionnement interne du moteur, le coeur de l?application. 23 Présentation du moteur 2. Thélia Comment lire le schéma ci-dessous ? Le schéma représente une page Internet élaborée par Thélia. Toutes les pages que vous prévoirez pour votre site seront composées de cette manière : chaque page va bien sûr faire appel au squelette html que vous aurez élaboré à son intention (rappelez-vous le couple php/ html qu?impose Thélia) ; dans mon exemple nous nous intéresserons à la page d?accueil de votre site : la page index.php. En effet, contrairement à ce que vous pensiez peut-être, le fichier le plus important pour une page donnée de votre site n?est pas le squelette mais le fichier php correspondant : c?est bien ce dernier qui est sollicité en premier à l?appel de la page, il contient le moteur qui appellera le squelette associé, pour le traiter ; le résultat du traitement sera une page Web complète, prête à être envoyée au navigateur du visiteur (voilà, en passant, pourquoi on traite php de générateur de page Web). Si vous jetez un coup d?oeil rapide au schéma ci-dessous, vous allez vous rendre compte que le squelette html n?entre véritablement en jeu que tard dans le moteur : même si on lui associe une variable ($fond) dés l?entrée sur la page, cette variable ne sera manipulée par le moteur qu?à partir de la ligne 249 : en effet, avant que le serveur ne s?y intéresse, il faut d?abord définir l?ensemble des fonctions et des classes que nous souhaitons le voir utiliser (pour qu?il puisse traiter le squelette justement, et les actions éventuelles demandées par le visiteur). La page index.php est représentée avec un fond gris. Elle contient le moteur, en fond blanc. Dans ce moteur, sont appelés des fichiers par inclusion (ce sont les modules avec un fond coloré. Notez que je ne représente pas les inclusions des fichiers "classes" pour ne pas sur- charger le modèle ; j?écris seulement le nom de la classe directement dans le moteur). Dans les colonnes de gauche : les inclusions des classes. Dans les parties centrales : les fonctions décrites par le fichier. Je fais donc apparaître les principales fonctions et classes du moteur : - Le nom des classes commence par une majuscule et se termine par l?extension ".class". Si elles sont encadrées de crochet [....] comprenez qu?elles sont incluses dans la classe repré- sentée immédiatement au-dessus d?elles. - Les fonctions sont suivies du signe "()". Quand il s?agit d?une description de la fonction elle est précédée du signe "+". Quand il s?agit d?exécuter la fonction, elle est précédée du signe "->". - Je ne fais pas apparaître les attributs et les méthodes des classes sauf exceptions. C?est une représentation simplifiée : tout n?apparaît pas pour ne pas parasiter la compréhen- sion globale. +90% du moteur est néanmoins représenté. 24 $fond = "index_page.thml"; $pageret = 1; $parsephp =1; Navigation.class [Panier.class Client.class Commande.class Promo.class] session_start() Moteur + bouclerubrique() + boucledossier() + boucleimage() + boucleclient() + boucledevise() + boucledocument() + boucleaccessoire() + boucleproduit() + bouclecontenu() + bouclecontenuassoc() + bouclepage() + bouclepanier() + bouclequantite() + bouclechemin() + bouclepaiement() + bouclepays() + bouclecaracteristique() + bouclecaracdisp() + bouclecaracval() + boucleadresse() + bouclecommande() + boucleventeprod() + boucletransport() + bouclerss() + boucledéclinaisons() + boucledeclidisp() + bouclestock() + boucledecval() + Navigation() index.php 25 Boucles Rubrique.class(2) Client.class Dossier.class(2) Contenu.class(2) Produit.class(2) Adresse.class(2) Module.class Commande.class Venteprod.class Statut.class Image.class(2) Document.class(2) Accessoire.class Pays.class(2) Zone.class Caracteristique. class(4) Devise.class Déclinaisons.class(5) Exdecprod.class Perso.class Contenuassoc.class Stock.class I Chargement des fonctions et classes + ajouter() + transport() + codepromo() + supprimer() + modifier() + connexion() + deconnexion() + modadresse() + paiement() + creercompte() + modifiercompte() + creerlivraison() + modifierlivraison() + supprimerlivraison() + chmdp() + Substitutions() + Filtres() 26 Actions substitrubrique() substitproduit() substitpanier() substitclient() substitpage() substitadresse() substitcommande() substitmessage() substitvariable() substitcaracteristi- que() substitdéclinaisons() substitimage() substitdossier() substitcontenu() substitparrain() Substitu- tions filtrevide() filtreegalite() filtrefonction() filtrevraifaux() Filtres Dossier Substitutions Dossier Filtres + liretag() + redirige() + chemin() + chemin_dos() + rewrite_rub() + rewrite_prod() + rewrite_dos() + rewrite_contenu() + ereg_url() + ereg_fic() + ereg_caracspec() + arbreBoucle() + arbreOption() + arbreOptionRub() + arbreBoucle_dos() + arbreOption_dos() + genpass() + gencode() + mail_fichier() + tabserialize() + supprAccent() + port() + urlexist() + modules_fonction() + admin_inclure() Cnx.class | Requete.class | Boseobj.class* | Client.class* Commande.class* Venteprod.class* Ventedeclidisp.class* Message.class* Messagedesc.class* Transzone.class* Variable.class* Promo.class* Perso.class Smtp.class PluginClassique. class* [Cache.class Moduledesc.class] 27 Divers Module.class Rubrique.class Dossier.class Contenu.class Produit.class + pre() + post() + filtre_connecte() + boucle_sinon() + boucle_simple() + moduleBoucle() + boucle_exec() + traitement_formulaire() + inclusion() substitution() flitre_connecte() traitement_formulaire() boucle_sinon() pre() boucle_simple() post() + Analyse() Si les variables suivantes sont initialisées : si aucune valeur n?est spécifiée, elles sont vides ou =0 $res= ""; $lang= ""; $parsephp= ""; $securise=0; $panier=0; $pageret=0; $reset=0; $transport=0; $obligetelfixe=0; $obligetelport=0; $pagesess=0; Création des variables ci-dessous dont la valeur est vide par défaut ou = $_REQUEST [?nom de la variable?] $action = "" $append = 0; $id = ""; $id_parrain = ""; $nouveau = ""; $ref = ""; $quantite = ""; $article = ""; $type_paiement= ""; 28 Parseur II Initialisation des variables $code = ""; $entreprise = ""; $siret = ""; $intracom = ""; $parrain = ""; $motdepasse1 = ""; $motdepasse2 = ""; $raison = ""; $prenom=""; $libelle = ""; $nom = ""; $adresse1 = ""; $adresse2 = ""; $adresse3 = ""; $cpostal = ""; $ville = ""; $pays = ""; $telfixe = ""; $telport = ""; $tel = ""; $email1 = ""; $email2 = ""; $email = ""; $motdepasse = ""; $adresse = ""; $id_rubrique = ""; $id_dossier = ""; $nouveaute = ""; $promo = ""; $page = ""; $totbloc= ""; $id_contenu = ""; $caracdisp = ""; $reforig = ""; $motcle = ""; $id_produit = ""; $classement = ""; $prixmin=""; $prixmax=""; $id_image = ""; $déclinaisons = ""; $declidisp = ""; $declival = ""; $declistock = ""; $commande = ""; $caracteristique = ""; $caracval = ""; 29 30 $_SESSION["navig"] = new Navigation(); $_SESSION["navig"]->panier $nbrart $tabarticle [ ] : new Article() $connecte $paiement $nouveau $urlprec $urlpageret $adresse $_SESSION["navig"]->client $_SESSION["navig"]->promo $quantite $perso [ ] : $_SESSION["navig"]->commande $id; $ref; $datemodif; $prix; $ecotaxe; $promo; $ligne; $garantie; $prix2; $rubrique; $nouveaute; $perso; $stock; $poids; $tva; $classement; $id; $produit; $titre; $chapo; $description; $postscriptum; $lang; $declinaison; $valeur; $id; $client; $adrfact; $adrlivr; $date; $datefact; $ref; $transaction; $livraison; $facture; $transport; $port; $datelivraison; $remise; $colis; $paiement; $statut; $lang; $id; $ref; $raison; $entreprise; $siret; $intracom; $nom; $prenom; $telfixe; $telport; $email; $motdepasse; $adresse1; $adresse2; $adresse3; $cpostal; $ville; $type; $parrain; $pourcentage; $id; $lang; $default; $tva; new Produit() new Produitdesc() new Perso() new Port() $id; $poids; $port; $id; $poids; $port; new Zone() $id; $poids; $port; new Pays() $_SESSION["navig"]->client new Pays() new Zone() $id; $ref; $raison; $entreprise; $siret; $intracom; $nom; $prenom; $telfixe; $telport; $email; $motdepasse; $adresse1; $adresse2; $adresse3; $cpostal; $ville; $type; $parrain; $pourcentage; $id; $lang; $default; $tva; $id; $poids; $port; $_SESSION["navig"]->formcli new Pays() new Zone() $id; $code; $type; $valeur; $mini; $utilise; $illimite; $datefin; $_SESSION["navig"]->promo III Initialisation de la session si non existante -> Funct. module_fonction(?inclusion?) -> Funct. inclusion() -> Funct. module_fonction(?action?) -> Funct. substitution() -> Funct. filtre_connecte() -> Funct. traitement_formulaire() -> Funct. pre() -> Funct. boucle_sinon() -> Funct. pre() -> Funct. boucle_simple() -> Funct. post() -> Funct. filtres() -> Funct. module_fonction(?post?) $res = file_get_content ($fond) $res=inclusion (res) $res = analyse ($res) $res = filtres ($res) echo $res; 31 -> module_fonction(?demarrage?) -> ajouter() -> transport() -> codepromo() -> supprimer() -> modifier() -> connexion() -> deconnexion() -> modadresse() -> paiement() -> creercompte() -> modifiercompte() -> creerlivraison() -> modifierlivraison() -> supprimerlivraison() -> chmdp() -> module_fonction(?pre?) $_REQUEST[?action?] = V Traitement du squelette html IV Traitement des actions du client 32 Présentation du fichier 3. moteur.php Dans le moteur de Thélia s?enchaînent 4 événements principaux : 1 : Chargement des fonctions et classes 2 : Initialisation des variables 3 : Traitement des actions 4 : Traitement du squelette Le chargement de l?ensemble des fonctions et classes (sur le 1. schéma : partie I) Rappelez-vous que cette partie (la plus grosse du moteur en terme de lignes de code) décrit l?ensemble des instructions que le serveur devra savoir exécuter concernant votre site (à ce stade il s?agit de les décrire non de les faires exécuter par le serveur) Concrètement, tous les fichiers du moteur (91 fichiers) sont appelés par inclusion pendant cette phase. Vous ne verrez pas 91 fois la fonction "include()" sur le fichier moteur.php : les inclusions peuvent s?effectuer en cascade : un fichier est inclus dans un fichier qui est inclus dans un fichier... qui est inclus dans le moteur. En plus de ces inclusions, une fonction est directement codée sur le fichier moteur.php : la fonction analyse() : une "super" fonction dont le rôle est d?enclencher presque toutes les autres. Cette phase de chargement et de description s?achève à la ligne 104 du fichier moteur.php. L?initialisation des variables (parties II et III) 2. A cet instant le moteur va initialiser les variables devant alimenter les fonctions définies juste au-dessus. Rappelez-vous : les fonctions ont été définies avec des variables : quand le concepteur de Thélia a écrit son programme, il ne pouvait évidemment pas deviner les valeurs qui seraient générées par le visiteur lors de ses interactions avec votre site... il a donc utilisé des variables, l?équivalent en programmation des inconnues dans les équations mathématiques. Cette partie du moteur consiste à indiquer au serveur quelles valeurs doivent prendre certai- nes variables. Quand un visiteur accède à votre site pour la première fois, le moteur effectue sa première passe, aucune valeur n?a été spécifiée par le client pour ces variables : le moteur les créé donc en leur attribuant une valeur... vide. 3 types de variables sont ainsi initialisés : - Les variables externes $_REQUEST[?....?] = Ce sont les variables qui vont transmettre aux fonctions du moteur les choix du visiteur lors 33 de ses interactions sur le site (lien, bouton, formulaire..) : ces variables vont pouvoir transfé- rer : -- Des ordres que le visiteur pourra envoyer au serveur : ces ordres correspondent concrè- tement à des fonctions que nous appellerons dans le cadre de Thélia des actions (et nous réserverons désormais ce terme à cette fonctionnalité). Ces fonctions sont définies dans le fichier fonctions/action.php (ajouter, créercompte, modifieradresse.... : nous disposons de 15 actions par défaut, les plugins peuvent venir enrichir la liste). 1 variable est prévue lors de l?initialisation : $action = $_REQUEST[?action?]. si cette variable reçoit une valeur, une fonction du fichier fonctions/action. php va être exécutée. -- Des valeurs renseignées par le visiteur à chaque interaction, en plus des actions (lorsqu?il fait un choix dans une liste déroulante par exemple ou qu?il clique sur un lien) : Thélia en prévoit 55 (la liste que j?ai repris sur le schéma) : si une valeur est renseignée pour ces va- riables, quelque part à un moment dans le moteur une fonction va être exécutée pour traiter ces valeurs. Ces valeurs seront essentiellement utilisées comme arguments dans les fonc- tions de type action ou comme arguments dans les fonctions prévues pour le traitement des boucles du squelette. Vous connaissez déjà le principe de variables externes, elles sont issues de l?univers html : elles sont transmises par la méthode GET ou par la méthode POST, sous la forme de pa- ramètres d?url dans le premier cas (par exemple : rubrique.php?rubrique_id=1&parent=0 : dans cette url "rubrique" et "parent" sont des paramètres auxquels on a attribué des valeurs : 1 et 0 et ont été transmis par la méthode GET) Prenons l?exemple des actions dont je viens de parler : elles vont en général être transmises de la manière suivante : Sur votre squelette html, un formulaire prévoira un champ caché, codé de la manière sui- vante : <form action= "panier.php" method="GET"> <input type= "hidden" name= "action" value= "ajouter"/>... Lorsque vous coderez ce type de balise html, un clic sur le bouton submit (le bouton "ajouter au panier" du template par exemple) génère une url de type : panier.php?action=ajouter&ref=1234&.... Le fichier panier.php contient le moteur.php. En php, vous pouvez récupérer un paramètre et sa valeur (ce qui apparaît après le "?" dans une url), ici l?action et sa valeur (ajouter), de la manière suivante : $_REQUEST[?action?]= " "; Cette variable une fois définie dans le moteur, il conviendra de programmer le code suivant 34 : Si $REQUEST[?action?] = "ajouter", alors exécuter la fonction ajouter() La fonction ajouter(), définie dans la partie 1), a besoin de la variable $ref. Ca tombe bien, dans cette phase d?initialisation des variables le moteur définit la variable $ref de la sorte : $ref=$_REQUEST[?ref?]; L?url créée par le clic dispose bien d?un paramètre &ref=1234 donc $ref = 1234 Quand nous aurons appris que la fonction ajouter($ref) consiste à rajouter une référence dans la variable de session $_SESSION[?navig?]->panier nous conceptualiserons bien l?enchaî- nement suivant : Le client est sur une page produit „ „ Il clique sur "ajouter au panier" „ „ l?url : panier.php?action=?ajouter&ref=?1234? est générée „ „ La page panier.php est appelée : elle contient le moteur, il est donc lu du début à la fin „ „ par le serveur. l?url contient 2 paramètres : action=ajouter&ref=1234 „ „ À l?endroit où nous sommes dans le script (l?initialisation des variables) le moteur initia- „ „ lise une variable $ref=$_REQUEST[?ref?] soit $ref=1234 Le moteur initialise une variable „ „ $action = $_REQUEST[?action?] soit $action=?ajouter? Un peu plus bas le moteur prévoit que si „ „ $action=?ajouter? alors exécuter la fonction ajouter() La fonction „ „ ajouter() définie plus haut ordonne au serveur : incrémente le panier de la valeur prise par $ref. - Les variables de session $_SESSION[?navig?] =" Ces variables que nous connaissons déjà sont initialisées ici : concrètement, il s?agit de créer un objet de classe "navigation" : cet objet contient 5 attributs = 5 variables qui sont elles-mê- mes des objets chacun instanciés par une classe spécifique ce qui donne : $_SESSION[?navig?]->panier „ „ $_SESSION[?navig?]->client „ „ $_SESSION[?navig?]->commande „ „ $_SESSION[?navig?]->promo „ „ -- Quelques autres variables spécifiques à Thélia Nous ne les détaillerons pas ici (elles vont servir à des fonctions importantes mais secondai- res) sauf une : la variable $res "res" pour "résultat" : il s?agit de la variable dont le contenu sera envoyé au navigateur du visiteur pour que s?affiche une page : echo $res; (= la dernière fonction du moteur que le serveur devra exécuter : "afficher la variable $res") $res contient donc l?ensemble de la page à afficher. La variable est initialisée ici vide puis, ligne 249, dans la variable $res est chargé le fichier dont le nom est $fond, c?est-à-dire (si 35 vous avez bien suivi) votre squelette html. Cette variable va subir une suite de traitements que vont lui appliquer la fonction analyse() et quelques autres, suite à quoi la page sera prête pour l?affichage : toutes les traces des bou- cles Thélia auront disparu, remplacées par le résultat du traitement du moteur : du bon vieux code html, 100% interprétable par un navigateur Internet. Lors de l?arrivée du visiteur sur votre site, le moteur initialise la variable $res à cet instant, avec une valeur vide, puis la remplira quelques lignes plus bas comme nous allons le décrire maintenant. La phase d?initialisation des variables s?achève ligne 202 Le traitement des actions (partie IV) 3. Arrivé à ce stade du parcours (sur les 7000 lignes environ du moteur complet, le serveur en a déjà parcouru environ 6900 et il n?a pas encore levé le petit doigt à part initialiser quelques variables), le serveur va enfin réellement entrer en action et exécuter quelque chose (il était temps). Nous savons maintenant le rôle que nous attendons du serveur : exécuter des instructions que nous pouvons sommairement séparer en 2 groupes : 1°) Exécution de fonctions prévues pour répondre aux „ „ actions du visiteur : ces actions vont être exécutées ici, les premières. Ces actions sont souvent des manipulations de va- riables de session ou de la base de données (ajoute à mon panier, modifie mon adresse, crée un compte....) : les fonctions utilisent pour se déclencher la valeur prise par la variable $action (ajouter, supprimer, creercompte...) et utilisent en arguments les variables du couple php/html (les variables externes). Les fonctions du fichier fonctions/actions.php vont être sollicitées par le moteur à cet instant dans le script. 2°) Exécution des fonctions pour traitement du squelette appelé : là encore, ces ins- „ „ tructions sont liées à un choix du visiteur, l?appel d?une url : le squelette sélectionné doit être traité : un ensemble de fonctions va être exécuté à cet effet, utilisant des variables passées en paramètre, comme argument. (Partie V du moteur nous les détaillerons plus tard) En programmation php on utilise traditionnellement la structure "switch" pour présenter les différentes actions prévues par un programme, et Thélia ne déroge pas à la règle : à la ligne 207 nous rencontrons cette structure significative : switch($action){ case ?ajouter? : ajouter($ref, $quantite, $append, $nouveau); break; case ?supprimer? : supprimer($article); break; case ?modifier? : modifier($article, $quantite); break; case ?connexion? : connexion($email,$motdepasse); break; ....... 36 Ce que nous allons traduire maintenant par : -- "Vérifie si la variable $action existe. -- Si oui, vérifie la valeur qu?elle a prise. -- Si sa valeur = "ajouter", alors exécute la fonction ajouter() avec, comme arguments, les valeurs prises par les variables $ref, $quantité, $append, $nouveau -- Si sa valeur = "supprimer", alors exécute la fonction supprimer() avec, comme argument la valeur prise par la variable $article... Etc Le traitement du squelette (partie V) 4. Pour le reste, il faut traiter les boucles contenues dans le squelette, au regard des paramè- tres passés par GET ou POST aux variables externes et précisant les choix du visiteur : nous allons avoir l?occasion de décrire précisément les fonctions mises en place pour traiter le méta-langage des boucles Thélia dans le prochain chapitre c?est pourquoi je ne rentrerais pas plus dans le détail que ce que nous savons désormais de cette ultime étape du moteur : - Les boucles Thélia sont traduites en langage php par des fonctions spécifiques (les fonc- tions du fichier fonctions/parseur.php : ces fonctions font appel aux fonctions du fichier fonc- tions/boucles.php) : elles sont transformées en boucles d?affichage php. - Ces boucles sont exécutées en fonction des paramètres renseignés (les variables exter- nes). - Il en résulte des données 100% html L?ensemble du squelette étant balayé, il en résulte une structure 100% hml prête pour l?affi- chage. Synthèse de la présentation du fichier moteur.php 5. Nous avons compris comment fonctionne le moteur Thélia : Un ensemble de fonctions prévues pour répondre aux choix du client. „ „ Ces fonctions vont traiter des variables = les arguments de la fonction „ „ Ces variables vont être alimentées par les choix du visiteur (transmis en GET ou „ „ POST) : Une variable „ „ $action pour traduire un ordre spécifique du visiteur : une action 55 variables pour préciser l?action du visiteur ou traduire ses choix à travers la navi- „ „ gation. Une variable $res pour traiter le squelette. „ „ Ainsi, pour finir, nous pourrions reprendre la première illustration pour la compléter 37 URL URL MOTEUR MOTEUR SQUELETTE SQUELETTE PAGE WEB PAGE WEB PARAMETRE PARAMETRE + ACTION ACTION INTER ACTION INTER ACTION + Sur ce schéma, nous pouvons observer que sur un site "propulsé par Thélia" 3 niveaux d?in- teractions sont possibles pour le visiteur : Niveau 1 : un appel à une url sans paramètre et sans action : typiquement l?entrée du „ „ visiteur sur le site : - Ex : index.php Niveau 2 : un appel à une url + des paramètres mais pas d?action : le visiteur se rend „ „ sur la rubrique "homme" de la boutique. - Ex : rubrique.php?rubrique_id=1... Niveau 3 : un appel à une url + une action + des paramètres : le visiteur ajoute une „ „ chemise à son panier - Ex : panier.php?action=ajouter&ref=1234.. Vous aurez remarqué que toute interaction est au moins un appel à une url (mais il peut s?agir de la page en cours, qui sera alors rechargée, affichant les modifications demandées, le cas échéant) : le moteur traitera donc à minima le squelette de cette url (normal : le sque- lette contient des boucles : le moteur doit les transformer en données réelles avant qu?il ne s?affiche) Ceci achève la présentation générale du fichier moteur.php, le coeur du programme Thélia. Nous pouvons envisager maintenant d?aller plus loin dans la découverte de l'application et dans la programmation php en général : nous allons soumettre une page à notre moteur et décrire plus précisément certains traitements qu?il va mettre en oeuvre dans ce cas, ce qui constituera le 4ième chapitre. Nous avons déjà évoqué le "templating" : dissocier le code PHP du code HTML : créer un application offrant un modèle de base de site Web dynamique sur lequel on greffe une mise en page HTML et un appel aux fonctions dynamiques par l'utilisation de tags prédéfinis. De nombreux moteurs de template sont disponibles mais Thélia a cela d'unique qu'il a re- groupé en un seul programme un moteur de template ET une application e-commerce com- plète. Voici explicitée la partie "template. 38 Fonctionnement du moteur : le traitement 4. d?une boucle Thélia Ce chapitre sera l?occasion de mettre en pratique l?ensemble de nos connaissances sur le sujet et d?approfondir le code du moteur : nous allons étudier le fonctionnement effectif du serveur exécutant le moteur, lorsqu?un visiteur entre sur un site Thélia primitif et simplifié à l?extrême pour les besoins de la présentation. Les rouages du moteur ne seront pas tous décrits : seules les fonctions principales seront mises en avant. Définissons d?abords un squelette pour la page index_page.html : <html> <body> <image src= ".../.../logo.jpg" /> <ul> <THELIA_TOTO type= "RUBRIQUE" parent= "0" > <li>#TITRE</li> </THELIA_TOTO> </ul> </body> </html> Une image (le logo) et une liste des rubriques de la boutique : cette liste est décrite sous la forme d?une boucle Thélia nommée TOTO : elle affiche les titres des rubriques qui n?ont pas de parent donc les rubriques de premier niveau (pas les sous-rubriques éventuelles).... Simpliste comme prévu. Un visiteur appelle votre page d?accueil (www.votresite.com) depuis Google par exemple, voici ce qui se passe : La page index.php est lue par le serveur : 1 Il initialise une variable $fond et lui attribue la valeur "index_page.html" (à ce stade, in- dex_page.html n?est qu?une chaîne de caractères, ce n?est pas le fichier squelette à propre- ment parler). 2 Le fichier moteur.php est inclus dans index.php : il est lu : la suite se passera sur ce fi- chier. 3 L?ensemble des fonctions et classes sont chargées sur le fichier moteur.php par inclu- sion. 4 La fonction analyse() est définie, elle organise l?exécution des fonctions décrites précé- demment. 5 Les variables sont initialisées : comme aucune navigation n?a été initiée par le visiteur 39 elles sont toutes vides ou =0. Une variable $res est aussi initialisée et comme les autres, prend une valeur vide. 6 Les variables de session sont initialisées : création d?un objet de classe navigation : la variable $navig. Rappelons que cet objet dispose d?attributs qui sont eux-même des objets pourvus d?attributs : la variable $navig se décompose donc en 5 objets : $panier, $client, $formcli, $commande, $promo. 7 Le moteur vérifie s?il doit exécuter une action demandée par le visiteur mais comme celui- ci n?a encore donné aucun ordre, aucune fonction n?est exécutée à ce stade. 8 La variable $res reçoit comme valeur le contenu du fichier dont le nom est $fond soit in- dex_page.html : le squelette de la page d?accueil est ainsi chargé dans le moteur pour être traité : ce traitement sera simple dans la mesure où aucun paramètres GET ou POST n?est encore renseigné : il va seulement falloir traiter les ordres décrits par le concepteur de la page sous la forme de boucles Thélia. 9 la variable $res va subir un traitement sur ses boucles Thélia : les parties suivantes décri- vent ce traitement, appliqué par la "super" fonction Analyse() et quelques autres; Avant la fonction Analyse() : La fonction Inclusions() 1. Le premier traitement consiste à appliquer une fonction Inclusion() sur le squelette : $res = inclusions($res); Cette fonction consiste à inclure tous les fichiers appelés dans le squelette par le type de code #INCLURE "page.html" : ainsi l?ensemble des données prévues sera passé à la fonction Analyse dans la variable $res. Nous n?avons pas utilisé la fonction #INCLURE dans notre squelette donc rien ne se passe à ce niveau. <- - - - - - - - - - - - - - Début de la fonction Analyse()- - - - - - - - - - - - - -> La fonction Substitutions() 2. Le second traitement consiste à appliquer une fonction substitutions() sur le squelette : $res = substitutions($res); Cette fonction consiste à traiter toutes les substitutions Thélia utilisées dans le squelette. Nous la détaillerons dans un autre exemple puisque notre squelette ne fait pas appel aux substitutions. La fonction filtres() 3. Le troisième traitement consiste à appliquer une fonction filtres() sur le squelette : $res = filtres($res); Cette fonction consiste à traiter tous les filtres Thélia utilisés dans le squelette. Nous la dé- taillerons dans un autre exemple puisque notre squelette ne fait pas appel aux filtres. La fonction preg_replace 4. $res = preg_replace("|<THELIA([^>]*)>\n|Us", "<THELIA\\1>", $res); Notre fonction Analyse() continue son traitement sur la variable $res, le squelette, et décrit maintenant une fonction particulière. C?est l?occasion de faire une pause sur certaines no- tions php concernant les fonctions et aborder les fonctions prédéfinies en php. Jusqu?à présent j?envisageais une fonction comme un concept assez abstrait qui pouvait aussi bien se traduire par "range la vaisselle" que par "traite les substitutions".... Plus concrètement, une fonction php va faire appel : A des structure de contrôle de type "si..." "ou si..." "pour chaque..." "sinon..."... „ „ Des opérateurs de type "+", "-", "OU" "ET", ">" "==".... „ „ D?autres fonctions définies préalablement dans le script „ „ D?autres fonctions pré définies intégrées à php „ „ Nous savions déjà, avec l?exemple de la fonction Analyse(), que les fonctions pouvaient faire appel à d?autres fonctions définies en amont dans un programme. Ici nous voyons que la fonction Analyse() fait appel à une fonction pré définie de php (inutile de la définir préalable- ment dans le moteur !) : preg_replace(). Je ne rentrerais pas ici dans la description de ces fonctions pré définies. Retenons pour notre projet que parmi toutes les fonctions pré définies disponibles dans php, le moteur Thélia en sollicitera plusieurs types (à la complexité variable) : Des fonctions pour manipuler une base de données (se connecter, mettre à jour, ex- „ „ traire...) Des fonctions pour manipuler des variables de session (créer, extraire, initialiser...) „ „ Des fonctions pour traduire les boucles Thélia codées dans le squelette „ „ Des fonctions généralistes pour afficher, retourner un résultat, compter, vérifier l?exis- „ „ tence.... Je présenterais en annexe une courte description des principales fonctions pré définies inté- grées à php et utilisées par Thélia : ce tableau vous servira pour interpréter le reste du code (l?objectif de la partie 1 étant de "savoir lire" le moteur). Les fonctions pour traduire les boucles du squelette en langage php font appel à des fonc- tions appelées expressions régulières ou expressions rationnelles, qui comptent parmi les 40 41 plus complexes à maîtriser. Nous ne rentrerons donc pas dans le détail de celles-ci à ce stade mais nous aurons l?occasion de les recroiser. De plus il ne sera pas nécessaire d?être un expert dans cette partie de php pour travailler sur (et avec) Thélia : le programmeur de Thélia a pris soin d?effectuer toute le travail à notre place et pour développer un plugin, nous n?aurons pas à nous soucier de la transcription des nouvelles boucles que nous souhaite- rions mettre à disposition des concepteurs de sites Thélia : nous nous servirons de la moto- risation existante pour faire le travail $res = preg_replace("|<THELIA([^>]*)>\n|Us", "<THELIA\\1>", $res); Cette instruction, pour information, prépare le squelette : elle identifie toutes les boucles Thélia et supprime les éventuels sauts de ligne caractéristiques /n qui pourraient être codés suite à la définition d'une boucle (<THELIA_TOTO type...>). Nous aurons l'occasion de com- prendre plus tard pourquoi le moteur exécute cette instruction. La boucle while... : les fonctions pre(), boucle_simple(), 5. post() while(strstr($res, "<THELIA")) { $boucles = pre($res); $res = boucle_simple($res, $boucles); $res = post($res); return $res;} Ce passage de quelques lignes (94 à 98) est un empilement complexe de fonctions que nous allons essayer de démêler maintenant : il est le noeud pour comprendre le langage des boucles Thélia et leur transmutation par le moteur (rappelons que le serveur est incapable d?interpréter une boucle Thélia telle que vous l?avez codé sur votre squelette : cette étape en modifie donc la syntaxe pour permettre leur interprétation par le serveur). La paragraphe est bâti autour d?une structure de boucle php "while" que nous traduirons ici par "pour chaque..." : while(strstr($res, "<THELIA")) { Traduisons par : Le moteur va parcourir le squelette, s?il rencontre au moins une structure de boucle Thélia, identifiée par la chaîne "<THELIA...." Il va alors appliquer au squelette le trai- tement prévu par les 3 fonctions exposées au-dessous : pre(), boucle_simple() et post() ; puis le moteur re balayera le squelette : s?il reste une boucle Thélia ou +, à nouveau il appliquera le traitement des 3 fonctions au squelette, et ainsi de suite jusqu?à ce que le squelette soit débarrassé de toutes ses boucles. Détaillons maintenant le traitement réservé au squelette à chaque fois qu?une boucle Thélia y est repérée : 42 $boucles = pre($res); Le moteur initialise d?abords une variable $boucles, résultat du traitement du squelette par la fonction pre(). A quoi sert le fonction pre() décrite dans le fichier fonctions/parseur.php, aux lignes 30 à 72 ? Nous aurons l'occasion de décrire cette fonction dans le chapitre suivant car pre() est solli- citée aussi pour le fonctionnement des boucles conditionnelles. Nous n'entrerons pas dans ses détails ici mais allons brièvement résumer le rôle qu'elle assume dans le moteur, concer- nant le traitement d'une boucle simple. La boucle "while" associée à la fonction pre() va permettre de traiter les boucles codées sur le squelette en plusieurs passages, selon leur niveau d?imbrication : un premier passage du moteur traitera les boucles de premier niveau, un second les sous-boucles (boucles imbri- quées dans une boucle), puis un 3ième passage traitera les sous-sous-boucles etc. A chaque passage, la fonction pre() retourne une variable $boucles qui est un tableau listant le nom des boucles du niveau traité. Pour assurer son rôle, la fonction pre() "cache" les paramètres de sortie des boucles qui ne seront pas traitée par la passe en cours : ces paramètres de sortie, que vous connaissez (#TITRE, #ID, #CHAPO....) seront hachés par la chaîne #THNO lors du traitement de la fonction pre(). Ainsi, seuls les paramètres de sortie des boucles concernées par la passe en cours conserveront leur syntaxe initiale ce qui permettra à la fonction suivante, boucle_sim- ple(), de traiter ces paramètres et pas les autres. A la fin du passage, la fonction post() remet dans l?état initial les paramètres modifiés avec #THNO par pre() en prévision de la passe suivante du moteur sur le squelette (traitement des boucles de niveau inférieur). A chaque passage du moteur sur le squelette, „ „ $boucles et $res vont être traitées par la fonction boucle_simple() : $res = boucle_simple($res, $boucles); Cette fonction du parseur va donc traiter toutes les boucles du niveau de la passe en cours, identifiées par leurs noms, enregistrés dans la variable $boucles. Chacune de ses boucles va subir un traitement consistant à identifier son type et les différents paramètres d?entrée (ru- brique= "...", id= "...."....) et de sortie (#ID, #CHAPO...) la définissant. Au coeur de la fonction boucle_simple(), une expression régulière traitée par la fonction php preg_match() accomplit presque tout le travail : preg_match("|<THELIA_" . $boucles[$i] . " ([^>]*)>(.*)</THELIA_" . $boucles[$i] . ">|Us", $lect, $liste); Traduction : La variable $res est balayée par le serveur, à la recherche de la chaîne composée de l?en- semble d?une boucle thélia (tout ce qui se trouve en les 2 balises <THELIA> et </THELIA>) dont le nom est stocké dans le variable $boucles : c?est la traduction de l?expression régulière colorée en bleu. Dans cette chaîne si elle est identifiée, un (sous-masque) identifie la chaîne 43 de caractère comprise entre <THELIA_... Et la balise fermante ">". Un second (sous-mas- que) identifie le contenu compris entre les 2 balises<THELIA> et </THELIA>. Une variable $liste, sous forme de tableau reçoit : -- $liste[0] : toute la chaîne identifiée par le masque -- $liste[1] : la chaîne du premier sous-masque -- $liste[2] : la chaîne du second sous-masque. Ensuite, dans la chaîne du premier sous-masque sera identifié le paramètre "type" ; sa valeur est à son tour identifiée par la fonction lireTag() et est stockée dans la variable $type_boucle. Ainsi, chaque boucle du niveau de la passe en cours est décomposée par la fonction bou- cle_simple() en trois variables -- la variable $type_boucle reçoit le type de la boucle -- la variable $args reçoit ses paramètres d?entrée -- la variable $texte reçoit le contenu entre les balises <THELIA> et </THELIA> de la boucle : les paramètres de sortie Puis, toujours au sein de la fonction „ „ boucle_simple(), chaque boucle traitée par la passe en cours est traitée par un nouvelle fonction : boucle_exec($type_boucle, $arg, $texte) boucle_exec() oriente le prochain traitement : en fonction du type de boucle traité ($type_bou- cle), le moteur appliquera la fonction boucleXXX correspondante (fonction décrite dans le fichier fonctions/boucles.php) avec $arg et $texte en argument. Cette fonction du fichier boucles.php va littéralement reconstituer du strict langage php „ „ en lieu et place du langage des boucles Thélia que vous utilisez dans vos squelettes. Ce lan- gage php est interprété en même temps pour générer le résultat final attendu (dans le cadre d?une boucle : des données html à afficher). Quelle que soit la boucle à traiter, les fonctions boucleXXX() respecteront grosso modo la même organisation : -- 1°) un ensemble de variables recevra les valeurs des paramètres d?entrée de la boucle grâce à la fonction lireTag (décrite dans divers.php) -- 2°) une variable $search, à partir de ces variables, reconstituera une requête Sql en bonne et due forme. une variable $query exécutera cette requête. -- 3°) Un ou plusieurs objet(s) de classe XXX sera ou seront instancié(s) dont les attributs recevront les valeurs obtenues par la requête sur la table XXX de la base de données (1 ligne trouvée = 1 objet). -- 4°) Les paramètres de sortie sont remplacés par leurs valeurs correspondantes, stockées dans les attributs de l?objet XXX. La structure en boucle prévue dans la fonction ("for" ou "while") explique que si plusieurs objets ont été instanciés, chaque paramètre de sortie (#TI- TRE, #ID, #CHAPO...) recevra plusieurs valeurs (vous prévoirez des listes html <ul><li>... Pour mettre en forme ce type de réponse) 44 -- 5°) Des sous-fonctions pourront être mises à exécution en fonction du type de boucle à traiter avant, pendant ou après les étapes décrites ci-dessus : par exemple une boucle ru- brique exécute la fonction boucleRubrique() : c?est cette dernière qui traite le niveau de la rubrique dans l?arborescence générale de vos rubriques. Ainsi à ce stade, les boucles de premier niveau ont disparu, remplacées par le résultat at- tendu ; la variable $res va passer dans la fonction post() pour "dé-cacher" les paramètres de niveau inférieur. La boucle "while" repart : si des sous-boucles existent, il reste des balises <THELIA... qui vont relancer un passage du moteur sur le squelette et ainsi de suite jusqu?à disparition totale des boucles, niveau par niveau. Résumons-nous : 1 Lorsque le serveur lit la boucle "while" il cherche une chaîne de type "<THELIA...." dans le squelette. S?il la trouve, il enchaînera les séquences qui vont suivre. Dans notre exemple le serveur identifie donc ici notre boucle rubrique. 2 Le squelette est traité par la fonction pre() : notre boucle rubrique est reconnue comme une boucle de premier niveau : elle sera donc traitée par la fonction suivante : boucle_sim- ple. 3 Le squelette passe une première fois dans la fonction boucle_simple(): la boucle identifiée précédemment est traitée : $boucle[1] = ?toto?; $type_boucle = ?rubrique?; $args = ?parent = "0"?; $texte = "<li>#TITRE</li>" 4 Cette boucle est traitée par boucle_exec() qui en conclue qu?il faut lui appliquer le traite- ment prévu par la fonction boucleRubrique().... 5 cette dernière remplace la syntaxe des boucles Thélia par du script php correspondant, permettant au serveur d?interpréter ce script et transformer ces données en données html. 6 la fonction post() dé-cache les paramètres de sortie des boucles encore sur le squelette. 7 le moteur effectue une nouvelle passe pour vérifier la présence de sous-boucles : dans notre exemple, une seule passe a suffit, le serveur peut continuer de dérouler le script mo- teur..... <- - - - - - - - - - - - - - - Fin de la fonction Analyse()- - - - - - - - - - - - - - -> La variable $res continuera d?être traitée par plusieurs fonctions que nous ne détaillerons pas. Le moteur s?achève, le serveur voit un ordre d?afficher cette variable (echo $res;). Il effectue encore quelques dernières actions puis s?arrête jusqu?à la prochaine interaction du visiteur. 45 $boucles = pre($res); $boucles[1] = ?TOTO?; $res= boucle_simple($res, $boucles): $type_boucle = ?RUBRIQUE? ; $arg = ?parent = "0"?; $texte = ?<ul><li>#TITRE</li>?; boucle_exec($type_boucle, $args, $texte); $res = boucleRubrique($texte, $args); function boucleRubrique($texte, $args); $query = SELECT titre FROM this->table WHERE ..... #TITRE = toutes les valeurs du champ "titre" de la table "rubri- quedesc" si la valeur du champ "parent" = 0 dans la table "ru- brique" (la valeur du champ "ID" dans les 2 tables assure la jointure) <ul> <THELIA_TOTO type= "RUBRIQUE" parent= "0" > <li> #TITRE </li> </THELIA_TOTO> <ul> <THELIA_ while(strstr($res, "<THELIA")) { : le moteur repère la présence d?une boucle dans le squelette TOTO RUBRIQUE parent="0" <li>#TITRE</li> #TITRE Boucles Html <ul> <li> Homme </li> <li> femme </li> <li> enfant </li> </ul> page à afficher Fonction analyse() Squelette appelé Sql Php Voici pour résumer une représentation graphique du traitement d'une boucle Thélia par le moteur : nous y voyons le travail des différentes fonctions rencontrées dans ce chapitre : res(), boucle_simple(), boucle_exec() et pour finir la fonction de traitement de la boucle à pro- prement parler, la fonction boucleRubrique(). Voilà comment une boucle Thélia est transfor- mée en données HTML pour affichage au navigateur du client. 46 Annexes 5. pour aller plus loin Quelques fonctions php utilisées par Thélia 1. Fonctions généralistes Fonctions de traduction des boucles Fonctions de gestion des variables de session Fonctions de gestion de la base de données Nom de la fonction Cette fonction : Exemple include() Inclut un fichier dans un autre fichier echo Affiche return () Retourne le résultat d?une fonction exécutée. (Ce qui est différent d?afficher le résultat) isset (arg) Vérifie si arg existe count() Compte le nombre de ligne d?un tableau explode(arg1, arg2) Coupe arg1 en segments déli- mités par arg2 (renvoie un ta- bleau listant les segments) strtolower() Renvoie une chaîne de carac- tère en minuscule strtoupper() Renvoie une chaîne de carac- tère en majuscule files_get_content() Charge le contenu d?un fi- chier header() Retourne un en-tête http method_exists() Vérifie que la méthode existe pour une classe preg_replace (arg1, arg2, arg3) Cherche dans arg3 l?expres- sion arg1 et la remplace par arg2 preg_match (arg1, arg2) Vérifie si dans arg2 existe arg1 (s?arrête au premier arg1 trou- vé) strstr (arg1, arg2) Retourne une sous-chaîne de arg1, allant de arg2 à la fin de la chaîne. str_replace (arg1, arg2, arg3) Retourne une chaîne ou un tableau, dont toutes les occur- rences de arg1 dans arg3 ont été remplacées par arg2 47 Nom de la fonction Cette fonction : Exemple session_start() Initialise une session select*from TABLE where CHAMP = Sélectionne dans la base de données depuis la table TA- BLE la valeur CHAMP insert into TABLE, values() Insert des valeurs dans la TA- BLE. delete from TABLE mysql_query() Définit une requête sur la base de données mysql_fetch_array () Retourne un tableau associatif listant les résultats d?une re- quête préalable sur la base mysql_fetch_object() Retourne un objet dont les at- tributs sont les résultats d?un requête préalable sur la base mysql_num_row() Compte le nombre de lignes retournées par une requête préalable sur la base mysql_insert_id() Retourne l?identifiant généré par la dernière requête INSERT MySQL Quelques fonctions génériques propres au moteur Thélia 2. Pour se simplifier le travail et gagner en lisibilité, le concepteur de Thélia a défini quelques fonctions génériques dont il se sert dans plusieurs fonctions principales. En voici quelques- unes : Nom de la fonction Cette fonction/méthode : Emplacement lireTag() Identifie les valeurs des para- mètres d?entrée des boucles fonctions/divers.php getVars() Extrait un ou plusieurs résultats de la base de données sous forme d?objet classes/baseobj.class.php add() Ajoute un enregistrement à la table dont le nom correspond à celui de la classe classes/requete..class.php charger() Charge un enregistrement de- puis la table dont le nom cor- respond à celui de la classe classes/requete..class.php maj() Met à jour un enregistrement à la table dont le nom corres- pond à celui de la classe classes/requete..class.php 48 La variable de session ?navig? 3. Théorie : Une variable de session en php est une variable dite superglobale parce qu?elle est „ „ accessible partout dans le script (alors qu?une variable classique, si elle est définie dans une fonction ou dans une classe, n?est disponible que pour la fonction ou pour la classe : elle ne peut pas être manipulée ailleurs) Ensuite, le contenu des variables de session a la particularité d?être conservé de page „ „ en page (contrairement au contenu d?une variable classique qui lui ne dure que le temps de la lecture du script par le serveur) : d?où leur utilisation pour conserver toutes les données du visiteur pour la durée de sa navigation sur votre site. Voici le détail de la variable de session $_SESSION[?navig?] : une variable complexe (mais pas compliquée !) composée de plusieurs objets, eux-mêmes dotés d?attributs pouvant être des objets ou des tableaux. Les valeurs (quand elles sont renseignées) sont issues d?une navigation sur un site vierge, équipé seulement du plugin "insertionplugin" ayant permis de remplir le site d?une quaran- taine de produits. Le visiteur, Jean Dupont, vient d?ajouter à son panier la référence DF0008, il s?était préalablement identifié. Navigation Object ( [client] => Client Object ( [id] => 1 [ref] => 080111141042JEA [raison] => 3 [entreprise] => [siret] => [intracom] => [nom] => dupont [prenom] => jean [telfixe] => 0102030405 [telport] => 0601020304 [email] => test@test.fr [motdepasse] => *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 [adresse1] => 17, rue du pré la reine [adresse2] => bureau xxx [adresse3] => étage xxx [cpostal] => 63100 [ville] => clermont-ferrand [pays] => 64 [parrain] => 0 [type] => 0 [pourcentage] => 0 49 [table] => client [bddvars] => Array ( [0] => id [1] => ref [2] => raison [3] => entreprise [4] => siret [5] => intracom [6] => nom [7] => prenom [8] => telfixe [9] => telport [10] => email [11] => motdepasse [12] => adresse1 [13] => adresse2 [14] => adresse3 [15] => cpostal [16] => ville [17] => pays [18] => parrain [19] => type [20] => pourcentage ) [host] => [login_mySql] => [password_mySql] => [db] => [link] => 0 ) [formcli] => Client Object ( [id] => [ref] => [raison] => [entreprise] => [siret] => [intracom] => [nom] => [prenom] => [telfixe] => [telport] => [email] => [motdepasse] => [adresse1] => [adresse2] => [adresse3] => [cpostal] => [ville] => [pays] => 50 [parrain] => [type] => [pourcentage] => [table] => client [bddvars] => Array ( [0] => id [1] => ref [2] => raison [3] => entreprise [4] => siret [5] => intracom [6] => nom [7] => prenom [8] => telfixe [9] => telport [10] => email [11] => motdepasse [12] => adresse1 [13] => adresse2 [14] => adresse3 [15] => cpostal [16] => ville [17] => pays [18] => parrain [19] => type [20] => pourcentage ) [host] => [login_mySql] => [password_mySql] => [db] => [link] => 0 ) [panier] => Panier Object ( [nbart] => 1 [tabarticle] => Array ( [0] => Article Object ( [produit] => Produit Object ( [id] => 38 [ref] => def0008 [datemodif] => 0000-00-00 00:00:00 [prix] => 17 [ecotaxe] => 0 [promo] => 0 [ligne] => 1 [garantie] => 0 [prix2] => 17 [rubrique] => 7 51 [nouveaute] => 0 [perso] => 0 [stock] => 1 [poids] => 0 [tva] => 19.6 [classement] => 8 [table] => produit [bddvars] => Array ( [0] => id [1] => ref [2] => datemodif [3] => prix [4] => ecotaxe [5] => promo [6] => ligne [7] => garantie [8] => prix2 [9] => rubrique [10] => nouveaute [11] => perso [12] => stock [13] => poids [14] => tva [15] => classement ) [host] => [login_mySql] => [password_mySql] => [db] => [link] => Resource id #27 ) [produitdesc] => Produitdesc Object ( [id] => 38 [produit] => 38 [titre] => Débardeur F T8 [chapo] => Débardeur femme [description] => Débardeur femme "THELIA" Classique [postscriptum] => [lang] => 1 [table] => produitdesc [bddvars] => Array ( [0] => id [1] => produit [2] => titre [3] => chapo [4] => description [5] => lang [6] => postscriptum ) 52 [host] => [login_mySql] => [password_mySql] => [db] => [link] => Resource id #27 ) [quantite] => 1 [perso] => Array ( [0] => Perso Object ( [déclinaisons] => 1 [valeur] => 1 ) ) ) [1] => [2] => ) ) [urlprec] => http://localhost/thelia_1.3.9/produit.php?rubrique=7 [urlpageret] => /thelia_1.3.9/produit.php?ref=def0008&id_rubrique=7 [connecte] => 1 [nouveau] => 0 [paiement] => 0 [adresse] => 0 [commande] => Commande Object ( [id] => [client] => [adrfact] => [adrlivr] => [date] => [datefact] => [ref] => [transaction] => [livraison] => [facture] => [transport] => [port] => [datelivraison] => [remise] => [colis] => [paiement] => [statut] => [lang] => [table] => commande [bddvars] => Array ( [0] => id [1] => client [2] => adrfact [3] => adrlivr 53 [4] => date [5] => datefact [6] => ref [7] => transaction [8] => livraison [9] => facture [10] => transport [11] => port [12] => datelivraison [13] => remise [14] => colis [15] => paiement [ 16] => statut [17] => lang ) [host] => [login_mySql] => [password_mySql] => [db] => [link] => 0 ) [promo] => Promo Object ( [id] => [code] => [type] => [valeur] => [mini] => [utilise] => [illimite] => [datefin] => [table] => promo [bddvars] => Array ( [0] => id [1] => code [2] => type [3] => valeur [4] => mini [5] => utilise [6] => illimite [7] => datefin ) [host] => [login_mySql] => [password_mySql] => [db] => [link] => 0 ) [page] => 1 [lang] => 1 [tabDiv] => ) Base de données objet->$cube : „ „ titre : cube „ „ id = 3 „ „ chapo = cube bleu „ „ prix = 100 „ „ table cube table triangle 54 Les fonctions boucleXXX du fichier boucles.php 4. Voici une première représentation de ces fonctions. Cette cinématique type est un modèle : les fonctions boucleXXX() sont plus complexes . Certaines en sont même assez éloignées : cependant le principe général reste vrai : <? php include_once("classes/cube.class.php"); include_once("classes/triangle.class.php"); function boucleCube ($arg, $texte) { $param1 = lireTag(param1, $arg); $param2 = lireTag(param2, $arg); $param3 = lireTag(param3, $arg); ... $cube = new Cube(); $search = "requête Sql en fonction des paramètres d?entrée param1, param2, param3 = cube bleu" $resul = sql_query( select*from $cube->table where $search) while (Sql_fetch_object($resul)) { $temp = str_replace("#TITRE", "$cube->titre", $texte); $temp = str_replace("#ID", "$cube->id", temp); $temp = str_replace("#CHAPO", "$cube->chapo", temp); $temp = str_replace("#PRIX", "$cube->prix", temp); ... } $res .= $temp; return $res; ?> Nous pouvons pour élargir cette représentation, proposer au final un exemple représentant une synthèse du moteur de template Thélia sous la forme ci-dessous. Elle a l?avantage de dévoiler l?ensemble des interactions liant les différents concepts rencontrés au cours de la lecture de ce document : -- les classes et les objets : leurs noms et leurs attributs -- les tables : leurs noms et leurs champs -- les boucles : leurs noms et leurs paramètres d?entrée et sortie Nous avons... bouclé la boucle : les noms des classes, des boucles et des tables ont une ressemblance évidente dans Thélia : à chaque fois qu?une classe possède le nom d?une ta- ble et d?une boucle vous savez maintenant que cette classe XXX sert à instancier un objet XXX qui sert à recevoir les valeurs stockées dans la table XXX : chaque champ de cette table correspondant à un attribut de l?objet XXX ; chaque paramètre d?entrée de la boucle de type XXX correspond aussi à un champ de la table XXX et donc à un attribut de l?objet XXX. Chaque paramètre de sortie filtre l?affichage des valeurs contenues dans ces attributs. Voyez une boucle comme un moteur de recherche sur la base. oui Est appelée par une boucle de type Rubrique Requête la table Rubrique En fonction des paramètres d?entrée de la boucle Initialise un objet de Classe Rubrique Stocke le résultat dans un ou + objet Rubrique Affiche les paramètres de sortie Rubrique 2 3 Une classe Classe ID Titre Parent Chapo Attributs : Ligne Descr Un objet Classe ID Titre Parent Chapo Attributs : Ligne descr rubrique 0 oui Une boucle Type Parent Ligne Titre Chapo Paramètres d?entrée Paramètres de sortie rubrique rubrique 2 homme 0 patati pata Une Table Table Ligne Parent Titre Chapo Champs de la table (échantillon la liste continue - - - - - - - - - - >) ID rubrique 1 0 0 0 1 2 3 4 accessoires homme femme enfant blablabla patati pata etc etc etc oui oui non bliblibli non .................. homme patati pata La fonction boucleRubrique() 1 4 5 Fonctions avancées du moteur de template 1. Thélia Nous avons vu le rôle joué par le moteur du FO de Thélia : 1°) prendre en compte les actions du visiteur sur votre site et 2°) offrir au marchand un moteur de template lui permettant de créer seul un site Web dynamique sans recours aux langages de programmation avancés tels php ou SQL. Nous allons dans ce chapitre y revenir et approfondir encore cette notion. Les boucle Thélia sont une interface pour coder des instructions dynamiques sur un site, ins- tructions traditionnellement écrites en PHP : Une boucle vise en général une table de la base de données. Les paramètres d?entrée définissent les critères d?extraction sur cette table. Les paramètres de sortie filtrent parmi les résultats d?une requête, les éléments à afficher sur le squelette. Les boucles conditionnelles et les filtres autorisent un degré supérieur de dyna- misme et de souplesse dans la gestion de l'affichage des données dynamiques. La fonction analyse complète 1. Nous allons maintenant revenir sur la "super" fonction analyse() : je l'avais volontairement simplifiée pour mettre en évidence le principe du traitement d'une boucle simple. La voici donc reproduite à nouveau mais nous allons prendre en compte désormais toutes les instructions liées à la traduction du langage des boucles (simples et conditionnelles). Ces instructions sont, pour rappel, codées des lignes 65 à 104 du fichier fonctions/moteur.php function analyse($res){ 1$res = preg_replace("|<THELIA([^>]*)>\n|Us", "<THELIA\\1>", $res); 2while(strstr($res, "<THELIA")) { $boucles = pre($res); $res = boucle_simple($res, $boucles); $res = post($res); } 3$res = str_replace("BTHELIA", "THELIA", $res); 4$res = boucle_sinon(explode("\n", $res)); 5while(strstr($res, "<THELIA")) { $boucles = pre($res); $res = boucle_simple($res, $boucles); $res = post($res); } return $res; } 1 $res, le squelette, subit un premier traitement par la fonction prédéfinie en php preg_re- place(). Cette instruction consiste à supprimer le retour à la ligne /n qui suit l'en-tête d'une boucle (son nom, son type et ses paramètres d'entrée). 56 Nous pouvons voir qu'analyse() exécute deux fois (2 et 5) l'ensemble des instructions que nous avons déjà abordé dans le chapitre précédent : la boucle "while" et sa suite de fonctions :pre(), boucle_simple() et post(). Entre les deux structures de boucle WHILE, deux lignes sont insérées et font appel à une nouvelle fonction du parseur : boucle_sinon(). Avant de nous y intéresser, reprenons la cinématique depuis le début. Voici une représentation de la "super" fonction analyse() et des éléments que nous allons étudier : Nous allons passer en revue toutes les fonctions mises à exécution par analyse(). Puis nous y appliquerons deux squelettes complexes composés de plusieurs boucles simples, imbri- quées et conditionnelles et observerons comment ce mécanisme modifie un squelette pour transformer les boucles qu'il contient en données affichables pour le navigateur du visiteur. 1 Première boucle "while" : l'ensemble des boucles Thélia sont traitées les unes après les autres par la fonction pre(). Cette fonction va notamment jouer sur la balise "<THELIA>" pour identifier si cette boucle devra être prise en considération par le premier jeu d'instructions suivant : les fonctions boucle_simple() 2 boucle_exec() 3 et boucleXXX() 4. La fonction pre() est codée sur le parseur, aux lignes 30 à 72. La voici dans son intégralité. 57 boucle simple post pre boucle simple post pre preg replace boucle _sinon boucle simple boucle _exec 1° boucle WHILE Identifie, dé-compile et traite les boucles hors des boucles conditionnelles boucle_sinon() Teste la condition et sélec- tionne le bloc à afficher en fonction du résultat 2nde boucle WHILE Identifie, dé-compile et traite les boucles contenues dans les boucles conditionnelles boucle _sinon boucle _exec 1 2 3 4 5 6 7 8 9 boucle XXX boucle XXX function pre(&$res){ $res = preg_replace("|<THELIA([^>]*)>|Us", "\nSAUT_THELIA<THELIA\\1>\nSAUT_THELIA", $res); $res = preg_replace("|</THELIA([^>]*)>|Us", "\nSAUT_THELIA</THELIA\\1>\nSAUT_THELIA", $res); $tab = explode("\n", $res); $profondeur = 0; $res=""; $bsinon = 0; $compt=0; $boucles=""; for($i = 0; $i<count($tab); $i++){ if(strstr($tab[$i], "<THELIA")) $profondeur++; if(strstr($tab[$i], "</THELIA")) $profondeur--; if(strstr($tab[$i], "<BTHELIA")) $profondeur++; if(strstr($tab[$i], "</BTHELIA")) $profondeur--; if(strstr($tab[$i], "<T_")) $bsinon=1; else if(strstr($tab[$i], "</T_")) $bsinon=0; else if(strstr($tab[$i], "<//T_")) $bsinon=0; if( ($profondeur == 2 && ! strstr($tab[$i], "<THELIA")) || $profondeur>2 ) $tab[$i] = str_replace("#", "#THNO", $tab[$i]); else if(strstr($tab[$i], "<THELIA") && $profondeur < 2){ preg_match("/<THELIA_([^ ]*) /", $tab[$i], $liste); $boucles[$compt++] = $liste[1]; } if($bsinon == 1 && strstr($tab[$i], "<THELIA")) $tab[$i] = str_replace("<THELIA", "<BTHELIA", $tab[$i]); else if($bsinon == 1 && strstr($tab[$i], "</THELIA")) $tab[$i] = str_replace("</THE- LIA", "</BTHELIA", $tab[$i]); $res .= $tab[$i] . "\n"; } $res = preg_replace("|\nSAUT_THELIA|Us", "", $res); $res = preg_replace("|\nSAUT_THELIA|Us", "", $res); return $boucles; } -- 1°) pre() recompose le squelette : chaque entête de boucle (<THELIA...>) et pied de boucle (</THELIA...>) est encadré d'un saut de ligne /n. Puis le squelette est découpé en autant de morceaux qu'il y a de sauts de lignes /n sur tout le squelette. Chaque segment est attribué à 58 une clé du tableau $tab[]. -- 2°) Chaque segment (chaque clé du tableau $tab) est analysé grâce à une boucle php "for". Au premier passage de la boucle "While" aucune modification n'a été faite sur le sque- lette, le serveur va donc rencontrer des segments débutant par "<THELIA...", "</THELIA...", "<T_...", "<T_..." et d'autres qui seront ignorés. -- a) Le calcul précédemment décrit du degré d'imbrication d'une boucle est assuré par la varia- ble interne $profondeur : Au premier segment contenant "<THELIA..." : $profondeur=1. À la passe suivante de "for", le segment suivant contient tout ce qui est compris entre l'en-tête et le pied de la boucle. Si aucune boucle n'est imbriquée, ce second segment sera traité avec la valeur $profondeur=1 : les paramètres de sortie ne seront pas hachés et vice versa. Si une boucle imbriquée est détectée dans le second segment, alors $profondeur=2 et la fonction pre() hache les paramè- tres de sortie. La balise fermante "</THELIA" décrémente $profondeur. -- b) Le mécanisme des boucles conditionnelles est assuré par la variable interne $bsinon et la modification de la balise "<THELIA..." en "<BTHELIA..." : Si un segment de type "<T_..." est identifié alors $bsinon =1. Si le segment suivant contient une balise "<THELIA" cette dernière est remplacée par "<BTHELIA" : elle est donc tempo- rairement ignorée par le traitement suivant de la boucle "while" : les balises <BTHELIA> seront remises en état à la sortie de la première boucle "While", la seconde boucle "While" est prévue pour traiter (éventuellement) le contenu de ces balises. La balise fermante "</T_" décrémente $bsinon. -- 3°) Les sauts Thélia mis en place pour "exploser" proprement le squelette dans la variable tableau $tab sont finalement supprimés : la structure générale du squelette est ainsi remis dans son état initial. A l'issue du traitement de „ „ pre(), le squelette est reconstitué dans son état initial mais les boucles contenues dans une structure conditionnelle (<T_>...<//T_>) sont renommées (<BTHELIA_>...</BTHELIA_>) Les paramètres de sortie des sous-boucles imbriquées sont hachées par la chaîne „ „ #THNO : le traitement suivant va donc se limiter aux boucles simples principales (ni condi- tionnelles ni sous-boucles). Une variable tableau „ „ $boucles liste le nom des boucles à traiter par la fonction suivante boucle_simple(). 2 Le squelette ainsi modifié est soumis à la fonction boucle_simple(). Cette fonction ana- lyse les boucles dont le nom est contenu dans $boucles. Elle est présente sur le parseur aux lignes 209 à 224 : 59 function boucle_simple($lect, $boucles){ for($i=0; $i<count($boucles); $i++){ preg_match("|<THELIA_" . $boucles[$i] . " ([^>]*)>(.*)</THELIA_" . $boucles[$i] . ">|Us", $lect, $liste); if(isset($liste[1])){ $type_boucle = lireTag($liste[1], "type"); $args = $liste[1]; $lect = preg_replace("|<THELIA_" . $boucles[$i] . " [^>]*>.*</THELIA_" . $boucles[$i] . ">|Us", boucle_exec($type_boucle, $args, $liste[2], "T_" . $boucles[$i]), $lect, 1); } } return $lect; } La fonction va donc s'exécuter autant de fois qu'il y a de lignes dans $boucles, donc pour chaque boucle précédemment identifiée par pre(). Nous ne revenons pas plus sur la suite de cette fonction que nous avons déjà eu l'occasion de décrire : à chaque passe de la fonction, une boucle de $boucles est visée (une seule boucle sera traitée à la fois car preg_match() s'arrête à la première occurrence trouvée), le type de boucle, ses paramètres d'entrée et ses paramètres de sortie étant identifiés, la fonction boucle_exec() est mise à contribution. 3 La fonction boucle_exec() travaille au niveau de la boucle qui lui est soumise, et non au niveau de la totalité du squelette. Elle identifie la fonction boucleXXX() à prendre en compte en fonction du type de la boucle. 4 la fonction boucleXXX() exécutée dépend du type de la boucle : le traitement opéré est très variable. Nous aurons souvent l'occasion de détailler le traitement effectué par les principales fonctions boucles du fichier fonctions/boucles.php au cours des développements suivants. Toutes les boucles de $boucles sont donc successivement remplacées par des donnés affi- chables par le traitement opéré par cette fonction. 5 la fonction post() remet les paramètres de sortie modifiés par le hachage #HTNO dans leur état initial pour le traitement suivant : la passe suivante de la première boucle WHILE : à ce stade toutes les boucles principales hors des blocs conditionnels ont disparu. La seconde passe se charge des sous-boucles imbriquées et toujours hors des structures conditionnel- les, la troisième des sous-sous-boucles imbriquées... En achevant le traitement opéré par la première boucle WHILE, seules les structures condi- tionnelles et les boucles qui y sont inclues sont encore en place dans le squelette. La suite de la fonction analyse() est dédiée au traitement de ces structures. 6 La première boucle WHILE ayant laissé des boucles de type "<BTHELIA" sur le squelette, la fonction preg_replace() se charge de les modifier en boucles de type "<THELIA" 60 7 $res passe ensuite dans la fonction boucle_sinon(). Plus précisément, il s'agit de la va- riable $lect, un tableau listant tous les segments délimités par des retours à la ligne /n sur $res. Voici cette fonction codée aux lignes 109 à 205 du parseur. function boucle_sinon($lect){ $i =0; $res=""; $texte=""; while($i<count($lect)) { $rec = $lect[$i]; A if(ereg("<T_([^>]*)", "$rec", $cut)) { $res=""; $avant=""; $apres=""; $sinon=""; $boucle=""; $compt = 0; $nomboucle = $cut[1]; $i++; a while( ! strstr($lect[$i], "//T_" . $nomboucle) && $i<count($lect)){ $res[$compt++] = $lect[$i++] . "\n"; } if( strstr($lect[$i], "//T_" . $nomboucle)) $deb=0; else { echo "La boucle $nomboucle n'est pas fermee correctement !"; exit; } $res[$compt] = $lect[$i]; $compt = 0; b while( ! strstr($res[$compt], "<THELIA_") && $compt<count($res)){ $avant .= $res[$compt++] . "\n"; } if( strstr($res[$compt],"<THELIA_")) $deb=0; else { echo "La boucle $nomboucle n'est pas fermee correctement !"; exit; } $args = $res[$compt]; c while( ! strstr($res[$compt], "</THELIA_") && $compt<count($res)){ $boucle .= $res[$compt++] . "\n"; } if( strstr($res[$compt],"</THELIA_")) $deb=0; else { echo "La boucle $nomboucle n'est pas fermee correctement !"; exit; } $boucle .= $res[$compt++] . "\n"; d while( ! strstr($res[$compt], "</T_$nomboucle") && $compt<count($res)){ $apres .= $res[$compt++] . "\n"; } 61 if( strstr($res[$compt],"</T_$nomboucle")) $deb=0; else { echo "La boucle $nomboucle n'est pas fermee correctement !"; exit; } $compt++; e while( ! strstr($res[$compt], "<//T_$nomboucle") && $compt<count($res)){ $sinon .= $res[$compt++] . "\n"; } if( strstr($res[$compt],"<//T_$nomboucle")) $deb=0; else { echo "La boucle $nomboucle n'est pas fermee correctement !"; exit; } f $type_boucle = lireTag($args, "type"); $rec = boucle_exec($type_boucle, $args, $boucle, "T_$nomboucle"); if( $rec == "") $texte .= $sinon; else { $texte .= $avant; $texte .= $boucle; $texte .= $apres; } $i++; } B else $texte .= $lect[$i++] . "\n"; } return $texte; } Une boucle php "while" traite chaque segment du squelette = chaque ligne de $lect. Pour chaque segment, la fonction boucle_sinon() exécute un ensemble d'instructions résumées maintenant : A Si dans le segment est identifiée une séquence de type "<T_...", la variable $cut[1], liste le nom de cette boucle conditionnelle. $nomboucle prend cette valeur. Le traitement s'opère alors de la manière suivante (étapes a à f) a Si pour une boucle "<T_...> identifiée, la balise fermante "<//T_" correspondante n'est pas trouvée dans le segment suivant, ce dernier est attribué tel quel, suivi d'un saut de ligne /n à $res[1]. Tous les segments suivants sont ainsi passés en revue à la recherche de la balise fermante de la boucle conditionnelle repérée dans le premier segment. Chaque ligne de $res[] stocke un segment en échec suivi d'un saut de ligne /n. La variable $res finit d'être alimentée quand le segment portant la balise fermante est identifié. Si pour une boucle "<T_...> identifiée la balise fermante correspondante est trouvée dans le segment, ce dernier est attribué tel quel sans saut de ligne, à $res[] et $deb=0 Attention donc : dans boucle_sinon(), $res équivaut au bloc de code placé entre les balises <T_> et </T_> et non à l'ensemble du squelette. 62 b A ce stade <T_>$res<//T_>, la fonction cherche une balise ouvrante de boucle simple "<THELIA_" dans l'ensemble de $res, c'est-à-dire entre les 2 balises de la boucle condition- nelle. Tous les segments en échec sont concaténés dans la variable $avant et séparés les uns des autres par un saut de ligne, jusqu'au segment portant la chaîne recherchée. Si cette balise ouvrante est identifiée, le segment la portant est attribué à la variable $arg et $deb=0. c La fonction cherche ensuite une balise fermante de boucle simple dans les segments de $res suivant le segment portant la balise ouvrante identifiée précédemment. Tous les segments en échec sont concaténés dans la variable $boucles, séparés les uns des autres par un saut de ligne, jusqu'au segment identifiant la balise recherchée. Ce dernier est aussi compilé dans la même variable $boucles. Les étapes b et c reconstituent donc dans $boucle la boucle simple située entre les balises "<T_" et "</T_" d'une boucle conditionnelle. d La fonction cherche ensuite une balise intermédiaire de boucle conditionnelle de type "</T_" et correspondant au nom de la boucle conditionnelle initiale ($nomboucle). Tous les segments en échec sont concaténés dans la variable $apres, séparés les uns des autres par un saut de ligne /n et ce jusqu'au segment portant la balise recherchée. e Enfin, la fonction recherche dans les segments qui suivent le dernier identifié précédem- ment, la présence de la balise fermante conditionnelle "<//T_" portant le nom de la boucle conditionnelle initiale ($nomboucle) : les segments en échec sont concaténés dans la varia- ble interne $sinon Ainsi, cette dernière sous-étape définit le contenu compris entre la balise semi fermante "</T_" et la balise fermante "<//T_" : c'est la signification que revêt $sinon. f Le type de la boucle simple identifiée entre les balises de la boucle conditionnelle est dé- terminé classiquement par la fonction lireTags() sur $arg. Au sein de la fonction boucle_sinon() les étapes 8 et 9 sont mises en place : Boucle_exec() est mise à exécution, donc la fonction boucleXXX() correspondante est solli- citée. Cet ensemble d'instructions remonte ou non un résultat : si un résultat est obtenu, le squelette est reconstitué par concaténation des variables $avant.$boucles.$apres. Dans le cas contraire le squelette est le résultat de la variable $sinon. B Sinon, si dans ce segment aucune séquence de type "<T_..." n'est identifiée alors ce segment est concaténé dans texte (=> pas d'impact de la fonction boucle_sinon() ) Le squelette ainsi traité, il peut être soumis à la seconde boucle WHILE et ses 3 fonctions pre(), boucle_simple() et post() : la séquence des étapes 1 à 5 est recopiée et donc ré- exécutée. 63 Les boucles imbriquées et les boucles conditionnelles 2. Voici pour finir deux exemples illustrant la cinématique générale de traitement d'un ensem- ble "complexe" de boucles par analyse(). Envisageons trois boucles imbriquées : une boucle principale nourrit de ses paramètres de sortie les paramètres d'entrée d'une sous-boucle qui alimente à son tour les paramètres d'entrée d'une sous-sous-boucle. Rubrique parent = "0" param sortie "ID" Rubrique parent = "ID" param sortie "ID" Produit rubrique ="ID" sortie "TITRE" 1 2 T-Shirt F Robe T-Shirt F T1 Robe T1 T-Shirt F T2 Robe T2 T-Shirt F T3 Robe T3 Nous allons parcourir le cheminement du moteur soumis au traitement de 2 ensembles de boucles et essayerons d'illustrer pourquoi il convient de respecter quelques principes (sim- ples) lorsque l'on code des boucles conditionnelles et imbriquées sur un squelette. Les voici énoncés : 11°) La condition d'une boucle conditionnelle joue sur la première boucle codée, pas sur la (ou les) sous-boucle(s) imbriquée(s) éventuelle(s). 2 2°) Une boucle conditionnelle ne peut pas être codée à l'intérieur d'une boucle imbriquée si la sous-boucle imbriquée porte des paramètres d'entrée dynamiques (des paramètres de sortie de la boucle principale) mais l'inverse est possible (voir l'illustration ci-dessous) 3 3°) Le nom d'une boucle conditionnelle doit obligatoirement porter le nom de la première boucle codée si et seulement si il existe au moins deux boucles conditionnelles sur un sque- lette. 4 4°) On ne peut pas imbriquer deux boucles conditionnelles Je présenterais différentes variables internes du moteur, à différents moments du script, tel- les qu'elles sont "vues" et manipulées par le serveur. Pour une meilleur lisibilité, j'ai supprimé les lignes vides dans les variables de type tableau, ce qui explique pourquoi les clés des tableaux ne se suivent pas. Je pars d'une installation vierge et active le plugin "Insertion produit" pour une boutique fonctionnelle que vous pouvez reproduire strictement avec les mêmes éléments en base. Pour limiter le nombre d'informations à traiter, j'ai désactivé une sous-rubrique femme et deux sous-rubriques homme et limite l'affichage de produits à 3 (num="3") dans ma boucle "produit". 64 1°) scénario : deux boucles simples imbriquées dans une boucle conditionnelle, le tout imbri- „ „ qué dans une boucle simple. Le squelette pris pour exemple est détaillé ci-dessous. Je rappelle par un contre-exemple ci-dessous la règle N° 2 si une boucle conditionnelle est incluse dans une sous-boucle simple imbriquée dans une boucle principale, le moteur ren- verra une erreur, un warning indiquant qu'un paramètre d'entrée n'a pas pu être identifié Dans mon exemple 2 rubriques ont une valeur "parent" = 0 : -- La rubrique Femme (ID=1) dispose de sous-rubriques : Robe (ID =6) T-shirt (ID =8) -- La rubrique homme (ID = 2) dispose d'une sous-rubrique : Chemise (ID =4) Toutes les sous-rubriques disposent de produits en ligne. L'alternative de la boucle condition- nelle (sinon) ne sera donc pas affichée. 65 <THELIA_RUB type="RUBRIQUE" parent="0"> #ID <T_PROD> <THELIA_RUB2 type="RUBRIQUE" parent="#ID"> #TITRE <THELIA_PROD type="PRODUIT" rubrique="#ID" num="3"> <ul><li>#TITRE</ul></li> </THELIA_PROD> </THELIA_RUB2> </T_PROD> <p>Non disponible</p> <//T_PROD> </THELIA_RUB> Boucle OK La boucle condi- tionnelle est incluse dans une boucle simple. La boucle im- briquée est dans la boucle conditionnelle <THELIA_RUB type="RUBRIQUE" parent="0"> #ID <THELIA_RUB2 type="RUBRIQUE" parent="#ID"> #TITRE <T_PROD> <THELIA_PROD type="PRODUIT" rubrique="#ID" num="3"> <ul><li>#TITRE</ul></li> </THELIA_PROD> </T_PROD> <p>Non disponible</p> <//T_PROD> </THELIA_RUB2> </THELIA_RUB> Boucle KO La boucle condi- tionnelle est incluse dans une boucle imbriquée : la valeur dynamique du pa- ramètre "rubrique" n'est pas traduit "#ID" Warning : mysql_num_rows(): supplied argument is not a valid MySQL result re- source ! <THELIA_RUB type="RUBRIQUE" parent="0"> #ID <T_PROD> <THELIA_RUB2 type="RUBRIQUE" parent="#ID"> #TITRE <THELIA_PROD type="PRODUIT" rubrique="#ID" num="3"> <ul><li>#TITRE</ul></li> </THELIA_PROD> </THELIA_RUB2> </T_PROD> <p>Non disponible</p> <//T_PROD> </THELIA_RUB> pre Dans la fonction pre() : $tab = explode(\n, $res) : Array( [14] = SAUT_THELIA<thelia_rub type="RUBRIQUE" parent="0"> [15] = SAUT_THELIA [16] = #ID [17] = <t_prod> [19] = SAUT_THELIA<thelia_rub2 type="RUBRIQUE" parent="#ID"> [20] = SAUT_THELIA [21] = #TITRE [23] = SAUT_THELIA<thelia_prod type="PRODUIT" rubrique="#ID" num="3"> [24] = SAUT_THELIA [25] = <ul><li>#TITRE</li></ul>; [27] = SAUT_THELIA</thelia_prod> [28] = SAUT_THELIA [30] = SAUT_THELIA</thelia_rub2> [31] = SAUT_THELIA [32] = </t_prod> [33] = <p>Non disponible</p> [34] = <!--/T_PROD--> [36] = SAUT_THELIA</thelia_rub> [37] = SAUT_THELIA) 1ère passe première boucle WHILE Dans le moteur : $res après pre() avant boucle_simple() : <thelia_rub type="RUBRIQUE" parent="0"> #ID <t_prod> <bthelia_rub2 type="RUBRIQUE" parent="#ID"> #THNOTITRE <bthelia_prod type="PRODUIT" rubrique="#THNOID" num="3"> <ul><li>#THNOTITRE</li></ul> </bthelia_prod> </bthelia_rub2> </t_prod> <p>Non disponible</p> <!--/T_PROD--> </thelia_rub> La fonction pre() renvoie le squelette traité et une variable $boucles, un tableau listant les boucles à traiter. $boucle = array ([0]=RUB) La fonction boucle_simple() exécute la fonction boucle_exec() qui exécute boucleRubrique() sur la boucle contenue dans $boucles (RUB). L'ensemble du code présent entre <THE- LIA_RUB> et </THELIA_RUB> est soumis à la fonction pour identification des paramètres de sortie. 1ère passe fonction boucle_simple() Dans boucle_simple : $liste[1] et $liste[2] soumises à boucle_exec() $liste[1] (paramètres d'entrée)= type="RUBRIQUE" parent="0" $liste[2] (paramètres de sortie) = #ID <t_prod> <bthelia_rub2 type="RUBRIQUE" parent="#ID"> #THNOTITRE <bthelia_prod type="PRODUIT" rubrique="#THNOID" num="3"> <ul><li>#THNOTITRE</li></ul> </bthelia_prod> </bthelia_rub2> </t_prod> <p>Non disponible</p> <!--/T_PROD--> Dans le moteur : $res après boucle_simple() et avant post() : 1 <t_prod> <bthelia_rub2 type="RUBRIQUE" parent="1"> #THNOTITRE <bthelia_prod type="PRODUIT" rubrique="#THNOID" num="3"> <ul><li>#THNOTITRE</li></ul> </bthelia_prod> </bthelia_rub2> </t_prod> <p>Non disponible</p> <!--/T_PROD--> 2 <t_prod> <bthelia_rub2 type="RUBRIQUE" parent="2"> #THNOTITRE <bthelia_prod type="PRODUIT" rubrique="#THNOID" num="3"> <ul><li>#THNOTITRE</li></ul> </bthelia_prod> </bthelia_rub2> </t_prod> <p>Non disponible</p> <!--/T_PROD--> 68 $res, l'ensemble du squelette est débarrassé de la boucle RUB : son paramètre de sortie #ID, affiche l'ID des deux rubriques dont parent="0". Puis $res est soumis à la fonction post() : Tous les tags hachés par #THNO sont remis dans leur état initial. Dans $res il ne reste plus de boucle <THELIA> : la première boucle WHILE passe donc une seule fois sur le sque- lette. $res, passe dans la fonction preg_replace() par laquelle les balises <BTHELIA> et </BTHE- LIA> sont transformées en balises THELIA pour être lues par la fonction suivante, boucle_si- non(). Dans la fonction boucle_sinon() : $lect après l'explode() : Array( [14] = 1 [15] = <t_prod> [16] = <thelia_rub2 type="RUBRIQUE" parent="1"> [17] = #TITRE [18] = <thelia_prod type="PRODUIT" rubrique="#ID" num="3"> [19] = <ul><li>#TITRE</li></ul> [20] = </thelia_prod> [21] = </thelia_rub2> [22] = </t_prod> [23] = <p>Non disponible</p> [24] = <!--/T_PROD--> [26] = 2 [27] = <t_prod> [28] = <thelia_rub2 type="RUBRIQUE" parent="2"> [29] = #TITRE [30] = <thelia_prod type="PRODUIT" rubrique="#ID" num="3"> [31] = <ul><li>#TITRE</li></ul> [32] = </thelia_prod> [33] = </thelia_rub2> [34] = </t_prod> [35] = <p>Non disponible</p> [36] = <!--/T_PROD--> ) Ici, le résultat de boucleRubrique() n'est pas vide : $res (le bloc conditionnel) est reconstitué par concaténation des variables $avant, $boucle et $apres. ? $res = [15] à [24] $type_boucle = RUBRIQUE $args =<thelia_rub2 type= "RUBRIQUE" parent="1"> $boucle = #TITRE <thelia_prod type= "PRODUIT" rubrique="#ID" num= "3"> <ul><li>#TITRE</li></ul> </thelia_prod> $sinon = <p>Non disponible</ p> boucleRubrique() != " " ? $res = [27 à [36] $type_boucle = RUBRIQUE $args =<thelia_rub2 type= "RUBRIQUE" parent="2"> $boucle = #TITRE <thelia_prod type= "PRODUIT" rubrique="#ID" num= "3"> <ul><li>#TITRE</li></ul> </thelia_prod> $sinon = <p>Non disponi- ble</> boucleRubrique() != " " Si une boucle conditionnelle est incluse dans une boucle imbriquée, le test de boucleRubrique() portera sur les paramètres d'entrée : $arg = <thelia_prod type="PRODUIT" rubrique="#ID" num="3"> : la valeur #ID n'étant pas traduite, cette requête renvoie une erreur SQL et affi- chera l'alternative ($sinon) puisqu'il n'existe pas de résultat. 69 1ère passe seconde boucle WHILE Dans la fonction pre() : $tab = explode(\n, $res) : Array( [14] = 1 [16] = SAUT_THELIA<thelia_rub2 type="RUBRIQUE" parent="1"> [17] = SAUT_THELIA [19] = #TITRE [22] = SAUT_THELIA<thelia_prod type="PRODUIT" rubrique="#ID" num="3"> [23] = SAUT_THELIA [25] = <ul><li>#TITRE</li></ul> [28] = SAUT_THELIA</thelia_prod> [29] = SAUT_THELIA [32] = SAUT_THELIA</thelia_rub2> [33] = SAUT_THELIA [36] = 2 [38] = SAUT_THELIA<thelia_rub2 type="RUBRIQUE" parent="2"> [39] = SAUT_THELIA [41] = #TITRE [44] = SAUT_THELIA<thelia_prod type="PRODUIT" rubrique="#ID" num="3"> [45] = SAUT_THELIA [47] = <ul><li>#TITRE</li></ul> [50] = SAUT_THELIA</thelia_prod> [51] = SAUT_THELIA [54] = SAUT_THELIA</thelia_rub2> [55] = SAUT_THELIA) La fonction pre() renvoie une variable $boucles, un tableau listant les boucles à traiter. $boucle = array ([0]=RUB2 [1]=RUB2) Dans le moteur : $res après pre() avant boucle_simple() : 1 <thelia_rub2 type="RUBRIQUE" parent="1"> #TITRE <thelia_prod type="PRODUIT" rubrique="#ID" num="3"> <ul><li>#THNOTITRE</li></ul> </thelia_prod> </thelia_rub2> 2 <thelia_rub2 type="RUBRIQUE" parent="2"> #TITRE <thelia_prod type="PRODUIT" rubrique="#ID" num="3"> <ul><li>#THNOTITRE</li></ul> </thelia_prod> </thelia_rub2> 1ère passe fonction boucle_simple() 70 Dans boucle_simple : $liste[1] et $liste[2] soumises à boucle_exec() $liste[1] (paramètres d'entrée)= type="RUBRIQUE" parent="1" $liste[2] (paramètres de sortie) = <thelia_prod type="PRODUIT" rubrique="#ID" num="3"> <ul><li>#THNOTITRE</li></ul> </thelia_prod> 2nde passe fonction boucle_simple() Dans boucle_simple : $liste[1] et $liste[2] soumises à boucle_exec() $liste[1] (paramètres d'entrée)= type="RUBRIQUE" parent="2" $liste[2] (paramètres de sortie) = <thelia_prod type="PRODUIT" rubrique="#ID" num="3"> <ul><li>#THNOTITRE</li></ul> </thelia_prod> Dans le moteur : $res après boucle_simple() avant post() : 1 T-Shirt F <thelia_prod type="PRODUIT" rubrique="6" num="3"> <ul><li>#THNOTITRE</li></ul> </thelia_prod> Robe <thelia_prod type="PRODUIT" rubrique="8" num="3"> <ul><li>#THNOTITRE</li></ul> </thelia_prod> 2 Chemise <thelia_prod type="PRODUIT" rubrique="4" num="3"> <ul><li>#THNOTITRE</li></ul> </thelia_prod> 71 2nde passe seconde boucle WHILE Dans la fonction pre() : $tab = explode(\n, $res) : Array ( [14] = 1 [17] = T-Shirt F [20] = SAUT_THELIA<thelia_prod type="PRODUIT" rubrique="6" num="3"> [21] = SAUT_THELIA [23] = <ul><li>#TITRE</li></ul> [26] = SAUT_THELIA</thelia_prod> [27] = SAUT_THELIA [31] = Robe [34] = SAUT_THELIA<thelia_prod type="PRODUIT" rubrique="8" num="3"> [35] = SAUT_THELIA [37] = <ul><li>#TITRE</li></ul> [40] = SAUT_THELIA</thelia_prod> [41] = SAUT_THELIA [46] = 2 [49] = Chemise [52] = SAUT_THELIA<thelia_prod type="PRODUIT" rubrique="4" num="3"> [53] = SAUT_THELIA [55] = <ul><li>#TITRE</li></ul> [58] = SAUT_THELIA</thelia_prod> [59] = SAUT_THELIA) La fonction pre() renvoie une variable $boucles, un tableau listant les boucles à traiter. $boucle = array ([0]=PROD [1]=PROD [2]=PROD) Dans le moteur : $res après pre() avant boucle_simple() : 1 T-Shirt F <thelia_prod type="PRODUIT" rubrique="6" num="3"> <ul><li>#TITRE</li></ul> </thelia_prod> Robe <thelia_prod type="PRODUIT" rubrique="8" num="3"> <ul><li>#TITRE</li></ul> </thelia_prod> 2 Chemise <thelia_prod type="PRODUIT" rubrique="4" num="3"> <ul><li>#TITRE</li></ul> </thelia_prod> 1ère passe fonction boucle_simple() Dans boucle_simple : $liste[1] et $liste[2] soumises à boucle_exec() $liste[1] (paramètres d'entrée)= type="PRODUIT" rubrique="6" num="3" $liste[2] (paramètres de sortie) = <ul><li>#TITRE</li></ul> 2nde passe fonction boucle_simple() 3ième passe fonction boucle_simple() Dans le moteur : $res après boucle_simple() avant post() : 1 2 T-Shirt F Chemise <ul><li>T-Shirt F T1</li></ul> <ul><li>Chemise T1</li></ul> <ul><li>T-Shirt F T2</li></ul> <ul><li>Chemise T2</li></ul> <ul><li>T-Shirt F T3</li></ul> <ul><li>Chemise T3</li></ul> Robe <ul><li>Robe T1</li></ul> <ul><li>Robe T2</li></ul> <ul><li>Robe T3</li></ul> Dans boucle_simple : $liste[1] et $liste[2] soumises à boucle_exec() $liste[1] (paramètres d'entrée)= type="PRODUIT" rubrique="8" num="3" $liste[2] (paramètres de sortie) = <ul><li>#TITRE</li></ul> Dans boucle_simple : $liste[1] et $liste[2] soumises à boucle_exec() $liste[1] (paramètres d'entrée)= type="PRODUIT" rubrique="4" num="3" $liste[2] (paramètres de sortie) = <ul><li>#TITRE</li></ul> 2ième scénario : Deux boucles conditionnelles dans une boucle simple. Je rappelle ci-des- sous par un contre-exemple la règle N° 4 : les deux boucles conditionnelles que nous pré- voyons ne sauraient être imbriquées : Les filtres Thélia 3. Pour personnaliser et dynamiser votre site, Thélia prévoit une batterie de filtres permettant de décupler les mises en page complexes. Comme la boucle conditionnelle qui autorise un affichage sous condition (si résultat/ sinon), les filtres conditionnent l'affichage de données sous d'autres conditions liées ou non à une valeur donnée X qui peut être dynamique. -- Filtre égalité : si une donnée est égale à la valeur X / Sinon -- Filtre différent : si une donnée est différente de la valeur X / sinon -- Filtre vide : si une donnée a la valeur vide / Sinon -- Filtre vrai : si une donnée satisfait à la valeur X / Sinon -- Filtre faux : si une donnée ne satisfait âs à la valeur X /Sinon -- Filtre minuscule -- Filtre majuscule -- Filtre sans tag Notons qu'un filtre a l'avantage de pouvoir être inclus au coeur même d'une boucle imbri- quée, contrairement à une boucle conditionnelle. Dans le moteur (ligne 268) une instruction spécifique prévoit l'interprétation des filtres pré- sents sur un squelette : $res = filtres($res); Cette instruction fait suite à l'exécution de la fonction analyse(). Le filtre a donc a sa disposition l'ensemble des valeurs interprétées par cette fonction (à ce stade les boucles ont disparu) L'instruction fait appel à la fonction filtre(), codée sur le fichier fonctions/filtres.php : cette fonction se contente d'orienter le traitement vers les fonctions du répertoire "filtres" du dos- sier "fonctions" en fonction du type de filtre rencontré. 74 Chaîne complète Sous-masque1 Sous-masque2 Sous-masque3 1 Le filtre égalité : function filtreegalite($texte){ preg_match_all("'\#FILTRE_egalite\(([^\|]*)\|\|([^\)]*)\|\|([^\)]*)\)'", $texte, $cut); $tab1 = ""; $tab2 = ""; for($i=0; $i<count($cut[2]); $i++){ if(trim($cut[1][$i]) == trim($cut[2][$i])){ $tab1[$i] = "#FILTRE_egalite(" . $cut[1][$i] . "||" . $cut[2][$i] . "||" . $cut[3] [$i] . ")"; $tab2[$i] = $cut[3][$i]; } else{ $tab1[$i] = "#FILTRE_egalite(" . $cut[1][$i] . "||" . $cut[2][$i] . "||" . $cut[3] [$i] . ")"; $tab2[$i] = ""; } } $texte = str_replace($tab1, $tab2, $texte); return $texte; } Comment fonctionne ce filtre ? Nous voyons que le squelette (ici $texte correspond à $res dans le moteur) est balayé par la fonction à la recherche de la (ou des) chaîne #FILTRE_ega- lite..... La fonction preg_match_all() décompose l'expression régulière recherchée en fonction des sous-masques de l'expression régulière qui lui est soumise : les sous-masques sont les parenthèses (non échappées c'est-à-dire sans "/" devant) à savoir : #FILTRE_egalite( #CHAPO|| 1 ||<div class="chapo">#CHAPO</div> ) -- Chaque sous-masque identifié est stocké dans $cut, un tableau multidimensionnel (un tableau stockant des tableaux) ; $cut[0] est un tableau qui contient les résultats qui satisfont le masque complet, $cut[1] est un tableau qui contient les résultats qui satisfont la première parenthèse capturante, etc. Donc si un seul filtre de ce type est présent sur le squelette nous obtenons le résultat suivant : $cut[0] => ( [0] = #FILTRE_egalite(#CHAPO || 1 || <div class="chapo">#CHAPO</div>)) $cut[1] => ( [0] = #CHAPO) $cut[2] => ( [0] = 1) $cut[3] => ( [0] = <div class="chapo">#CHAPO</div>) 75 Chaîne complète Sous-masque1 Sous-masque2 -- Un boucle "for" liste l'ensemble des valeurs stockées dans le 2ème sous-masque ($cut[2]), c'est-à-dire la valeur de référence pour laquelle un test d'égalité va être effectué. Pour chaque valeur de $cut[2], si cette valeur est égale à la valeur stockée dans le premier sous-masque ($cut[1]) pour le même filtre ($i) la valeur du troisième sous-masque ($cut[3]) pour ce filtre est affichée. Sinon rien ne s'affiche. 2 Le filtre "différent" fonctionne exactement sur le même principe : nous ne le détaillerons pas plus. 3 Le filtre vide : function filtrevide($texte){ preg_match_all("`\#FILTRE_vide\(([^\|]+)\|\|([^\)]+)\)`", $texte, $cut); $tab1 = ""; $tab2 = ""; for($i=0; $i<count($cut[2]); $i++){ if(trim($cut[1][$i]) != ""){ $tab1[$i] = "#FILTRE_vide(" . $cut[1][$i] . "||" . $cut[2][$i] . ")"; $tab2[$i] = $cut[2][$i]; } } $i++; preg_match_all("'\#FILTRE_vide\(\|([^\)]+)\)'", $texte, $cut); for($j=0;$j<count($cut[1]);$j++){ $tab1[$i] = "#FILTRE_vide(|" . $cut[1][$j] . ")"; $tab2[$i] = ""; $i++; } $texte = str_replace($tab1, $tab2, $texte); return $texte; } #FILTRE_vide(#CHAPO||<div class="chapo">Non disponible</div>) Dans le cas de ce filtre, 2 paramètres seulement sont précisés et remontés par le traite- ment : les sous-masques 1 ($cut[1]) et 2 ($cut[2]). Si la valeur de $cut[1] est vide, alors la valeur de $cut[2] est affichée. Notez que nous pouvons également coder un filtre "vide" de cette manière : #FILTRE_vide(#CHAPO) 3. Appréhender Thélia par la pratique : Développer un plugin (1/2) La première partie de ce document était consacrée à la prise en main théorique du moteur et du langage php. Vous avez certainement, avant, pendant et après sa lecture, enrichi vos connaissances en programmation php et c?est une bonne chose. Il est bientôt temps de va- loriser tous ces efforts : nous allons envisager de développer un plugin pour Thélia. Rappel : Thélia est fourni à la base avec un script moteur prévoyant un certain nombre de fonctionnalités. Ajouter un plugin va permettre de rajouter des fonctionnalités au moteur. Ce rajout peut nécessiter ou non : La création de tables supplémentaires dans la base „ „ La création d?une interface de gestion de la nouvelle fonctionnalité dans le back of- „ „ fice. Nous allons créer un plugin assez complexe qui fera appel à tout ce que nous avons vu du moteur et plus encore. Je scinderais l?explication en 2 parties : nous allons étudier la pre- mière partie ici, consacrée à la partie Front Office (les fonctionnalités supplémentaires pour le client). Dans la 4ième partie de ce document nous aborderons les apports du plugins sur le Back Office (les fonctionnalités supplémentaires pour le marchand), lorsque nous nous serons arrêtés sur l?étude du back Office de Thélia (3ième partie). Néanmoins, avant de coder notre premier plugin, nous allons encore approfondir notre dé- couverte du moteur et précisément comprendre comment le système de plugin a été implé- menté dans Thélia. Chapitre 1 : présentation des plugin Thélia Chapitre 2 : présentation du projet : le plugin devis Chapitre 3 : codage du plugin devis (partie front office). Je considère que vous avez pris connaissance des informations concernant les plugins, fournies par le Wiki Thélia 76 Présentation des plugin dans 1. Thélia Principes généraux des plugins Thélia Thélia prévoit 3 types de plugins : les plugins classiques, les plugins paiement et les plugins transport, chacun organisé par une classe dédiée. Nous nous intéresserons au premier type uniquement. Une classe PluginsClassiques est incluse dans le moteur. Lorsque vous créez un plugin Thé- lia, vous créez une nouvelle classe qui étend la classe PluginsClassiques. Vos plugins bé- néficient ainsi des attributs et méthodes de cette classe (par défaut). L?héritage d?une classe mère à une classe fille est en effet une caractéristique de la programmation orientée objet. Au niveau du moteur Thélia, le terme "module" fait toujours référence à la gestion d?un plugin : la classe module.class organise l?inclusion des fichiers d?un plugin sur le moteur. La classe PluginsClassiques 1. L?affiliation à cette classe assurera à vos plugins : Son inclusion dans l?interface "gestion des plugins" du BO Thélia (activer/désactiver le „ „ plugin) Les connexions à la base de données (si besoin): ceci parce que PluginsClassiques „ „ est elle-même une fille de la classe baseobj qui, de filiation en filiation remonte à la connexion à la base. Votre nouvelle classe 2. Thélia impose de respecter une organisation et une syntaxe rigoureuses pour les plugins : Question organisation, le plugin sera contenu dans un dossier à son nom, stocké dans „ „ le dossier client/plugin de Thélia. Il sera composé d?au moins un fichier "classe". Si une in- terface d?administration est prévue côté Back Office, celle-ci sera décrite dans un second fichier. D?autres fichiers sont envisageables, nous y reviendrons. Question syntaxe : si notre plugin s?appelle "devis", il sera contenu dans un dossier „ „ "devis", le fichier classe qui le définira se nommera Devis.class.php et l?interface d?adminis- tration se nommera devis_admin.php Les méthodes que vous décrirez dans la classe respecteront également une syntaxe impo- sée pour les raisons que nous allons détailler maintenant. Les points d?entrées prévus pour les plugins dans le moteur. 3. Le moteur Thélia prévoit à plusieurs endroits du script une fonction modules_fonction() dont le rôle est de prendre en compte les instructions prévues par des fichiers stockés dans le dossier client/plugin et répondant à un nommage précis (les plugins donc). Ceci explique pourquoi, au sujet des plugins dans Thélia, la syntaxe du nom de votre dossier et des fichiers 77 le composant est importante : une erreur, et le moteur ignorera tout votre travail. Les méthodes que vous décrirez devront aussi respecter certaines règles de nommage. El- les seront lues dans le script grâce à la fonction modules_fonction() présente dans le moteur. De la sorte les méthodes des plugins (=les fonctions dans la classe) sont bien intégrées au script moteur principal comme si elles y étaient directement codées. La fonction modules_fonction() est présente à 6 endroits dans le script moteur : modules_fonction ("demarrage"); (ligne 203) „ „ modules_fonction ("pre"); (ligne 236) „ „ modules_fonction ("inclusion"); (ligne 245) „ „ modules_fonction ("action"); (ligne 252) „ „ modules_fonction ("post"); (ligne 261) „ „ modules_fonction ("apres"); (ligne 272) „ „ Par ailleurs elle apparaît dans d?autres fichiers du moteur Thélia notamment concernant la gestion du BO. Nous parlerons principalement dans cette partie des 6 instructions ci-des- sus. Que nous dit le fichier divers.php (lignes 653 à 676) sur la fonction modules_fonction() ? function modules_fonction($fonc, $args = "", $nom = ""){ $search = ""; if($nom != "") $search .= "and nom=?$nom?"; $modules = new Modules(); $query = "select * from $modules->table where actif=?1? $search order by classe- ment"; $resul = mysql_query($query, $modules->link); while($row = mysql_fetch_object($resul)){ $nomclass = $row->nom; $nomclass[0] = strtoupper($nomclass[0]); if(! file_exists(realpath(dirname(__FILE__)) . "/../client/plugins/" . $row->nom . "/" . $nomclass . ".class.php")) return; include_once(realpath(dirname(__FILE__)) . "/../client/plugins/" . $row->nom . "/" . $nomclass . ".class.php"); $tmpobj = new $nomclass(); if(strtolower(get_parent_class($tmpobj)) != "pluginsclassiques" && strtolower(get_parent_class($tmpobj)) != "pluginspaiements" && strtolower(get_parent_ class($tmpobj)) != "pluginstransports") return ""; if(method_exists($tmpobj, $fonc)) $tmpobj->$fonc($args); } 78 A chaque fois que le serveur rencontre la fonction modules_fonction(), il inclut dans le mo- teur (par include) les fichiers class.php des plugins activés. Puis il instancie un objet pour chaque nouvelle classe incluse. La fonction modules_fonction() présente à chaque fois un argument différent dans le moteur : demarrage, pre, inclusion, action, post, apres.... Si la nouvelle classe incluse (votre plugin) possède une méthode dont le nom correspond à cet argument, cette fonction est exécutée à ce moment. 2 remarques : Voilà pourquoi le nommage des méthodes dans une classe plugin n?est pas complè- „ „ tement libre : pour que les instructions d?un plugin s?exécutent, elles devront porter l?un des noms prévus. D?autres méthodes pourront être créées avec des noms différents mais ces dernières ne pourront qu?être utilisées par l?une des méthodes dont le nom est pré défini : elles ne pourraient pas être exécutées seules par le serveur (qui ne les trouverait pas) Thélia prévoit 7 moments principaux pour déclencher les fonctionnalités de votre plu- „ „ gin (les 6 du moteur plus 1 dans la fonction boucle_exec, une sous-fonction de la "super" fonction analyse() : -- avant l?exécution des actions du moteur : la méthode devra s?appeler function demar- rage() -- après l?exécution des actions du moteur : la méthode devra se nommer function pre (). -- avant les inclusions du moteur : la méthode devra se nommer function inclusion (). -- avant la "super" fonction analyse() : la méthode devra se nommer function action (). -- au moment du traitement des boucles : la méthode devra se nommer function boucle () -- avant la fonction filtre du moteur : la méthode devra se nommer function post (). -- après affichage de la page : la méthode devra se nommer function apres (). Au delà de ces points d?entrée permettant aux plugins d?enrichir les fonctionnalités „ „ moteur actionnées par la navigation du visiteur, d?autres points d?entrée sont prévus dans Thélia qui ne dépendent plus nécessairement de la navigation : de la même façon, vous les actionnerez en fonction de règles de syntaxe précises (extrait du wiki : " -- La méthode statut() sera appelée lors du changement du statut d?une commande (via in- terface admin). -- La méthode modprod() sera appelée lors de la modification d?un produit (via interface d?ad- min) -- La méthode confirmation() sera appelée lors d?une confirmation de commande par la ban- que distante. -- La méthode avantclient() sera appelée lors de la création d?un client, juste avant le renvoi sur la page de création. -- La méthode apresclient() sera appelée lors de la création d?un client, juste après sa créa- tion et le renvoi sur la page nouveau.php. -- La méthode avantcommande() sera appelée lors de -- La méthode aprescommande() sera appelée lors de la création d?une commande juste avant l?appel des plugins de paiement.") Enfin remarquons un autre appel aux fichiers d?un plugin, le premier, tout au début du „ „ 79 moteur, lignes 36 à 42 : $modules = new Modules(); $query="select * from $modules->table where actif=?1?"; $resul= mySql_query($query, $modules->link); while($row = mySql_fetch_object($resul)) if(file_exists("client/plugins/" . $row->nom . "/inclure_session.php")) include_once("client/plugins/" . $row->nom . "/inclure_session.php"); Se traduira par : Si, pour un plugin activé, le serveur détecte dans le dossier plugin correspondant un fichier nommé inclure_session.php, il l?inclut à cet endroit. Nous aurons l?occasion d?exploiter ce point d?entrée dans les prochains chapitres consacrés à la création d?un plugin. Les méthodes des plugins 4. Nous savons désormais où et comment dans le moteur vont s?implémenter les méthodes que vous souhaitez décrire pour les fonctionnalités du plugin : 16 points d?entrées sur le mo- teur accessibles par 16 méthodes aux nom pré définis. Reste 2 méthodes pré définies qui simplifieront son écriture : function init() : méthode appelée à l?activation du plugin. Elle peut permettre de créer „ „ des tables par exemple. function destroy() : méthode appelée à la désactivation d?un plugin. Utile pour vider le „ „ cache, supprimer des tables ... Quelques précisions : Si un paramètre spécifique „ „ action et sa valeur sont prévus pour le visiteur par le plugin nous choisirons la méthode action() du plugin pour décrire les instructions attendues et ce, pour deux raisons : -- 1°) La fonction modules_fonction(?action?) est placée après les actions natives du moteur : nous pouvons donc envisager d?utiliser les mêmes valeurs (ex : ajouter) pour le plugin : son action écrasera l?action précédente générée par la fonction native. Bien sûr nous pouvons aussi envisager de gérer des valeurs attendues différentes qui ne perturberont pas les fonc- tions natives (ex : ajouterdevis). -- 2°) Elle est placée après la fonction moteur inclusion() : on peut envisager d?interagir via les plugins sur le contenu des fichiers inclus dans le squelette. Attention : dans le cadre d?un plugin, la fonction nommée "action" ne traite pas les valeurs de la variable $action du moteur ! Le terme "action" dans ce cas ne sert qu?à définir le moment où la méthode action() dans le fichier classe d?un plugin devra être exécutée. Les fonctions natives destinées à traiter les valeurs de la variable $action (ajouter, modifier, creercompte...) ne traiteront pas non plus les nouvelles valeurs que vous prévoyez d?attribuer au paramètre "action" : vous devrez tout redéfinir dans la méthode function action() de la classe. 80 Le projet : création d?un plugin 2. devis Présentation du projet : Gérant une entreprise orientée B2B, mes clients, des entreprises ou des administrations, ont souvent des services achats, bardés de procédures contraignantes et incontournables. Les entreprises et les administrations imposent donc souvent un devis comme pièce préa- lable à tout achat, aussi petit soit-il. Mon site Internet n?est donc pas adapté à ma clientèle : il assure bien son rôle de vitrine mais je dois dépenser un temps précieux pour traiter les contacts téléphoniques et fax rendus obligatoires entre la décision de mon client et son acte d?achat.... J?ai donc décidé de créer un plugin devis. Voici le rôle qu?il va devoir assurer : Un visiteur aura à sa disposition sur le site, en plus de son panier, un "devis virtuel", „ „ vide par défaut, qu?il pourra remplir au fur et à mesure de sa navigation sur les fiches produits : sur chacune d?elle se trouvera donc un bouton "ajouter au devis", en plus du bouton "ajouter au panier". Il pourra accéder à une représentation de son devis. „ „ Il pourra modifier la quantité de chaque référence ajoutée au devis „ „ Il pourra supprimer une référence ajoutée au devis. „ „ S?il valide son devis il y aura une double vérification : „ „ Vérification 1 -- Si le client est connecté on passe à la vérification 2 -- Si le client n?est pas identifié il doit le faire. S?il n?a pas de compte il doit en créer. Vérification 2 -- Si toutes les références du devis sont représentées par au moins une ligne dans la table "devis" un devis automatique est généré et envoyé au client, une copie est envoyée au mar- chand. -- Si une référence du devis n?est pas présente dans la table "devis", le devis est envoyé au marchand. Un message alerte le client qu?il recevra un devis par mail dans les 48H. L?action de validation du devis est donc une action complexe : elle fera l?attention d?un para- graphe particulier. Le plugin créera une nouvelle table "devis" munie de 5 champs : ID, REF, QTEMIN, „ „ QTEMAX, PRIX. Dans le BO, le marchand peut, pour une référence de son catalogue, créer une ou plusieurs ligne(s) en renseignant pour chacune une valeur pour QTEMIN, QTEMAX et PRIX. Il définit ainsi des tranches de prix dégressifs en fonction de la quantité renseignée dans le devis (je me suis inspiré du plugin existant "Dégressif" pour conceptualiser cette par- tie de mon plugin) Dans cette partie, nous allons nous contenter de créer la table devis grâce à une méthode de la classe devis. Les éléments de manipulation de cette table étant des fonctionnalités 81 BO, elles seront décrites dans la 4ième partie de ce document. Le devis doit prendre en compte les déclinaisonss d?un produit „ „ A chaque fois que le client valide un devis, celui-ci se vide „ „ A chaque fois qu?il quitte le site, si un devis est en cours il est perdu. „ „ La gestion de stock standard est incluse dans le devis „ „ Le devis devra porter un numéro unique. „ „ Le devis devra être daté. „ „ Le devis ne pourra être envoyé qu?une fois. „ „ Ainsi, pour garder à l?esprit le travail à accomplir et pourquoi, nous pouvons représenter le plugin de la façon suivante : 82 83 inclure_session.php : définit la classe Virtueldevis pour la ges- tion de la variable de session. La conception orientée objet de Thélia permet d?envisager la clas- se Virtueldevis comme une extension de classe panier. Devis.class.php : définit la classe Devis pour la gestion globale des fonctionnalités du plugin (FO et BO) -- La méthode init() crée une table Devis à l?activation du plugin -- La méthode demarrage () initialise une variable de session : $_SESSION [?virtueldevis?] -- La méthode boucle() définit la boucle devis pour implémenter le devis dans le squelette -- La méthode action () décrit le comportement attendu pour : -- ajouter une référence au devis -- modifier une quantité d?une référence sur le devis -- supprimer une référence d?un devis -- valider un devis Chacune de ces actions est une "sous" méthode, appelée par la méthode action() devis_admin.php : définit les éléments liés à la gestion des fonctionnalités Back Office : permettre au marchand d?enregis- trer des grilles tarifaires pour un produit sur le BO Cette partie sera détaillée ultérieurement Thélia Client Plugin Devis Table "devis" Base De Données création gestion Le fichier inclure_session.php 1. Notre plugin nécessite une nouvelle variable de session virtueldevis qui stockera les référen- ces choisies par le client pour son devis, au fur et à mesure de sa navigation. Son fonctionnement est identique au panier classique à part la validation. Nous allons bé- néficier de la souplesse de Thélia, orienté objet et limiter notre propre code en héritant de la classe Panier <?php include_once(realpath(dirname(__FILE__)) . "/Panier.class.php"); include_once(realpath(dirname(__FILE__)) . "/Article.class.php"); class Virtueldevis extends Panier { var $nbart; var $tabarticle; function Virtueldevis(){ $this->nbart = 0; $this->tabarticle=array(); } } ?> Le fichier Devis.class.php 2. Il va compiler l?essentiel des informations nécessaires au fonctionnement du plugin. Voici un aperçu des éléments qui composeront ce fichier : 1) la classe "devis" est une extension de la classe PluginsClassiques : cette classe „ „ sera incluse à l?entrée du fichier. Sa filiation établie. 2) Nous définirons des attributs à la classe Devis : en l?occurrence les variables du „ „ panier sont peu nombreuses mais la ressemblance avec le panier montre ses limites : les attributs de notre classe devis ne seront effectivement pas utiles pour la partie Front Office mais seront nécessaire pour "manipuler" la table devis : dans le script du moteur du BO, les données de cette table seront manipulées grâce à un objet de l?instance "devis" comme nous le savons désormais : nous organisons donc les attributs de notre classe en fonction des champs dont nous avons besoin et dont nous allons doter notre table : -- ID -- REF -- QTEMIN -- QTEMAX -- PRIX 84 3) Nous souhaitons que le marchand puisse organiser des grilles tarifaires en fonction „ „ de la quantité demandée sur le devis : nous devons prévoir de créer une nouvelle table "de- vis" dans la base. 4) La méthode demarrage() initialisera la variable de session $_ „ „ SESSION[?virtueldevis?]. 5) Les „ „ actions qui seront possibles pour le visiteur seront décrites ici : ajouterdevis, modifierdevis, supprimerdevis, validerdevis : la méthode action() de mon plugin organisera le traitement des actions du visiteur grâce à 4 sous méthodes de la classe, qui seront donc, logiquement décrites avant la méthode action(). 6) la méthode boucle() sera l?occasion de décrire les traitements de la nouvelle bou- „ „ cle "devis" que nous souhaitons mettre à disposition pour constituer un squelette devis. html. Voici le résultat attendu sauf les méthode validerdevis et boucle, détaillées plus bas ainsi quelque quelques autres fonctionnalités que je ne représenterais pas pour ne pas parasiter la compréhension de la structure principale: <?php include_once(realpath(dirname(__FILE__)) . "/../../../classes/PluginsClassiques.class. php"); include_once(realpath(dirname(__FILE__)) . "/../../../classes/Article.class.php"); class Devis extends PluginsClassiques{ var $id; var $ref; var $qtemin; var $qtemax; var $prix; var $table = "devis"; var $bddvars=array("id", "ref", "qtemin", "qtemax", "prix"); function devis(){ $this->PluginsClassiques("devis"); } function init(){ $query_devis = "CREATE TABLE `devis` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `ref` TEXT NOT NULL , `qtemin` FLOAT NOT NULL , `qtemax` FLOAT NOT NULL , `prix` FLOAT NOT NULL );"; 85 $resul_devis = mySql_query($query_devis, $this->link); } function demarrage(){ if(! isset($_SESSION["virtueldevis"])){ $_SESSION["virtueldevis"] = new Virtueldevis(); $_SESSION["virtueldevis"]->lang= "1"; } } function ajouterdevis($ref, $quantite, $tdeclidisp= "", $append, $nouveau){ $existe = 0; for($i=0; $i<$_SESSION["virtueldevis"]->nbart+1; $i++) if(isset($_SESSION["virtueldevis"]->tabarticle[$i])) if(isset($_SESSION["virtueldevis"]->tabarticle[$i]->produit->ref) && $_ SESSION["virtueldevis"]->tabarticle[$i]->produit->ref == $ref){ if(! count($tdeclidisp)) {$existe = 1; $indice = $i;} for($j=0; $j<count($_SESSION["virtueldevis"]->tabarticle[$i]->perso); $j++){ if($_SESSION["virtueldevis"]->tabarticle[$i]->perso[$j] == $tdeclidisp[$j]) {$existe = 1; $indice = $i;} else { $existe = 0; break; } } } if(!$existe OR $nouveau == 1) $_SESSION["virtueldevis"]->tabarticle[$_SESSION["virtueldevis"]->nbart] = new Article($ref, $quantite, $tdeclidisp); else if($existe && $append) $_SESSION["virtueldevis"]->tabarticle[$indice]->quantite += $quantite; if(isset($_SESSION["virtueldevis"]->tabarticle[$_SESSION["virtueldevis"]- >nbart]) && isset($_SESSION["virtueldevis"]->tabarticle[$_SESSION["virtueldevis"]->nbart]- >produit) && $_SESSION["virtueldevis"]->tabarticle[$_SESSION["virtueldevis"]->nbart]- >produit->ref) $_SESSION["virtueldevis"]->nbart++; } function supprimerdevis($id){ if(! $_SESSION["virtueldevis"]->tabarticle[$id]) return; $_SESSION["virtueldevis"]->tabarticle[$id]= ""; 86 for($i=$id+1; $i<$_SESSION["virtueldevis"]->nbart+1; $i++) if(isset($_SESSION["virtueldevis"]->tabarticle[$i])) $_SESSION["virtueldevis"]->tabarticle[$i-1] = $_ SESSION["virtueldevis"]->tabarticle[$i]; $_SESSION["virtueldevis"]->tabarticle[$_SESSION["virtueldevis"]->nbart]= ""; $_SESSION["virtueldevis"]->nbart- -; } function charger_tranche($ref, $quantite){ return $this->getVars("select * from $this->table where ref=\"$ref\" and $quantite>=qtemin and $quantite<qtemax"); } function modifierdevis($article, $quantite){ $_SESSION["virtueldevis"]->tabarticle[$article]->quantite = $quantite; $devis = new Devis(); if($devis ->charger_tranche($article, $quantite)){ $_SESSION["virtueldevis"]->tabarticle[$article]->prix = $devis->prix; } } function validerdevis(){ } function action(){ switch($_REQUEST[?action?]){ case ?ajouterdevis? : $this->ajouterdevis($_REQUEST[?ref?], $_ REQUEST[?quantite?], $_REQUEST[?append?], $_REQUEST[?nouveau?]); break; case ?supprimerdevis? : $this->supprimerdevis($_REQUEST[?article?]); break; case ?modifierdevis? : $this->modifierdevis($_REQUEST[?article?], $_ REQUEST[?quantite?]); break; case ?validerdevis? : $this->validerdevis(); break; } } function boucle($texte, $args){ } }?> 87 Quelques remarques sur le code : Je n?ai pas réinventé la roue pour mon plugin : les méthodes ajouterdevis(), modifierde- „ „ vis() et supprimerdevis() sont adaptées des méthodes du panier (comparez-les à ajouter(), modifier() et supprimer() de la classe Panier pour comprendre). Néanmoins une différence notable tient au fait que nous souhaitons que le prix d?une „ „ référence dans le devis dépende de la quantité renseignée. Là encore ma flemme naturelle alliée à mon manque d?assurance dans le domaine de la programmation ne m?ont pas pous- sé à tout repenser : je suis tranquillement allé me renseigné sur la conception du plugin "Dé- gressif" pour implanter cette fonctionnalité : j?ai donc comme ce plugin prévu une méthode charger_tranche dans ma classe, méthode identifiant un prix d?une référence en fonction de la quantité commandée. Cette méthode est appelée dans modifierdevis (qui sert à préciser une quantité) pour spécifier le bon prix à enregistrer dans ma variable de session pour une référence et une quantité données. Ceci dit, il reste 3 parties importantes à réaliser pour finaliser notre classe Devis. La méthode boucle() dans les classes plugin Nous savons maintenant que la fonction modules_fonction() du moteur exécute les méthodes des plugins dans un ordre qui dépend de leur nom. Une méthode néanmoins échappe à cette règle : en effet vous ne trouverez nulle part dans Thélia l?instruction module_fonction(?boucle?). Une fonction spécifique pour l?intégration des boucles des plugins est prévue et décrite dans le fichier parseur.php (lignes 227 à 245) : moduleBoucle() function moduleBoucle($type_boucle, $texte, $args){ $type_fonction = strtolower($type_boucle); $modules = new Modules(); $query = "select * from $modules->table where nom=?$type_fonction? and actif=?1?"; $resul = mySql_query($query, $modules->link); if(! mySql_num_rows($resul)) return ""; $type_fonction[0] = strtoupper($type_fonction[0]); $tmpobj = new $type_fonction(); if(method_exists($tmpobj, "boucle")) return $tmpobj->boucle($texte, $args); else return ""; } 88 moduleBoucle() est actionnée lors de l?exécution de la fonction boucle_exec() que nous connaissons déjà. Elle oriente le serveur sur le traitement à appliquer aux boucles dont le paramètre "type" est différent des types de boucles prévus par défaut dans le moteur. Notons que la conception du code nous impose de créer une boucle de type XXX pour un plugin XXX. Ainsi, pour $type_boucle = XXX : le serveur reçoit l? ordre d?exécuter la méthode boucle() du plugin XXX avec en argument les paramètres $texte et $arg. Nous devons donc, dans la méthode boucle() de notre plugin XXX, préciser au serveur com- ment interpréter les paramètres possibles d?entrée et de sortie concernant la boucle de type XXX. Avant de s?intéresser au résultat, notons un détail : j?utiliserai la fonction boucle() pour traiter certaines données que la conception de Thélia nous demanderait plutôt de traiter sous forme de substitutions : ce qui m?amène à une aparté sur cette fonctionnalité que vous devez cer- tainement connaître en tant qu?utilisateur de Thélia. Quelles différences distinguent les boucles des substitutions ? Vous connaissez sans doute la réponse "fonctionnelle" mais posons-nous la question sous l?angle du moteur : vous utilisez les 2 dans vos squelettes pour indiquer au serveur qu?il faut afficher des informations contenues dans la base de données. Les substitutions et les bou- cles sont donc proches ; pourtant elles sont différentes à bien des égards : -- Nous savons maintenant qu?une fonction substitutions() du moteur traite les substitutions possibles des squelettes alors qu?un fichier boucle.php traite les boucles dans le cadre de la fonction analyse(). -- Les substitutions sont accessibles partout dans le squelette et pas seulement dans une boucle ou en fonction d?un paramètre d?url. -- Sous l?angle moteur, les substitutions ne sont pas traitées sous forme de boucles php ("for" ou "while") : elles affichent zéro ou une information, les boucles pourront afficher 0, une ou n information(s). -- Les substitutions remontent la valeur d?un champ de la base, les boucles remontent un ensemble de champs (une ligne entière) : chaque ligne est stockée dans un objet. Dans mon plugin ces 2 fonctionnalités sont regroupées et exécutées par la méthode bou- cle() function boucle($texte, $args){ $ref = lireTag($args, "ref"); for($i=0; $i<$_SESSION[?virtueldevis?]->nbart; $i++){ $quantite = $_SESSION[?virtueldevis?]->tabarticle[$i]->quantite; $prix = $_SESSION[?virtueldevis?]->tabarticle[$i]->produit->prix; $total += $prix*$quantite; } 89 for($i=0; $i<$_SESSION[?virtueldevis?]->nbart; $i++){ $temp = str_replace("#REF", $_SESSION[?virtueldevis?]->tabarticle[$i]- >produit->ref, $texte); $temp = str_replace("#QUANTITE", $_SESSION[?virtueldevis?]->tabarticle[$i]- >quantite, $temp); $temp = str_replace("#ARTICLE", $i, $temp); $temp = str_replace("#TITRE", $_SESSION[?virtueldevis?]->tabarticle[$i]- >produitdesc->titre, $temp); $temp = str_replace("#PRIX", $_SESSION[?virtueldevis?]->tabarticle[$i]- >produit->prix, $temp); $temp = str_replace("#SUPPRURL", "devis.php?action=" . "supprimerdevis" . "&amp;" . "article=" . $i, $temp); $res .= $temp; } $temp = str_replace("#TOTAL", $total, $temp); return $res; } Toutes les fonctions d?interprétation d?une boucle Thélia (les méthodes boucle() des plu- gins comme les fonctions bouclesXXX du fichier boucle.php) sont décrites à peu près de la même façon : Les méthodes boucle() et les fonctions boucleXXX() acceptent : „ „ -- un 1er argument : $texte : si vous avez suivi le cheminement du moteur jusqu?ici vous en déduisez que cette variable contient la chaîne de caractères placée entre les balises <THELIA> et </THELIA> de la boucle XXX, soit les paramètres de sortie. -- et un 2nd : $arg : si vous avez suivi le cheminement du moteur jusqu?ici vous en déduisez que cette variable contient la chaîne de caractères qui suit la balise <THELIA_nomdelabou- cle type="XXX"... jsqu?à la balise fermante ">" soit les noms des paramètres d?entrée et les valeurs associées. Les paramètres d?entrée et de sortie de la boucle XXX correspondent à des champs de „ „ la table XXX de la base*. Une série de variables récupère la valeur des paramètres d?entrée de la boucle (ils sont contenus dans $arg) grâce à la fonction générique lireTag() appliquée à $arg (lireTag() est décrite dans le fichier divers.php) Une variable „ „ $search récupère ces valeurs et les traite pour en faire une requête Sql globale sur la table XXX. Cette requête est lancée par une nouvelle variable „ „ $query_XXX. Une troisième variable „ „ $resul_XXX récupère le résultat de cette requête : ce résultat est égal à zéro, une ou plusieurs ligne(s) de la table . Une 4ième variable „ „ $row récupère sous la forme d?objet de classe XXX le contenu de 90 $resul_XXX : chaque ligne retournée est un objet dont les attributs récupèrent les valeurs des champs de la ligne. Une boucle php "for" ou "while" utilise une 5ième variable „ „ $temp pour appliquer les valeurs remontées dans l?objet XXX aux paramètres de sortie de la boucle ($texte). Voilà comment en quelques lignes de code vous créez une nouvelle boucle Thélia en géné- ral. *Néanmoins la boucle panier échappe sensiblement à la règle générale (notamment parce que le panier n?est pas une table de la base mais seulement une variable de session). Notre classe Devis dont la conception est proche du panier suit pareillement une structure non conventionnelle : elle cumule des appels à la variable de session virtueldevis et à la table "devis" de la base de données (pour obtenir le prix à prendre en compte en fonction de la quantité désirée). La fonction validerdevis() du plugin devis Cette fonction est relativement complexe à mettre en oeuvre dans la mesure où nous allons intégrer des notions de connecté/non connecté, envoi de mail avec un PDF en fichier joint. 1°) connecté/non connecté Des méthodes plus subtiles pourraient s?envisager mais pour rester simple voilà comment va fonctionner notre devis lorsque le client va cliquer sur "valider le devis" : Nous souhaitons 2 comportements possibles: - Toutes les références du devis sont référencées dans la table : un devis automatique est généré immédiatement vers le client et un message sur le site l?en informe. - Une référence ou plus n?est pas disponible dans la table : un devis personnalisé sera adres- sé au client sous 48H et un message sur le site l?en informe. Nous concevrons 2 pages devisauto et devisperso qui afficheront les 2 types de messages (et les informations complémentaires que nous souhaiterons ajouter pour le client sur la suite à donner au devis). Ces 2 pages seront sécurisées par l?insertion de la variable $securise = 1 sur les pages devi- sauto.php et devisperso.php ceci impose une identification préalable du client : il est orienté vers la page d?identification s?il n?est pas connecté à son compte. Il doit créer un compte le cas échéant. 2°) La validation Chaque référence stockée dans la variable de session virtueldevis va être comparée avec les références stockées dans la table "devis" : - si toutes les références du devis sont représentées dans la table on appelle l?url devisauto. php. - sinon on appelle devisperso.php 91 <?php function charger_ref($ref){ return $this->getVars("select * from $this->table where ref=\"$ref\""); } function validerdevis(){ for ($i=0; $i<$_SESSION[?virtueldevis?]->nbart; $i++){ $ref = $_SESSION[?virtueldevis?]->tabarticle[$i]->produit->ref; $devis2 = new Devis(); $devis2->charger_ref($ref); if(!isset($devis2->ref)){ header("location: devisperso.php"); exit; } } header("location: devisauto.php"); exit; } 3°) Générer le devis Nous allons finaliser la validation de notre devis de cette manière : Le devis PDF, qu?il soit automatique ou personnalisé, sera généré par la fonction validerde- vis. Il sera stocké dans un dossier spécifique. -- Sur la page devisauto.php un lien permet au client d?accéder au PDF pour l?enregistrer, l?imprimer.... -- Sur la page devisperso.php un message informe le client qu?il recevra un devis sous 48H. Le marchand dispose d?une vue dans le BO sur le dossier stockant les devis générés : il peut les consulter ou les supprimer. Pour chaque devis la date et le type sont affichés. 92 93 94 Livre 2 Le Back Office Comprendre Agir Modifier Enrichir Adapter Voyage au centre d?une boucle 4. Appréhender Thélia par la théorie : L?administration (Back Office) Nous avons compris comment fonctionne un site "propulsé par Thélia" : l?organisation du moteur, son fonctionnement dans le traitement des boucles et des interactions du client sur le Front Office, l?inclusion des méthodes et des boucles des plugins. Avant de finaliser notre fonctionnalité Devis, nous devons maintenant aborder l?autre face de Thélia : son Back Office. Nous savons que Thélia est fondé sur une base de données : nous avons vu comment, à partir du Front Office (le site Web) les données de cette base pouvaient-être manipulées : le client peut, dans une certaine mesure enregistrer (ex : action : creercompte), extraire (ex : traitement d?une boucle Thélia), mettre à jour, effacer des informations de la base de don- nées. Le back Office est l?autre interface Thélia prévue pour manipuler la base de données et elle dispose évidemment des moyens adéquats pour faire son travail. Comme le Front Office, il s?agit d?un ensemble d?instructions php ; ce langage de programmation est l?interface idéale entre un base Sql et un homme et de plus il est capable de générer du html à afficher sur le navigateur du e-marchand en fonction de ses interactions à travers les vues du BO : le BO est donc un site Web sécurisé (il faut un login et un mot de passe pour y accéder) en php : plus précisément il s?agit d?un sous-site de votre site Web : http://monsite.com/admin. Cette partie sera plus courte que les deux premières parce que la logique qui a conduit à son élaboration reste strictement la même : un script moteur en php, des actions, des pages à afficher, une base à manipuler... Nous n?y reviendrons pas mais chercherons à appliquer cette modélisation aux fichiers composant le BO. Chapitre 1 : présentation du BO Thélia et généralités Chapitre 2 : le schéma du BO Thélia Chapitre 4 : Les plugins dans le BO Thélia Chapitre 5 : Annexes pour aller plus loin avec Thélia Sec4:96 Présentation générale du BO 1. Thélia La première partie de cet ouvrage s?efforçait de vous présenter Thélia sous la forme d?un programme composé de 4 couches et quatre seulement. Je voulais montrer la logique de bout en bout qui conduit à la mise en place d?un site e-commerce complet et fonctionnel côté client. Pour autant nous savons que la pratique d?une activité commerciale ne se réduit pas à l?activité des clients, bien au contraire : l?activité préalable du marchand conditionne l?exis- tence même de ces clients. Thélia est une application complète et prévoit donc l?ensemble des éléments structurels pour que le marchand exerce son activité : le BO. Comme tout site Web, le BO de „ „ Thélia est accessible depuis une page index.php (à l?appel de l?url : www.monsite.com/admin : admin est la valeur par défaut : il est conseillé de Instruction Instruction Instruction Instruction Instruction Instruction Instruction Moteur Classe Classe Classe Classe Classe Classe Classe Base de données Table Table Table Table Table Table Table Site web Interaction Affichage Une interaction du visiteur sur le site Web... ... Aboutit à l?affichage d?une page Web Instruction Instruction Instruction Instruction Instruction Instruction Instruction Classe Classe Classe Classe Classe Classe Classe Back Office Interaction Affichage Une interaction du marchand sur le BO... ... Aboutit à l?affichage d?une page Web Moteur BO Marchand Client Sec4:97 modifier le nom du dossier admin pour freiner un temps soit peu les tentatives d?intrusions) : c?est un formulaire d?identification : si OK l?utilisateur est routé vers la page accueil.php. Le BO n?ayant pas vocation à être re-designé par un webmaster ou un graphiste, sa „ „ conception ne prévoit plus d?un côté des squelettes html et de l?autre un moteur php inclus sur chaque page : les 2 fonctionnalités (affichage et instructions) sont imbriquées concernant le BO : celui-ci possède donc une structure classique de site Web créé en php, où chaque page est un mélange de code php et de code html. Pour la même raison, il n?y a évidemment plus aucune notion de boucle Thélia dans l?univers BO et c?est une bonne chose : nous avons vu la charge demandée au serveur pour la traduction des boucles Thélia et il conve- nait de la limiter au maximum : vous n?aurez donc pas le loisir de designer aussi facilement votre BO que votre site Web (même s?il reste la possibilité de modifier les css aisément). Il en résulte une impression de complexité mais nous saurons démêler le méli-mélo „ „ d?instructions et reconstituer la structure logique du 2nd moteur php de Thélia : le BO Ce moteur est constitué des fichiers contenus dans le dossier "admin" de Thélia : „ „ -- Les 66 fichiers de ce dossier composent sa structure logique. -- Les classes dédiées à l?univers BO sont en plus stockées comme toutes leurs consoeurs dans le dossier homonyme. Les classes déjà rencontrées dans la partie Front Office pour- ront bien sûr être utiles et utilisées par le BO. -- Enfin un fichier du premier moteur est inclus dans les pages php du BO : divers.php : les fonctions génériques qu?il décrit sont ré-utilisées ; certaines sont même dédiées au BO (par exemple : admin_inclure()). -- Ainsi ce second moteur est au moins aussi complexe que le premier mais malheureuse- ment plus délicat à appréhender car ses limites sont moins précisément définies et organi- sées. Voici un aperçu des principaux concepts détaillés lors de la découverte du moteur principal et appliqués au moteur du BO : nous nous rendrons mieux compte à quel point, ainsi présen- tés, ces 2 univers sont comparables : Types : Données spécifiques au BO : Emplacement : Les Classes administrateur.class.php dossier classe contrib.class.php dossier classe racmodule.class.php dossier classe statut.class.php dossier classe statutdesc.class.php dossier classe venteproduit.class.php dossier classe venteadresse.class.php ventedeclidisp.class.php Sec4:98 Types : Données spécifiques au BO : Emplacement : Les Actions identifier() accueil.php modclassement() caracteristique.php supprimer() caracteristique.php modifier() client.php supprimer() client.php modclassement() contenuassoc.php ajouter() contenuassoc.php supprimer() contenuassoc.php ajouter() promo.php modifier() promo.php supprimer() promo.php ajouter() promo_modifier.php modifier() promo_modifier.php modifier() variable.php modifier() variable_modifier.php modclassement() parcourir.php modifier() gestadm.php modifier() gestadm_modifier.php modclassement() produit_modifier.php modifier() produit_modifier.php ajouter() produit_modifier.php acdec() produit_modifier.php desdec() produit_modifier.php supprimer() produit_modifier.php modclassement() caracteristique_modifier.php modifier() caracteristique_modifier.php maj() caracteristique_modifier.php ajouter() caracteristique_modifier.php ajcaracdisp() caracteristique_modifier.php majcaracdisp() caracteristique_modifier.php supprimer() caracteristique_modifier.php supprcaracdisp() caracteristique_modifier.php modclassement() rubrique_modifier.php modifier() rubrique_modifier.php ajouter() rubrique_modifier.php supprimer() rubrique_modifier.php supprimg() rubrique_modifier.php modclassement() déclinaisons_modifier.php modifier() déclinaisons_modifier.php ajouter() déclinaisons_modifier.php ajdeclidisp() déclinaisons_modifier.php majdeclidisp() déclinaisons_modifier.php supprimer() déclinaisons_modifier.php supprdeclidisp() déclinaisons_modifier.php modclassement() contenu_modifier.php Sec4:99 Types : Données spécifiques au BO : Emplacement : Les Actions modifier() contenu_modifier.php ajouter() contenu_modifier.php supprimer() contenu_modifier.php modifier() devise_modifier.php ajouter() devise_modifier.php supprimer() devise_modifier.php modclassement() dossier_modifier.php modifier() dossier_modifier.php ajouter() dossier_modifier.php supprimer() dossier_modifier.php supprimg() dossier_modifier.php modifier() gestadmin_modifier.php modifier() message_modifier.php modclassement() photo_produit.php modifier() photo_produit.php ajouter() photo_produit.php supprimer() photo_produit.php modclassement() photo_rubrique.php modifier() photo_rubrique.php ajouter() photo_rubrique.php supprimer() photo_rubrique.php modclassement() photo_dossier.php modifier() photo_dossier.php ajouter() photo_dossier.php supprimer() photo_dossier.php modclassement() accessoire.php ajouter() accessoire.php supprimer() accessoire.php Les points d?entrée des Plugins admin_inclure() -- ?pre? -- ?accueil? -- ?caracteristiquemodifier? -- ?déclinaisonsmodifier? -- ?rubriquemodifier? -- ?produitmodifier? -- ?contenumodifier? -- ?clientvisualiser? -- ?commandedetails? divers.php -- pre.php -- accueil.php -- caracteristique_modifier.php -- déclinaisons_modifier.php -- rubrique_modifier.php -- produit_modifier.php -- contenu_modifier.php -- client_visualiser.php -- commandedetails.php modules_fonction () -- ?modprod? -- ?statut? -- ?confirmation? divers.php -- produit_modifier.php -- commandedetails.php -- action.php include_once("../client/ plugins/$nom/$nom" . "_ad- min.php"); module.php Sec4:100 Sec4:101 Le schéma du BO de 2. Thélia Contrairement à la présentation du moteur du Front Office qui s?appliquait à montrer la logi- que de fonctionnement du moteur principal de Thélia, le schéma du Back Office ci-dessus détaille la structure du site BO : l?ensemble des 55 fichiers du dossier "admin" constituent la structure des différentes pages de votre interface d?administration : chaque page php du dossier "admin" organise les fonctionnalités moteur (en php) + les données relatives à l?af- fichage (en html) pour la page correspondante du site BO : le moteur et le site BO ne font donc qu?un. Sur le schéma apparaissent : -- Toutes les pages php du BO (en gras si des sous-pages existent) -- Les liens html, représentés en pointillés -- Les inclusions, représentées par des carrés colorés selon cette nomenclature : ________________________________________________________________________ entete.php Inclusion de entete.php ce fichier décrit l?en-tête mais aussi le menu principal. P Inclusion de pre.php : A Inclusion de auth.php : sécurise les pages du BO. Gère les droits dans le BO. D Inclusion de divers.php (dossier "fonctions" de Thélia) - - Inclusion d?une classe (dossier "classe" de Thélia) : toutes ne sont pas représentées IF Inclusion d?un fichier classe d?un plugin AI Inclusion d?un fichier de type xxx.admin.xxx.php par la fonction admin_inclure() MF Inclusion d?une méthode d?un plugin par la fonction modules_fonction() ________________________________________________________________________ Sec4:101 module_liste P A MO CB entete.php pied.php module entete.php pied.php P A IF accueil V A entete.php pied.php P AI D produit_modifier entete.php pied.php R IM DO RD C D AC ZO RC CD DD SK EX D P A AI MF Login Accueil Client Commande Catalogue Contenu Code promo Configuration Modules promo entete.php pied.php D P A R IM DO RD C D AC ZO RC CD DD ST EX AI MF Index entete.php pied.php P A CL CO client client_creer entete.php D P A R VP VA ST+ CO CL PA+ AI pied.php client_visualiser entete.php D P A R VP VA ST+ CO CL PA+ AI pied.php client_modifier entete.php D P A R VP VA ST+ CO CL PA+ AI pied.php promo_modifier entete.php pied.php D P A R IM DO RD C D AC ZO RC CD DD ST EX AI MF 1.4 Inside D A R parcourir entete.php P pied.php rubrique_modifier entete.php pied.php D P A AI MF caracteristique _modifier caracteristique entete.php entete.php pied.php pied.php D P A declinaison _modifier declinaison entete.php entete.php pied.php pied.php D P A C D P A AI CD C RC R D DD D RD R D P A D P A R R R+ RC C VA IM CO CA C+ C CL RD D D+ contenu_associe.php rubrique.php caracteristique.php P VA TZ R contenu_associe.php accessoires.php Accueil Client Commande Catalogue Contenu Code promo Configuration Modules contenu_mofifier entete.php pied.php D P A R IM DO RD C D AC ZO RC CD DD ST EX AI MF dossier_modifier entete.php pied.php D P A R IM DO RD C D AC ZO RC CD DD ST EX AI MF entete.php pied.php P A CL CO commande commande_creer entete.php D P A R VP VA ST+ CO CL PA+ AI pied.php commande_details entete.php P A R VP CO AI pied.php D VA ST+ CL PA+ entete.php pied.php P A CL CO livraison entete.php pied.php P A CL CO facture entete.php pied.php P A CL CO listedos configuration entete.php P A AI pied.php messages _modifier messages transport devises zone plugin variables admin entete.php entete.php entete.php entete.php entete.php entete.php entete.php entete.php pied.php pied.php pied.php pied.php pied.php pied.php D P A D P A D P A D P A D P A D P A D P A D P A Le fichier auth.php 1. Toutes les pages du BO sont sécurisées par l?inclusion du fichier auth.php. Ce fichier impose qu?une variable de session $_SESSION[?util?] soit initialisée et dotée d?une valeur pour l?attribut ID; dans le cas contraire le visiteur est routé sur la page d?identification : index.php avant tout traitement et affichage de la page appelée. $_session[?util?] est une variable initialisée à la page d?accueil si et seulement si le visiteur peut s?identifier préalablement sur la page index.php. Cette variable est une instanciation de la classe administrateur. Le fichier administrateur.class.php nous renseigne sur ses attributs : -- var $id; -- var $identifiant; -- var $motdepasse; -- var $prenom; -- var $nom; -- var $niveau; Etudions (pour le plaisir) comment la page accueil.php initialise cette variable de session et comment celle-ci sécurise l?ensemble du BO (lignes 31 à 51) : session_start(); if(!isset($action)) $action= ""; if(!isset($_SESSION["util"])) $_SESSION["util"]=new Administrateur(); if(isset($_POST[?identifiant?]) && isset($_POST[?motdepasse?])){ $utilisateur = str_replace(" " , "", $_POST[?identifiant?]); $motdepasse = str_replace(" ", "", $_POST[?motdepasse?]); } if($action == "identifier") { $admin = new Administrateur(); if(! $admin->charger($identifiant, $motdepasse)) {header("Location: index.php");exit;} else{ $_SESSION["util"] = new Administrateur(); $_SESSION["util"] = $admin; } } else if($_SESSION["util"]->id == "") {header("Location: index.php");exit;} -- Les paramètres identifiant, mot de passe et action="identifier" sont transmis par le formu- laire de la page index.php. Les variables correspondantes ($identifiant, $motdepasse, $ac- Sec4:104 tion) sont initialisées. -- une variable $admin, objet de la classe Administrateur est initialisée. -- Les variables $identifiant et $motdepasse sont passées à la méthode charger() de $admin : leurs valeurs sont comparées aux valeurs des champs identifiant et motdepasse de la table administrateur : la méthode charger() est décrite dans Administrateur.class.php (lignes 47 à 55) : function charger($identifiant, $motdepasse){ $query = sprintf("select * from $this->table where identifiant=?%s? and motdepasse=PASSWORD(?%s?)", mySql_real_escape_string($identifiant), mySql_real_escape_string($motdepasse)); return $this->getVars($query); } -- Traduction : si, avec les valeurs renseignées dans le formulaire, la requête sur la table ad- ministrateur remonte un résultat, la variable de session $_SESSION[?util?] est créée et prend le résultat de la requête : elle est donc dotée d?un ID. A noter que l?usage des fonctions sprintf() et mysql_real_escape_string() sont chargés de sécuriser cette requête pour éviter des injec- tions Sql. -- Comme toutes les autres pages (qui incluent auth.php), la page accueil.php n?est pas ac- cessible si le visiteur ne s?est pas identifié : car dans ce cas la variable de session ?util? n?est pas dotée d?ID : le visiteur subit donc une redirection vers index.php : c?est la signification de la seconde redirection imposée par la page accueil.php et le fichier auth.php : if($_SESSION["util"]->id == "") {header("Location: index.php");exit;} A noter : dans le fichier auth.php est prévu un point d?entrée pour plugin de ce type : „ „ admin_inclure(?pre?); Il est étonnant de noter qu?il existe comme nous allons le voir tout de suite un fichier du BO nommé pre.php : le point d?entrée pour les plugins pourrait donc nous faire croire qu?un fi- chier de type plugin_admin_pre.php permettrait l?inclusion de ce fichier dans le fichier pre. php : nous voyons qu?il n?en n?est rien : l?inclusion se situera au niveau du fichier auth.php. Ceci reste anecdotique dans la mesure où les deux fichier pre.php et auth.php sont des fi- chiers eux-même inclus au même endroit sur les principales pages du BO : tout au début de chaque page, avant tout codage des instructions et de l?affichage d?une page du BO. Sec4:105 Le fichier pre.php 2. L?inclusion du fichier pre.php permet l?initialisation des variables externes nécessaires pour alimenter les fonctions du BO ; Ces variables qui, rappelons-le, sont créées pour recevoir les valeurs des paramètres transmis par GET ou POST par le e-marchand, prennent classique- ment la forme : $variable = $_REQUEST[?variable?]. Le fichier pre.php inclut également le fichier divers.php, que nous avons croisé lors de la découverte du moteur du FO. A ce sujet on peut se demander pourquoi le fichier divers.php est une seconde fois directe- ment prévu sur différentes pages du BO de manière redondante donc : en réalité le fichier pre.php n?est pas réellement inclus 2 fois sur les pages du BO (ceci provoquerait une erreur fatale) parce que la fonction d?inclusion utilisée n?est pas exactement include() mais include_ once() : le serveur vérifie si le fichier appelé n?est pas déjà présent dans le script. Néanmoins cette fonction est gourmande en ressources et on se demande quel est l?objectif de ce dou- ble appel au fichier diver.phps dans de nombreuses pages du BO. <?php foreach ($_POST as $key => $value) $$key = $value; foreach ($_GET as $key => $value) $$key = $value; error_reporting(E_ALL ^ E_NOTICE); include_once(realpath(dirname(__FILE__)) . "/../fonctions/divers.php"); ?> Sec4:106 La page produit_modifier.php (BO 3. Thélia) Cette page est la fiche produit côté BO. Là encore nous allons nous intéresser aux fonction- nalités moteur présentes sur cette page et particulièrement aux fonctions ajouter() et modi- fier() qui y sont codées. Ce sont des "super" fonctions dans la mesure où elles exécutent une batterie de sous-fonc- tions toutes importantes. Sec4:107 Les plugins dans le BO 4. Thélia Vous avez à votre disposition plusieurs types de points d?entrée sur le BO pour inclure les fonctionnalités prévues par votre plugin. En voici une présentation rapide : L?inclusion du fichier Nomduplugin_admin.php 1. IF Nous avons déjà évoqué cette fonctionnalité et c?est d?ailleurs celle que nous mettrons en place pour la gestion dans le BO du plugin Devis : si vous prévoyez un fichier Nomduplu- gin_admin.php dans le dossier de votre plugin (où "Nomduplugin" est remplacé par... Le nom de votre plugin), vous avez accès à une page complète dans le BO pour son administration par le marchand. Exemple : Devis_admin.php Cette page est accessible depuis la page "modules d?administration" du BO. Elle est géné- rée automatiquement par Thélia dés que le fichier Nomduplugin_admin.php est présent et elle est du type : module.php?nom=nomduplugin. Elle est accessible depuis un lien généré lui aussi par Thélia et portant le nom de votre plugin sur la page "modules d?administration". Elle contient évidemment ce que vous définirez dans votre fichier Nomduplugin_admin.php : vous y prévoirez donc à la fois des instructions php et du code html. La fonction modules_fonction() 2. MF L?utilisation de Nomduplugin_admin.php n?influence pas les différentes fonctionnalités pré- sentes par défaut dans le BO. La fonction modules_fonction() que nous connaissons bien maintenant est mise à contri- bution dans le BO : sur deux pages, des modifications fonctionnelles sont possibles par l?inclusion de certaines méthodes décrites dans les Classes des plugins : nous rencontrons modules_fonction() sur les deux pages suivantes : Dans „ „ produit_modifier.php la fonction modules_fonction(?modprod?) vous permet de modifier le comportement de la fonction modifier() de la fiche produit (produit_modifier.php) en définissant une méthode modprod() dans votre classe. Dans „ „ commandedetails.php la fonction modules_fonction(?statut?) vous permet de mo- difier le comportement du changement de statut pour une commande (commandedetails. php) en définissant une méthode statut() dans votre classe. La fonction admin_inclure() 3. AI Ce troisième type de point d?entrée offre la possibilité de modifier les principales pages du BO en y incluant du code (des instructions php et de l?affichage html). Sec4:108 La fonction admin_inclure est décrite dans le fichier divers.php (lignes 681 à 689), voyons comment elle propose aux concepteurs de plugins de modifier l?interface des principales pages du BO : <?php function admin_inclure($type){ $modules = new Modules(); $query = "select * from $modules->table where actif=?1? order by classement"; $resul = mySql_query($query, $modules->link); while($row = mySql_fetch_object($resul)) if(file_exists("../client/plugins/" .$row->nom . "/" . $row->nom. "_ admin_$type.php")) include_once("../client/plugins/" .$row->nom . "/" . $row->nom. "_ admin_$type.php"); } ?> Nous savons désormais lire ce genre de structures : A chaque inclusion de la fonction admin_inclure(XXX) sur une page du BO, le serveur inclut le fichier nommé Nomduplugin_admin_XXX.php s?il existe. Nous avons donc la possibilité de prévoir plusieurs fichiers pour l?ajout de fonctionnalités et de vues sur les principales pages du BO. Ces fichiers doivent porter un nom précis, déterminé par les arguments de la fonction admin_inclure() et déterminant à quel endroit du BO vous pouvez intervenir. En voici la liste : XXX_admin_pre.php „ „ XXX_admin_accueil.php „ „ XXX_admin_caracteristiquemodifier.php „ „ XXX_admin_déclinaisonsmodifier.php „ „ XXX_admin_rubriquemodifier.php „ „ XXX_admin_produitmodifier.php „ „ XXX_admin_contenumodifier.php „ „ XXX_admin_clientvisualiser.php „ „ XXX_admin_commandedetails.php „ „ Où XXX est le nom de votre plugin. Nous pouvons donc, par la méthode des plugins ajouter du code en plus (ils sera situé en bas des pages, à la suite du code natif) mais nous ne pourrons pas modifier les fonctionnalités présentes et leur présentation avec les plugins. Il faudra modifier directement l?application. En guise de conclusion concernant la présentation théorique des plugin dans Thélia, rappe- lez-vous ceci : vous pouvez prévoir tous les fichiers que vous voulez pour créer un plugin mais tous ceux qui ne seront pas nommés en respectant les différentes règles de syntaxe Sec4:109 que nous avons découvert depuis le début, devront être inclus dans ces derniers sinon ils ne pourraient pas être lus par le serveur. Mais il peut être intéressant de prévoir de tels fichiers annexes, notamment pour mutualiser l?écriture du code entre la classe du plugin et les fi- chiers d?inclusion dans le BO ou tout autre action de simplification ou de lisibilité.... Sec4:110 Annexes pour aller plus loin avec 5. Thélia Annexe 1 : Synthèse des points d?entrée pour les plugins 1. Ce tableau récapitule l?ensemble des points d?entrée pour modifier ou améliorer le moteur avec la méthode des plugins. Méthodes du fichier Nomduplugin.class.php Moteur FO / Moteur BO Emplacement de la fonction modules_fonction() appelant la méthode init FO + BO Cette méthode est exécutée à l?activation du plu- gin dans le BO () demarrage FO Cette méthode est exécutée avant toute exécu- tion d?une instruction native du moteur (moteur.php -> ligne 202) destroy FO + BO Cette méthode est exécutée lors de la désactiva- tion du plugin dans le BO pre FO Cette méthode est exécutée après l?exécution des actions natives du moteur et avant le chargement du squelette (moteur.php -> ligne 236) inclusion FO Méthode exécutée entre le chargement du sque- lette et le traitement des inclusions codées dans ce squelette (moteur.php->ligne 245) action FO Méthode exécutée après les inclusions et avant la fonction analyse() (moteur.php->ligne 252) post FO Méthode exécutée après la fonction analyse() (moteur.php->ligne 261) apres FO Méthode exécutée après l?affichage de la page (moteur.php->ligne 272) statut BO Cette méthode s?exécute lors d?un changement de statut de la commande. (commande_details.php->ligne 75) modprod BO Cette méthode s?exécute lors de la validation d?une modification d?une fiche produit dans le BO (commande_details.php->ligne 75) avantcommande FO Cette méthode s?exécute lors de la validation d?une commande sur le front office, avant l?exécu- tion des instructions natives de la fonction paie- ment() (action.php->ligne 139) aprescommande FO Cette méthode s?exécute lors de la validation d?une commande sur le front office, après l?exécu- tion des instructions natives de la fonction paie- ment() (action.php->ligne 332) mail FO Cette méthode s?exécute lors de la validation d?une commande sur le front office, après l?exécu- tion des instructions natives de la fonction paie- ment() (action.php->ligne 339) Sec4:111 Méthodes du fichier Nomduplugin.class.php Moteur FO / Moteur BO Emplacement de la fonction modules_fonction() appelant la méthode avantclient FO Cette méthode s?exécute lors de la création d?un compte client sur le front office, avant l?exécution des instructions natives de la fonction creercomp- te() (action.php->ligne 388) apresclient FO Cette méthode s?exécute lors de la création d?un compte client sur le front office, après l?exécution des instructions natives de la fonction creercomp- te() (action.php->ligne 398) confirmation FO Méthode exécutée lors de l?exécution de la fonc- tion paiement() du plugin chèque ou du plugin virement, avant la redirection vers la page paie- ment correspondante (virement.class.php- >ligne 42 et cheque.class.php->ligne 43) Fichiers supplémentaires Moteur FO / Moteur BO Emplacement de l?inclusion du fichier inclure_session.php FO Ce fichier sera inclus avant le démarrage de la session (moteur.php->ligne 42) Nomduplugin_admin.php BO Ce fichier génère une page complète accessible depuis le menu modules d?administration dans le BO (moteur.php->ligne 42) Fichiers supplémentaires in- clus par la fonction admin_in- clure() Moteur FO / Moteur BO Emplacement de la fonction admin_inclure() Nomduplugin_admin_pre.php BO Ce fichier sera inclus sur toutes les pages sécuri- sées par auth.php. (auth.php->ligne 34) Nomduplugin_admin_accueil. php BO Ce fichier sera inclus sur la page accueil.php, après les fonctions natives de cette page. (accueil.php->ligne 176) Nomduplugin_admin_rubri- quemodifier.php BO Ce fichier sera inclus sur la page rubrique_modi- fier.php, après les fonctions natives de cette page. (rubrique_modifier.php->ligne 447) Nomduplugin_admin_produit- modifier.php BO Ce fichier sera inclus sur la page produit_modifier. php, après les fonctions natives de cette page. (produit_modifier.php->ligne 843) Nomduplugin_admin_decli- naisonmodifier.php BO Ce fichier sera inclus sur la page declinaison_mo- difier.php, après les fonctions natives de cette page. (declinaison_modifier.php->ligne 473) Nomduplugin_admin_caracte- ristiquemodifier.php BO Ce fichier sera inclus sur la page caracteristi- que_modifier.php, après les fonctions natives de cette page. (caracteristique_modifier.php- >ligne 460) Sec4:112 Méthodes du fichier Nomduplugin.class.php Moteur FO / Moteur BO Emplacement de la fonction modules_fonction() appelant la méthode Nomduplugin_admin_conte- numodifier.php BO Ce fichier sera inclus sur la page contenu_modi- fier.php, après les fonctions natives de cette page. (contenu_modifier.php->ligne 352) Nomduplugin_admin_com- mandedetails.php BO Ce fichier sera inclus sur la page accueil.php, après les fonctions natives de cette page. (commande_details.php->ligne 408) Nomduplugin_admin_promo- modifier.php BO Ce fichier sera inclus sur la page promo_modifier. php, après les fonctions natives de cette page. (promo_modifier.php->ligne 146) Nomduplugin_admin_clientvi- sualiser.php BO Ce fichier sera inclus sur la page clientvisualiser. php, entre l?affichage de ses coordonnées et l?affichage de ses commandes. (client_visuali- ser_modifier.php->ligne 241) Sec4:113 Livre 3 Le client dans Thélia Comprendre Agir Modifier Enrichir Adapter Voyage au centre d?une boucle 5. Appréhender Thélia par la théorie : La gestion du client Dans le cadre de cette courte partie nous allons étudier quelques éléments de Thélia liés au client et à son compte. Nous passerons en revue les éléments du moteur dédiés à la création et à la gestion d'un compte client : les éléments utilisés et les éléments implémentés mais non exploités par défaut. Comme tous les composants de votre activité que prévoit Thélia et que nous avons commencé et continuerons de passer en revue, la gestion du client bénéficie dans le pro- gramme d'une socle de fonctionnalités primaires et indispensables sur lequel il est possible d'envisager des personnalisations et d'y greffer des modules spécifiques pour répondre à des besoins spécifiques. Nous allons être amenés à décrire les instructions du programme, relatives à la constitution d'un compte client. Puis nous envisagerons le mécanisme de sécurisation des pages d'un site Thélia. Enfin nous rassemblerons dans un 3ième chapitre ce qu'il faut savoir d'un client dans le par- cours d'une commande. La gestion d'un compte client dans 1. Thélia Sec4:118 Livre 3 Le catalogue Thélia Comprendre Agir Modifier Enrichir Adapter Voyage au centre d?une boucle 5. Appréhender Thélia par la théorie : L?organisation du catalogue 120 121 L?arborescence des rubriques dans 1. Thélia Dans Thélia vous pouvez organiser vos produits et votre contenu dans une structure illimitée en terme de catégories et de niveaux hiérarchiques. Nous allons étudier le mécanisme d?ar- borescence pour les produits (les rubriques) mais le système est similaire pour le contenu (les dossiers). Les rubriques dans le BO : les pages parcourir.php et rubri- 1. que_modifier.php Les pages parcourir.php du BO affichent les rubriques d?un même niveau hiérarchique, „ „ à la racine, le niveau zéro, s?affichent donc les rubriques principales que vous allez créer. Si une rubrique ne contient pas de sous-rubriques, la page parcourir.php affiche un tableau de synthèse des produits qu?elle contient. A noter : depuis la version 1.4 de Thélia chaque niveau ne peut plus contenir qu?un seul type d?objet : soit une ou plusieurs sous-rubrique(s) soit des produits. Les pages rubrique_modifier.php permettent d?éditer chaque rubrique. „ „ Toutes les rubriques sont stockées dans la même table ?rubrique? de la base de données, quel que soit leur niveau dans la hiérarchie de l?arborescence. Le champ ?parent? permet de préciser le niveau hiérarchique : les rubriques principales contenant toutes les autres ont une valeur parent =0 En tant qu?utilisateur, le système d?arborescence peut paraître déroutant à première vue par- ce que les ID des rubriques ne sont pas sollicités pour organiser les niveaux hiérarchiques : ils sont générés par le programme par auto-incrémentation : l?ID d?une rubrique dépend donc seulement du moment où vous la créez, et non de son niveau dans la hiérarchie. De plus le niveau hiérarchique ne suffit pas pour une rubrique : il convient aussi de savoir à quelle rubrique "mère" elle est rattachée : Thélia organise cette double information de la façon suivante : la valeur du champ ?niveau? pour une rubrique donnée est = à l?ID de sa ru- brique mère. Ceci explique pourquoi la notion de ?parent? peut paraître obscure : la valeur du champ ?niveau? n?est pas une valeur hiérarchique comme on pourrait s?y attendre (exemple : sous-rubrique de niveau 1 : valeur = 1) mais une valeur d?ID. Lorsque vous avez compris cette nuance, la hiérarchisation des rubriques dans Thélia de- vient plus évidente. Voyons par un schéma comment pourraient être organisées les rubriques et sous-rubriques d?un catalogue dans Thélia : remarquez comment les valeurs du paramètre "parent" ne sui- vent pas la logique arithmétique attendue à priori : la profondeur de l?arborescence est dé- duite de la filiation d?une rubrique fille à une rubrique mère et non d?une valeur numérique. Lorsque vous cliquez sur un lien "parcourir" sur parcourir.php, la nouvelle page parcourir .php affichera les rubriques dont la valeur de leur champ ?parent? est = à l?ID de la rubrique que vous souhaitez parcourir, ce qui correspond bien aux sous-rubriques dans le système de Thélia ; en effet sur la parcourir.php on trouve aux lignes 229 à 248: $rubrique = new Rubrique(); $rubriquedesc = new Rubriquedesc(); $query = "select * from $rubrique->table where parent=\"$parent\" order by classement"; $resul = mysql_query($query, $rubrique->link); $i=0; while($row = mysql_fetch_object($resul)){ $rubriquedesc->charger($row->id); ... <li style="width:54px;"><a href="parcourir.php?parent=<?php echo($rubriquedesc- >rubrique); ?>" >parcourir</a></li> La filiation au même niveau hiérarchique est codé dans l?url ?ajouter une sous-rubrique? de chaque page parcourir.php : <a href="rubrique_modifier.php?parent=<?php echo($parent); ?>"><?php if($parent == "") { ?>AJOUTER UNE RUBRIQUE<?php } else {?>AJOUTER UNE SOUS-RUBRIQUE<?php } ?></a> La gestion de l?arborescence des rubriques sur le FO : le 2. paramètre ?parent? 122 Rubrique ID = 1 Parent = 0 Rubrique ID = 4 Parent = 0 Rubrique ID = 6 Parent = 0 Rubrique ID = 2 Parent = 1 Rubrique ID = 3 Parent = 1 Rubrique ID = 7 Parent = 4 Rubrique ID = 9 Parent = 4 Rubrique ID = 8 Parent = 6 Rubrique ID = 5 Parent = 2 Rubrique ID = 10 Parent = 2 Rubrique ID = 11 Parent = 9 Rubrique ID = 12 Parent = 9 Niveau = 0 Niveau = 1 Niveau = 2 Nous connaissons bien la boucle "rubrique" maintenant. Nous allons nous arrêter sur la res- titution des éléments de l?arborescence en fonction de leur niveau hiérarchique : Avec le système mis en place par Thélia pour créer et maintenir une arborescence, nous comprenons mieux comment coder les boucles "rubrique" pour obtenir des menus répon- dant à nos besoins personnels : le paramètre ?parent? en tant que paramètre d?entrée, de sortie ou de substitution vous permet de réaliser des menus poussés et personnalisés sans avoir recours à des scripts php ou javascript. Prenons un exemple avec les deux codes ci-dessous : 1°) Code du menu principal horizontal <THELIA_MENU_RUBRIQUES type="RUBRIQUE" parent="0"> </THELIA_MENU_RUBRIQUES> 2°) Code du menu contextuel vertical gauche <T_RUBRIQUE> <ul> <THELIA_RUBRIQUE_1 type="RUBRIQUE" parent= "#RUBRIQUE_ID"> <li><a href="#URL"> #TITRE</a></li> </THELIA_RUBRIQUE_1> </ul> </T_RUBRIQUE> <ul> <THELIA_RUBRIQUE_3 type="RUBRIQUE" parent= "#RUBRIQUE_PARENT"> <li><a href="#URL"> #TITRE</a></li> </THELIA_RUBRIQUE_3> </ul> <//T_RUBRIQUE> Ces quelques lignes permettent d?envisager une navigation très répandue actuellement, consistant à désolidariser le menu de navigation : un menu horizontal présente les rubriques principales, la navigation à l?intérieur de chacune d?elles étant organisée par un menu vertical gauche : Un clic sur une rubrique principale produira : „ „ 1°) Rubriques principales (parent=0) 2°) sous-rubriques affiliées à la rubrique principale "femme" Accueil Rubrique homme Rubrique femme Rubrique Enfant Robes Jupes Sacs à main 123 123 Un clic sur la rubrique jupe produira : „ „ 1°) Rubriques principales (parent=0) 2°) sous-rubriques affiliées à la rubrique "jupe" Accueil Rubrique homme Rubrique femme Rubrique Enfant Mini jupes Jupes courtes Jupes longues Jupes Un clic sur jupe courte produira : „ „ 1°) Rubriques principales (parent=0) 2°) affichage des sous- rubriques de même ni- veau que "jupe courte" Accueil Rubrique homme Rubrique femme Rubrique Enfant Mini jupes Jupes courtes Jupes longues Avec notre code, le menu de gauche se comporte dynamiquement : si la rubrique en cours contient des sous-rubriques le menu de gauche affiche les sous-rubriques disponibles. Si la rubrique en cours ne contient pas de sous-rubrique, le menu de gauche affiche la liste des rubriques de même niveau et de même rubrique mère. D?autres paramètres de la boucle "rubrique" permettent d?envisager des menus plus person- nalisés liés à la gestion de l?arborescence des rubriques. Le paramètre ?pasvide? et la fonction arbreBoucle() 3. Nous pouvons avec une boucle Thélia empêcher l?affichage des rubriques vides (sans pro- duit) par son paramètre d?entrée ?pasvide?. Cette fonctionnalité est rendue possible grâce à la fonction arbreBoucle() à laquelle boucleRubrique() fait appel à la ligne 128 du fichier fonctions/ boucles.php : 124 if($pasvide != ""){ $rec = arbreBoucle($rubrique->id); if(substr($rec, strlen($rec)-1) == ",") $rec = substr($rec, 0, strlen($rec)-1); if($rec) $virg=","; else $virg=""; $tmprod = new Produit(); $query4 = "select count(*) as nbres from $tmprod->table where rubrique in(?" . $rubrique- >id . "?$virg$rec) and ligne=?1?"; $resul4 = mysql_query($query4, $tmprod->link); if(!mysql_result($resul4, 0, "nbres")) continue; } Voici le code de la fonction arbreBoucle() prévu aux lignes 299 à 315 du fichier divers.php : function arbreBoucle($depart, $profondeur=0, $i=0){ $rec=""; $i++; if($i == $profondeur && $profondeur != 0) return; $trubrique = new Rubrique(); $query = "select * from $trubrique->table where parent=\"$depart\""; $resul = mysql_query($query, $trubrique->link); while($row=mysql_fetch_object($resul)){ $rec .= $row->id . ","; $rec .= arbreBoucle($row->id, $profondeur,$i); } return $rec; } La fonction „ „ arbreBoucle() se comporte de cette manière : -- 1°) Elle retourne un résultat nul si un paramètre $profondeur=1 lui est attribué. Dans le ca- dre de la boucle "rubrique" cette instruction n?a pas d?importance car nous ne lui fournissons qu?un paramètre d?ID de rubrique (la variable $depart dans la fonction). Mais arbreBoucle() est également sollicitée par la boucle "produit" et à cette occasion cette instruction joue un rôle que nous verrons plus bas. --2°) Elle exécute une requête sql sur la table ?rubrique? : elle en extrait les rubriques dont la valeur de ?parent? = $depart soit l?ID de la rubrique dans le cadre de la boucle "rubrique" : elle identifie donc les sous-rubriques affiliées à la rubrique qui porte l?ID. -- 3°) Elle créée une variable $rec qu?elle va alimenter d?une chaîne numérique selon la mé- thodologie suivante : -- Pour chaque sous-rubrique identifiée (boucle "while") -- Elle récupère chaque ID de sous-rubrique identifiée -- suivi d?une virgule -- suivi des ID des sous-sous-rubriques pour chacune d?elles. 125 Reprenons mon arborescence de la page 88 et imaginons que vous souhaitiez afficher dans un menu toutes les rubriques principales non vides vous coderiez ceci : <THELIA_RUBRIQUE type="RUBRIQUE" parent="0" pasvide="1" > <li> #TITRE</li> </THELIA_RUBRIQUE> Chaque rubrique avec parent=0 (rubriques 1, 4 et 6) va être traitée par la fonction arbreBou- cle() lors du traitement de la boucle par la fonction boucleRubrique() puisque vous attribuez une valeur à ?pasvide? cela produira le résultat suivant : Rubrique 1 : $rec = 2,5,10,3 Rubrique 4 : $rec = 7,11,12,9 Rubrique 6 : $rec= 8 La suite se situe dans la fonction „ „ boucleRubrique() : Pour chaque valeur remontée par $rec une requête est ensuite exécutée pour calculer le nombre de produits contenus dans la rubrique correspondant à la passe en cours et ses sous-rubriques et sous-sous-rubriques... associées. Dans mon exemple, pour la rubrique 1 : calcul le nombre de produits contenus dans les rubriques 1, 2, 5, 3, et 10. Si aucun produit n?est contenu dans ces rubriques, la rubrique 1 ne s?affichera pas. Etc. L?usage d?arbreBoucle() dans boucleProduit() La fonction arbreBoucle() est aussi présente dans le traitement des boucles "produit" à la ligne 767 du fichier fonctions/boucles/php : if($rubrique!= ""){ $srub = ""; $tabrub = explode(",", $rubrique); for($compt = 0; $compt<count($tabrub); $compt++){ $rec = arbreBoucle($tabrub[$compt], $profondeur); if(substr($rec, strlen($rec)-1) == ",") $rec = substr($rec, 0, strlen($rec)-1); if($rec) $virg=","; $srub .= $tabrub[$compt] . $virg . $rec . $virg; } if(substr($srub, strlen($srub)-1) == ",") $srub = substr($srub, 0, strlen($srub)-1); $search .= " and rubrique in($srub)"; } "Rubrique" est un paramètre d?entrée de la boucle "Produit" : lorsqu?il est renseigné (et nous voyons ici qu?il peut être multiple en appliquant à ce paramètre la nomenclature suivante : rubrique="1, 2, 5"), les instructions ci-dessus se chargent d?identifier les produits contenus dans ces rubriques. Un second paramètre d?entrée, le paramètre "profondeur" est également sollicité dans ce passage : si il est renseigné il détermine le degré de profondeur à prendre en compte dans l?arborescence pour identifier les produits appelés par la boucle. Si $profondeur ="" ou =0, seuls les produits contenus dans les rubriques visées par le para- mètre "rubrique" seront identifiés. Si $profondeur=1 : les sous-rubriques sont prises en comp- te. (Notons que contrairement au paramètre "parent", la valeur du paramètre $profondeur est donc une valeur hiérarchique (1=sous-rubrique de niveau 1). 126 Le mécanisme de navigation à travers le ca- 2. talogue Thélia Votre site Web propose donc une navigation à travers votre catalogue, organisé en rubriques (pour les produits) et en dossiers (pour le contenu). Dans ce chapitre nous allons aborder le mécanisme de navigation mis en place pour se rendre d?une page à l?autre et savoir instan- tanément où l?on se situe dans l?arborescence (par rapport à la page d?accueil : index.php) Nous aurons donc l?occasion d?étudier deux boucles prévues pour améliorer l?expérience de votre visiteur sur votre site : la boucle page et la boucle chemin. Structure du site et URL 1. Voici pour commencer une modélisation-type d?un site Thélia avec le template de base : Index.php panier.php adresse.php commande.php virement.php cheque.php produit.php produit.php produit.php rubrique.php Srubrique.php Srubrique.php Srubrique.php dossier.php Sdossier.php Sdossier.php Sdossier.php contenu.php connexion.php merci.php regret.php contact.php moncompte. php comptemodi- fier.php commande_vi- sualiser.php commmande_ details.php creercompte. php Livraison_ adresse.php livraison_modi- fier.php recherche.php 01 02 03 04 05 06 07 08 contenu.php contenu.php mdpoublie.php Nous voyons que la structure d?un site Thélia est constitué de pages php appelant des „ „ squelettes html portant des éléments graphiques et des boucles Thélia. L?ensemble de ces pages est relié par un jeu d?adresses url que nous allons détailler maintenant. Observons le modèle ci-dessus. Chaque ligne en pointillé correspond à une adresse „ „ url. Certaines seront codifiées par vous en fonction de l?architecture que vous mettrez en place pour votre site. D?autres sont prédéfinies dans l?application. 127 Nous allons lister les url dont vous pourriez avoir besoin pour votre boutique. Les lignes co- lorées correspondent à des url prédéfinies par Thélia. Voici les principales, leur apparition dans le template de base et une courte description : N° URL squelette Commentaire #VARIABLE(urlsite) index.html (chemin) Accès à la page d?accueil, urlsite est définie dans le BO (page variable.php) et stockée dans la table ?variable?. Elle est disponible partout même hors d?une bou- cle Elle décrit un chemin absolu et donc doit être adaptée selon que votre site est local (localhost/ thelia) ou distant (http://www.votresite.com) index.php toutes les pages (chemin) la façon la plus simple d?appeler la page d?ac- cueil #URLSOMMAIRE X IDEM 01 rubrique.php?id_ rubrique=1 index_page.html rubrique.html Dans un menu dynamique, cette url sera adap- tée à la rubrique sélectionnée grâce à une bou- cle rubrique : rubrique.php?rubrique_id=#ID produit. php?ref=xxx&id_ rubrique=xxx X Accès à la fiche du produit dont ref=xxx 01 02 #URL index_page.html rubrique.html produit.html Accès à une fiche produit ou une rubrique Paramètre de sortie de la boucle produit et de la boucle rubrique. Correspond à l?url : produit.php?ref=xxx&id_rubrique=xxx (bou- cle produit) ou rubrique.php?id_rubrique=xxx ( boucle rubri- que) 03 #PANIER produit.html Mise au panier d?un produit. Correspond à l?url : panier.php?action=ajouter?ref=XXX Paramètre de sortie de la boucle produit. Elle n?est pas fonctionnelle si le produit possède une ou plusieurs déclinaisons #PANIERAPPEND X Mise au panier d'un produit avec incrémenta- tion de la quantité si la REF est déjà présente dans le panier (+1 par défaut). Correspond à l'url panier.php?action=ajouter&ref=XXX&appen d=1 04 adresse.php panier.html Accès à la page adresse.php : correspond à l?éta- pe de validation du panier. Route vers la page adresse.php, pour initialiser la commande. #PLUSURL X Ajout d?une quantité (+1) pour un produit dans le panier. Correspond à l?url : panier.php?action =modifier?article=XXX?quantite=quantite+1 Paramètre de sortie de la boucle panier. 128 N° URL squelette Commentaire #MOINSURL X Diminution d?une quantité (-1) pour un produit dans le panier. Correspond à l?url : panier.php?action=modifier?article=XXX?qua ntite=quantite -1 Paramètre de sortie de la boucle panier. #SUPPRURL panier.html Suppression d?un produit du panier. Correspond à l?url : panier.php?action=supprimer?ref=XXX Paramètre de sortie de la boucle panier. compte_modifier. php adresse.html Accès à la page de modification de l?adresse de facturation (compte_modifier.php) livraison_adresse. php Accès à la modification d?une adresse de livrai- son livraison_modifier. php adresse.html Accès à la page de création d?une adresse de li- vraison adresse.php?action= modadresse &adresse=xxx adresse.html Sélection d?une adresse de livraison différente de l?adresse de livraison par défaut. 05 #URLCMD adresse.html Accès à la page commande.php lors d?une com- mande (valider moyen de paiement et promo). Correspond à l?url : commande.php&action=transport&id=xxx Paramètre de sortie de la boucle transport. Exécute la fonction transport() Transmet le moyen de transport choisi à la varia- ble de session commande.php? action=codepromo &code=XXX commande.html Validation du code promo. Exécute l?action co- depromo() 06 #URLPAYER commande.html Validation de la commande (enregistrement en base) et accès à la page correspondant au moyen de paiement sélectionné. Correspond à l?url : commande.php&action=paiement&type_ paiement=XXX Exécute la fonction paiement() 07 connexion.php toutes les pages sécurisées Auto-routage par Thélia (header (location: connexion.php) vers connexion.php si demande d?accès à une page sécurisée (parcours com- mande ou compte client) pour un client non identifié #URLSUIV Tous Route vers la page enregistrée dans la variable de session $_SESSION['navig']->urlprec (voir infra) 129 N° URL squelette Commentaire #URLPREC Tous Route vers la page enregistrée dans la variable de session $_SESSION['navig']->urlprec (voir infra) #URLRECHERCHE menu.html Les redirections temporaires 2. Normalement lorsque un client appelle une URL celle-ci ouvre la page attendue. Pourtant vous savez qu?en certaines occasions la page cible ne sera pas immédiatement affichée mais une page intermédiaire sera intercalée entre la page actuelle et la page appelée. Cette redirection imposé est typiquement mis en place lorsque le client appelle une page sécurisée alors qu?il n?est pas identifié : la page connexion est présentée parce que le moteur prévoit à la ligne 227 l?instruction générale suivante : if($securise && ! $_SESSION["navig"]->connecte) { header("Location: connexion.php"); exit; } Si le client s?authentifie correctement ou créé un compte, il accède alors à la page appelée initialement. Cette fonctionnalité est présente à plusieurs reprises sur le site (par exemple lors d?une com- mande si vous avez besoin de modifier vos coordonnées, vous êtes dirigés sur la page de mise à jour de votre compte. Lorsque vous validez les modifications, vous êtes à nouveau dirigés sur la commande en cours...) Comment Thélia retient-il la page cible (la commande) pour y revenir après l?authentification du client sur connexion.php ou suite à la validation de ses coordonnées sur compte_modifier. php ? -- Sur les fichiers .php du template de base nous disposons ou non d?une variable $pageret=0 ou $pageret=1 -- La variable de session $navig dispose pareillement de trois attributs spécifiques à la ges- tion de la navigation. : - $urlprec - $urlpageret - $page -- Enfin, dans le moteur, nous trouvons des instructions pour manipuler ces variables (lignes 189 à 196) : 130 if(isset($_SERVER[?HTTP_REFERER?])) $_SESSION["navig"]->urlprec = $_SERVER[?HTTP_ REFERER?]; if($_SERVER[?QUERY_STRING?]) $qpt="?"; else $qpt=""; if($pageret && isset($_SERVER[?HTTP_REFERER?])) $_SESSION["navig"]->urlpageret = $_ SERVER[?PHP_SELF?] . $qpt . $_SERVER[?QUERY_STRING?]; else if($_SESSION["navig"]->urlpageret=="") $_SESSION["navig"]->urlpageret = "index. php"; Lorsque le client appelle une page, le serveur lit la page .php correspondante. Il peut alors croiser 2 cas de figures : La page contient „ „ $pageret=0 (si la page ne contient pas cette variable, le moteur l?ini- tialisera avec une valeur =0) La page contient „ „ $pageret=1 Thélia fait appel aux variables de serveur $_SERVER pour orienter le visiteur lors de sa na- vigation. $_SERVER[?HTTP_REFERER?] : L?adresse de la page (si elle existe) qui a conduit le „ „ client à la page courante. $_SERVER[?QUERY_STRING?] : La chaîne de requête, si elle existe, qui est utilisée „ „ pour accéder à la page. $_SERVER[?PHP_SELF?] : Le nom du fichier en cours d?exécution, par rapport à la „ „ racine Web. Cas pratique : 1°) un visiteur accède à votre page d?accueil Les attributs de $navig sont renseignés de la manière suivante : $urlprec „ „ ="" $urlpageret „ „ = "index.php" $page „ „ = "0" 2°) il clique sur la rubrique 1 : $urlprec „ „ ="http://www.monsite/index.php" $urlpageret „ „ = "monsite/rubrique.php?id_rubrique=1" $page „ „ = "1" Conclusion : $urlprec „ „ de $navig retient la page à partir de laquelle une url a été appelée. $urlpageret „ „ de $navig retient la page appelée si, sur cette page est présente la varia- ble $pageret=1 (sinon : index.php) Dés lors, codée dans toutes les actions nécessitant une redirection temporaire, lorsqu?un client valide ce type d?action, nous trouvons la ligne suivante : 131 redirige($_SESSION[?navig?]->urlpageret); Dans Thélia, c?est le cas pour les actions suivantes (fichier fonctions/action.php) - Connexion() --> ligne 106 - Modifiercompte() --> ligne 469 - Creercompte() --> ligne 485 - Creerlivraison() --> ligne 524 Notons que la fonction livraison_modifier() n?est pas concernée par cette instruction : à la va- lidation elle redirige systématiquement le client vers adresse.php, la page qui gère l?adresse de livraison lors de la prise de commande. Ceci donne une cinématique peu pertinente si le client, hors de tout process de commande, accède à son compte et modifie une adresse de livraison déjà enregistrée : à la validation il accède donc à la page adresse.php qui est clairement liée au parcours d?une commande : les différentes étapes passées, en cours et suivantes d?une commande sont affichées, ce qui ne correspond pas à la situation du client (il n?est pourtant pas entrain de passer une commande). La pagination et la boucle page 3. 1°) Considérations générales sur la gestion de l?affichage des produits et des contenus Plusieurs variables entrent en jeu dans le moteur pour la gestion de l?affichage d?un ensemble de produits ou de contenus en plus de la boucle "page" à proprement parler. Des variables internes, des paramètres de boucles, des attributs de $navig. Nous allons faire le tour des rôles de chacun afin de pouvoir paramétrer finement l?affichage des produits et des contenus dans Thélia si nécessaire. Nous allons ici nous intéresser à des variables internes du moteur jusque-là passées „ „ sous silence : -- $page -- $totbloc -- $pagesess Nous allons croiser aussi des paramètres d?entrée des boucles "produit" et "contenu". „ „ Il s?avère que ces paramètres sont à peine abordés dans le Wiki voire en sont carrément absents. Cela sera l?occasion de les présenter dans ce chapitre : -- Pour la boucle "produit" : deb, num, passage, bloc, courant, profondeur et forcepage. -- Pour la boucle "contenu" : deb, num, bloc, courant, profondeur. De facto des paramètres de sorties des mêmes boucles seront mises à l?ouvrage : „ „ -- Pour la boucle "produit" : #COMPT et #DEBCOURANT -- Pour la boucle "contenu" : #DEBCOURANT. Nous aborderons enfin le rôle de l?attribut „ „ $page de $navig découvert plus haut. Nous allons explorer la boucle produit. Cette boucle complexe (+400 lignes de code) ne sera 134 pas détaillée ici : J?en présente ci-dessous juste un extrait reconstitué, la boucle "produit" dépouillée de tout ce qui ne concerne pas les éléments qui nous intéresse maintenant. Voici d?abords la cinématique générale de cette boucle "produit" virtuelle : La boucle appelle les valeurs de variables internes initialisées dans le moteur „ „ Elle récupère les paramètres d?entrée éventuellement renseignés. „ „ Ces valeurs sont manipulées à travers le script pour répondre à divers objectifs que „ „ nous reprendrons après avoir présenté l?extrait que voici : 135 function boucleProduit($texte, $args, $type=0){ global $page, $totbloc, $ref, $pagesess; $deb = lireTag($args, "deb"); $num = lireTag($args, "num"); $passage = lireTag($args, "passage"); $bloc = lireTag($args, "bloc"); $courant = lireTag($args, "courant"); $profondeur = lireTag($args, "profondeur"); $forcepage = lireTag($args, "forcepage"); if($bloc) $totbloc=$bloc; if(!$deb) $deb=0; if($page) $_SESSION[?navig?]->page = $page; if($pagesess == 1) $page = $_SESSION[?navig?]->page; if(!$page || $page==1 ) $page=0; if(!$totbloc) $totbloc=1; if($page) $deb = ($page-1)*$totbloc*$num+$deb; if($forcepage != "") { if($forcepage == 1){ $forcepage = 0; $deb = 0; } if($forcepage) $deb = ($forcepage-1)*$totbloc*$num+$deb; } 747 $comptbloc=0; 755 if($courant == "1") $search .= " and ref=\"$ref\""; else if($courant == "0") $search .= " and ref<>\"$ref\""; if($profondeur == "") $profondeur=0; $tabrub = explode(",", $rubrique); for($compt = 0; $compt<count($tabrub); $compt++){ $rec = arbreBoucle($tabrub[$compt], $profondeur); $srub .= $tabrub[$compt] . $virg . $rec . $virg;} 797 if($bloc == "-1") $bloc = "999999999"; if($bloc!= "" && $num!= "") $limit .= " limit $deb,$bloc"; else if($num!= "") $limit .= " limit $deb,$num"; 136 if($classement != "titre" && $classement != "titreinverse"){ $query = "select * from $produit->table where 1 $search $order $limit"; $classement = "produit"; } else { $query = "select * from $produit->table, $produitdesc->table where $pro- duit->table.id=$produitdesc->table.produit and $produitdesc->table.lang=\"" . $_ SESSION[?navig?]->lang . "\" $search $order $limit"; $classement = "produitdesc"; } $resul = mysql_query($query, $produit->link); $nbres = mysql_num_rows($resul); $saveReq = "select * from $produit->table where 1 $search"; if(!$nbres) return ""; if($type) return $query; $saveReq = str_replace("*", "count(*) as totcount", $saveReq); $saveRes = mysql_query($saveReq); $countRes = mysql_result($saveRes, 0, "totcount") . ""; 991 $compt = 0; 993 while( $row = mysql_fetch_object($resul) ){ $compt++; if($passage != "" && $comptbloc>$passage-1) break; if($num>0) if($comptbloc>=ceil($countRes/$num) && $bloc!= "") continue; if($comptbloc == 0) $debcourant=0; else $debcourant = $num * ($comptbloc); $comptbloc++; 1164 if($deb != "" && !$page) $debcourant+=$deb-1; 1067 $temp = str_replace("#COMPT", "$compt", $temp); $temp = str_replace("#DEBCOURANT", "$debcourant", $temp);} 1°) La gestion de l?affichage des produits en présence d?une boucle "page" „ „ Cette boucle sera détaillée plus bas. Disons juste à ce stade qu?elle manipulera la variable $page et l?attribut $page de $navig ; les valeurs que prendront ces variables correspondront logiquement aux différentes pages qu?opérera le découpage de la boucle "page" sur une 137 boucle "produit". Nous voyons ici comment leur valeur est déterminée dans le script : -- Par défaut la variable $page est initialisée par le moteur ligne 159 et prend une valeur vide. -- Cette valeur évolue si une page contenant une boucle "produit" est appelée : elle prend alors la valeur $page=0 par défaut. -- En tant que variable externe du moteur, elle évolue aussi si un paramètre ?page? et une valeur sont passées dans l?url par le client ; cette valeur alimente la variable $page, ligne 159 de moteur.php et l?attribut $page de $navig ici (cet attribut a par défaut la valeur =0 comme le stipule le constructeur de la classe Navigation). Nous remarquons que les pages ayant pour valeur $page=0 seront traitées de la même façon que les pages $page=1. Nous allons voir quand nous l?étudierons comment la boucle "page" génère un tel paramètre ?page? dans l?url. -- Nous voyons enfin qu?une variable $pagesess=1 peut être incluse sur un fichier .php du template ce qui aura pour conséquence que la dernière page (générée par la boucle "page" sur une boucle "produit") affichée par un client sera appelée à la réouverture du squelette : Prenons un cas pratique : Sur notre squelette rubrique.html nous codons une boucle "produit" qui va remonter 100 pro- duits de la base. Nous ne souhaitons afficher que 10 produits par page. Nous aurons donc sur le squelette codé une boucle "produit" avec le paramètre ?num?=10 et une boucle "page" pour assurer la division des pages si nécessaire (en l?occurrence ça l?est puisqu?avec 100 produits nous dépassons de beaucoup le ?num?=10 : le paramètre ?num? et la boucle "page" vont diviser l?affichage de cette boucle en 10 pages de 10 produits). Si, sur rubrique.php du template, vous avez codé $pagesess=1 et qu?à la page 6, le client quitte la page rubrique.html (pour, par exemple, une page produit.html), alors à son retour sur rubrique.html la page 6 lui sera affichée. 2°) La gestion des paramètres d?entrée ?deb? et ?num? de la boucle "produit" „ „ Vous devez normalement savoir vous servir de ces paramètres : nous voyons ici comment ils sont pris en compte par la boucle "produit" dans un usage simple, c?est-à-dire sans pa- ramètre d?entrée ?bloc? ou ?passage? renseigné : ils filtrent parmi les produits identifiés par la requête sql globale les produits à afficher : le premier ($deb) et le nombre de produits sui- vants ($num) à prendre en compte pour l?affichage des paramètres de sortie renseignés. Le paramètre d?entrée ?classement? altère le résultat puisqu?il modifie l?ordre de remontée Nous voyons également que la variable $page modifie le résultat de l?affichage d?une boucle "produit" avec au moins le paramètre ?num? renseigné : considérons la ligne suivante : if($page) $deb = ($page-1)*$totbloc*$num+$deb; Traduisons en suivant le même cas pratique : -- En arrivant sur la première page, la valeur de $page=0, l?instruction ci-dessus est igno- rée. -- En accédant à la 2nde page, $page=2 : la valeur de $deb devient alors égale à la valeur de $num : les produits affichés seront donc les 10 suivants dans l?ordre établi par la requête initiale. L?instruction applique un coefficient multiplicateur pour fonctionner en fonction du nombre de page généré par la boucle "page" et donc de la valeur possible de ?num?. 138 -- Le paramètre d?entrée ?forcepage? peut être renseigné d?une valeur numérique : les ins- tructions suivantes de la fonction boucleProduit() nous expliquent que cette valeur force l?af- fichage des mêmes produits à travers les différentes pages générées par la boucle "page" : nous pouvons par exemple imaginer coder une seconde boucle produit avec le paramètre d?entrée ?promo?=1, ?num?=?3? et forcepage=?1? pour afficher sur les 10 pages de la boucle de mon cas pratique, les 3 premiers produits en promotion de ma boucle. 3°) La gestion des paramètres d?entrée ?bloc?, ?passage? et de la variable „ „ $totbloc La fonction boucleProduit() nous apprend qu?un paramètre d?entrée ?bloc? peut être envisagé et nous savons qu?un paramètre d?url ?totbloc? est pareillement possible et sera traduit par une variable homonyme ligne 160 du moteur. Rien dans le Wiki ni le template de base ne nous permet d?en savoir plus : une étude du script est donc nécessaire pour envisager d?en bénéficier. -- Si ?bloc? est renseigné, $totbloc prend sa valeur. -- $totbloc est un coefficient multiplicateur pour le calcul de $deb. -- ?bloc? fonctionne avec ?num? : si ce second n?est pas renseigné son influence est ignorée lors de la définition de la requête SQL dans le cas contraire il sert à limiter l?affichage des produits remontés par la boucle selon les mêmes critères que ?num?. -- la variable $comptbloc est un compteur sur les produits remontés par la requête. Pour cha- cun de ces produits, si ce numéro est supérieur au rapport nombre de produit/num alors que ?bloc? est renseigné, le produit est ignoré. Conclusion : ?bloc? comme ?num? permettent de limiter le nombre de produits à afficher dans une boucle "produit" mais dans le premier nous limitons en fonction d?un ratio (le paramètre ?num? est utilisé comme un coefficient) alors que dans le second nous limitons l?affichage selon un nombre déterminé (?num? est alors une valeur arithmétique). Toujours le même cas pratique : une boucle "produit" avec num="2" et bloc="10" remontant 100 produits et une boucle "page" : Thélia traduira par "afficher la première moitié des pro- duits identifiés avec 10 produits par page soit 5 pages de 10 produits". -- Enfin le paramètre ?passage? limite le nombre de passages qu?effectuera la boucle : dans notre cas pratique, si num="10" et passage="20", vous afficherez 2 pages de 10 produits. 4°) Les paramètres de sortie de la boucle "produit" „ „ #COMPT restitue le nombre de produits que la page va afficher, suite à l?ensemble des considérations décrites plus haut. #DEBCOURANT : restitue pour un produit de la boucle une valeur numérique correspondant à la valeur de ?num? multipliée par la position de ce produit dans le résultat de la requête (cet- te position est calculée par une variable auto-incrémentée : le premier produit portera donc le numéro =0 selon cette variable et son #DEBCOURANT aussi par la même occasion). 139 140 2°) La boucle page Pour améliorer l?ergonomie de nos pages rubriques, nous disposons donc de la boucle "page" permettant d?organiser dynamiquement l?affichage sur plusieurs pages des rubriques volumineuses, plutôt que d?afficher sur une seule page tout leur contenu. Une boucle "page" se code à la fin d?une page de type rubrique et affiche un résultat de ce type : typeaff=0 typeaff=1 typeaff=0 Précédente 1 - 2 - 3 - 4 - 5 Suivante La boucle "page" n?est pas très facile à comprendre au début. C?est l?occasion de la prendre par le bout de son code source et de la découper sans vergogne en petits saucissons plus faciles à avaler. Avant de s?y coller, rappelons simplement qu?une boucle "page" est un jeu d?url. Deux types sont disponibles avec la même boucle en fonction d?un paramètre : les liens suivant/précé- dent (typeaff=0) et les liens page 1, page2, page 3... (typeaff=1) Le paramètre ?max? permet de limiter le nombre de liens de pages à afficher. La fonction bouclePage() est codée des lignes 1339 à 1347 du fichier fonctions/boucles.php 1°) Récupération des paramètres d?entrée „ „ function bouclePage($texte, $args){ global $page, $id_rubrique; $num = lireTag($args, "num"); $courante = lireTag($args, "courante"); $pagecourante = lireTag($args, "pagecourante"); $typeaff = lireTag($args, "typeaff"); $max = lireTag($args, "max"); $affmin = lireTag($args, "affmin"); $avance = lireTag($args, "avance"); $type_page = lireTag($args, "type_page"); Cette boucle ne répond pas au modèle type décrit dans la première partie de ce document. Elle ne manipule aucune donnée de la base. Les paramètres d?entrées configurent l?affi- chage. Elle fait appel aux variables $page et $rubrique_id, définies hors de la fonction (lors de l?ini- tialisation des variables). 141 2°) Initialisation de la variable „ „ $bpage $i=""; if( $page<=0) $page=1; $bpage=$page; $res= ""; $bpage est initialisée avec la valeur =1 ou la valeur de $page si $page>1 3°) Traitement du paramètre type_page „ „ $cnx = new Cnx(); if(!$type_page) $query = boucleProduit($texte, str_replace("num", "null", $args), 1); else $query = boucleContenu($texte, str_replace("num", "null", $args), 1); -- Si le paramètre d?entrée ?type_page?=1 alors la fonction boucleContenu() est exécutée. -- Sinon la fonction boucleProduit() est exécutée. Nous remarquons que ces fonctions seront exécutées avec la variable $texte, et la variable $arg, dépouillée de son paramètre ?num?, remplacé par ?null?. Ces deux variables sont issues de la boucle page en cours d?exécution. Autrement dit boucleContenu() ou boucleProduit() n?ont à priori ni paramètre d?entrée ni para- mètre de sortie à traduire : elles ne sont pas capables de traduire les paramètres de la boucle "page" : seul le paramètre d?entrée ?num? est commun mais nous voyons qu?il est transformé pour ne pas être pris en compte. -- Pour effectuer la requête souhaitée par la fonction boucleProduit() ou boucleContenu() nous pourrons (et allons) inclure le paramètre d?entrée ?rubrique? de la boucle "produit" (ou ?dossier? dans le cadre d?une boucle "dossier") dans la boucle "page" : rubrique= "#RUBRIQUE_ID" : ce paramètre ne faisant pas partie d?une boucle "page", il est ignoré par elle mais quand cel- le-ci appelle la fonction boucleProduit() et lui présente ses propres paramètres en argument, boucleProduit() reconnaît le paramètre ?rubrique? et peut s?exécuter. -- Par ailleurs nous attribuons pour la première fois une valeur à l?argument $type des fonc- tions boucleProduit() et boucleContenu(). Si vous relisez l?extrait repris plus haut, vous com- prendrez l?instruction if($type) return $query; de la ligne 985 : le traitement de boucleProduit() est partiel dans le cadre de son exécution dans bouclePage() : elle n?affichera aucun paramè- tre de sortie car l?instruction ci-dessus la termine avant, si $type=1. Nous souhaitons en effet seulement récupérer dans la fonction bouclePage() la requête de boucleProduit() (en l?occur- rence, avec ?rubrique?="#RUBRIQUE_ID", les produits attachés à la rubrique en cours) et non l?afficher, le 3ième argument, $type , étant transmis avec la valeur =1. Les instructions suivantes de la boucle "page" vont manipuler cette requête et l?exécuter : 4°) Calcul des valeurs de „ „ $page et de $nbpage if($query != ""){ $pos = strpos($query, "limit"); if($pos>0) $query = substr($query, 0, $pos); $resul = mysql_query($query, $cnx->link); $nbres = mysql_num_rows($resul); } else $nbres = 0; $page = $bpage; $nbpage = ceil($nbres/$num); -- La valeur de $bpage est transférée à $page. -- La requête issue de boucleProduit() est débarrassée de son paramètre ?limit? avant d?être exécutée par bouclePage(). Elle remonte donc le nombre de produits pour la rubrique en cours. -- La valeur de $nbpage est le résultat arrondi à l?entier supérieur de la division du nombre de produits identifiés par la requête par la valeur du paramètre ?num?. Donc: 1°) ?num? doit être renseigné et différent de 0 pour une boucle "page" et 2°) pour un comportement cohérent de la fonctionnalité, il convient de renseigner avec les mêmes valeurs les paramètre ?num? de la boucle "produit" et des différentes boucles "pages" d?un squelette parce que ces deux paramètres représentent la même chose (le nombre de produits à afficher sur 1 page) dans les deux boucles. 3°) Ce qui est vrai pour le paramètre 'rubrique' dans une boucle "page", l'est aussi des autres paramètres que vous aurez renseigné dans votre boucle "produit" : ils devront être reproduits dans la boucle "page" pour un comportement cohérent de la pagination de vos rubriques. 5°) Calcul des valeurs de „ „ $pagesuiv et de $pageprec if($page+1>$nbpage) $pagesuiv=$page; else $pagesuiv=$page+1; if($page-1<=0) $pageprec=1; else $pageprec=$page-1; if($nbpage<$affmin) return; if($nbpage == 1) return; -- La valeur des ces deux variables dépend de la valeur prise par $page : si -- Si $nbpage est inférieur au paramètre d?entrée ?affmin? la fonction se termine. 6°) Gestion des liens de page „ „ if($typeaff == 1){ if(!$max) $max=$nbpage+1; if($page && $max && $page>$max) $i=ceil(($page)/$max)*$max-$max+1; if($i == 0) $i=1; $fin = $i+$max; for( ; $i<$nbpage+1 && $i<$fin; $i++ ){ $temp = str_replace("#PAGE_NUM", "$i", $texte); $temp = str_replace("#PAGE_SUIV", "$pagesuiv", $temp); $temp = str_replace("#PAGE_PREC", "$pageprec", $temp); $temp = str_replace("#RUBRIQUE", "$id_rubrique", $temp); if($pagecourante && $pagecourante == $i){ if($courante =="1" && $page == $i ) $res .= $temp; else if($courante == "0" && $page != $i ) $res .= $temp; else if($courante == "") $res .= $temp; } else if(!$pagecourante) $res .= $temp; }} -- Une boucle php "for" restitue autant de paramètres de sortie qu?il y a de pages ($nbpage) ce nombre étant limité par la valeur de ?max?. -- Dans la boucle la paramètre de sortie #page_num est déterminant ; il est traditionnelle- ment utilisé pour afficher un lien vers la page sous la forme d?un numéro égal à sa valeur. Il est aussi et surtout utilisé pour générer le paramètre d?url "page" et sa valeur associée. La boucle "produit" présentée plus haut nous a appris que ce paramètre détermine la valeur de $deb qui elle-même, associée à $num, détermine les produits à afficher sur la page par rapport à une requête de la boucle "produit". <a href="rubrique.php?id_rubrique=#RUBRIQUE_ID&page=#PAGE_NUM">#PAGE_ NUM</a> -- Le mécanisme permettant de désactiver le lien sur la page courante (la page affichée) est décrit : en prévoyant deux boucles typeaff=1, l?une avec ?courante?=1 et l?autre avec ?cou- rante?=0 nous pouvons afficher toutes les pages sous forme de numéros mais différencier la page courante des autres pages. En l?occurrence l?affichage de la page courante ne sera pas un lien et nous pouvons prévoir en plus d?autres effets d?affichage pour ce numéro. 6. Appréhender Thélia par la théorie : Caractéristiques et déclinaisons Nous voici arrivés à une partie importante de l?étude que vous êtes entrain de lire : c?est lorsque j?ai voulu en savoir plus sur le fonctionnement des déclinaisons dans Thélia que j?ai compris combien cette fonctionnalité semblait compliquée et imbriquée dans l?ensemble du programme : j?ai compris que les déclinaisons ne se laisseraient apprivoiser qu?après la totalité de l?application appréhendée dans sa globalité. Vous avez devant les yeux le résultat de cette démarche. Avec les caractéristiques et les déclinaisons nous disposons d?un outil générique souple et puissant pour adapter Thélia à notre propre conception d?une fiche produit, sans code ou plugin supplémentaire. Nous reviendrons sur la définition de ces deux fonctionnalités mais disons tout de suite que les caractéristiques sont des éléments descriptifs d?un produit alors que les déclinaisons sont différentes versions d?un même produit et qu?elles sont destinées à être précisées par le client lors d?une commande. A partir de ces 2 fonctionnalités nous pouvons envisager de mettre en place de nombreux détails spécifiques pour un produit : besoin de gérer les codes barres pour vos produits ? Besoin d?organiser vos produits dans une ou plusieurs arborescences transversales en plus de l?organisation en rubriques et sous-rubriques ? Besoin d?afficher la disponibilité de vos produits ? Vous déclinez vos produits et les proposez à la personnalisation ?..... Tous ces besoins et bien d?autres sont couverts par les fonctionnalités caractéristique et déclinaison. C?est dire l?importance qu?ils revêtent dans le système Thélia : un fiche produit par défaut est assez pauvre : cela garantit que vous n?aurez aucune fonctionnalité dont vous ne vous serviriez pas. L?organisation de vos caractéristiques et déclinaisons personnalisées réparties sur les fiches produits qui en auront besoin vous donne la main pour personnaliser l?applica- tion. Chapitre 1 : Définitions des caractéristiques et déclinaisons dans Thélia. Chapitre 2 : L?attribution des caractéristiques et déclinaisons aux produits (BO Thélia) Chapitre 3 : Le fonctionnement des caractéristiques Thélia Chapitre 4 : Le fonctionnement des déclinaisons Thélia 144 Définitions des caractéristiques et des décli- 1. naisons dans Thélia Avant de nous lancer dans l?étude de ces deux fonctionnalités qui semblent plus complexes qu?elles ne le sont vraiment, rappelons quelques points de vocabulaire. Une „ „ caractéristique d?un produit est un élément de ce produit le caractérisant. La ca- ractéristique type est la marque d?un produit. Pour un produit nous pouvons donc associer une caractéristique (la marque) avec une valeur de caractéristique (ex : Adidas). Dans Thélia la valeur pour une caractéristique se nomme une caracdisp. -- A priori, concernant un produit, nous lui aurons attribué pour une caractéristique donnée, une valeur et une seule (Adidas) ; pourtant, rien ne nous empêche d?imaginer une caracté- ristique pour laquelle un produit possèderait plusieurs valeurs : par exemple si nous définis- sons comme caractéristique le critère "label" nous pouvons envisager qu?un produit possède plusieurs labels : plusieurs valeurs de la même caractéristique "label" seront attribuées à ce type de produit. -- A priori aussi, pour un produit donné, la valeur de sa caractéristique ne changera pas avec le temps (une chaussure Adidas restera de la même marque). Cependant là encore nous pouvons envisager de nous servir des caractéristiques différemment et prévoir des critères évoluant dans le temps : ainsi nous pouvons définir le critère "disponibilité" en tant que caractéristique (ses valeurs pourraient alors être : 48H, 1 semaine, 2 semaines, 1 mois, Rupture de stock...) : le marchand fera évoluer dans la fiche produit du BO la valeur de la caractéristique "disponibilité" pour ce produit. -- Dans tous les cas cependant vous remarquerez que le client ne peut modifier une carac- téristique, ni faire un choix entre plusieurs caractéristiques : vous les afficherez (en tout ou partie) pour information sur la fiche produit côté FO. Elle seront aussi utiles dans le cadre d?un moteur de recherche comme nous aurons l?occasion de le voir dans une autre partie. Une „ „ déclinaison d?un produit est un caractère pouvant être décliné pour ce produit : la déclinaison type est la taille lorsque vous proposez du textile à la vente par exemple. Contrai- rement aux caractéristiques, vous comprenez qu?un produit muni d?une déclinaison ne peut être commandé par un client avant qu?il ne choisisse la déclinaison souhaitée. Comme les caractéristiques, nous créons donc des déclinaisons (exemple : taille) auxquelles nous atta- chons des valeurs possibles (exemple : S, M, L, XL...) appelées declidisp : sur le FO, pour un produit donné, les déclinaisons qui y sont attachées sont affichées et leurs valeurs dispo- nibles sont proposées traditionnellement sous forme d?une liste déroulante. Pour chaque „ „ declidisp d?une déclinaison, deux critères sont à paramétrer au niveau de chaque fiche produit du BO pour les produits munis de cette déclinaison : le stock et le surplus tarifaire. Nous verrons comment fonctionne ces deux paramètres. Nous sommes donc invités dans Thélia à créer des caractéristiques et des déclinaisons „ „ et pour chacune d?entre elles, nous créons des valeurs possibles (caracdisp et declidisp). 145 Puis nous attribuons (ou non) aux produits ces caractéristiques et déclinaisons et précisons, concernant les caractéristiques la (ou les) valeur(s) prédéfinies de chaque caractéristique au niveau de chaque produit. Concernant les déclinaisons nous précisons le stock et le surplus tarifaire pour chaque declidip. Il est possible d?envisager, pour les caractéristiques, une seconde manière de les uti- „ „ liser : nous pouvons créer une caractéristique sans lui attribuer de valeur prédéfinie (decli- disp) : au niveau de chaque fiche produit nous disposons alors d?un champ libre permet- tant de renseigner la valeur de cette caractéristique pour ce produit. Ce type de valeur de caractéristique est appelée une caracval. Ce peut être utile si par exemple vous prévoyez une caractéristique commune à de nombreux produits mais dont les valeurs possibles sont nombreuses et variées (voire uniques) : plutôt que de créer une longue liste de caracdisp prédéfinis, le plus simple est de créer une caractéristique sans declidisp et renseigner une caracval au niveau de chaque fiche produit. Ceci dit, nous remarquons que traditionnellement désormais dans Thélia, des classes, „ „ des boucles et des tables homonymes sont prévues pour manipuler tout ce beau petit monde comme le montre ce tableau récapitulatif : Caractéristique Déclinaison Classes Dossier classe rubcaracteristique.class.php caracteristique.class.php caracteristiquedesc.class.php caracdisp.class.php caracdispdesc.class.php caracval.class.php rubdeclinaison.class.php declinaison.class.php declinaisondesc.class.php declidisp.class.php declidispdesc.class.php stock.class.php Tables Base de données rubcaracteristique caracteristique caracteristiquedesc caracdisp caracdispdesc caracval rubdeclinaison declinaison declinaisondesc declidisp declidispdesc stock exdecprod Boucles Fichier boucles.php caracteristique caracdisp caracval declinaison declidisp decval stock -- Nous voyons qu?une table ?exdecprod? et qu?une autre ?stock? sont liées à la gestion des déclinaisons sans correspondance dans le système des caractéristiques -- Nous voyons également qu?il existe une boucle "decval" mais pas de table ?decval? : nous n?avons pas mentionné la capacité de créer une déclinaison "libre", sur le même principe que les caractéristiques. Il n?existe pas plus de table "decval" que de classe Decval. Nous reviendrons sur le rôle de cette boucle. 146 La gestion des caractéristiques dans 2. Thélia Nous allons voir comment sur un fiche produit du BO, une caractéristique est rendue disponi- ble pour un produit, donc affichée dans le module correspondant. La première étape consiste à créer une caractéristique ou une déclinaison et de lui prévoir des valeurs. Nous décrirons le fonctionnement pour une caractéristique mais le schéma est identique pour les déclinai- sons. La seconde étape consiste à attribuer cette caractéristique à la rubrique contenant le produit. La création d?une caractéristique : caracteristique.php et 1. caracteristique_modifier.php Le mode de fonctionnement est typique du BO : depuis caracteristique.php qui affiche les caractéristiques créées, 2 liens mènent vers le même type de page caracteristique_modifier .php : si vous n?embarquez pas d?ID de caractéristique (lien ?créer une caractéristique?) la page est vierge, vous définissez les différentes valeurs et le bouton ?valider? ajoute une ligne aux tables ?caracteristique?, ?caracteristiquedesc?, ?caradisp?, ?caracdispdesc? et ?rubcarac- teristique?. Si vous définissez un ID (lien ?éditer?) vous avez accès aux valeurs enregistrées dans ces mêmes tables pour cet ID et pouvez les modifier (excepté les ID qui sont des va- leurs auto-incrémentées. Cette différence est possible tout simplement : le chargement de la page caracteristique_mo- difier.php effectue dans les 2 cas (avec ou sans ID en paramètre) une requête sur la table ?caracteristique? : sans ID cette requête retourne un résultat nul : les champs sont donc vides ! Le seul codage nécessaire pour assurer la différence est dans le lien de validation : Vous remarquerez que la page entière est un formulaire : les actions diffèrent selon la pré- sence ou non d?un ID car à différents endroits du formulaire vous trouverez comme à la ligne 309 ce type d?instruction : <input type="hidden" name="action" id="zoneaction" value="<?php if(!$id) { ?>ajouter<?php } else { ?>modifier<?php } ?>" /> Classiquement la page caracteristique_modifier.php contient les éléments d?affichage mais aussi et surtout la motorisation php propre à cette page : les actions disponibles pour le marchand (par exemple ?ajouter? ou ?modifier?) et leurs paramètres sont décrits et appelés en premier. Nous ne les détaillerons pas plus ici ; le principe est toujours le même : lorsqu?on valide une modification ou une création, la page est rechargée, l?url transmet des paramè- tres, le serveur lit les instructions php et exécute les instructions prévues en fonction des paramètres d?url (création ou mise à jour de lignes pour les tables sus-citées). Nous allons revenir en détail sur les fonctionnalités de rattachement au sein de ces fonctions php de la page caracteristique_modifier tout de suite. 147 Le rattachement d?une caractéristique aux rubriques et aux 2. produits function ajouter($lang, $id, $titre, $chapo, $description, $tabdisp, $affiche){ $caracteristique = new Caracteristique(); $caracteristique->charger($id); if($caracteristique->id) return; $caracteristique = new Caracteristique(); $query = "select max(classement) as maxClassement from $caracteristique->table"; $resul = mysql_query($query, $caracteristique->link); $maxClassement = mysql_result($resul, 0, "maxClassement"); $caracteristique->id = $id; if($affiche!="") $caracteristique->affiche = 1; else $caracteristique->affiche = 0; $caracteristique->classement = $maxClassement + 1; $lastid = $caracteristique->add(); $caracteristiquedesc = new Caracteristiquedesc(); $caracteristiquedesc->chapo = $chapo; $caracteristiquedesc->description = $description; $caracteristiquedesc->caracteristique = $lastid; $caracteristiquedesc->lang = 1; $caracteristiquedesc->titre = $titre; $caracteristiquedesc->chapo = ereg_replace("\n", "<br/>", $caracteristiquedesc- >chapo); $caracteristiquedesc->description = ereg_replace("\n", "<br/>", $caracteristiquedesc- >description); $caracteristiquedesc->add(); $rubrique = new Rubrique(); $query = "select * from $rubrique->table"; $resul = mysql_query($query, $rubrique->link); while($row = mysql_fetch_object($resul)){ $rubcaracteristique = new Rubcaracteristique(); $rubcaracteristique->rubrique = $row->id; $rubcaracteristique->caracteristique = $lastid; $rubcaracteristique->add(); } header("Location: " . $_SERVER[?PHP_SELF?] . "?id=" . $lastid); } 148 Suivons le raisonnement depuis le début : 1°) la fonction „ „ ajouter() ci-contre est extraite de la page caracteristique_modifier.php aux lignes 105 et suivantes. Les valeurs rentrées par le marchand dans le formulaire de la page caracteristique_modifier.php ont alimenté les variables internes homonymes qui sont passées en arguments de la fonction. Nous voyons que celle-ci : a] Crée une nouvelle ligne dans la table ?caracteristique? . Cette table gère uniquement l?ID des caractéristiques, leur affichage ou non dans une boucle "caracteristique" (champ ?affiche?) et leur classement les unes par rapport aux autres (champ ?classement?). b] Ajoute de la même façon une ligne à la table ?caracteristiquedesc? pour stocker les valeurs du formulaire. Notons que la variable $lastid initialisée dans la phase précédente a reçu, lors de l?exécution de la méthode add() (que nous décrirons plus tard), la valeur de l?ID de la nouvelle ligne. Cette valeur sera transmise aux différentes tables (ici ?caracteristique- desc?) pour assurer les liaisons (des "jointures") nécessaires lorsqu?un seul objet (ici une caractéristique) est réparti entre plusieurs tables d?une base de données. c] Ajoute une ligne dans la table ?rubcaracteristique? pour chaque rubrique existante. Pour chacune une ligne lie l?ID de la rubrique et l?ID de la caractéristique créée ($lastid). Le même schéma se répète pour les valeurs de ces caractéristiques : les caracdisp „ „ : les tables ?caracdisp? et ?caracdispdesc? sont sollicitées lors d?une création d?un caracdisp comme le montre la fonction des lignes 187 à 202 de caracteristique_modifier.php : function ajcaracdisp($id, $caracdisp, $lang){ if(!$lang) $lang=1; $tcaracdisp = new Caracdisp(); $tcaracdisp->caracteristique = $id; $res = $tcaracdisp->add(); $tcaracdispdesc = new Caracdispdesc(); $tcaracdispdesc->caracdisp = $res; $tcaracdispdesc->lang = $lang; $tcaracdispdesc->titre = $caracdisp; $tcaracdispdesc->add(); } L?ID de la caractéristique en cours (une caractéristique doit être créée pour pouvoir créer une caracdisp) sert de jointure pour relier une valeur à sa caractéristique dans la table ?ca- racdisp?. La variable $res assure la jointure ID caracdisp -> caracdispdesc. A ce stade, du point de vue de la base de données nous avons créé une "chaîne relation- nelle" liant les rubriques, les caractéristiques et les valeurs de ces caractéristiques ce qui peut être illustré de cette façon : 149 $lastid $lastid $lastid $res $id RUBCARACTERISTIQUE RUBRIQUE CARACTERISTIQUE CARACTERISTIQUEDESC ID CARACTERISTIQUE RUBRIQUE ID CARACTERISTIQUE ID TITRE CARACDISP ID CARACTERISTIQUE CARACDISPDESC ID CARACDISP A ce stade, aucun produit n?est attaché à une caractéristique. Du point de vue de la „ „ base, une caractéristique s?attache d?abords à une rubrique. Sur la page rubrique_modifier. php nous accédons à l?interface permettant de gérer le lien créé initialement entre une ca- ractéristique et une rubrique, aux lignes 660 à 700, nous pouvons attacher ou détacher une caractéristique de la rubrique en cours. Ces actions manipuleront logiquement la table ?rub- caracteristique?. Voyons ensuite comment une caractéristique et sa valeur sont finalement attachées à un produit : Nous devons nous rendre sur le fichier produit_modifier.php du BO : les lignes 325 à „ „ 371 de la fonction modifier() ou les lignes 496 à 534 de la fonction ajouter() nous renseignent sur le mécanisme de liaison que nous suivons. Elles sont reproduites ci-contre et sont iden- tiques concernant la gestion des caractéristiques d?un produit pour les deux fonctions. -- Les caractéristiques disponibles pour un produit dépendent de la rubrique contenant ce produit : si un caractéristique est attachée à cette rubrique une valeur de cette caractéristi- que peut être définie pour ce produit. -- La table ?caracval? est sollicitée pour la liaison entre la valeur d?une caractéristique et l?ID d?un produit. Pour afficher dans une fiche produit du FO les valeurs de leurs caractéristiques, même prédéfinies, il faudra manipuler conséquemment la boucle "caracval" : celle-ci n?est pas réservée à l?affichage des valeurs personnalisées d?une caractéristique libre mais stocke également les valeurs prédéfinies choisies par le marchand pour un produit. Ainsi pour cha- que ligne de la table ?caracval?, soit son champ ?declidisp? sera renseigné (caractéristique prédéfinie), soit son champ ?valeur? (la valeur créée au niveau de la fiche produit par le mar- chand pour une caractéristique libre). Base de données 150 function modifier($id, $lang, $ref, $prix, $ecotaxe, $promo, $prix2, $rubrique, $nouveau- te, $perso, $poids, $stock, $tva, $ligne, $titre, $chapo, $description, $postscriptum) ... $rubcaracteristique = new Rubcaracteristique(); $caracteristiquedesc = new Caracteristiquedesc(); $caracval = new Caracval(); $query = "select * from $rubcaracteristique->table where rubrique=? ". $produit- >rubrique . "?"; $resul = mysql_query($query); while($row = mysql_fetch_object($resul)){ $caracval = new Caracval(); $deb="caract"; $deb2="typecaract"; $val=$row->caracteristique; $var = $deb.$val; $var2 = $deb2.$val; global $$var; global $$var2; $query2 = "delete from $caracval->table where produit=?" . $produit->id . "? and caracteristique=?" . $row->caracteristique . "?"; $resul2 = mysql_query($query2); if($$var2 == "c" && $$var != "") foreach($$var as $selectval) { if($selectval != ""){ $caracval->produit = $produit->id; $caracval->caracteristique = $row->caracteristique; $caracval->caracdisp = $selectval; $caracval->add(); } } else if($$var != "") { $caracval->produit = $produit->id; $caracval->caracteristique = $row->caracteristique; $caracval->valeur = $$var; $caracval->add(); } } ... Ainsi nous pouvons finaliser la modélisation des liens unissant les différentes tables de la 151 base de données concernant la gestion des caractéristiques pour un produit de la manière suivante : $lastid $lastid $lastid $id RUBCARACTERISTIQUE RUBRIQUE CARACTERISTIQUE CARACTERISTIQUEDESC CARACTERISTIQUE RUBRIQUE ID CARACTERISTIQUE ID CARACDISP ID CARACTERISTIQUE CARACDISPDESC CARACDISP Base de données $id PRODUIT ID CARACVAL PRODUIT CARACTERISTIQUE CARACDISP $res $selectval $id Les boucles liées aux caractéristiques : 3. Les boucles natives : „ „ -- "caracteristique" : vous donne accès aux contenux des tables ?rubcaracteristique?, ?carac- teristique? et ?caracteristiquedesc? -- "declidisp" : vous donne accès aux contenus des tables ?declidisp? et ?declidispdesc?. -- "decval" : vous donne accès au contenu de la table ?caracval? : soit la valeur prédéfinie de la caractéristique pour ce produit; soit la valeur libre dans le cadre d?une caractéristique libre. Pour afficher les valeurs des caractéristiques disponibles au niveau d?une rubrique, il „ „ existe un petit plugin indispensable, "caracdisprub" très pratique si par exemple vous avez des gammes de produits variées, qui vont partager les même caractéristiques mais pas les mêmes valeurs : les valeurs des caractéristiques pour les produits peuvent varier du tout au tout selon les gammes donc selon les rubriques vraisemblablement et vous apprécierez avec ce plugin de pouvoir afficher sur les pages rubriques les valeurs disponibles pour celles-ci : en effet le plugin ajoute une nouvelle boucle à votre arsenal : la boucle "caracdisprub". 152 La gestion des déclinaisons dans 3. Thélia La gestion d?une déclinaison côté marchand 1. Le raisonnement suivi pour les caractéristiques est repris pour les déclinaisons concernant notamment l?organisation des tables et l?organisation des pages et de la motorisation php : nous connaissons les classes, les tables et les boucles impliquées dans leur gestion (voir supra). Nous savons que le système diffère sensiblement parce dans la base des tables sont mises à contribution pour la gestion des déclinaisons qui n?existent pas pour les caractéris- tiques. Sur la page declinaison_modifier.php, les tables ?rubdeclinaison?, ?declinaison?, ?decli- „ „ naisondesc?, ?declidisp? et ?declidispdesc? sont manipulées : des lignes sont créées ou mises à jour. Sur la page rubrique_modifier.php les déclinaisons créées sont attachées ou déta- „ „ chées des rubriques du catalogue : la table ?rubdeclinaison? est manipulée. Sur la page produit_modifier.php les déclinaisons attachées à la rubrique sont disponi- „ „ bles pour les produits de cette rubrique les tables ?exdecprod? et ?stock? sont manipulées de la manière suivante : -- Pour chaque valeur prédéfinie (declidisp), le marchand renseigne le stock de cette dé- clinaison, le surplus tarifaire éventuel et peut activer ou désactiver cette declidisp pour ce produit : ainsi sur cette page, la table ?stock est manipulée? : pour un declidisp donné (champ ?declidisp?) attaché à un produit donné (champ ?produit?) des valeurs de stock et de surplus sont enregistrées. -- Pour comprendre comment fonctionne l?activation/désactivation il faut comprendre les ac- tions acdec et desdec de produit_modifier.php : celles-ci sont exécutées lorsque le marchand clique sur le lien activer/désactiver de chaque declidisp d?un produit sur la fiche produit du BO (ligne 1047 de la page rubrique_modifier.php) <?php if($res) { ?> <a href="produit_modifier.php?ref=<?php echo($ref); ?>&produit=<?php echo($produitdesc->produit); ?>&rubrique=<?php echo($rubrique); ?>&action=acdec&id=<?php echo($declidispdesc->declidisp); ?>>Activer</a> <?php } else {?> <a href="produit_modifier.php?ref=<?php echo($ref); ?>&produit=<?php echo($produitdesc->produit); ?>&rubrique=<?php echo($rubrique); ?>&action=desdec&id=<?php echo($declidispdesc->declidisp); ?>">Désactiver</a> En fonction d?une variable $res définie quelques lignes au dessus, le lien génère une activa- tion ou une désactivation. Cette variable est ainsi initialisée ligne 1040 : $exdecprod = new Exdecprod(); $res = $exdecprod->charger($produit->id, $row2->id); 153 Les actions acdec et desdec sont explicitées dans le "switch" de la page (lignes 76 et 77) : case ?acdec? : moddecli($produit, $id, 1); break; case ?desdec? : moddecli($produit, $id, 0); break; Nous voyons qu?il s?agit d?exécuter la fonction moddecli() dans les 2 cas mais avec le para- mètre $type =1 pour l?activation et $type=0 pour la désactivation ce qui donne : function moddecli($produit, $declidisp, $type){ $exdecprod = new Exdecprod(); if(! $type) { $exdecprod->produit = $produit; $exdecprod->declidisp = $declidisp; $exdecprod->add(); } else { $exdecprod->charger($produit, $declidisp); $exdecprod->delete(); } } Donc : -- Si, pour un produit donné et une declidisp disponible pour ce produit, une ligne existe dans la table ?exdecprod? (= $res est non vide), alors la fonction moddecli() est exécutée avec $type=1. Puisque type n?est pas=0, alors la ligne en question est supprimée de la table. Dans le cas contraire, c?est l?inverse. -- Conclusion : ?exdecprod? stocke les declidsp exclues (désactivées) pour un produit, ce que nous confirme la fonction boucledeclidisp() ligne 2623 du fichier fonctions/boucles.php : if($exdecprod->charger($produit, $tabliste[$i])) continue; Ainsi une declidisp désactivée est un ajout de ligne dans ?exdecprod? et non une action sur ?declidisp? ou sur ?stock? : voilà pourquoi lorsque vous réactivez cette déclidisp son stock est à nouveau disponible et non remis à zéro. Autrement dit, si vous observez la modélisation (simplifiée) de la base ci-dessous, vous pou- viez vous demander pourquoi les tables ?stock? et ?exdecprod? semblent assurer les mêmes jointures (produit->declidisp) : en fait la première stocke l?information réelle et la seconde stocke l?information relative à l?activation ou non de la declidisp dans le BO. 154 155 RUBDECLINAISON RUBRIQUE DECLINAISON DECLINAISONDESC DECLINAISON RUBRIQUE ID DECLINAISON ID DECLIDISP ID DECLINAISON DECLIDISPDESC DECLIDISP Base de données EXDECPROD PRODUIT DECLIDISP PRODUIT ID STOCK DECLIDISP PRODUIT Les boucles liées à la gestion des déclinaisons 2. function boucleDeclinaison($texte, $args){ global $declinaison; $id = lireTag($args, "id"); $rubrique = lireTag($args, "rubrique"); $produit = lireTag($args, "produit"); $courante = lireTag($args, "courante"); $exclusion = lireTag($args, "exclusion"); $search =""; $res=""; if($rubrique!="") $search.=" and rubrique=\"$rubrique\""; if($id!="") $search.=" and id=\"$id\""; if($exclusion!="") $search .= " and id not in ($exclusion)"; $rubdeclinaison = new Rubdeclinaison(); $tmpdeclinaison = new Declinaison(); $tmpdeclinaisondesc = new Declinaisondesc(); $query = "select DISTINCT(declinaison) from $rubdeclinaison->table where 1 $search"; if($id != "") $query = "select * from $tmpdeclinaison->table where 1 $search"; $resul = mysql_query($query, $rubdeclinaison->link); $nbres = mysql_num_rows($resul); if(!$nbres) return ""; La boucle "déclinaison" Préparation d?une requête SQL : + $arg contient l?ensemble des paramètres d?entrée de la boucle. Les variables homo- nymes reçoivent les valeurs renseignées pour ces paramètres grâce à la fonction liretag() + Les variables internes nécessaires sont initialisées. + $search va définir la requête en fonction des valeurs des paramètres d?entrées $de- clinaison et/ou $id + Instanciation de 3 objets : - $tdeclidisp - $tdeclidispdesc - $exdecprod while( $row = mysql_fetch_object($resul)){ if($courante == "1" && ($row->id != $declinaison)) continue; else if($courante == "0" && ($row->id == $declinaison)) continue; if($id != "") $tmpdeclinaisondesc->charger($row->id, $_ SESSION['navig']->lang); else $tmpdeclinaisondesc->charger($row->declinaison, $_ SESSION['navig']->lang); if($id != "") $temp = str_replace("#ID", "$row->id", $texte); else $temp = str_replace("#ID", "$row->declinaison", $texte); $temp = str_replace("#TITRE", "$tmpdeclinaisondesc->titre", $temp); $temp = str_replace("#CHAPO", "$tmpdeclinaisondesc->chapo", $temp); $temp = str_replace("#DESCRIPTION", "$tmpdeclinaisondesc- >description", $temp); $temp = str_replace("#PRODUIT", "$produit", $temp); $res .= $temp;} return $res;} function boucleDeclidisp($texte, $args){ global $declidisp; $declinaison = lireTag($args, "declinaison"); $id = lireTag($args, "id"); $produit = lireTag($args, "produit"); $classement = lireTag($args, "classement"); $stockmini = lireTag($args, "stockmini"); $courante = lireTag($args, "courante"); $num = lireTag($args, "num"); $search = ""; $liste= ""; $tabliste[0]= ""; $res= ""; if($declinaison!= "") $search.=" and declinaison=\"$declinaison\""; if($id !="") $search.=" and id=\"$id\""; $tdeclidisp = new Declidisp(); $tdeclidispdesc = new Declidispdesc(); $exdecprod = new Exdecprod(); if($classement == "alpha") $order="order by titre"; else if($classement == "alphainv") $order="order by titre desc"; $query = "select * from $tdeclidisp->table where 1 $search"; $resul = mysql_query($query, $tdeclidisp->link); $i=0; $stockok = 0; while($row = mysql_fetch_object($resul)){ if($stockmini && $produit){ $stock = new Stock(); $stock->charger($row->id, $produit); if($stock->valeur<$stockmini) continue; $stockok = 1; } $liste .= "?" . $row->id . "?,"; $tabliste[$i++] = $row->id; } if($stockmini && !$stockok) return ""; $liste = substr($liste, 0, strlen($liste) - 1); Préparation d?une requête SQL : + $arg contient l?ensemble des paramètres d?entrée de la boucle. Les variables homo- nymes reçoivent les valeurs renseignées pour ces paramètres grâce à la fonction liretag() + Les variables internes nécessaires sont initialisées. + $search va définir la requête en fonction des valeurs des paramètres d?entrées $de- clinaison et/ou $id + Instanciation de 3 objets : - $tdeclidisp - $tdeclidispdesc - $exdecprod + Exécution de la requête SQL sur la table ?declidisp? + $resul reçoit le résultat obtenu par la requête sur la base + Si 1°) $stockmini et $produit sont rensei- gnées et 2°) si ?valeur? pour le couple pro- duit/declidisp dans ?stock? est supérieur à $stockmini alors $stockok=1 + $liste reçoit l?ID de la declidisp (concaté- nation des ID séparés par une virgule) +$tabliste, est un tableau : l?ID de la decli- disp est stocké dans une ligne +Si$stockminiestrenseignéet $stockok=0 alors fin de la fonction + La dernière virgule de $liste est ôtée Pour chaque declidisp identifié par la re- quête : 156 La boucle "declidisp" if($classement != ""){ $liste2=""; if($liste != ""){ $query = "select * from $tdeclidispdesc->table where declidisp in ($liste) and lang=?" . $_SESSION[?navig?]->lang . "? $order"; $resul = mysql_query($query, $tdeclidispdesc->link); } i=0; while($row = mysql_fetch_object($resul)){ $liste2 .= "?" . $row->declidisp . "?,"; $tabliste2[$i++] = $row->declidisp; } $liste2 = substr($liste2, 0, strlen($liste2) - 1); } if($classement != "" && isset($tabliste2)) $tabliste = $tabliste2; if(! count($tabliste)) return ""; for($i=0; $i<count($tabliste); $i++){ if($num != "" && $num == $i) break ; if($courante == "1" && ($tabliste[$i] . "-" != $declidisp)) continue; else if($courante == "0" && ($tabliste[$i] ."-" == $declidisp)) continue; if($exdecprod->charger($produit, $tabliste[$i])) continue; $tdeclidisp = new Declidisp(); $tdeclidisp->charger($tabliste[$i]); $tdeclidispdesc = new Declidispdesc(); $tdeclidispdesc->charger_declidisp($tabliste[$i], $_SESSION[?navig?]- >lang); if(! $tdeclidispdesc->titre) $tdeclidispdesc->charger_ declidisp($tabliste[$i]); $temp = str_replace("#ID", $tdeclidispdesc->declidisp, $texte); $temp = str_replace("#DECLINAISON", $tdeclidisp->declinaison, $temp); $temp = str_replace("#TITRE", "$tdeclidispdesc->titre", $temp); $temp = str_replace("#PRODUIT", "$produit", $temp); $res .= $temp; } return $res; } function boucleDecval($texte, $args){ $article = lireTag($args, "article"); $declinaison = lireTag($args, "declinaison"); $ref = lireTag($args, "ref"); if($article == "") return ""; La boucle "decval" + Prise en compte du paramètre d?entrée $num + Paramètre d?entrée $courante : -- =1: seule la declidisp correspondant à $declidisp (paramètre d?url) est prise en compte. -- =0 : si declidisp = $declidisp elle est igno- rée + Si declidisp est désactivée pour ce pro- duit (présente dans ?exdecprod?) elle est ignorée. + $tdeclidisp reçoit l?ID de la declidisp et $tdeclidispdesc les valeurs associées + Les paramètres de sortie pour cette de- clidisp sont renseignés : -- #ID : l?ID de la declidisp -- #DECLINAISON : l?ID de la déclinaison -- #TITRE : le titre de declidisp -- #PRODUIT : l?ID du produit Si $classement est renseigné et $liste non vide : + Une requête sur la table ?declidispdesc? est exécutée pour remonter les lignes cor- respondant à la $liste de declidisp en fonction de $order. Pour chaque declidispdesc identifiée par la requête : + $liste2 reçoit l?ID de la declidispdesc (concaténation des ID séparés par une virgule) +$tabliste2, est un tableau : l?ID de la de- clidispdesc est stocké dans une ligne + la dernière virgule de $liste2 est ôtée Pour chaque declidisp stockée dans $ta- bliste : + si $classement est renseigné, l?ordre des ID de $liste2 remplace $liste1 + $arg contient l?ensemble des paramètres d?entrée de la boucle. Les variables homo- nymes reçoivent les valeurs renseignées pour ces paramètres grâce à la fonction liretag(). + $article est obligatoire 157 $res = ""; $tdeclinaison = new Declinaison(); $tdeclinaisondesc = new Declinaisondesc(); $tdeclidisp = new Declidisp(); $tdeclidispdesc = new Declidispdesc(); for($compt = 0; $compt<count($_SESSION[?navig?]->panier- >tabarticle[$article]->perso); $compt++){ $tperso = $_SESSION[?navig?]->panier->tabarticle[$article]- >perso[$compt]; if($declinaison != "" && $declinaison != $tperso->declinaison) continue; $tdeclinaison->charger($tperso->declinaison); $tdeclinaisondesc->charger($tdeclinaison->id, $_ SESSION[?navig?]->lang); if($tdeclinaison->isDeclidisp($tperso->declinaison)){ $tdeclidisp->charger($tperso->valeur); $tdeclidispdesc->charger_declidisp($tdeclidisp->id, $_SESSION[?navig?]->lang); $valeur = $tdeclidispdesc->titre; } else $valeur = $tperso->valeur; $temp = str_replace("#DECLITITRE", "$tdeclinaisondesc->titre", $texte); $temp = str_replace("#DECLINAISON", "$tdeclinaisondesc- >declinaison", $temp); $temp = str_replace("#REF", "$ref", $temp); $temp = str_replace("#ARTICLE", "$article", $temp); $temp = str_replace("#VALEUR", "$valeur", $temp); $temp = str_replace("#DECLIDISP", "$tdeclidispdesc->declidisp", $temp); $res .= $temp; } return $res; } function boucleStock($texte, $args){ $declidisp = lireTag($args, "declidisp"); $produit = lireTag($args, "produit"); $article = lireTag($args, "article"); $declinaison = lireTag($args, "declinaison"); if($declinaison) for($i=0; $i<count($_SESSION[?navig?]->panier->tabarticle[$article]- >perso); $i++) if($_SESSION[?navig?]->panier->tabarticle[$article]->perso[$i]- >declinaison == $declinaison) $declidisp = $_SESSION[?navig?]->panier- >tabarticle[$article]->perso[$i]->valeur; if($produit == "") return ""; + Initialisation de $res + Instanciation de 4 objets : - $tdeclinaison - $tdeclinaisondesc - $tdeclidisp - $tdeclidispdesc Pour chaque déclinaison / declidisp ($per- so) enregistrée dans le panier pour cet $article : + Dans $tdeclinaison et $tdeclinaison- desc sont chargées depuis la base, les va- leurs correspondant à l?ID de déclinaison + Si le paramètre d?entrée $déclinaison est renseigné déclinaison doit lui correspon- dre. + Si déclinaison possède une ou + declidisp dans ?declidisp?, les valeurs de celle corres- pondant à declidisp sont chargées dans $tdeclidisp et $tdeclidispdesc et $valeur en prend le titre. + Sinon $valeur prend l?ID de la declidisp du panier. + Les paramètres de sortie sont renseignés : -- #DECLITITRE : le titre de déclinaison -- #DECLINAISON : l?ID de déclinaison -- #REF : la ref de l?article -- #ARTICLE : l?ID de l?article -- #VALEUR : la valeur de $valeur -- #DECLIDISP : l?ID de declidisp + $arg contient l?ensemble des paramètres d?entrée de la boucle. Les variables homo- nymes reçoivent les valeurs renseignées pour ces paramètres grâce à la fonction liretag(). Si $declinaison est renseigné en entrée Pour chaque déclinaison / declidisp ($per- so) enregistrée dans le panier pour cet $article : + Si declinaison =$declinaison, alors $de- clidisp = declidisp + Si $produit n?est pas renseigné fin de la fonction 158 La boucle "stock" if($declidisp != ""){ $stock = new Stock(); $stock->charger($declidisp, $produit); $stock_dispo = $stock->valeur; } else { $tmpprod = new Produit(); $tmpprod->charger_id($produit); $stock_dispo = $tmpprod->stock; } $tmpprod = new Produit(); $tmpprod->charger_id($produit); $prix = $tmpprod->prix + $stock->surplus; $prix2 = $tmpprod->prix2 + $stock->surplus; $temp = str_replace("#ID", "$stock->id", $texte); $temp = str_replace("#PRIX2", "$prix2", $temp); $temp = str_replace("#PRIX", "$prix", $temp); $temp = str_replace("#SURPLUS", "$stock->surplus", $temp); $temp = str_replace("#DECLIDISP", "$declidisp", $temp); $temp = str_replace("#PRODUIT", "$produit", $temp); $temp = str_replace("#VALEUR", "$stock_dispo", $temp); $temp = str_replace("#ARTICLE", "$article", $temp); $compt ++; if(trim($temp) !="") $res .= $temp; return $res; } function bouclePanier($texte, $args){ $produitdesc = new Produitdesc(); $produitdesc->charger($_SESSION[?navig?]->panier->tabarticle[$i]- >produit->id, $_SESSION[?navig?]->lang); $declidisp = new Declidisp(); $declidispdesc = new Declidispdesc(); $declinaison = new Declinaison(); $declinaisondesc = new Declinaisondesc(); $dectexte = ""; $decval = ""; for($compt = 0; $compt<count($_SESSION[?navig?]->panier- >tabarticle[$i]->perso); $compt++){ $tperso = $_SESSION[?navig?]->panier->tabarticle[$i]->perso[$compt]; $declinaison->charger($tperso->declinaison); if($declinaison->isDeclidisp($tperso->declinaison)){ $declidisp->charger($tperso->valeur); $declidispdesc->charger($declidisp->id); $decval .= $declidispdesc->titre . " "; } else $decval .= $tperso->valeur . " "; $declinaisondesc->charger($tperso->declinaison, $_SESSION[?navig?]- >lang); $dectexte .= $declinaisondesc->titre . " " . $declidispdesc->titre . " "; } La boucle "panier" (extraits) + Si $declidisp est renseigné, un objet $stock_dispo est instancié et reçoit les va- leurs de stock du couple produit/declidisp dans ?stock? + Sinon la valeur du champ ?stock? de la ta- ble ?produit? correspondant à $produit est assigné à $stock_dispo + Les paramètres de sortie sont renseignés : -- #ID : ID de la ligne du couple produit/ declidisp dans ?stock? -- #PRIX2 : Prix2 du $produit + surplus -- #PRIX : Prix du $produit + surplus -- #SURPLUS : surplus attaché au couple $produit/declidisp dans ?stock? -- #DECLIDISP : ID du paramètre d?entrée declidisp -- #PRODUIT : l?ID du paramètre d?entrée produit. -- #VALEUR : valeur de $stock_dispo -- #ARTICLE : numéro d?$article 159 + 4 objets sont initialisés : -- $declidisp -- $declidispdesc -- $declinaison -- $declinaisondesc + 2 variables internes sont initialisées : -- $dectexte -- $decval Pour chaque déclinaison / declidisp ($per- so) enregistrée dans le panier Pour afficher les déclinaisons sur une fiche produit et pour transmettre au panier les valeurs sélectionnées par un client pour ces déclinaisons, il est nécessaire de prévoir un formulaire dans la boucle "produit", proposé dans le Wiki et illustré de cette manière : 160 type = "PRODUIT" ref ="#PRODUIT_REF" type = "DECLINAISON" rubrique ="#RUBRIQUE_ID" produit="#ID" type = "DECLIDISP" declinaison ="#ID" produit="#PRODUIT" stockmini="1" <option value="#ID" /> #TITRE </option> #TITRE <select name="declinaison#ID"> </select> Produit Déclinaison Declidisp Rubrique Stock Exdecprod ? La boucle DECLINAISON Détermine si pour un produit donné une ou plusieurs déclinaisons sont actives dans sa rubrique de rattache- ment. ? La boucle DECLIDISP Pour chaque déclinaison identifiée, vérifie si les declidisp sont actives au niveau de chaque produit (exdec- prod) ? La boucle DECLIDISP Si produit et stockmini sont rensei- gnés, vérifie l'état du stock et filtre l'affichage en fonction de la valeur renseignée dans stockmini. ? La boucle PRODUIT Boucle principale, générant l'ID du produit initiant la chaîne. b La table EXDECPROD Donne les declidisp désactivées pour un produit lors de l'exécution d'une boucle DECLIDISP a La table STOCK Donne le stock d'une declidisp pour un produit, sans notion d'activation ni au niveau de la rubrique ni au ni- veau du produit. Affichage des declidisp acti- ves d'un produit pour les décli- naisons actives au niveau de la rubrique La combinaison de boucles proposée par défaut restitue bien les declidisp activées au „ „ niveau du produit (contrôle réalisé au sein de la boucle declidisp) pour les déclinaisons acti- vées au niveau de la rubrique de rattachement (contrôle réalisé par la boucle "déclinaison"). C'est même la seule à permettre de bénéficier pleinement de ces deux fonctionnalités du BO. Cependant, elle pose un problème d'affichage concernant les declidisp : si un rubrique contient des produits qui ont besoin de la déclinaison et d'autres qui n'en n'ont pas besoin ces derniers afficheront la déclinaison mais sans declidisp (désactivées au niveau de la fiche produit) : la boucle conditionnelle joue sur l'activation ou non de la déclinaison au niveau de sa rubrique. Les règles de codage d'une boucle conditionnelle interdisent de la placer au niveau de la declidisp. L?url par défaut #PANIER ne transmet pas au panier la déclidisp sélectionnée par un „ „ client c'est pourquoi il faut passer par un formulaire pour transférer au panier la référence du produit mais aussi sa déclinaison et la declidisp sélectionnée. Il est nécessaire de transmet- tre ces valeurs, non seulement pour les afficher dans le panier et la commande mais aussi pour le déstockage qui intervient ultérieurement -- 1°) Dans Thélia, Le mot "perso" est attaché à cette notion de déclinaison (+ valeur de cette déclinaison) d?un article dans le panier. Perso est une classe, c?est aussi un attribut de la variable de session $_SESSION[?navig?]->panier->article et plus précisémment, dans un article, $perso est un tableau listant des objets de classe Perso comme le confirme la classe Article (classes/Article.class.php) : var $perso=array(); function Article($ref, $quantite, $perso=""){ $this->perso = new Perso(); $this->produit = new Produit(); $this->produit->charger($ref); $this->produitdesc = new Produitdesc(); $this->produitdesc->charger($this->produit->id); $this->quantite = $quantite; $this->perso = $perso; Imaginons un produit muni d'une déclinaison taille (ID=1) et couleur (ID=2) sélectionné avec les valeurs (S) et (bleu) par un client. -- 2°) L?url obtenue par un validation du formulaire sera du type : panier.php?action=ajouter&ref=1234&delinaison1=5&declinaison2=7 -- 3°) Les deux paramètres d?url concernant les déclinaisons taille (ID=1) et couleur (ID=2) et leurs valeurs associées (S =5 et bleu=7) sont récupérés et stockés dans la variable de session $_SESSION[?navig?]->panier->article->perso par la fonction ajouter() exécutée suite à l?action du client, transmise elle aussi en paramètre d?url. ajouter() est logiquement décrite sur le fichier fonction/actions.php. Simplifiée pour notre démonstration, elle donne : 161 function ajouter($ref, $quantite=1, $append=0, $nouveau=0){ $perso = array(); $i=0; foreach ($_REQUEST as $key => $valeur) { if(strstr($key, "declinaison")){ $perso[$i] = new Perso(); $perso[$i]->declinaison = substr($key, 11); $perso[$i]->valeur = stripslashes($valeur); $i++; } } $_SESSION[?navig?]->panier->ajouter($ref, $quantite, $perso, $append, $nouveau); -- 4°) Nous reviendrons en détail sur l?intégralité du processus mais pour l?instant conten- tons-nous de suivre le parcours des déclinaisons : au final, dans la variable de session $_ SESSION[?navig?]->panier->article nous retrouverons bien les valeurs demandées par le client, stockées de la manière suivante : [perso] => Array ( [0] => Perso ( [déclinaisons] => 1 [valeur] => 5 ) [1] => Perso ( [déclinaisons] => 2 [valeur] => 7 ) ) La boucle "declidisp" possède un paramètre d'entrée "produit" qui doit être couplée „ „ avec le paramètre "stockmini" pour être pris en compte par la boucle : en natif, cette règle empêche d'envisager la gestion des déclinaisons or de la gestion des stocks des declidisp. Nous ne pouvons pas utiliser la boucle "declidisp" dans une boucle "panier" car dans ce der- nier, nous ne disposons pas de l?ID du produit, seulement de sa REF. Pour les ID dans un panier, nous raisonnerons toujours en terme d?articles : la boucle "decval" est prévue pour boucler sur les articles d?un panier et le panier lui-même est muni de paramètres de sortie traduisant les déclinaisons et déclidisp qui y sont stockées. La boucle "stock" peut être envisagée de 2 manières : „ „ -- a) Incluse traditionnellement dans une boucle "panier" sur le squelette panier.html, elle porte le paramètre d?entrée ?declinaison? : de la sorte elle boucle sur les articles du panier. -- b) Elle peut aussi s?envisager hors du panier : elle ne devra plus alors porter le paramètre d?entrée ?declinaison? mais ?declidisp? : elle bouclera ainsi sur la table ?stock? et permettra donc d?afficher l?état d?un stock pour une declidisp sur un autre squelette que panier.html. 162 Session PANIER Boucle STOCK Table STOCK Affiche STOCK Table PRODUIT declinaison= "IDDEC" declidisp="" produit= "IDPROD" declinaison= "" declidisp="IDDISP" produit= "IDPROD" Si IDDEC== perso- >declinaison IDDISP = perso->valeur Sinon Stock = valeur stock de IDPROD stock = valeur pour IDDISP ou stock = valeur pour IDDISP La boucle "decval" prévoit la possibilité que la valeur de la déclinaison (declidisp) enre- „ „ gistrée dans le panier ne corresponde à aucune declidisp dans la table ?declidisp? : dans ce cas de figure, elle restitue dans le panier 1°) le titre de la déclinaison et 2°) la valeur "brute" correspondante et non le titre de la declidisp correspondante puisqu?el- le n?existe pas. En effet "decval", charge l?ID de la déclinaison stocké dans $perso. Puis elle lui attribue une valeur. Cette valeur est le titre de la declidisp correspondante si celle-ci existe dans la table homonyme, sinon la valeur stockée dans perso est restituée : if($tdeclinaison->isDeclidisp($tperso->declinaison)){ $tdeclidisp->charger($tperso->valeur); $tdeclidispdesc->charger_declidisp($tdeclidisp->id, $_SESSION[?navig?]->lang); $valeur = $tdeclidispdesc->titre; } else $valeur = $tperso->valeur; Ceci en vertu de la méthode isdeclidisp() de la classe Déclinaison, utilisée par la boucle. Cette méthode, décrite dans le fichier classes/Declinaison.class.php se présente ainsi : function isDeclidisp(){ $declidisp = new Declidisp(); $query = "select * from $declidisp->table where declinaison=\"" . $this->id . "\""; $resul = mysql_query($query); return mysql_num_rows($resul); } 163 function boucleProduit($texte, $args, $type=0){ $declinaison = lireTag($args, "declinaison"); $declidisp = lireTag($args, "declidisp"); $declival = lireTag($args, "declival"); $declistockmini = lireTag($args, "declistockmini"); if($declidisp != ""){ if(! strstr($declinaison, "-")) $declinaison .= "-"; if(! strstr($declidisp, "-")) $declidisp .= "-"; if(! strstr($ldeclistockmini, "-")) $ldeclistockmini .= "-"; $ldeclinaison = explode("-", $declinaison); $ldeclidisp = explode("-", $declidisp); $ldeclistockmini = explode("-", $declistockmini); $i = 0; $liste=""; $exdecprod = new Exdecprod(); $stock = new Stock(); while($i<count($ldeclinaison)-1){ $declinaison = $ldeclinaison[$i]; $declidisp = $ldeclidisp[$i]; $declistockmini = $ldeclistockmini[$i]; $query = "select * from $exdecprod->table where declidisp=?$declidisp?"; $resul = mysql_query($query); if(mysql_num_rows($resul)) while($row = mysql_fetch_object($resul)) $liste .= "?$row->produit?, "; if($liste!="") { $liste = substr($liste, 0, strlen($liste) - 2); $search .= " and $produit->table.id not in($liste)"; 164 Ainsi, la boucle "decval" est aux articles d?un panier ce que la boucle "declidisp" est aux pro- duits. Elle est prévue pour restituer la valeur d?une déclinaison (declidisp) mais elle boucle sur les ID des déclinaisons stockés dans le tableau [perso]. Ces ID sont soumis à la méthode isdeclidisp() qui vérifie si pour cet ID, un ID de declidisp existe dans la table ?declidisp?. -- Si oui, son titre est chargé depuis la table ?desclidispdesc? et alimente la valeur de la décli- naison. Le contrôle de cohérence sur les declidisp activées/désactivées n?est pas nécessaire et non implémenté : au stade du panier, seules les declidisp activées peuvent être stockées 165 if($declistockmini != ""){ $query = "select * from $stock->table where declidisp=?$declidisp? and valeur>=?$declistockmini?"; $resul = mysql_query($query); if(mysql_num_rows($resul)) while($row = mysql_fetch_object($resul)) $liste .= "?$row->produit?, "; if($liste!= "") { $liste = substr($liste, 0, strlen($liste) - 2); $search .= " and $produit->table.id in($liste)"; } else return ""; } $i++; }} $query = "select * from $produit->table where 1 $search $order $limit"; dans l?attribut $valeur d?un objet $perso parce que la boucle "declidisp" précédente a exclu les declidisp désactivées. -- Sinon la valeur "brute" (telle qu?elle est remontée dans $perso->valeur) alimente la valeur de la déclinaison (declidisp). C?est en quelques sorte une manière de prévoir une fonction- nalité de type "déclinaison libre" en mode simplifié (les valeurs libres de déclidisp sont co- dées directement sur le squelette, et n?auront ni stock ni surplus) Table DECLIDISPDESC Affiche DECLIDISP declinaison= "IDDEC" article= "IDART" Si des declidisp sont attri- buées à IDDEC Alors Sinon declidisp = perso->valeur Session PANIER Table DECLIDISP Boucle DECVAL declidisp = titre corres- pondant à perso->valeur Nous disposons donc de plusieurs manières pour afficher dans un panier le couple „ „ déclinaison/valeur sélectionnée : la boucle "decval" restitue strictement la même chose que le paramètre de sortie #DECVAL de la boucle "panier". Quant à #DECTEXTE peu ou prou de différence : il renvoie la déclinaison et sa valeur si cette dernière est bien une declidisp 166 et non une valeur personnalisée. La boucle "produit" dispose également de paramètres d?entrée relatifs aux déclinai- „ „ sons : nous notons : ?declinaison?, ?declidisp?, ?declival? et ?declistockmini?. Les deux premiers, ?declinaison? et ?declicdisp? permettent de cibler par une boucle "produit" les produits pour lesquels une ou plusieurs declidisp ont été définies et sont activées. Ceci est très utile pour restituer des recherches basées sur une valeur de déclinaison (par exem- ple chercher tous les T-shirts de taille S), ou pour des mises en forme complexes de vos pro- duits. Pour les 2 suivants, quelques détails vont s?imposer. Nous allons à nouveau présenter la fonction boucleProduit(), mais à nouveau incomplète, simplifiée pour l?observer cette fois-ci sous l?angle des déclinaisons -- Nous voyons comment sont restitués dans une boucle "produit" les produits selon la ou les declidisp dont ils sont munis : comme l?indique le Wiki le paramètre ?declidisp? est attaché au paramètre ?declinaison? : les 2 doivent être renseignés pour que le boucle affiche quelque chose : en effet la boucle php "while" boucle sur les différentes valeurs renseignées dans le paramètre ?declinaison? donc aucun produit ne sera identifié si seul le paramètre ?declidisp? est utilisé car dans ce cas, "while" n?effectue aucune passe. -- Nous voyons ensuite comment doivent se coder ce type de boucle : <THELIA_PROD type="PRODUIT" declinaison="1-2" declidisp= "5-7"> BLABLABLA </THELIA_PROD> Cet exemple remonte le produit de l?exemple précédent, le T-shirt bleu de taille S et tous les produits partageant ces deux declidisp. -- Nous pouvons observer le verrou opéré par la table ?exdecprod? sur le même modèle qu?avec la boucle "declidisp" : les produits pour lesquels une declidisp est désactivée ne se- ront pas affichés par une boucle "produit" portant cette declidisp en paramètre d?entrée. -- Nous voyons aussi le rôle tenu par le paramètre ?declistockmini? : si nous avons renseigné les paramètres ?declinaison? et ?declidisp?, nous pouvons rajouter ce troisième paramètre pour n?afficher que les produits dont la declidisp ciblée est munie d?un stock supérieur ou égal à la valeur de ce paramètre. -- en revanche, nous n?observons aucun traitement d?aucune sorte du paramètre ?declival?. Issu d?une ancienne version de Thélia ou prédéfini pour une fonctionnalité qui n?est pas na- tive, ?declival? ne modifie en rien le traitement d?une boucle "produit". La recherche par mots-clés dans 4. Thélia De très nombreuses manières de concevoir un moteur de recherche sont possibles dans Thélia. Ce qui m?intéresse ici concerne la fonctionnalité des mots clés implémentée dans Thélia et sollicitée pour le moteur de recherche par défaut du template de base. Le moteur est un formulaire très simple : „ „ Livre 4 La commande Thélia Comprendre Agir Modifier Enrichir Adapter Voyage au centre d?une boucle LAppréhender Thélia par la pratique 7. Appréhender Thélia par la théorie : La parcours d?une commande Maintenant que nous avons découvert les deux parties constitutives de Thélia, le Front Of- fice et le Back Office, nous pouvons envisager d?étudier une notion transversale capitale à maîtriser si vous souhaitez utiliser Thélia dans de bonnes conditions : le parcours d?une commande. Le parcours d?un acte d?achat sur Internet est complexe, il doit prendre en compte de nom- breux facteurs législatifs, financiers, comptables, technologiques, régionaux... De cette com- plexité naît une grande diversité de besoins. Or Thélia n?est pas forcément exemplaire sur ce point car sa grande simplicité a du mal à prendre en compte cette diversité : pour s?en convaincre il suffit d?aller faire un tour sur le forum : il déborde de questionss sur les factures, les mails de confirmation, la TVA, la fac- turation à l?étranger, les systèmes de paiement des banques ou de PayPal, les prix TTC ou HT... L?objectif de ce chapitre est donc de présenter la "motorisation" d?une commande type, afin de bien comprendre par la suite où et comment personnaliser le moteur si nécessaire pour qu?il réponde à vos besoins spécifiques. Une commande type est bien entendue parfaitement fonctionnelle et opérationnelle par dé- faut, mais elle ne prend en compte qu?un nombre limité de situations et vous souhaiterez peut-être modifier une ou plusieurs étape(s) d?une commande pour que Thélia s?adapte à votre situation personnelle. Cette partie (théorique) et la suivante (pratique) devraient vous aider. Une commande débute sur le Front Office et se poursuit sur le Back Office jusqu?à son terme (la commande est expédiée ou annulée), par étapes successives : nous allons en étudier les principales séquences : Chapitre 1 : Les étapes constitutives d?une commande Thélia Chapitre 2 : Validation de l?adresse et du mode de livraison #URLCMD Chapitre 3 : La gestion des remises dans Thélia Chapitre 4 : La validation de la commande et du moyen de paiement #URLPAYER Chapitre 5 : Le paiement en ligne ou différé avec Thélia Chapitre 6 : La gestion d?une commande Thélia dans le Back Office Chapitre 7 : La gestion du stock dans Thélia Chapitre 8 : La gestion du port dans Thélia Chapitre 9 : La gestion de la TVA dans Thélia Chapitre 10 : La gestion de la facture et du bon de livraison dans Thélia Chapitre 11 : Les mails automatiques de la commande Thélia Chapitre 12 : Annexe : la fonction paiement() en détail 168 169 Les étapes constitutives d?une commande 1. Thélia La partie Front Office d?une commande 1. Une commande ne s?enregistre dans la base de données qu?à partir du moment où cinq conditions sont remplies par le client depuis le Front Office : -- 1 -- Un panier validé -- 2 -- Un client identifié (=> compte client créé) -- 3 -- Une adresse de livraison validée -- 4 -- Un mode de livraison validé -- 5 -- Un moyen de paiement validé : notons que cette condition diffère d?un paiement va- lidé. En plus de ces 5 conditions obligatoires, 2 paramètres optionnels peuvent être pris en comp- te dans une commande : -- les remises/promotions et -- le paiement en ligne. Concrètement la motorisation de ce parcours pour le client (côté Front Office donc) est or- ganisée par un jeu d?URL liant les pages suivantes : Panier.php validation du panier commande.php validation d?une promotion validation du moyen de paie- ment 1 5 6 adresse.php #URLCMD #URLPAYER $securise=1 $securise=1 $securise=1 connexion.php Identification client 2 adresse.php validation d?une adresse livraison validation du mode de livraison 3 4 cheque.php Information client 7 virement.php Information client 7 paiment CB Paiement client 7 Il est maintenant temps de nous intéresser de nouveau à la variable de session $_ SESSION[?navig?] , qui compte dans ses attributs un objet : la variable $commande. Puisque des éléments constitutifs d?une commande vont devoir être conservés lors de ce parcours de page en page, il est logique de croiser le chemin de notre variable de session ?navig? à ce moment. Je recopie les principaux attributs de l?objet $commande ici : [commande] => Commande Object ( [id] => [client] => [adrfact] => [adrlivr] => [date] => [datefact] => [ref] => [transaction] => [livraison] => [facture] => [transport] => [port] => [datelivraison] => [remise] => [colis] => [paiement] => [statut] => [lang] => Nous allons détailler dans les prochains chapitres comment cet objet est utilisé lors du par- cours de la commande. De la même façon, la variable $_SESSION[?navig?]dispose d?un attribut $promo pour enregistrer les éléments nécessaires à la gestion d?une éventuelle promo enregistrée par le client sur la page commande. Une fois la partie Front Office achevée, l?enregistrement d?une commande dans la base de données est le point de départ de sa gestion dans le BO par le marchand. La partie Back Office de la commande 2. La commande va désormais évoluer et continuer son parcours selon le statut que le mar- chand lui appliquera via le BO (commande_details.php) A ce stade la commande n?est plus une variable de session mais une ligne dans la table "commande" de la base de données. Comme la variable, cette table est munie d?un champ "statut" et d?un champ "transaction" qui seront mis à contribution pour sa progression jusqu?à son terme. statut = 1 Non payé 8 statut = 2 Payé 10 statut = 3 Traitement 11 statut = 4 Envoyé 12 transaction 1 9 manuel auto manuel manuel statut = 5 Annulé 13 170 171 Une commande s?achève lorsque son statut passe en "envoyé" ou en "annulé". Ces com- mandes ne sont plus visibles directement depuis l?interface du BO mais restent en base et accessibles au client. La commande : classe, table, boucle et variable de session. 3. Nous savons désormais quel lien se cache derrière cette unicité des noms dans Thélia „ „ : une commande est caractérisée par un ensemble de paramètres, ces paramètres corres- pondent : -- aux champs de la table "commande", -- aux attributs de la classe Commande, -- donc aux attributs de la variable $_SESSION[?navig?]->commande, instanciée par cette clas- se, -- aux paramètres d?entrée et sortie des boucles "commande". La table "commande" contient toutes les commandes validées, c?est-à-dire pour les- „ „ quelles un client a utilisé l?url #URLPAYER : nous allons détailler plus bas cette url. La variable de session „ „ $_SESSION[?navig?]->commande contient les éléments constitutifs de la commande en cours lors d?une navigation sur le site : elle est sollicitée à partir de l?url #URLCMD (validation du transport) pour stocker le choix du transport et l?adresse de livrai- son puis par #URLPAYER qui finit d?alimenter ses attributs. La boucle "commande" affiche la ou les données relatives aux commandes de la table, „ „ enregistrées pour un client. Appliquons-nous maintenant à entrer dans le détail des différentes étapes que je viens de vous présenter. La première étape que nous allons découvrir n?est pas une étape de la com- mande à proprement parler : mettre un article au panier ne garantit en rien que le client ini- tialisera une commande ; néanmoins le panier est lui-même le fondement depuis lequel les données seront puisées par Thélia pour alimenter une commande. Nous allons donc nous y arrêter un moment. Nous ne détaillerons pas en revanche l?étape 2 optionnelle : elle concerne l?identification du client ou la création d?un compte et ces étapes ont déjà été abordées plus haut. Du produit à l?article : l?url 2. #PANIER et l?url adresse.php Etape 1 Un produit devient un article du panier lorsqu?un client utilise le lien #PANIER, un para- „ „ mètre de sortie de la boucle "produit". #PANIER est une contraction de l?url panier.php?action=ajouter&ref=xxx où xxx est la référence du produit identifié par la bou- cle. Lorsqu?un produit est déclinable, sa déclinaison et la valeur sélectionnée sont trans- „ „ mises au panier grâce à un formulaire dont le bouton ?submit? génère les paramètres d?url adéquats. Le Wiki donne la version-type pour ce formulaire : <form action="panier.php" method="post"> <input type="hidden" name="action" value="ajouter" /> <input type="hidden" name="ref" value="#REF" /> <T_DECLI> <THELIA_DECLI type="DECLINAISON" rubrique="#RUBRIQUE_ID" produit="#ID"> <p> #TITRE : <select name= "declinaison#ID" > <THELIA_DECLIDISP type="DECLIDISP" declinaison="#ID" produit="#PRODUIT" stockmini="1"> <option value="#ID">#TITRE</option> </THELIA_DECLIDISP> </select> </THELIA_DECLI> </p> </T_DECLI> Aucune déclinaison <//T_DECLI> <input type="submit" value="panier"> </form> Dans les deux cas „ „ l?action ajouter() est exécutée. Elle se présente de la façon suivante aux lignes 29 à 52 du fichier fonctions/action.php : -- Elle définit une quantité minimum =1 -- Elle intialise une variable $perso : un tableau qui stockera les valeurs transmises en para- mètres d?url de type : déclinaisonID et leurs valeurs associées. -- Elle exécute la méthode ajouter(] de la classe Panier. Elle lui transmet la référence du produit, la quantité définie, les valeurs stockées dans le tableau $perso et 2 variables supplé- mentaires optionnelles : -- $append -- $nouveau -- L?attribut $panier de la variable de session $_SESSION[?navig?] reçoit les résultats de l?exécu- tion de la méthode ajouter(). 172 173 function ajouter($ref, $quantite=1, $append=0, $nouveau=0){ if($quantite == "" || $quantite == 0) $quantite = 1; $perso = array(); $i = 0; foreach ($_REQUEST as $key => $valeur) { if(strstr($key, "declinaison")){ $perso[$i] = new Perso(); $perso[$i]->declinaison = substr($key, 11); $perso[$i]->valeur = stripslashes($valeur); $i++; } } $_SESSION[?navig?]->panier->ajouter($ref, $quantite, $perso, $append, $nouveau); } La méthode „ „ ajouter() de Panier est décrite sur le fichier classes/panier.class.php aux lignes 43 à 70 : function ajouter($ref, $quantite, $tdeclidisp= "", $append, $nouveau){ $existe = 0; for($i=0; $i<$this->nbart+1; $i++) if(isset($this->tabarticle[$i])) if(isset($this->tabarticle[$i]->produit->ref) && $this->tabarticle[$i]->produit->ref == $ref){ if(! count($tdeclidisp)) {$existe = 1; $indice = $i;} for($j=0; $j<count($this->tabarticle[$i]->perso); $j++){ if($this->tabarticle[$i]->perso[$j] == $tdeclidisp[$j]) {$existe = 1; $indice = $i;} else { $existe = 0; break; } } } if(!$existe || $nouveau == 1) $this->tabarticle[$this->nbart] = new Article($ref, $quantite, $tdeclidisp); else if($existe && $append) $this->tabarticle[$indice]->quantite += $quantite; if(isset($this->tabarticle[$this->nbart]) && isset($this->tabarticle[$this->nbart]- >produit) && $this->tabarticle[$this->nbart]->produit->ref) $this->nbart++; } La méthode ajouter() contrôle le contenu du panier : -- 1°) Si l?action ajouter() concerne un produit déjà présent dans le panier (même référence et mêmes déclinaison (ID) + valeur associée (ID declidisp ou autre)) l?action ajouter() dans le panier est : a) soit ignorée (par défaut) b) soit la quantité du produit dans le panier est incrémentée de la quantité sélectionnée de- puis le FO ou =1 par défaut. Pour obtenir cette solution, il faut ajouter un paramètre d?url dans l?adresse #PANIER (ou le bouton submit si vous passez par un formulaire) : append=1 c) soit répétée : le produit apparaît deux fois dans le panier. Pour obtenir cette solution, il faut ajouter un paramètre d?url dans l?adresse #PANIER (ou le bouton submit si vous passez par un formulaire) : nouveau=1 -- 2°) Si le produit ajouté n?est pas présent dans le panier, il y est ajouté par l?instanciation d?un objet de classe Article : le constructeur de cette classe génère un objet $article complet à partir des valeurs transmises en argument. -- 3°) Dans ce dernier cas, l?attribut $nbart de $panier est incrémenté. Voici l?intégralité du fichier classes/panier.class.php : „ „ class Article { var $produit; var $produitdesc; var $quantite; var $perso=array(); function Article($ref, $quantite, $perso=""){ $this->perso = new Perso(); $this->produit = new Produit(); $this->produit->charger($ref); $this->produitdesc = new Produitdesc(); $this->produitdesc->charger($this->produit->id); $this->quantite = $quantite; $this->perso = $perso; for($i=0;$i<count($perso); $i++){ $declinaison = new Declinaison(); $declinaison->charger($perso[$i]->declinaison); if($declinaison->isDeclidisp()){ $stock = new Stock(); $stock->charger($perso[$i]->valeur, $this->produit->id); if($stock->surplus != 0){ $this->produit->prix += $stock->surplus; $this->produit->prix2 += $stock->surplus; } } } } } -- Le constructeur de la classe, la fonction Article(), génère un objet $article ; il alimente les attributs de cet $article qui sont d?autres objets ($produit, $produitdesc et $perso, un tableau 174 listant des objets) + une variable $quantite -- Le constructeur prévoit également la modification du prix d?un article dans le panier en fonction du surplus tarifaire d?une declidisp. -- L?objet $article est ajouté à la variable de session $panier comme le prévoit la première fonction décrite supra, l?action ajouter(). Finalement, l?ajout d?un produit au panier (un article) peut se résumer de la manière „ „ suivante (en orange, les arguments passés à chaque fonction) : Base de données Produit Action ajouter() Produitdesc Declinaison Stock $nbrart $tabarticle [ ] $quantite $perso [ ] $id; $perso; $ref; $tva $datemodif; $stock $classement; $prix; $ecotaxe; $promo; $ligne; $garantie; $prix2; $rubrique; $nouveaute; $declinaison; $valeur; new Article() $navig-> Panier $id; $produit; $titre; $chapo; $description; $postscriptum; $lang; new Produit() new Perso() new Produitdesc() Méthode ajouter() Action ajouter() Constructeur article() REF QUANTITE APPEND NOUVEAU REF QUANTITE APPEND TDECLIDISP NOUVEAU REF QUANTITE PERSO article article New Si $existe=1 & $append=1 récupère les déclinaisons et leurs valeurs Si $existe=0 id; perso; ref; tva datemodif; stock classement; prix; ecotaxe; promo; ligne; garantie; prix2; rubrique; nouveaute; id produit; titre; chapo; description; postscriptum; lang; Charge les données correspondant à REF depuis les tables pro- duit et produitdesc Si PERSO contient des décli- naisons munies de declidisp avec un surplus défini, ce dernier modifie le prix id declidisp produit; valeur; surplus Compare REF et TDECLIDISP avec le panier id classement Si $existe=0 ou si $nouveau=1 Charge l?article dans la variable de session $navig->panier Créé un objet $article avec les données de la base Enfin notons que nous pouvons prévoir qu?un panier sera vidé à l?appel d?une page si „ „ sur celle-ci nous prévoyons une variable $reset=1 sur le fichier php correspondant. 175 La validation du mode de livraison et de 3. l?adresse de livraison = adresse.html et l?url #URLCMD Etapes 3 et 4 A ce niveau (adresse.html) le client valide une adresse de livraison et un type de transport, ceci en cliquant sur l?url #URLCMD. Ces 2 étapes sont obligatoires La validation de l?adresse de livraison 1. Cette étape consiste à définir l?adresse de livraison : celle-ci est par défaut l?adresse de fac- turation mais Thélia autorise le client à créer une ou plusieurs adresses alternatives pour la livraison (attention : il n?est pas possible de faire de la livraison multi-points avec Thélia par défaut : une seule adresse de livraison sera enregistrée pour une commande). L?attribut $adresse de la variable de session $_SESSION[?navig?] est en charge de l?enregistre- ment de l?adresse de livraison pour une commande : Sur la page adresse.html l?adresse de facturation est proposée par défaut pour la livraison (aucune adresse de livraison spécifique n?a encore été sélectionnée par le client). La créa- tion d?une adresse de livraison génère une nouvelle ligne dans la table ?adresse? de la base de données. Si une adresse spécifique est créée et sélectionnée (les adresses de livraison créées par un client s?affichent par une boucle ?adresse?), son ID alimentera l?attribut $adres- se : en effet la sélection d?une adresse de livraison spécifique sur la page adresse.html est une url du type : adresse.php?action=modadresse&adresse=xxx : xxx est l?id de l?adresse sélectionnée. modadresse() se charge de stocker la valeur xxx dans l?attribut $adresse de la variable de session ?navig?. si aucune adresse de livraison n?est sélectionnée ($adresse = 0), la fonction paiement() que nous allons détailler, utilisera l?adresse de facturation (l?adresse par défaut d?une fiche client) pour consolider la commande. La validation du mode de livraison 2. Première chose à savoir : les transports dans Thélia sont gérés sous la forme de plugins. Comme les plugins classiques ils sont entreposés avec les mêmes règles de syntaxe dans le même dossier client/plugins. Un plugin Transport est composé d?une classe étendant la classe PluginsTransport qui est dédiée à ce type de plugin ; celle-ci est elle-même une ex- tension de la classe PluginsClassiques. Ce qui les ditingue est notamment leur attribut $type = 3 (plugin classique) ou $type = 2 (plugin transport). 176 177 Voici la filiation complète de la classe Colissimo par exemple : Cnx Identification : les données en base sont accessibles Baseobj la classe mère pour les connexions PluginTrans- port charger() charger_id() Requête Requêtes sur la base add(); maj(); PluginClassi- que $type=2 Colissimo Les plugins transport héri- tent des clas- ses suivantes : #URLCMD est un paramètre de sortie de la boucle "transport" décrite dans fonctions/bou- cles.php après la ligne 2341. Voyons ce que nous apprend la fonction boucleTransport() dont voici un extrait : $query = "select * from $modules->table where type=?2? and actif=?1? $search order by clas- sement"; $resul = mysql_query($query, $modules->link); $nbres = mysql_num_rows($resul); while( $row = mysql_fetch_object($resul)){ $modules = new Modules(); $modules->charger_id($row->id); include_once("client/plugins/" . $modules->nom . "/$nom.class.php"); $tmpobj = new $nom(); ... $temp = str_replace("#URLCMD", "commande.php?action=transport&amp;id=" . $row- >id, $temp); C?est la valeur = transport du paramètre d?url action qui effectue le travail lors de l?utilisation du lien #URLCMD : pour chaque ("while") plugin de type transport (type =2) activé (actif=1), un objet de classe Module est instancié permettant l?inclusion du fichier classe du plugin grâce auquel le tag #URLCMD du squelette est remplacé par une url contenant en paramè- tre une action (valeur = transport) et un ID (valeur = ID du plugin). L?action transport(), décrite dans le fichier fonctions/actions.php (lignes 55 à 71) est relative- ment simple à déchiffrer. La voici en intégralité : function transport($id){ $transzone = new Transzone(); $pays = new Pays(); if($_SESSION[?navig?]->adresse != "" && $_SESSION[?navig?]->adresse != "0"){ $adr = new Adresse(); $adr->charger($_SESSION[?navig?]->adresse); $pays->charger($adr->pays); } else $pays->charger($_SESSION[?navig?]->client->pays); if( ! $transzone->charger($id, $pays->zone)) return; $_SESSION[?navig?]->commande->transport = $id; } La fonction transport() consiste essentiellement : -- 1°) A vérifier que le mode de livraison choisi est possible dans le pays de livraison -- 2°) A alimenter l?attribut $transport de la variable de session $_SESSION[?navig?]->commande avec l?ID du plugin choisi lorsque le visiteur à sélectionné un mode de livraison et transmis en paramètre d? url. En plus de renseigner l?adresse et le mode de livraison dans la variable de session $_ SESSION[?navig?] #URLCMD nous dirige vers la page commande.php pour la prochaine éta- pe. 178 La gestion des remises dans 4. Thélia = la page commande.html Etape 5 La page commande.html permet d?effectuer 2 opérations : - La première, optionnelle, consiste à enregistrer un code promo si le client dispose d?une remise : nous allons nous arrêter un instant pour décortiquer cette fonctionnalité. -- La seconde, systématique est la validation du moyen de paiement : nous étudierons cette fonction à l?étape suivante. Le gestion d?une remise par Thélia se décompose en 2 étapes principales : -- 1°) Le marchand enregistre une promo dans le BO -- 2°) Le client, sur la page commande lors d?un achat, entre ce code promo et bénéficie ainsi de la remise sur le total de sa commande. La création d?une remise par le marchand 1. Cette possibilité est offerte par le BO sur la page promo_modifier.php : le marchand créé un code auquel il attache une remise en euro ou en pourcentage, le type de promo (remise unitaire ou remise illimitée) et éventuellement une date de validité et/ou un minimum de com- mande. Notons qu?une remise n?est pas attribuée à un client particulier : toute personne capable d?entrer le code créé bénéficiera de la promo prévue. Notons aussi qu?aucun contrôle de cohérence n?est prévu pour la création de codes promo : vous pouvez enregistrer plusieurs fois le même code promo (ce qui peut poser problème) ou omettre n?importe quel champ, la remise sera créée. Attention dés lors que vous créez des codes promo : un minimum de rigueur, d?organisation et de sécurisation sont nécessaires. La page promo_modifier.php est un formulaire dans lequel le marchand enregistre les valeurs souhaitées pour la remise. Le lien "valider les modifications" organise l?enregistrement dans la table promo des remises ainsi créées, selon une cinématique bien connue maintenant : L?utilisation de l?url "valider les modifications" point vers la page précédente : promo. „ „ php. L?url embarque un paramètre : action et sa valeur : modifier ou ajouter. Elle embarque également l?ensemble des éléments constitutifs d?une promo sous la forme de paramètres et leurs valeurs (code=123456&type=S...). Sur la page promo.php : „ „ -- 1°) Des variables reçoivent les valeurs des paramètres d?url grâce à la fonction du fichier pre.php. 179 -- 2°) Les fonctions modifier() et ajouter() sont décrites et exécutées : elle insèrent dans la table promo la nouvelle promo (ou mettent à jour une promo existante), grâce à l?instan- ciation d?une classe Promo dont les attributs reçoivent les valeurs des paramètres d?url et correspondent logiquement aux champs de la table (nous connaissons bien maintenant ce principe), table que cet objet va alimenter grâce à la fonction générique add(). function ajouter( $code, $type, $valeur, $mini, $utilise, $illimite, $jour, $mois, $annee){ $promo = new Promo(); $promo->code = $code; $promo->type = $type; $promo->utilise = $utilise; $promo->illimite = $illimite; $promo->valeur = $valeur; $promo->mini = $mini; $promo->datefin = $annee . "-" . $mois . "-" . $jour . " " . "00:00:00"; $promo->add(); } L?utilisation d?une remise par le client 2. Sur la page commande.php un champ de formulaire permet au client d?entrer un code „ „ promo puis de le valider. Ce formulaire génère une url de ce type : commande.php?action=codepromo&code=XXX L?action „ „ = codepromo est décrite par la fonction codepromo() du fichier fonctions/action. php : function codepromo($code){ $promo = new Promo(); $promo->charger($code); $_SESSION[?navig?]->promo = $promo; } Nous voyons que codepromo() fait appel à la méthode charger($code) de la classe Promo : function charger($code){ $datedj = date("Y-m-d H:i:s"); return $this->getVars("select * from $this->table where code=\"$code\" and (datefin>?$datedj? or datefin=?0000-00-00?) and utilise=?0?"); } Charger($code) ne retourne un résultat que sous trois conditions : -- 1°) si le $code saisi (XXX) correspond à un code enregistré dans la table 180 -- 2°) si pour le $code saisi la valeur du champ "utilise" = 0 -- 3°) si pour le $code saisi la date de validité de la promo est respectée Si ces trois conditions sont remplies, la fonction charge les données de la promo (issues de la table promo) dans la variable de session $_SESSION[?navig?]->promo. A la validation du moyen de paiement (#URLPAYER) la fonction „ „ paiement() de Thélia va, entre autres choses, manipuler cette promo : -- 1°) D?abord elle va la faire transiter depuis la variable $_SESSION[?navig?]->promo jusqu?à la variable $_SESSION[?navig?]->commande : la valeur correspondante de la promo alimente la valeur de l?attribut $remise de l?objet $commande. -- 2°) Cette valeur sert modifier le total de la commande. -- 3°) Enfin la fonction paiement() va modifier la valeur du champ "utilise" de la table (il pas- sera de 0 à 1 si la remise est unitaire). A noter : le système de promo dans Thélia ne prévoit pas par défaut de report : si un client utilise un code promo de 100? sur une commande dont le total est 50?, cette commande sera éditée avec un total négatif (-50?) mais le code promo ne sera pas décrémenter de 50? : s?il était unitaire il n?est plus utilisable et s?il est illimité sa valeur reste de 100?. Nous allons retrouver la fonction paiement() dans le prochaines étapes. Pour l?instant j?ai juste extrait de cette fonction les lignes correspondant au traitement d?un code promo valide, renseigné par le client : if($_SESSION[?navig?]->promo->id != ""){ if($_SESSION[?navig?]->promo->type == "1" && $_SESSION[?navig?]->promo->mini <= $total) $commande->remise += $_SESSION[?navig?]->promo->valeur; else if($_SESSION[?navig?]->promo->type == "2" && $_SESSION[?navig?]->promo->mini <= $total) $commande->remise += $total * $_SESSION[?navig?]->promo->valeur / 100; $_SESSION[?navig?]->promo->utilise = 1; $commande->maj(); $temppromo = new Promo(); $temppromo->charger_id($_SESSION[?navig?]->promo->id); if(! $temppromo->illimite) $temppromo->utilise= "1"; $temppromo->maj(); $_SESSION[?navig?]->promo = new Promo(); } 181 La validation d?une commande = la valida- 5. tion du moyen de paiement = commande.html et l?url #URLPAYER Etape 6 Le choix du moyen de paiement (sur la page commande.html) valide la commande : ceci est réalisé par un clic sur l?url #URLPAYER. Comme les transports, les moyens paiement sont gérés sous forme de plugins : ils disposent d?une classe dédiée PluginsPaiements et sont de $type=1. Bien qu?elle valide définitivement une commande, #URLPAYER ne finalise pas pour autant le parcours de la commande sur le Front Office : en effet #URLPAYER est un paramètre de sortie de la boucle "paiement" présente sur la page commande.html et redirige le client vers une nouvelle page, en fonction du mode de paiement qu?il aura choisi. Voyons comment : Le fichier fonctions/boucles.php nous renseigne, aux lignes 1671 à 1732, sur le traitement de cette url par la fonction bouclePaiement() : $query = "select * from $modules->table where type=?1? and actif=?1? $search order by clas- sement"; $resul = mysql_query($query, $modules->link); $nbres = mysql_num_rows($resul); while( $row = mysql_fetch_object($resul)){ $modules = new Modules(); $modules->charger_id($row->id); include_once("client/plugins/" . $modules->nom . "/$nom.class.php"); $tmpobj = new $nom(); ... $temp = str_replace("#URLPAYER", "commande.php?action=paiement&amp;type_paie- ment=" . $row->id, $temp); ... A nouveau l?action=paiement est la clé de voûte de #URLPAYER ; pour chaque ("while") plu- gin de type paiement (type =1) activé (actif=1), un objet de classe Module est instancié per- mettant l?inclusion du fichier classe du plugin grâce à quoi le tag #URLPAYER du squelette est remplacé par une url contenant en paramètre une action (valeur = paiement) et un type de paiement (valeur = ID du plugin). Par exemple : si vous avez activé le plugin "chèque", le plugin "virement" et le plugin "cic", la boucle suivante : <ul> <THELIA_PAIEMENT type ="PAIEMENT"> 182 <li>#URLPAYER</li> </THELIA_PAIEMENT> </ul> Produira comme résultat les 3 liens suivants : - Chèque (commande.php?action=paiement&type_paiement=6) - virement (commande.php?action=paiement&type_paiement=7) - CIC (commande.php?action=paiement&type_paiement=9) Voyons comment le fichier fonctions/actions.php décrit la fonction paiement() aux lignes 129 et suivantes que nous avons commencé à détailler précédemment. Si vous lisez le fichier original vous remarquerez que c?est une action assez complexe : je n?ai pas recopié toute la fonction mais je me suis focalisé sur l?essentiel du traitement de sa variable interne : la variable $commande. La fonction paiement() prend logiquement la varia- ble $type_paiement en argument, paramètre que nous avons renseigné en url. Voici un aperçu de toutes les fonctions qu?elle assume : La fonction „ „ paiement() utilise une sous-fonction modules_fonction(?avantcommande?) au début et modules_fonction(?aprescommande?) à la fin qui nous permettent d?agir via un plugin sur la fonction paiement. Une troisième modules_fonction(?mail?) permet l?envoi d?un mail lors de la validation de „ „ la commande depuis un plugin paiement Elle déstocke le produit et sa déclinaison en fonction de la quantité. „ „ Elle insert dans la table "commande" la nouvelle commande validée. „ „ Elle insert dans les tables "venteprod" et ventedeclidsip les produits et leurs déclinai- „ „ sons Elle insert dans la table "venteadr" les adresses de facturation et de livraison. „ „ Elle calcule le poids total du panier. „ „ Elle calcule la remise à appliquer au total et met à jour la table promo. „ „ Elle prend en compte le mode de transport choisi et le coût associé. „ „ Elle prend en compte l?identité du client, renseignée dans la variable de session „ „ $client. Elle alimente la variable de session „ „ $_SESSION[?navig?]->commande des valeurs man- quantes qu?elle aura d?abord calculé : date, numéro de commande, numéro de livraison... Elle exécute la méthode „ „ paiement() du mode de paiement choisi par le client. Parmi toutes ces fonctionnalités, voyons maintenant lesquelles concernent sa variable in- terne $commande : $commande „ „ , un objet de classe Commande, est initialisé, ses attributs reçoivent en premier lieu les valeurs suivantes : -- $transport = la valeur stockée dans $_SESSION[?navig?]->commande->transport -- $id = la valeur stockée dans $_SESSION[?navig?]->client->id -- $date = une valeur générée par la fonction php date() 183 -- $ref = une valeur alphanumérique unique composée de la lettre C+date+3 premières let- tres du prénom du client. -- $livraison = une valeur alphanumérique unique composée de la lettre L+date+3 premières lettres du prénom du client. -- $transaction = une valeur numérique liée à la date -- $remise = 0 -- $adrlivr = un objet dont les attributs reçoivent les valeurs stockées dans $_SESSION[?navig?]- >adresse (les données relatives à l?adresse de livraison du client) ou $_SESSION[?navig?]->client (les données relatives à l?adresse de facturation du client si adresse de livraison non spéci- fiée). - $adrfact = les valeurs stockées dans $_SESSION[?navig?]->client->adresse -- $facture = 0 -- $statut =1 -- $paiement = la valeur de l?argument de la fonction ($type_paiement) : soit l?ID du plugin du paiement choisi par le client. Les attributs de „ „ $commande sont chargés dans la table "commande". Le port est calculé. „ „ La variable de session „ „ $_SESSION[?navig?]->commande reçoit les valeurs des attributs de la variable $commande. Un objet instancié par la classe du plugin paiement choisi par le client est initialisé. „ „ La variable „ „ $commande est passée dans la méthode paiement() du plugin choisi. Voici simplifiées, les lignes de code de la fonction paiement() du fichier fonctions/actions.php . L?intégralité de la fonction n?est pas représentée, seules les instructions qui exécutent l?en- semble de la cinématique expliquée à l?instant sont reproduites. Une variable interne $com- mande, objet de classe Commande est instanciée et manipulée de la façon suivante : 184 function paiement($type_paiement){ ... $commande = new Commande(); $commande->transport = $_SESSION[?navig?]->commande->transport; $commande->client = $_SESSION[?navig?]->client->id; $commande->date = date("Y-m-d H:i:s"); $commande->ref = "C" . date("ymdHis") . strtoupper(substr($_SESSION[?navig?]- >client->prenom,0, 3)); $commande->livraison = "L" . date("ymdHis") . strtoupper(substr($_SESSION[?navig?]- >client->prenom,0, 3)); $commande->transaction = date("His"); $commande->remise = 0; $commande->adrlivr = $adrlivr; $commande->facture = 0; $commande->statut="1"; $commande->paiement = $type_paiement; $commande->lang = $_SESSION[?navig?]->lang; $idcmd = $commande->add(); $commande->charger($idcmd); $commande->port = port(); if($commande->port == "" || $commande->port<0) $commande->port = 0; $_SESSION[?navig?]->commande = $commande; $commande->maj(); $nomclass=$modules->nom; $nomclass[0] = strtoupper($nomclass[0]); include_once("client/plugins/" . $modules->nom . "/" . $nomclass . ".class.php"); $tmpobj = new $nomclass(); $tmpobj->paiement($commande); } En annexe de cette partie, vous trouverez la fonction paiement() décrite dans son intégralité. Nous aurons l?occasion de re croiser ces lignes ainsi que celles déjà découvertes concernant cette "super" fonction. Pour l?instant, la commande n?est pas finalisée. La fonction paiement() appelle la méthode du plugin Paiement sélectionné. Nous allons décrire tout de suite cette dernière phase. 185 Le paiement = la redirection de l?url 6. #URL- PAYER Etape 7 Le dernière ligne de mon extrait ci-dessus nous confirme que la méthode paiement() du plu- gin paiement sollicité au moment du choix d?un moyen de paiement par le client est exécu- tée sur la variable $commande (Ne pas confondre la fonction paiement() du fichier fonctions/ actions.php vue plus haut et les méthodes paiement() décrites dans les classes des plugins de type paiement que nous allons étudier maintenant). Cette méthode diffère en fonction du plugin mais le principe reste le même pour tous : elle re- dirige le client vers une page spécifique au mode de paiement. Voici par exemple la méthode extraite du plugin "virement" (toutes les méthodes paiement() ressemblent à celle-ci) : function paiement($commande){ modules_fonction("confirmation", $commande); $urlsite = new Variable(); $urlsite->charger("urlsite"); header("Location: http://" . $_SERVER[?HTTP_HOST?] . "/virement.php"); exit; } -- Dans le cadre d?un paiement en ligne par carte bancaire, cette redirection pointe vers une page préparant la requête vers les serveurs sécurisés de la banque, puis exécute cette re- quête pour que la transaction s?effectue sur ces serveurs. Le paiement en ligne sur les ser- veurs de la banque ne fait pas partie de thélia et ne sera donc pas abordé ici. -- Dans le cadre d?un paiement différé (virement, chèque...), la page appelée est la dernière : elle contient des informations pour le client sur la méthode pour effectuer le paiement selon le mode de paiement sélectionné. L?exemple proposé ci-dessus nous montre également un nouveau point d?entrée pour les plu- gins concernant les modes de paiement chèque et virement : la fonction modules_fonction() est présente et embarque l?argument "confirmation" permettant d?imaginer une méthode du même nom dans un plugin pour modifier le comportement de la fonction paiement pour ces modes de paiement. Conclusion : le paiement est effectif (retour de la banque OK) ou non (retour KO ou paiement différé) mais dans les 2 cas la commande est validée et elle a été enregistrée dans la table "commande". L?étape 6 s?achève. Depuis l?étape précédente la commande est une donnée du BO et peut être manipulée par le marchand. Avant d?y revenir voici une synthèse de la partie Front Office d?une commande : 186 panier.php Panier 1 connexion.php Coordonnées client 2 adresse.php Adresse mode de livraison 3 $_SESSION[?navig?] page paiement Paiement en ligne ou différé 6 $_SESSION[?navig?]->panier = validation des données panier $_SESSION[?navig?]->client = validation des données client Table "commande" $_SESSION[?navig?]->adresse = validation adresse livraison commande.php Promo Moyen paiement 4 1 2 3 4 5 #URLCMD #URLPAYER adresse.php Table "venteprod" Table "venteadr" id client adrfact adrlivr date ref transaction livraison facture transport port remise paiement statut id ref titre chapo quantite prix tva commande id raison nom prenom adress1 adress2 CP 1 1 2 3 1 C1 1 L1 0 1 1 0 3 6 1 2 3 $_SESSION[?navig?]->promo = validation d?une promo xxx xxx xxxxx xxxxxxx xxxx xxx xxxx xxxx xxxx xxxxxx xxxxxx xxxxxxx xxx $_SESSION[?navig?]->commande 5 = Validation moyen de paiement + agrégation des données de la commande dans la variable de session $commande + injection en base des données BASE DE DONNEES Table "ventedeclidisp" Table "client" Table "promo" id venteprod declidisp xxxx xxxx xxxxxx xxxxxx xxxxxxx xxx 1 1 187 Nous voyons que le clic sur #URLPAYER exécute un ensemble de fonctions : -- 1°) qui agrègent dans l?objet $commande (une variable de session) l?ensemble des élé- ments constitutifs d?une commande. -- 2°) qui calculent les valeurs nécessaires à certains attributs de $commande (date, numéro de commande...) puis -- 3°) qui injectent les valeurs obtenues par les attributs de cet objet dans la table "com- mande". L?ensemble des éléments d?une commande ne sont pas tous stockés dans cette table : la fonction paiement() sollicite les tables venteprod, venteadr et ventedeclidisp pour stocker les valeurs relatives aux références du panier, aux déclinaisons de ces même références et aux adresses de livraison et de facturation. De la même façon les coordonnées client ne sont pas recopiées dans la table commande : celle-ci assure donc les jointures nécessaires aux différentes tables de la base pour constituer une commande complète et elle contient en plus quelques données spécifiques stockées nulle part ailleurs (N°, date....). 2 remarques : -- Dans la base il n?existe pas de jointure entre la commande et la promo : la valeur de la remise stockée dans le champ "valeur" de la table promo alimente la valeur de l?attribut $re- mise de $_SESSION[?navig?]->commande. Une fois cette opération effectuée lors de l?exécution de la fonction paiement(), plus rien ne lie la table promo à la table commande. C?est regretta- ble car il ne sera pas possible de tracer avec quelle promo un client a bénéficié d?une remise sur sa commande. -- Le total d?une commande ne fait l?objet d?aucun champ, dans aucune table de la base. C?est donc une valeur qui sera calculée à chaque fois qu?on aura besoin de la faire apparaî- tre. Heureusement il fait l?objet d?une substitution qui facilite grandement le travail et donne l?impression au webmaster qu?il s?agit d?une donnée "statique" de la base comme beaucoup d?autres. 188 Les statuts d?avancement de la commande 7. dans le BO Thélia Etape 8 à Etape 13 De retour dans le BO nous allons nous intéresser à l?avancement de la commande via l?inter- face prévue : commande_détails.php. Plus précisément nous allons étudier le comportement de la commande selon le statut que nous lui appliquerons. Statut = 1 [non payé] „ „ : à la validation d?une commande et son enregistrement en base de données, le statut =1 est appliqué par défaut à la commande. Statut = 2 [payé] „ „ : la commande est considérée comme payée, cette étape génère donc la facture du client. Nous allons détailler cette fonctionnalité dans quelques instants. Statut = 3 [traitement] „ „ : la commande est en cours de traitement : cette étape ne gé- nère aucun événement pour le client. Statut = 4 [envoyé] „ „ : Après envoi de la commande, cette étape génère un mail de confirmation d?envoi au client. Nous allons nous y intéresser plus loin. Fin de la commande. Statut = 5 [annulé] „ „ : la commande est annulée, aucun événement pour le client. 189 La gestion du 8. stock Nous avons isolé la gestion du stock du parcours global de la commande pour ne pas sur- charger la présentation. Pourtant, cette étape intervient à nouveau dans le cadre de l?exécu- tion de la fonction paiement() (étape 6). Ceci est particulièrement important à comprendre car c?est un point litigieux pour beaucoup de marchands et un débat chronique sur le forum. La cause du problème : Rappelons-nous la chose suivante : malgré son nom, #URLPAYER qui actionne la fonction paiement() n?est pas l?étape de validation du paiement mais l?étape de validation du moyen de paiement. Autrement dit, la fonction exécute le dé-stockage des références commandées indépendamment du paiement effectif de la commande. Les raisons invoquées par les développeurs : cette technique permet de réserver le produit (puisqu?avec un stock à 0 une commande n?est plus possible) dans le cas des commandes par chèque ou virement. Cet argument est justement la cause du malheur de ses détracteurs : cette fonctionnalité bloque la capacité d?achat d?un second client potentiel alors que le pre- mier n?a pas encore payé : la règle du "premier arrivé (entendez le premier à payer) premier servi" ne peut pas s?appliquer dans cette configuration. Voyons maintenant comment Thélia gère nativement ce déstockage en nous intéressant à nouveau à un morceau de la fonction paiement() : for($compt = 0; $compt<count($_SESSION[?navig?]->panier->tabarticle[$i]->perso); $compt++){ if(is_numeric($_SESSION[?navig?]->panier->tabarticle[$i]->perso[$compt]->valeur)){ $stock->charger($_SESSION[?navig?]->panier->tabarticle[$i]->perso[$compt]->valeur, $_SESSION[?navig?]->panier->tabarticle[$i]->produit->id); $stock->valeur-=$_SESSION[?navig?]->panier->tabarticle[$i]->quantite; $stock->maj(); } $produit = new Produit(); $produit->charger($_SESSION[?navig?]->panier->tabarticle[$i]->produit->ref); $produit->stock-=$_SESSION[?navig?]->panier->tabarticle[$i]->quantite; $produit->maj(); Voyons maintenant ce qui est prévu pour la mise à jour du stock côté BO (produit_modifier. php) : La valeur du champ ?stock? pour un produit est libre : vous pouvez renseigner n?importe „ „ quelle valeur, elle sera prise en compte (= mise à jour du champ stock de la table ?produit?) lors de la mise à jour/validation de la page tant que le produit n?a aucune déclinaison. Les choses se compliquent pour les produits avec déclinaison(s) : nous avons déjà eu „ „ l?occasion de détailler en profondeur les déclinaisons dans une précédente partie et l?objet 190 de ce paragraphe n?est pas de revenir sur ce qui a déjà été dit mais nous allons approfondir maintenant la mécanique du stock pour les déclinaisons . Lorsqu?un produit possède des dé- clinaisons, vous avez deux types de stock sur sa fiche produit dans le BO : le stock du produit et le stock de chaque valeur de la déclinaison. Ces 2 stocks sont sommairement reliés l?un à l?autre par l?instruction que nous allons voir maintenant. En tant qu?utilisateur vous avez sans doute constaté comment les deux types de stocks sont liés : le stock produit est la somme des stocks de chaque valeur de déclinaison. Cette liaison est bonne si le produit n?a qu?une seule déclinaison. Mais à partir de deux déclinaisons pour un produit son calcul devient faux : le stock produit doit rester la somme des valeurs d?une seule déclinaison et non la somme des valeurs de toutes les déclinaisons du produit. Un exemple simple sera plus explicite : Imaginons que vous vendiez des T-shirts. Vous proposez plusieurs tailles : S, M, L, XL. Les stocks pour chaque taille sont : S=10, M=10, L=25, XL=5. Votre stock produit affichera donc correctement 50. Si en plus, vous déclinez vos T-shirts en plusieurs couleurs : bleu, jaune, rouge, dont les stocks sont bleu=20, jaune=20, rouge=10 alors la valeur de votre stock général pour votre produit T-shirt affi- chera 100, alors que nous avons toujours seulement 50 T-shirts en stock. Le problème s?accentue quand Thélia déstocke puisque à ce moment-là il déstocke „ „ convenablement ! En effet le code reproduit ci-dessus nous le montre : Pour un T-shirt jaune XL acheté, Thélia diminue -- le stocke global de 1, -- le stock de XL de 1 -- et le stock de jaune de 1 ! La désynchronisation empire donc au fur et à mesure entre le stock général du produit et les stocks des déclinaisons de ce produit. De plus, aucun contrôle de cohérence n?est prévu entre les stocks de 2 déclinaisons „ „ : dans mon exemple précédent, vous pourriez enregistrer des stocks de couleur comme bleu=10, jaune=10, rouge=10 ce qu?accepterait Thélia : pourtant vos valeurs seraient tota- lement incohérentes par rapport aux valeurs des tailles : la somme des stocks de chaque déclinaison doit être identique pour avoir un sens : si j?ai 50 t-shirt, je dois avoir 50 tailles en stock et 50 couleurs en stock. Nous implémenterons cette règle dans Thélia comme TP dans la dernière partie. Notons enfin qu?il n?y a aucune gestion de stock en cas d?annulation de commande : „ „ vous devrez re-stocker manuellement les produits contenus dans la commande annulée, ces produits ayant été dé-stockés lors de la validation de la commande. Voici les lignes de code concernées sur la page produit_modifier.php (lignes 195 à „ „ 240) : 191 $query = "select * from $rubdeclinaison->table where rubrique=? " . $rubrique . "?"; $resul = mysql_query($query); $nb = 0; while($row = mysql_fetch_object($resul)){ $declinaisondesc->charger($row->declinaison); $query2 = "select * from $declidisp->table where declinaison=?$row->declinaison?"; $resul2 = mysql_query($query2); $nbres = mysql_num_rows($resul2); while($row2 = mysql_fetch_object($resul2)){ $var="stock" . $row2->id; $var2="surplus" . $row2->id; global $$var, $$var2; $stock = new Stock(); if ($stock->charger($row2->id,$produit->id) == 0) { $stock->declidisp=$row2->id; $stock->produit=$produit->id; $stock->valeur=$$var; $stock->surplus=$$var2; $stock->add(); $nb += $stock->valeur; } else { $stock->valeur=$$var; $stock->surplus=$$var2; $stock->maj(); $nb += $stock->valeur; } } } if($nb) $produit->stock = $nb; $produit->maj(); 192 La gestion du 9. port Nous allons retracer la logique un peu compliquée modelant la gestion des frais de port et ce plus complètement que précédemment et décrire l?intégralité de cette fonctionnalité. Nous savons déjà qu?un type de transport (par exemple : Colissimo) est un plugin donc une classe, affiliée à la classe dédiée PluginsTransports. Une boucle "transport" affiche sur la page adresse.html les plugins transports activés. Nous savons en tant qu?utilisateurs que sur cette même page, le montant des frais de livraison pour la commande s?affiche pour chaque mode de transport proposé (donc pour chaque plugin transport activé, affiché par la boucle). Nous allons voir comment. Nous savons enfin que l?url #URLCMD transmet l?ID du transport choisi et que la fonction transport() se charge de stocker cet ID dans l?attribut $transport de $commande. Au-delà, nous disposons d?un tableau de bord pour la gestion des frais de livraison dans le BO, les pages : -- transport.php -- zone.php Nous disposons de quatre tables pour la gestion du port : ?transzone?, ?zone?, ?pays? et ?pays- desc? (ces 2 dernières servent également à la gestion de la langue utilisée sur votre site). Les quatre classes homonymes sont stockées dans le dossier classe. Notons que la boucle "transport" ne répond pas exactement au modèle type des boucles : il n?existe pas de table ?transport? dans la base pas plus qu?il n?existe de classe ?transport?. Il va nous falloir comprendre à quoi servent tous ces éléments. Nous allons décrire ici ce que propose nativement Thélia et son plugin optionnel Colissimo International. Dans la prochaine partie nous réserverons un chapitre à la personnalisation de ces paramètres. Comment calculer des frais de livraison pour une comman- 1. de ? Nativement dans Thélia, 0, 1 ou 2 critères peuvent être pris en compte : -- 1°) La zone de livraison -- 2°) Le poids de la commande. Notons tout de suite que : -- 1°) rien ne permet d?offrir les frais de port à partir d?un certain montant de commande. -- 2°) Plus généralement, rien n?est prévu pour appliquer un port en fonction du total de la commande. C?est regrettable car il s?agit de pratiques commerciales répandues et nous nous applique- rons à développer ces fonctionnalités plus tard. Notons aussi que lorsque nous parlons de zone de livraison ici, nous faisons référence à du transport international, étant entendu que la France représente la zone 1. Huit autres zones internationales + 1 zone DOM + 1 zone TOM sont définies en fonction du découpage com- munément adopté par les principaux transporteurs internationaux français. Ce découpage est organisé par la table ?pays?. Dans le BO Thélia la zone 1 est nommée la zone France. Les 10 autres zones internationales sont codifiées de 1 à 10 (dans la table ?pays? les zones sont représentées de 1(France) à 11) 193 194 Ainsi nous pouvons envisager de facturer des frais de livraison : -- Au forfait (frais fixes) : le plugin "forfait" prévu dans Thélia devra être activé. Si vous lui at- tribuez différentes zones, la valeur du port peut varier en fonction de chaque zone. -- En fonction du poids de la commande (plugin Colissimo intégré à l?application : par défaut ce plugin ne prend pas en compte les zones internationales). -- En fonction du poids de la commande et du pays de livraison : nous pourrions envisager de paramétrer le plugin Colissimo nous-même ou utiliser un plugin spécifique, "Colissimo International" qui fera tout le travail à notre place. L?absence de frais de livraison sur une commande est également possible en activant le plugin "place" intégré, correspondant à la non facturation de ce type de frais (le client vient retirer son colis sur place) Le paramétrage du port dans le BO 2. Nous pouvons paramétrer dans le BO les zones et les types de transport activés depuis la page de configuration des plugins. Les zones : la page „ „ zone.php Thélia prévoit la possibilité de livraison à l?international : dans la table ?paysdesc? chaque pays est doté d?un ID, la France porte le numéro 64 (classement alphabétique). La table ?pays? attribue à chaque ID un numéro de zone (de 1 à 11) comme vu précédemment (pour la France = 1). La table ?zone? stocke ces 11 zones prédéfinies. Sur la page zone.php vous pouvez : -- 1°) Editer chaque zone prédéfinie en ajoutant ou supprimant des pays attachés à cette zone. -- 2°) Créer une (ou plus) nouvelle zone et lui attribuer un ou plusieurs pays : elle sera stockée comme les autres dans la table ?zone?. Notons qu?un contrôle de cohérence lie les pays que vous pouvez manipuler et les pays déjà enregistrés dans une autre zone : si vous créez une nouvelle zone sans avoir préalablement supprimé un ou plusieurs pays d?une ou plusieurs zones prédéfinies vous n?aurez aucun pays disponible à attribuer à votre nouvelle zone car tous les pays de la table ?pays? sont déjà attribués aux 11 zones prédéfinies dans Thélia. Or il faut nécessairement qu?un pays soit attribué à une zone pour que le système fonctionne : + Un ou plusieurs pays sont attribués à une zone sur zone.php + Une ou plusieurs zones sont attribuées à un mode de livraison sur transport.php. -- 3°) Pour chaque zone prédéfinie ou créée vous pouvez attribuer une valeur au champ forfait : il indiquera le coût à prendre en compte si le type de transport ?forfait? est activé, en fonction de la zone que vous aurez paramétré dans transport.php pour ce type de transport et du pays de l?adresse de livraison. Le type de transport : la page „ „ transport.php Pour chaque plugin transport activé vous disposez sur cette page d?une interface pour para- métrer les zones prédéfinies ou créées à appliquer pour chacun d?eux. Ainsi, par défaut vous pouvez activer les modes de livraison suivants et les paramétrer ici : - Retrait sur place - Colissimo - Forfait Des plugins optionnels sont disponibles ; notons : - Colissimo International - Ecopli - Tarif lettre - Points relais TNT Cette interface vous permet de lier une (ou +) zone(s) à un type de livraison : ce lien sera stocké dans la table ?transzone?. Lorsque vous activez le plugin transport "Colissimo" ou "place", ils sont immédiatement opérationnels parce que par défaut à l?initialisation ils se voient attribuer la zone France. Pour le plugin ?forfait? en revanche il vous faudra non seu- lement l?activer mais également lui attribuer ici une zone (au moins la zone France et éven- tuellement une ou plusieurs zone(s) internationale(s)) et sur la page zone.php renseigner le champ ?forfait? d?une valeur pour chaque zone attribuée. Le reste de la mécanique de facturation des frais de livraison s?opère sur les pages du Front Office comme nous allons le voir maintenant. Les frais de port sur le Front Office 3. Revenons à la commande et détaillons la motorisation prévue par Thélia : 1°) le montant des frais de port Lorsque le client valide son panier, il est routé sur la page adresse.html et son adresse de livraison par défaut est son adresse de facturation. La pays (donc la zone de livraison) est à ce moment défini par le pays du compte client. S?il choisi (ou créé) une adresse de livrai- son spécifique, la page adresse.html est rechargée + l?attribut $adresse de la variable de session $commande est alimenté de l?ID de l?adresse de livraison sélectionnée (depuis table ?adresse?) La boucle "transport" affiche les plugins transports activés + disponibles (cf infra 2°)et le coût associé. Ce coût est affiché par le paramètre de sortie #PORT de la boucle. Voyons ce que renvoie ce paramètre, défini dans le code par la fonction boucleTransport() déjà croisée. 195 function boucleTransport($texte, $args){ ... $query = "select * from $modules->table where type=?2? and actif=?1? $search order by classement"; ... $resul = mysql_query($query, $modules->link); ... while( $row = mysql_fetch_object($resul)){ ... $port = round(port($row->id), 2); $port = number_format($port, 2, ".", ""); ... $temp = str_replace("#PORT", "$port", $temp); } return $res; } Nous voyons que lors du traitement d?une boucle transport par le serveur, le tag #PORT est remplacé par la variable $port, dont la valeur est le résultat de la fonction port() avec en argu- ment pour cette fonction l?ID du plugin transport pris en compte par le passage de la boucle "while". La fonction port() se retrouve dans la fonction paiement() qui calcule et renseigne les attributs de la variable de session $commande et la table ?commande? : l?objet $commande dispose d?un attribut $port et la table ?commande? dispose d?un champ ?port? permettant de stocker la valeur ainsi calculée (à ne pas confondre avec l?attribut $transport de $comman- de ou le champ ?transport? de la table ?commande? : ces éléments stockent l?ID du plugin transport choisi par le client). Ainsi la fonction paiement() prévoit-elle les lignes suivantes : $commande->port = port(); if($commande->port == "" || $commande->port<0) $commande->port = 0; La fonction port() est présente aussi dans la fonction bouclePanier() en plus des fonctions boucleTransport() et paiement(). La fonction port() est décrite dans le fichier divers.php aux lignes 590 à 640. Cette fonction permet d?orienter le calcul des frais de livraison en fonction du plugin transport choisi : la mé- thode calcule() du plugin transport sélectionné est au final mis à contribution pour déterminer ce calcul comme nous pouvons le voir ci-dessous : 196 function port($type=0){ if($_SESSION[?navig?]->commande->transport == "" && !$type) return -1; if( $_SESSION[?navig?]->adresse != 0) $chadr=1; else $chadr=0; $modules = new Modules(); if(!$type) $modules->charger_id($_SESSION[?navig?]->commande->transport); else $modules->charger_id($type); if($modules->type != "2") return -1; $p = new Pays(); if($chadr){ $adr = new adresse(); $adr->charger($_SESSION[?navig?]->adresse); $p->charger($adr->pays); $cpostal = $adr->cpostal; } else { $p->charger($_SESSION[?navig?]->client->pays); $cpostal = $_SESSION[?navig?]->client->cpostal; } $zone = new Zone(); $zone->charger($p->zone); $nom = $modules->nom; $nom[0] = strtoupper($nom[0]); if(! file_exists("client/plugins/" . $modules->nom . "/$nom.class.php")){ return -1; } include_once("client/plugins/" . $modules->nom . "/$nom.class.php"); $port = new $modules->nom(); $port->nbart = $_SESSION[?navig?]->panier->nbart(); $port->poids = $_SESSION[?navig?]->panier->poids(); $port->total = $_SESSION[?navig?]->panier->total(); $port->zone = $p->zone; $port->pays = $p->id; $port->unitetr = $zone->unite; $port->cpostal = $cpostal; return $port->calcule();} La fonction port() de divers.php construit un objet $port de la classe du plugin transport sé- lectionné. Les attributs de cet objet sont empruntés à la classe Pluginstransports à laquelle il est affilié : ces attributs correspondent aux éléments constitutifs des frais de livraison pour une commande. Une fois les attributs de l?objet $port alimentés, l?objet exécute sa méthode calcule(), dont la formule varie évidemment en fonction du plugin. Voici celle du plugin Colissimo : function calcule(){ if($this->poids<=0.5) return 6; else if($this->poids>0.5 && $this->poids<=1) return 6.50; else if($this->poids>1 && $this->poids<=2) return 7; else if($this->poids>2 && $this->poids<=3) return 8; else if($this->poids>3 && $this->poids<=5) return 9; else if($this->poids>5 && $this->poids<=7) return 10; else if($this->poids>7 && $this->poids<=10) return 12; else if($this->poids>10 && $this->poids<=15) return 14; else if($this->poids>15 && $this->poids<=30) return 20; else if($this->poids>30) return 20; } Et voici en comparaison la méthode calcule() du plugin forfait, beaucoup plus simple : function calcule(){ return $this->unitetr;} Le tag #PORT sur la page adresse.html comme la fonction „ „ paiement() renvoient donc bien le montant à prendre en considération pour les frais de port. La fonction paiement() se charge d?enregistrer ce montant dans le champ ?port? de la table ?commande? et ajoute cette valeur dans le calcul du total de la commande. Donc lorsque la fonction paiment() aura été exécutée, le montant du port sera disponible dans le champ ?port? de la table ?commande? et la fonction port() ne sera plus sollicitée : voyez par exemple la page commande_details.html : le tag #PORT de la boucle "commande" fait appel à la valeur du champ ?stock? de la table ?commande? : ainsi, contrairement à plusieurs valeurs d?une commande qui ne sont pas des valeurs stockées mais calculées à la demande (c?est le cas par exemple du total d?une com- mande, TTC ou HT ou du montant de la tva... : aucun champ d?aucune table de la base ne prévoit un stockage de ces valeurs) le montant des frais de livraison bénéficie d?un stockage dans la base. La fonction „ „ port() prévoit que si à un moment de son exécution, une condition n?est pas remplie le calcul retournera la valeur = -1. Thélia prévoit que si le montant du port < 0 alors le montant est =0. Voici illustrée pour conclure la fonction port() (avec le plugin Colissimo) et ses interactions avec les principaux éléments d?une commande rencontrés jusqu?ici, exécutée par paiement() : 198 2°) Les modes de livraison disponibles : Nous expliquions au début de ce chapitre que pour pouvoir passer une commande „ „ sur votre site, il était nécessaire qu?au moins un pays soit attribué à une zone elle-même attribuée à un mode de transport activé : en effet Thélia organise la gestion des zones de la façon suivante : La boucle "transport" affichant les modes de livraison disponibles est codée sur le squelette adresse.html. Or l?exécution de cette boucle, prévue par la fonction boucleTransport() que nous avons déjà eu l?occasion de décrire, prévoit une instruction bien particulière lors du passage de la boucle "while" : 199 commande xxx PORT xxx xxx xxx ref port client facture livraison Client xxx xxx xxx xxx id client pays ville cpostal adresse xxx IDP xxx xxx id client pays ville cpostal pays IDP IDZ xxx xxx xxx id zone tva defaut lang IDP port[] $port = new Colissimo Pluginstransports $nbart $total $poids $pays $zone $unitetr $cpostal +calcule() $nbart $tabarticle[] + nbart() + total() + poids() + unitetr() Panier Navig $client $adrfact $adrlivr $datefact $ref $transport $port $datelivraison Commande Client $adresse $transport calcule[] $id $nom $prenom $adresse $cpostal $pays $tel zone IDZ xxx unite id nom unite paiement[] paiement[] transzone IDTZ IDZ IDT id zone transport IDT 1 2 3 4 = 0 = ID IDA IDA ... $transzone = new Transzone(); while( $row = mysql_fetch_object($resul)){ if( ! $transzone->charger($row->id, $pays->zone)) continue; ... La même précaution est prise dans l?exécution de la fonction transport() : function transport($id){ $transzone = new Transzone(); ... if( ! $transzone->charger($id, $pays->zone)) return; $_SESSION[?navig?]->commande->transport = $id; ... } Que veulent dire ces lignes ? La table ?transzone? stocke les zones (définies dans la table ?zone?) attribuées à chaque mode de transport (par défaut ou depuis la page transport.php du BO). L?adresse de livraison sélectionnée (ou par défaut l?adresse de facturation) stocke l?ID d?un pays pour lequel la table ?pays? attribue une zone. -- L?instruction n°1 ci-dessus empêche le moyen de livraison de s?afficher si le pays de l?adresse choisie pour la livraison appartient à une zone qui n?est pas attribuée au mode de transport concerné par la passe en cours de la boucle "while". (page adresse.html) -- La n°2 termine l?exécution de la fonction transport() qui s?exécute lors de l?appel à la page commande.php. Notons que ce second verrou n?est pas efficace : Ex : vous ne livrez qu?en France mais si le client crée une adresse de livraison à l?étranger puis sélectionne son adresse de facturation en France, il a accès à un mode de transport (pour la France) qu?il sélectionne pour passer sur la page suivante (commande.html). Il peut copier l?url obtenue puis revenir à la page adresse pour sélectionner son adresse de livraison à l?étranger et repartir sur la page com- mande en collant l?url récupérée : sa commande sera validée avec un port pour la France mais une adresse de livraison à l?étranger. voici un meilleur verrou : if( ! $transzone->charger($id, $pays->zone)) header("location: adresse.php"); Comment paramétrer Thélia pour la livraison à l?étranger : le plugin "Colissimo Interna- „ „ tional" téléchargé, installé et activé, il s?agira de lui attribuer les zones de 1 à 8 depuis trans- port.php. Vous prendrez garde de conserver actif le plugin Colissimo intégré qui continuera de se charger du port pour la France. 200 La gestion de la 10. TVA Nous allons faire ici le tour de la question de la TVA dans Thélia. Concernant la TVA dans le BO nous savons que c?est est une "variable" du site que „ „ nous pouvons configurer dans variable.php. Dans chaque fiche produit du BO, la valeur de cette variable est proposée par défaut mais vous pouvez la personnaliser à ce niveau pour chaque produit. Si ce système n?est pas très automatisé et peut se révéler fastidieux, il a le mérite d?être simple : il tient en quelques lignes de code sur la page produit_modifier.php (lignes 427 à 433) : if($produit->tva == ""){ $tvar = new Variable(); $tvar->charger("tva"); $tva = $tvar->valeur; } else $tva=$produit->tva; Concernant la TVA dans les boucles et la base de données, énumérons les endroits où „ „ nous la croisons : -- La TVA est un paramètre de la boucle "produit" et affiche pour celui-ci la valeur stockée dans le champ ?TVA? de la table ?produit?. -- La TVA est un paramètre de sortie des boucles "panier" et "venteprod". Dans le premier cas sa valeur est issu de la variable de session $navig, dans le second elle est issue de la table ?venteprod? (dans les 2 cas leurs valeurs sont initialement issues de la table ?produit?) -- Elle est également un élément du calcul dynamique qui se cache derrière les paramètres de sortie concernant certains totaux du panier : #TOTALHT, #TOTALCMDPORTHT, #TO- TALCMDSPORTHT... -- En revanche le montant de la TVA n?est pas stocké en base et ne fait partie d?aucune bou- cle concernant son montant pour un panier ou pour une commande. Concernant la TVA pour les livraisons internationales, notons que la table ?paysdesc? „ „ enregistre pour chaque pays une valeur 0 ou 1 dans un champ ?TVA?, cette valeur permet de facturer (=1) ou non (=0) la TVA pour une commande livrée à l?étranger. Vous devrez ac- cédez à la base (par phpmyAdmin par exemple) pour voir cette valeur et éventuellement la modifier : elle n?apparaît nulle part dans le BO. Passons maintenant en revue les substitutions : la TVA est une donnée du fichier fonc- „ „ tions/substitutions/substitpanier.php : -- Par défaut, sa valeur est incluse dans le script substitpanier depuis la (ou les) valeur(s) enregistrée(s ) dans l?attribut $tva des $article du $panier de la variable de session $navig. -- Elle entre en jeu dans les calculs remplaçant les tags #PRIXHT, #PORT, #TO- TALCMDPORT. -- Par ailleurs un certain nombre de lignes concernant la tva sont commentées dans ce fi- chier ce qui veut dire que ces instructions ne sont pas activées par défaut (mais puisqu?elles sont là ma curiosité me pousse à me demander pourquoi) A SUIVRE 201 Gestion de la 11. facture et du bon de livrai- son 1°) La facture L?édition de la facture est gérée par le statut = 2 d?une commande dans le BO. Ceci grâce aux lignes suivantes de commande_details.php : if($statutch == 2 && $commande->facture == 0) $commande->genfact(); La méthode genfacture() de la classe Commande est mise à exécution : function genfact(){ if($this->facture) return 0; $this->datefact = date("Y-m-d"); $query = "select max(facture) as mfact from $this->table"; $resul = mysql_query($query, $this->link); if(mysql_result($resul, 0, "mfact")>0) $this->facture = mysql_result($resul, 0, "mfact") + 1; else $this->facture = 1000; } Nous voyons que cette méthode se "contente" de renseigner une date pour l?attribut $date- fact et un numéro de facture pour l?attribut $facture de l?objet $commande instancié à l?appel de commande_details.php. Quelques lignes plus bas, l?objet $commande servira à mettre à jour la table ?commande?. 2 remarques : -- 1°) Les factures débuteront par le numéro 1000 mais vous pouvez à cet endroit adapter cette valeur en fonction de votre propre nomenclature. -- 2°) Une facture est visible dés le statut =1 depuis le BO mais vous verrez si vous utilisez le lien "visualiser la facture en pdf" que la facture d?une commande non payée n?est ni datée ni numéroté. Tout ceci nous renseigne bien peu sur l?édition effective de la facture d?une commande. Il vous faut comprendre que dans Thélia une facture en tant que telle n?existe pas. C?est un "objet" créé dynamiquement, à la demande. Quels sont les besoins en la matière ? Et com- ment Thélia y répond-il ? 202 La tâche n?est pas simple car dans Thélia plusieurs fichiers portent le même nom : facture. php. Démêlons le chemin que suit le serveur pour générer dynamiquement une facture en pdf à partir des tables liées à une commande dans la base de données Thélia. Dans le BO sur la page commande_details.php ou sur le FO sur la page commande_ „ „ details.html un lien "visualiser la facture au format pdf" est une url qui appelle le fichier : [1] client/pdf/facture.php?ref=xxx Ce fichier inclut lui-même le fichier [2] admin/facture.php dans lequel un objet „ „ $com- mande est instancié et récupère les éléments de la commande (ref=xxx) stockés dans les tables de la base. admin/facture.php inclut enfin le 3ième fichier facture [3] client/pdf/modèle/facture.php „ „ : ce dernier est plus complexe : il constitue la trame dynamique de la facture, telle que l?or- ganise la classe pré-définie fpdf. Si vous souhaitez créer un nouveau type de document .pdf (par exemple des devis) ou modifier en profondeur les modèles existant, il est intéressant de se pencher quelques instants sur cette page et son fonctionnement. fpdf est une classe php stockée dans le dosssier "lib" qui contient les "librairies" exter- „ „ nes, ajoutées à Thélia comme jquery, phpmailer (depuis 1.3.8) et fpdf. Comme toute classe, fpdf est constituée d?attributs et de méthodes que Thélia va appeler pour se simplifier le travail : le principe est le suivant : un modèle vierge en pdf, logiquement appelé facture.pdf est stocké dans le dossier client/pdf/doc : vous pouvez par défaut ouvrir ce fichier (avec tout logiciel le permettant : c?est le cas d?Acrobat ou d?Illustrator par exemple) et rajouter votre logo, vos informations légales, modifier la couleur des cellules... Vous ne changerez pas en revanche la position et la taille de ces cellules : elles sont disposées de sorte que l?écriture générée dynamiquement par le fichier [3] client/pdf/modèle/facture.php soit présentée cor- rectement : [3] client/pdf/modèle/facture.php va en effet utiliser ce template et le remplir des éléments constitutifs de la commande. Les attributs et méthodes de fpdf sont utilisés pour la disposition des éléments et la pagination. Ils appellent le template du même nom et mettent les données en page. Finalement l?url facture.php?ref=xxx met en action cette cinématique : le serveur récupère les données des tables et les injecte dans le template facture.pdf qu?il affiche au client (FO) ou au marchand (BO). Cette méthode est simple et fonctionnelle. Elle s?appuie sur la boucle ?commande? codée sur la page commande_details.html du FO pour perfectionner un petit peu le système : en effet nous ne souhaitons pas générer de facture vers le client si sa commande n?est pas payée : dés lors le lien "visualiser la facture en pdf" est imbriquée dans une boucle "commande" dont le paramètre statut est ="paye". Ainsi tant que la commande est non payée le client n?a accès à aucune facture. Ceci dit le système mériterait d?être un tout petit peu amélioré : en effet par défaut une com- mande annulée (entendez une commande supprimée) a un statut=5 donc sur son compte le client aura accès à cette commande annulée et au pdf de la facture de celle-ci, même si la commande n?a jamais été payée. 203 2°) Le bon de livraison (BL) Il suit le même raisonnement mis à part quelques détails : -- Nul besoin d?une fonction similaire à genfact() pour les BL : le numéro a été créé lors de la création de la commande (champ ?livraison? de la table ?commande) : cette valeur alimentera le numéro de BL Quant à la date elle sera celle du moment où le pdf est affiché : le BL est généré par le mar- chand au moment de remettre le colis au transporteur. Pour la facture le problème était dif- férent : la date d?une facture est fixe et correspond au moment du paiement ce qui explique le pourquoi de la fonction genfact(). -- Il n?y a pas d?affichage prévu du BL sur le FO puisque le BL est un document à imprimer par le marchand et à joindre au colis. 204 La gestion des 12. mails de la commande Cette fonctionnalité est importante : bien conçue, elle évite de coûteux appels téléphoniques liés aux suivis des commandes. Pour connaître l?état d?avancement d?une commande, le client peut suivre son statut depuis son espace client mais ce réflexe n?est pas toujours acquis et être pro-actif en communiquant par mail sur les principales étapes franchies par une commande est gage de sérieux et de sérénité pour le client, plus apte à patienter pourvu qu?il soit régulièrement informé. Quels événements d?une commande pourraient générer un mail de suivi ? -- Validation de la commande -- Paiement effectif de la commande / mise à disposition de la facture -- Envoi de la commande au client -- Annulation d?une commande Par défaut dans Thélia, seuls la validation et l?envoi génèrent automatiquement un mail vers le client. Voyons comment : Le mail de confirmation de commande 1. Concernant le mail de confirmation d?une commande vous vous doutez que la "super" fonc- tion paiement() va se charger du boulot ! Nous la retrouvons donc encore une fois ici, avant sa dernière ligne (ligne 337) : include_once("client/plugins/" . $modules->nom . "/" . $nomclass . ".class.php"); modules_fonction("mail", $commande, $modules->nom); La fonction paiement() inclut donc le fichier classe du plugin paiement sélectionné et exécute la méthode mail() de la classe PluginsPaiements à laquelle tout plugin paiement est affilié. La méthode mail() est décrite aux lignes 72 à 128 du fichier classes/PluginPaiements.class. php : voici la partie consacrée à l?envoi du mal côté client (un second mail est prévu côté marchand, j?ai supprimé les lignes le concernant) 205 function mail($commande){ $sujet=""; $corps=""; $msg = new Message(); $msg->charger("mailconfirmcli"); $msgdesc = new Messagedesc(); $msgdesc->charger($msg->id); $sujet = $this->substitmail($msgdesc->titre, $commande); $corps = $msgdesc->description; $corps = $this->substitmail($corps, $commande); $emailcontact = new Variable(); $emailcontact->charger("emailcontact"); $sujet2 = $this->substitmail($msgdesc->titre, $commande); $corps2 = $this->substitmail($corps2, $commande); $client = new Client(); $client->charger_id($commande->client); $nomsite = new Variable(); $nomsite->charger("nomsite"); $mailclient = new PHPMailer(); $mailclient->IsMail(); $mailclient->FromName = $nomsite->valeur; $mailclient->From = $emailcontact->valeur; $mailclient->Subject = $sujet; $mailclient->MsgHTML($corps); $mailclient->AddAddress($client->email,$client->nom." ".$client->prenom); $mailclient->send(); Voici quelques remarques sur la fonctionnalité : Le terme "message" dans Thélia fera toujours référence à la fonctionnalité des mails „ „ automatiques : les modèles de ces derniers sont entreposés dans les tables ?message? et ?messagedesc? de la base. Des classes homonymes sont définies et rangées dans le dossier classe ; des objets „ „ homonymes vont permettre d?appeler ces données de la base dans le script pour les mani- puler (manipulées par les méthodes de l?objet, stockées dans ses attributs). mail() „ „ va exécuter une sous-méthode substitmail() : cette dernière (que je ne repro- 206 duirais pas car vous devez à ce stade avoir une bonne idée du code qui la compose) créé des substitutions Thélia propres aux contenus des messages : Vous ne vous étonnerez pas que nous n?évoquions pas ici une boucle message mais de "simples" substitutions : nous allons créer des #tags pour appeler dynamiquement des éléments unitaires de type #NOM_ CLIENT, #REF_COMMANDE... Les données client seront issues de la variable de session „ „ $_SESSION[?navig?]->commande alimentant la variable $commande. Les objets „ „ $nomsite et $emailcontact de classe Variable rapatrient dans le script l?url de votre site et son adresse mail que vous avez défini dans le BO. Une fois tous les éléments constitutifs du mail ainsi récupérés dans le script, Thélia fait „ „ appel à une classe prédéfinie PHPmailer pour la mise en forme et l?envoi effectif du mail au client et/ou au marchand. Cette classe est entreposée dans le dossier lib et offre au program- meur de Thélia de prendre en charge l?ensemble du code nécessaire à l?envoi du mail. Les mails par défaut dans Thélia sont réduits à leur strict minimum et ne sont mis en forme par aucun code html. PHPmailer permet de mettre en forme facilement un mail et nous ver- rons comment dans la prochaine partie. Le mail de confirmation d?envoi du colis 2. 207 Annexes : pour aller plus loin avec 13. Thélia La fo 1. nction paiement() en détail Nous avons vu l?importance de l?url #URLPAYER qui exécute la fonction paiement() du fichier fonctions/action.php : cette fonction est la frontière entre le traitement FO et BO d?une com- mande Elle exécute un jeu d?instructions nécessaires au bon déroulement d?une commande : elle met à jour les différentes tables de la base, assurant sa gestion et son suivi dans le temps. Nous avons eu l?occasion de présenter la fonction paiement() en détail mais morce- lée. La voici reconstituée intégralement et commentée ligne par ligne. De cette manière il est envisageable de la modifier avec beaucoup de précaution. Avant voici d?abords une synthèse des tables impactées par cette fonction : 1°) les tables sollicitées pour récupérer les éléments nécessaires à la commande 2°) les tables modifiées par la commande 208 209 1°) Les tables d?où sont chargés [charger()] les éléments constitutifs d?une commande paiement() commande venteadr stock produit venteprod ventedecli disp promo Enregistre une nouvelle commande Enregistre deux nouvelles adresses : une de livraison et une adresse de facturation Charge la valeur de la pro- mo dans la commande et désactive la promo unitaire Met à jour le stock des déclinaisons Enregistre 1+ ventedeclidisp Enregistre 1 ou + venteprod Met à jour le stock produit new new new new new new new new new new new new new new new new new new new new new new new maj maj maj id client date facture livraison id nom prenom adresse cpostal id declidisp produit valeur surplus id ref prix stock poids id ref titre chapo quantite id declidisp venteprod id type illimite utilise mini paiement() new new new new new new new new new new new new new new new new new new new new new new new id client date facture livraison 2°) Les tables où sont ajoutés [add()] ou mis à jour [maj()] les éléments constitutifs d?une com- mande 209 210 + Identification préalable nécessaire + panier non vide ou redirection vers index.php + Inititialisation des variables internes : $total=0 $nbart=0 $poids=0 $unitetr=0 + Inclusion méthode plugin + Initialisation d?un objet $module + Chargement du plugin paiement sélectionné + Initialisation d?un objet $commande + Alimentation des attributs de $commande : -- Calcul des valeurs pour les attributs : $date $ref $livraison $transaction $remise=0 -- Ou récupération des valeurs pour : $transport $client + Initialisation d?un objet $client. + Chargement dans ses attributs des données client issues de la table client. + Initialisation d?un objet $adr de classe Ven- teadr . + Alimentation des attributs de $adr avec les valeurs de $client (N° de tel sont concaténés) + Ajout à la table venteadr des valeurs collec- tées dans $adr. + L?ID de la nouvelle venteadr est chargé dans l?attribut $adrfact de $commande (jointure). + Initialisation d?un nouvel objet $adr de classe Venteadr. + Initialisation d?un objet $livraison de classe Adresse. + Si l?attribut $adresse de ?navig? contient un ID : - Les valeurs de l?adresse correspondante à l?ID dans la table ?adresse? complètent les attributs de $adr. + Sinon si l?attribut $adresse est vide ou nul : - Les attributs de $adr sont alimentés par les valeurs relatives à l?adresse parmi les attributs de l?objet $client.(=> adresse de facturation) function paiement($type_paiement){ if(! $_SESSION[?navig?]->client->id || $_SESSION[?navig?]- >panier->nbart < 1) header("Location: index.php"); $total = 0; $nbart = 0; $poids = 0; $unitetr = 0; modules_fonction("avantcommande"); $modules = new Modules(); $modules->charger_id($type_paiement); $commande = new Commande(); $commande->transport = $_SESSION[?navig?]->commande- >transport; $commande->client = $_SESSION[?navig?]->client->id; $commande->date = date("Y-m-d H:i:s"); $commande->ref = "C" . date("ymdHis") . strtoupper(substr($_ SESSION[?navig?]->client->prenom,0, 3)); $commande->livraison = "L" . date("ymdHis") . strtoupper(substr($_SESSION[?navig?]->client->prenom,0, 3)); $commande->transaction = date("His"); $commande->remise = 0; $client = New Client(); $client->charger_id($_SESSION[?navig?]->client->id); $adr = new Venteadr(); $adr->raison = $client->raison; $adr->nom = $client->nom; $adr->prenom = $client->prenom; $adr->adresse1 = $client->adresse1; $adr->adresse2 = $client->adresse2; $adr->adresse3 = $client->adresse3; $adr->cpostal = $client->cpostal; $adr->ville = $client->ville; $adr->tel = $client->telfixe . " " . $client->telport; $adr->pays = $client->pays; $adrcli = $adr->add(); $commande->adrfact = $adrcli; $adr = new Venteadr(); $livraison = new Adresse(); if($livraison->charger($_SESSION[?navig?]->adresse)){ $adr->raison = $livraison->raison; $adr->nom = $livraison->nom; $adr->prenom = $livraison->prenom; $adr->adresse1 = $livraison->adresse1; $adr->adresse2 = $livraison->adresse2; $adr->adresse3 = $livraison->adresse3; $adr->cpostal = $livraison->cpostal; $adr->ville = $livraison->ville; $adr->tel = $livraison->tel; $adr->pays = $livraison->pays; } else { $adr->raison = $client->raison; $adr->nom = $client->nom; $adr->prenom = $client->prenom; ligne 129 ligne 141 ligne 154 ligne 171 211 $adr->adresse1 = $client->adresse1; $adr->adresse2 = $client->adresse2; $adr->adresse3 = $client->adresse3; $adr->cpostal = $client->cpostal; $adr->ville = $client->ville; $adr->tel = $client->telfixe . " " . $client->telport; $adr->pays = $client->pays; } $adrlivr = $adr->add(); $commande->adrlivr = $adrlivr; $commande->facture = 0; $commande->statut="1"; $commande->paiement = $type_paiement; $commande->lang = $_SESSION[?navig?]->lang; $idcmd = $commande->add(); $commande->charger($idcmd); $venteprod = new Venteprod(); for($i=0; $i<$_SESSION[?navig?]->panier->nbart; $i++){ $declidisp = new Declidisp(); $declidispdesc = new Declidispdesc(); $declinaison = new Declinaison(); $declinaisondesc = new Declinaisondesc(); $dectexte = "\n"; $produit = new Produit(); $stock = new Stock(); for($compt = 0; $compt<count($_SESSION[?navig?]->panier- >tabarticle[$i]->perso); $compt++){ if(is_numeric($_SESSION[?navig?]->panier->tabarticle[$i]- >perso[$compt]->valeur)){ $stock->charger($_SESSION[?navig?]->panier->tabarticle[$i]- >perso[$compt]->valeur, $_SESSION[?navig?]->panier- >tabarticle[$i]->produit->id); $stock->valeur-=$_SESSION[?navig?]->panier->tabarticle[$i]- >quantite; $stock->maj(); } $tperso = $_SESSION[?navig?]->panier->tabarticle[$i]- >perso[$compt]; $declinaison->charger($tperso->declinaison); $declinaisondesc->charger($declinaison->id); if($declinaison->isDeclidisp($tperso->declinaison)){ $declidisp->charger($tperso->valeur); $declidispdesc->charger_declidisp($declidisp->id); $dectexte .= "- " . $declinaisondesc->titre . " : " . $declidis- pdesc->titre . "\n"; } else $dectexte .= "- " . $declinaisondesc->titre . " : " . $tperso- >valeur . "\n"; } $produit = new Produit(); $produit->charger($_SESSION[?navig?]->panier->tabarticle[$i]- >produit->ref); + Les valeurs obtenues pour les attributs de $adr sont chargées dans la table venteadr. + L?ID de la nouvelle venteadr est chargé dans l?attribut $adrlivr de $commande (jointure). + Les attributs suivants de l?objet $commande sont renseignés : $facture=0 $statut=1 $paiement $lang + L?objet $commande est complet : les valeurs de ses attributs sont ajoutés dans la table com- mande. + La variable $idcmd reçoit l?ID de la nouvelle commande enregistrée dans la table + $commande est ré actualisée avec la table. + Initialisation d?un objet $venteprod + Pour chaque article du panier : ligne 215 + Initialisation de 6 objets : $declidisp $declidispdesc $declinaison $declinaisondesc $produit $stock + Initialisation d?une variable supplémentaire : $dectexte + Dans les attributs de l?objet $stock sont char- gées les valeurs stockées dans la table stock pour cette déclinaison de cet article. + L?attribut $valeur de $stock est décrémenté en fonction de la quantité dans le panier pour cet article. + La table stock est mise à jour avec les valeurs de $stock (=> maj du champ ?valeur?) + Dans $déclinaison et $declinaisondesc sont chargées les valeurs stockées dans les tables ?declinaison? et ?declinaisondesc? correspon- dant à cette déclinaison. + La variable $dectexte est manipulée : elle ser- vira plus bas à compléter la valeur du champ ?titre? de la ligne ajoutée dans la table ?vente- prod? pour cet article. $dectexte prend une valeur de type ?declinaison : valeurdeclinaison? (Ex : taille : XL) + Pour chaque déclinaison enregistrée dans le panier pour cet article (1 ligne de $perso) : ligne 231 ligne 256 + Un objet $produit est instancié : dans ses at- tributs sont chargées les valeurs de l?article du panier (passe en cours de la boucle "for") 212 $produit->stock-=$_SESSION[?navig?]->panier->tabarticle[$i]- >quantite; $produit->maj(); $prodtradesc = new Produitdesc(); $prodtradesc->charger($_SESSION[?navig?]->panier- >tabarticle[$i]->produit->id, $_SESSION[?navig?]->lang); $venteprod->quantite = $_SESSION[?navig?]->panier- >tabarticle[$i]->quantite; if( ! $_SESSION[?navig?]->panier->tabarticle[$i]->produit- >promo) $venteprod->prixu = $_SESSION[?navig?]->panier- >tabarticle[$i]->produit->prix; else $venteprod->prixu = $_SESSION[?navig?]->panier- >tabarticle[$i]->produit->prix2; $venteprod->ref = $_SESSION[?navig?]->panier->tabarticle[$i]- >produit->ref; $venteprod->titre = $prodtradesc->titre . " " . $dectexte; $venteprod->chapo = $prodtradesc->chapo; $venteprod->description = $prodtradesc->description; $venteprod->tva = $_SESSION[?navig?]->panier->tabarticle[$i]- >produit->tva; $venteprod->commande = $idcmd; $idvprod = $venteprod->add(); for($compt = 0; $compt<count($_SESSION[?navig?]->panier- >tabarticle[$i]->perso); $compt++){ $tperso = $_SESSION[?navig?]->panier->tabarticle[$i]- >perso[$compt]; $declinaison->charger($tperso->declinaison); if($declinaison->isDeclidisp($tperso->declinaison)){ $vdec = new Ventedeclidisp(); $vdec->venteprod = $idvprod; $vdec->declidisp = $tperso->valeur; $vdec->add(); } } $total += $venteprod->prixu * $venteprod->quantite; $nbart++; $poids+= $_SESSION[?navig?]->panier->tabarticle[$i]->produit- >poids; } $pays = new Pays(); $pays->charger($_SESSION[?navig?]->client->pays); if($_SESSION[?navig?]->client->pourcentage>0) $commande- >remise = $total * $_SESSION[?navig?]->client->pourcentage / 100; $total -= $commande->remise; + Initialisation d?un objet $prodtradesc de clas- se Produitdesc dans les attributs duquel sont chargées les valeurs de la table produitdesc pour cet article. + L?attribut $quantite de l?objet $venteprod re- çoit la quantité renseignée dans le panier pour cet article. + Si la valeur de l?attribut $promo du panier =1 le prix promo est retenu pour alimenter l?attri- but $prixu de $venteprod + Sinon le prix normal est appliqué à $prixu + La $ref et la $tva de $venteprod sont alimen- tées par leurs équivalents du panier. + Les autres attributs de $venteprod sont ali- mentés par leurs équivalents de $prodtra- desc. + Jointure entre l?attribut $commande de $ven- teprod et l?ID de la commande en base. + La table venteprod est alimentée par les attri- buts de $venteprod + La variable $idvprod reçoit l?ID de la nouvelle venteprod enregistrée dans la table ligne 279 ligne 293 ligne 300 + Son attribut $stock est décrémenté de la quantité enregistrée pour cet article + La table produit est maj (champ ?stock?) ligne 262 +Dans $declinaison sont chargées les valeurs de la table ?declinaison? correspondant à cette déclinaison + Istanciation d?un objet $vdec de classe ven- tedeclidisp + l?attribut $venteprod de $vdec reçoit l?ID de la venteprod enregistrée dans la table pour cet article (jointure) + Maj de la table ventedeclidisp depuis $vdec + $total est incrémenté du prix de l?article x sa quantité. + $nbart est incrémenté de 1. + $poids est incrémenté du poids de l?article X sa quantité. + Un objet $pays est instancié et alimenté par les valeurs du pays du client. + Si le client bénéficie d?une remise générale elle est calculée et déduite du montant de $to- tal. + Pour chaque déclinaison enregistrée dans le panier pour cet article (1 ligne de $perso) : Quelques remarques supplémentaires sur la fonction paiement() : Elle ne prend pas en compte les variations tarifaires des déclinaisons (le champs "sur- „ „ plus" des déclinaisons). Ceci est normal : cette variation doit être appliquée dés l?étape du panier, pour que l?affichage du prix de l?article dans le panier en tienne compte. Le prix d?un produit ajouté au panier est ainsi ajusté en fonction du surplus de la déclinaison par le constructeur de la classe Article, la méthode article() (fichier classes/article.class.php ligne 58 à 63) : 213 if($_SESSION[?navig?]->promo->id != ""){ if($_SESSION[?navig?]->promo->type == "1" && $_ SESSION[?navig?]->promo->mini <= $total) $commande->remise += $_SESSION[?navig?]->promo->valeur; else if($_SESSION[?navig?]->promo->type == "2" && $_ SESSION[?navig?]->promo->mini <= $total) $commande->remise += $total * $_SESSION[?navig?]->promo->valeur / 100; $_SESSION[?navig?]->promo->utilise = 1; $commande->maj(); $temppromo = new Promo(); $temppromo->charger_id($_SESSION[?navig?]->promo->id); if(! $temppromo->illimite) $temppromo->utilise="1"; $temppromo->maj(); $_SESSION[?navig?]->promo = new Promo(); } $commande->port = port(); if($commande->port == "" || $commande->port<0) $commande- >port = 0; $_SESSION[?navig?]->commande = $commande; $commande->maj(); modules_fonction("aprescommande", $commande); $nomclass=$modules->nom; $nomclass[0] = strtoupper($nomclass[0]); include_once("client/plugins/" . $modules->nom . "/" . $nom- class . ".class.php"); modules_fonction("mail", $commande, $modules->nom); $tmpobj = new $nomclass(); $tmpobj->paiement($commande); } + Inclusion méthode plugin + Inclusion méthode plugin. + Si la variable de session $promo est rensei- gnée et si le montant de la commande est su- périeur au montant minimum exigé pour cette promo : -- Si la promo est de type montant sa valeur alimente l?attribut $remise de $commande. -- Si la promo est de type pourcentage, la va- leur de la remise est calculé par rapport à $to- tal et cette valeur alimente l?attribut $remise de $commande. + Si la promo est unitaire (pas illimitée), la table promo est mise à jour : la valeur du champ "uti- lise" de la promo soumise passe de 0 à 1. + La variable de session $promo est remise à 0 + L?attribut $port de $commande reçoit le calcul de la fonction port(). +A la variable de session $commande sont af- fectées les valeurs de la variable $commande. + Mise à jour de la donnée commande de la table commande. ligne 300 ligne 300 ligne 300 + Un nouvel objet de la classe du plugin paie- ment sélectionné est instancié. + La fonction paiement() de cette classe est exécutée sur $commande. + Une nouvelle variable $nomclasse prend le nom de la classe du plugin paiement sélec- tionné. + La classe correspondante est incluse. 214 if($declinaison->isDeclidisp()){ $stock = new Stock(); $stock->charger($perso[$i]->valeur, $this->produit->id); if($stock->surplus != 0){ $this->produit->prix += $stock->surplus; $this->produit->prix2 += $stock->surplus;} Les paramètres de sorties #PRIX et #PRIX2 de la boucle "stock" sont aussi corrigés en fonc- tion du surplus de la declidisp sélectionnée pour ce produit. Les jointures nécessaires dans la base entre les tables ?commande?, ?venteprod?, ?ven- „ „ tedeclidisp? et ?venteadr? sont programmées dans la méthode add() de la classe Requete (classes/requete.class.php lignes 53 à 59) : appelée par une classe affiliée à la classe Re- quete, cette méthode ajoute une ligne dans la table du même nom et en plus retourne l?ID de cette ligne grâce à la fonction php mysql_insert_id() : function add(){ $query = "insert into $this->table(" . $this->getListVarsSql() . ") values(" . $this- >getListValsSql() . ")"; $resul = mysql_query($query, $this->link); return mysql_insert_id(); } Ainsi : -- Lorsque sur un objet de classe Commande est exécutée la méthode add() une nouvelle ligne est ajoutée à la table ?commande?, une valeur auto-incrémentée est affectée au champ ?ID? de cette ligne, cet ID est affecté à la variable $idcmd qui à son tour est affectée au champ ?commande? de la table ?venteprod?. -- Lorsque sur un objet de classe Venteprod est exécutée la méthodeadd() une nouvelle ligne est ajoutée à la table ?venteprod?, une valeur auto-incrémentée est affectée au champ ?ID? de cette ligne, cet ID est affecté à la variable $idvprod qui à son tour est affectée au champ ?venteprod? de la table ?ventedeclidisp?. -- Lorsque sur un objet de classe Venteadr est exécutée la méthode add() une nouvelle ligne est ajoutée à la table ?venteadr?, une valeur auto-incrémentée est affectée au champ ?ID? de cette ligne, cet ID est affecté à la variable $adrlivr qui à son tour est affectée au champ ?adr- livr? de la table ?commande?. -- Lorsque sur un objet de classe Venteadr est exécutée la méthode add() une nouvelle ligne est ajoutée à la table ?venteadr?, une valeur auto-incrémentée est affectée au champ ?ID? de cette ligne, cet ID est affecté à la variable $adrfact qui à son tour est affectée au champ ?adr- fact? de la table ?commande?. Bilan sur les valeurs (?) disponibles 2. Voici un état des lieux des valeurs disponibles dans Thélia pour un panier, une commande et une facture. Les valeurs stockées sont en gras (la table est entre parenthèses). Les valeurs calculées non. -- Les #TAGS paramètres de sortie d?une boucle sont précédés du nom de la boucle entre accolades. -- Les #TAGS des substitutions sont précédés du type de substitution entre crochets. Valeur (?) Panier Commande Facture produit HT {panier} #PRIXUHT X +prixht produit TTC {panier} #PRIXU (produit) {venteprod} #PRIXU (venteprod) prixu (venteprod) tva produit (en %) {panier} #TVA (produit) {venteprod} #TVA (venteprod) TVA (venteprod) montant tva produit X X X produit*quantité HT {panier} #TOTALHT X +prixht*quantité produit*quantité TTC {panier} #TOTAL {commande} #TOTALPROD +prixu*quantité montant tva produit*quantité X X X total commande HT sans port (1) {panier} #TOTSANSPORTHT (2) [#PANIER_]TOTALHT2 X décliné en fonction des 3 taux principaux : + 2,1% (2) + 5,5% (2) +19,6% (2) total commande TTC sans port (1) {panier} #TOTSANSPORT (2) [#PANIER_]TOTAL (2) {commande} #TOTALCMD X total commande HT avec port (1) {panier} #TOTPORTHT (2) X X total commande TTC avec port (1) {panier} #TOTPORT (2) [#PANIER_]TOTPORT2 {commande} #TOTALCMDPORT + total tva commande (en %) (1) X X Pour 3 taux : 2,1%, 5,5%, 19,6% montant tva commande sans port (1) X X décliné en fonction des 3 taux principaux : + 2,1% (2) + 5,5% (2) +19,6% (2) montant tva commande avec port (1) X X X port HT (1) {panier} #PORTHT (2) X X port TTC (1) {panier} #PORT (2) {commande} #PORT (commande) port (commande) montant tva port (1) X X X remise HT X X X 215 216 Valeur (?) Panier Commande Facture remise TTC [#PANIER_]REMISE {commande} #REMISE (comman- de) + remise montant tva remise X X X total commande HT hors remise validée X X X total commande TTC hors remise validée X X X (1) Toutes ces valeurs s?entendent remise incluse (2) Ces valeurs varient selon que le client a validé une promo/remise ou non sur la page commande.php. Le fichier facture.php en détail 3. la facture modifie la remise si elle était de type somme : elle est transformée en pourcen- tage LAppréhender Thélia par la pratique 1. 8. Appréhender Thélia par la pratique : Modifications fonctionnelles J?ai rassemblé dans cette courte partie quelques modifications dont j?ai eu besoin pour l?im- plémentation d?un site Thélia orienté B2B. Avec mon plugin devis ces modifications mineures du code source de Thélia me donnent entière satisfaction. Mais je rappelle que la program- mation php n?est ni mon métier ni même ma spécialité, tout juste une passion naissante née dans le cadre de la découverte du programme, le même enthousiasme qui m?a poussé à tout mettre par écrit : je ne vous encourage pas à les mettre en place sur votre propre sys- tème : ils sont décrits ici pour vous montrer à quel point Thélia est facile à modifier et adapter : cela est dû à la grande clarté de son code . Ainsi j?ai identifié un ensemble de petites optimisations à prendre en compte plus deux mo- difications plus lourdes : -- Envoi d?un mail de confirmation de paiement + mise à disposition de la facture -- Amélioration de la gestion du stock pour les produits avec déclinaison(s) -- Factures .pdf non disponible pour une commande annulée -- Enregistrement des prix en HT pour le marchand. -- Enrichissement des statuts d?une commande dans le BO Cette partie vous résume ce que j?ai mis en place et comment. Chapitre 1 : Chapitre 2 : Chapitre 3: 217 218 Livre 5 La base de données Comprendre Agir Modifier Enrichir Adapter Voyage au centre d?une boucle LAppréhender Thélia par la pratique 1. 9. Appréhender Thélia par la théorie : Présentation de la base de données Thélia est bâti sur une base de données... C?est un programme en php qui permet de dialoguer avec cette base de données dont nous savons qu?elle est de type SQL. Dans le jargon informatique la base est un SGBDR.... Navigateur Serveur html Moteur php Base SQL Découvrir les fondamentaux des SGBDR (systèmes de gestion de bases de données rela- tionnelles) n?est pas foncièrement indispensable pour maîtriser Thélia. Pourtant c?est une partie intéressante et très structurante pour la compréhension globale de l?application. Les bases de données relationnelles sont des systèmes d?organisation de l?information s?ap- puyant sur de solides bases mathématiques qu?il n?est pas question d?aborder ici. Mais les quelques principes généraux qu?il convient de connaître et que vous trouverez partout ex- posés sont suffisamment simples pour trouver leur place entre ces pages (cela vous évitera quelques recherches). Ces principes font l?objet du chapitre 1, chapitre qui peut donc comme son homologue de la première partie être ignoré par les développeurs aguerris. Ceci fait nous pourrons présenter la modélisation complète de la base Thélia et l?ensemble des interactions liant ses tables. Nous allons donc rencontrer les différentes tables croisées au cours de cette étude et en dé- couvrirons beaucoup d?autres. Nous comprendrons mieux comment la couche php travaille au niveau de la couche sql. Au cours de la présentation de la base, nous aurons l?occasion de nous focaliser sur un concept de Thélia particulièrement sensible : les déclinaisons. Nous tracerons le chemine- ment parcouru par une déclinaison lorsqu?un client commande un produit avec une déclinai- son, du point de vue de la base de données ; il est intéressant de suivre ce parcours : nous allons voir qu?il fait appel à un très grand nombre de tables de la base de donnée bâtie par thélia. Cette réflexion alimentera la prochaine partie du document consacré à ce concept et à celui des caractéristiques. Chapitre 1 : Survol des concepts théoriques du SGBDR choisi par Thélia Chapitre 2 : Principes généraux d?une requête SQL avec PHP Chapitre 2 : Principes généraux sur la manipulation de la base par le moteur Thélia Chapitre 3 : Schéma général de la base de données Thélia 220 Survol des concepts théoriques du SGBDR 1. utilisé par Thélia Nous allons découvrir quelques notions indispensables pour comprendre ensuite l?organisa- tion de la base. Une base de données est un moyen informatique d?enregistrer de l?information. L?autre „ „ moyen envisageable est le fichier plat : un simple fichier (.text par exemple) ouvert en écri- ture dans lequel vous écrivez littéralement vos informations, à la ligne les unes à la suite des autres. Il n?y a pas beaucoup plus à dire pour comprendre l?intérêt et l?importance du concept des bases de données dans l?informatique moderne. Une base de données relationnelles est composée de... relations. Dans notre langage „ „ nous avons commencé et continuerons d?utiliser le terme table à la place de relation, ce qui est un synonyme couramment accepté et plus facile à appréhender. Voici présentées quelques autres notions qui faciliteront notre compréhension mutuelle (si plusieurs termes sont acceptés je les signale et surligne celui que j?utiliserais toujours dans ce document : je prends pour exemple une table générique simplifiée et fictive que je nommerais ?client? : ID Nom Prenom Adresse Ville Client 1 Dupont Jean 3 rue une Paris 2 Durand Pierre 10 Bd deux Marseille 3 Dujnou Michelle 7 rue trois Rennes 4 Dupont Luc 10 Bd deux Nancy Ligne Enregistrement Tuple Valeur Champ Colonne Attribut Clé primaire Clé A l?installation, vous créez une base vierge que Thélia peuple de 50 tables semblables „ „ à celle-ci : elles portent un nom unique et sont composées d?un nombre variable de champs eux-aussi nommés. La plus part sont vides à l?installation (quelques unes sont alimentées de valeurs prédéfinies). Ces tables sont liées entre elles par des relations logiques concrétisées par la no- „ „ tion de clés : dans une table d?un SGBDR vous pouvez définir un champ comme une clé primaire. Cette notion détermine le champ dont la valeur permet d?identifier formellement chaque ligne de la table : dans ma table ?client? nous voyons qu?il a fallu prévoir un champ 221 ID, une valeur numérique unique pour identifier formellement chaque client car concernant les autres champs, ils contiennent des informations potentiellement partagées par plusieurs clients (pouvant porter le même nom, le même prénom ou habiter la même ville). Dans une base assez complexe, toutes les tables porteront un tel champ ID qui sera systématiquement défini comme clé primaire de la table. Les clés primaires sont donc utilisées pour établir des relations entres plusieurs tables „ „ de la base. Pourquoi et comment ? -- Nous savons maintenant à quel point la programmation (en général) s?attache à modéliser dans des instructions informatiques des objets (et les comportements de ces objets) de la vie réelle. Certains "objets" qu?un programme comme Thélia va devoir modéliser sont comple- xes. La complexité se traduit par le découpage, le séquençage : dans les SGBDR un objet complexe sera modélisé par plusieurs tables liées par des relations logiques. Client Commande ID Nom Prenom Adresse Ville ID Client Montant Date Livraison 1 Dupont Jean 3 rue une Paris 1000 1 395.00 15/04/2009 2 Clé primaire Clé étrangère Adresse_livraison ID N° rue Adresse Code Postal Ville 2 10 Rue blanche 13005 Marseille Clé étrangère Clé primaire Ainsi une commande, un objet complexe, sera modélisée par plusieurs tables dans notre base de données. Dans mon exemple ci-dessus : 3. En plus de gérer la complexité, cette méthodologie offre un évidente efficacité puisqu?on on pourra travailler sur la mutualisation des données à travers les tables d?une base de données relationnelles. -- En faisant référence dans une table à une clé primaire d?une autre table on interconnecte ces deux tables de façon fiable puisque une clé = une ligne unique. Une "clé primaire" est appelée "clé étrangère" quand elle est ainsi enregistrée dans une autre table. Ces interconnexions sont étudiées de près dans l?ingénierie des systèmes relationnels „ „ et font l?objet de règles complexes mais nécessaires lorsqu?il s?agit de maintenir des systè- mes de bases de données compliqués ou stratégiques. Nous allons nous contenter de pré- 222 senter les principaux types de relations que nous rencontrerons dans un SGBDR. Dans la base de données Thélia nous croiserons des tables liées par des relations de ty- pes différents classés de la manière suivante : 1°) Relation de type un-vers-un : lorsqu?une ligne d?une table ne fait référence qu?à une seule ligne d?une autre table. Dans mon exemple ci-dessus, la relation qui lie la table commande à la table adresse_livraison est de ce type : pour une commande il ne peut exister qu?une adresse de livraison. 2°) Relation de type un-vers-plusieurs : lorsqu?une ligne d?une table fait référence à plusieurs lignes d?une autre table. Dans notre exemple, la relation qui lie ?client? à ?commande? est de ce type : pour un client il peut exister plusieurs commandes. Pour une commande il ne peut exister qu?un seul client. 3°) Relation de type plusieurs-vers-plusieurs : plusieurs lignes d?une table sont liées à plu- sieurs lignes d?une autre table : cette configuration pourrait exister si par exemple nous dis- posions d?une table ?produit? et que nous décidions d?enregistrer ces produits directement dans la table ?commande? : plusieurs commandes pourraient contenir plusieurs produits. Ce type de relation pose problème car très complexe. La science des SGBDR recommande alors de créer une table intermédiaire spécifique dédiée à la liaison entre les produits et les commandes. Plus généralement les bases de données relationnelles s?appliqueront à respecter un „ „ certain nombre de règles pour assurer leur fiabilité (les données ne sont pas corrompues) et leur rapidité. La conception d?une base doit garantir que les données seront le moins redon- dantes possible, que les champs des tables resteront le moins vide possible, que les champs enregistreront des valeurs atomiques c?est-à-dire constituées d?un seul élément... La base de données Thélia est bâtie dans le respect des principes présentés ici : ces règles simples vous expliquent si vous consultez la base, la présence de tables dont vous ne soup- çonniez peut-être pas l?existence ou dont vous ne compreniez pas jusqu?à présent la raison d?être. Pour finir, évoquons la nécessité de définir dans un SGBDR le type de valeur attendu „ „ pour un champ d?une table. Les types possibles sont : - varchar() ou char() : caractères (longueur maximale de la chaîne) - text - int : nombre entier - float (...,...): nombre (largeur de l?affichage, nombre de chiffres après la virgule) - date : date Des mots clés optionnels (des contraintes) peuvent aussi préciser le type de donnés pour le champ d?une table : - NOT NULL (valeur obligatoire) - AUTO_INCREMENT (valeur auto incrémentée) - UNSIGNED (valeur non négative) - PRIMARY KEY (clé primaire) 223 Principes généraux d?une requête SQL avec 2. PHP Quelques lignes sur le principe d?instructions SQL dans un script PHP : une requête de ce type sera traditionnellement décomposée en plusieurs instructions. Une requête vers la base de données est en effet composée : -- 1°) D?une requête : un ensemble d?instructions et de contraintes ciblant précisément les données à chercher dans la base. -- 2°) D?une connexion à cette base : suite identification, un canal est ouvert entre la base SQL et le script, par lequel se font les échanges de données (la requête dans un sens et son résultat dans l?autre) -- 3°) A partir de ces 2 éléments, il va falloir exécuter la requête et récupérer les résultats remontés par la requête : une variable sera mise à contribution pour stocker le résultat ob- tenu. Dans un script PHP ces différentes étapes sont codées de la manière suivante : 224 225 Principes généraux sur la manipulation de la 3. base par le moteur Thélia Au cours de cette étude nous avons souvent observé comment le script moteur de „ „ Thélia manipulait la base de données : qu?il s?agisse d?y extraire des données, d?en ajouter, d?en mettre à jour ou d?en supprimer nous savons qu?un schéma type, basé sur la program- mation par objet, est systématiquement mis à exécution : des objets, disposent d?attributs correspondant aux champs d?une table de la base, et de méthodes permettant de manipuler cette table à partir des valeurs prises par leurs attributs ou au contraire, de stocker dans ces attributs les valeurs chargées depuis la table pour les manipuler ou les afficher. Nous allons synthétiser maintenant cette fonctionnalité. L?ensemble du moteur accède à la base de données en instanciant des objets affililés „ „ à la classe Baseobj. Cette classe est elle-même héritée de la classe Requête qui à son tour hérite de la classe Cnx. Cette chaîne de filiation clarifie grandement le script et affranchit le développeur de fastidieuses requêtes SQL. Listons d?abords les 55 classes concernées par la filiation à Baseobj : Rubdeclinaison Rubriquedesc Statut statutdesc Stock Transzone Variable Venteadr Ventedeclidisp Venteprod Zone Rubcaracteristique Rubrique Message Messagedesc Module Moduledesc Pays Paysdesc PluginsClassiques PluginsTransport PluginsPaiement Port Produit Produitdesc Promo Racmodule Contrib Declidispdesc Declinaison Declinaisondesc Devise Document Documentdesc Dossier Dossierdesc Exdecprod Image Imagedesc Lang Declidisp Administrateur Accessoire Adresse Cache Caracdisp Caracdispdesc Caracteristiquedesc Caracteristique Caracval Client Commande Contenu Contenuassoc Contenudesc Baseobj 226 Si nous ouvrons les fichiers des classes listées ci-dessus nous y voyons des similitu- „ „ des dans la structure des attributs : toute classe attachée à Baseobj dispose de deux attri- buts spécifiques, en plus de ses attributs "classiques" : $table et $bddvars. Ces deux attributs spéciaux sont mis à contribution pour le fonctionnement des connexions à la base comme nous allons le découvrir maintenant. -- Le premier, $table, a comme valeur le nom de la table (et de la classe) -- Le second, $bddvars est un tableau associatif dont les valeurs correspondent aux champs de la table et aux attributs "classiques" de la classe. Donc pour toutes les classes concer- nées $bddvars[0] = ID Ainsi par exemple pour la classe Accessoire : -- $table = "accessoire" -- $bddvars = array ("id", "produit", "accessoire", "classement"); + $bddvars[0]=ID + $bddvars[1]= produit ... Toutes les classes bénéficiant de l?héritage disposent d?un constructeur de classe qui „ „ appelle le constructeur de la classe-mère baseobj : par exemple pour la classe Accessoire : function Accessoire(){ $this->Baseobj(); } En cascade, l?instanciation d?un objet de ce type de classe (accessoire par exemple) entraîne la mise à disposition des attributs et méthodes des classes mères pour cet objet : function Baseobj(){ $this->Requete(); } function Requete(){ $this->Cnx(); } Notez que les classes Cnx, Requete et Baseobj sont appelées des classes abstraites : elles ne sont pas destinées à instancier des objets mais seulement par héritage à transmettre leurs attributs et méthodes. Celles-ci pourront être utilisées telles quelles par l?objet fille ou celui-ci pourra adapter une ou plusieurs de ces méthodes : on dit alors que la Classe fille (la classe concrète, c?est-à-dire l?objet instancié, par opposition à la classe abstraite) surcharge la méthode de la classe mère. Nous avons déjà évoqué le principe d?héritage dans la programmation par objet. Ce type de programmation, à laquelle Thélia est entièrement dévouée, s?est imposée comme une norme incontournable, l?encapsulation qu?elle confère aux instructions d?une classe et la lo- gique "naturelle" qu?elle développe permettent de maintenir facilement le programme avec le temps et garantissent de voir régulièrement de nouvelles versions de notre application préférée proposées en téléchargement. 227 Sans entrer dans le détail donc, rappelons pour notre besoin que les classes affiliées seront aptes à manipuler les attributs et méthodes de leurs classes mères : les valeurs de ces attri- buts seront héritées elles aussi mais chaque classe peut définir ses propres valeurs par dé- faut (tout comme elle peut surcharger les méthodes de la classe mère) Nous allons donc pré- senter chaque classe de la chaîne de filiation, en commençant par la source, la classe Cnx et remonter jusqu?à la classe concrète pour dérouler ensuite dans le bon sens le cheminement des différents appels à la base de données et comprendre le rôle de chaque classe. La classe mère : Cnx. 1. La classe à laquelle nous avons affaire ici est particulière. C?est la "couche basse SQL" du programme. : elle enregistre dans ses attributs les informations indispensables pour qu?un dialogue soit possible entre PHP et la base. A nouveau, il n?est pas question ici de dévelop- per un cours complet sur SQL : la littérature s?y consacrant est florissante et les principes de base simples à assimiler. Mais pour notre propos nous rappellerons qu?une requête SQL par php impose une identification préalable des différents protagonistes : les attributs de l?objet Cnx enregistrent ces valeurs lors de l?installation du programme et son constructeur ouvre la connexion lors de l?instanciation dans le moteur d?un objet de classe concrète : class Cnx{ var $host= "localhost"; var $login_mysql= "root"; var $password_mysql= ""; var $db = "thelia140"; var $table = ""; var $link= ""; function Cnx(){ $this->link = @mysql_connect($this->host, $this->login_mysql, $this->password_mysql); if(! $this->link && $_REQUEST[?erreur?] != 1) header("Location: maintenance.php?erreur=1"); mysql_select_db($this->db, $this->link); } } Cet exemple est tiré d?une installation en local sur un environnement XAMPP : les identifiants à la base de données sont génériques pour ce type d?installation locale. Si vous modifiez ul- térieurement ces valeurs, il ne faudra pas oublier de reporter ces modifications ici sous peine de ne plus rien voir fonctionner. Si la base de données n?est pas accessible au moteur, le visiteur est redirigé vers un page maintenance.php. Le constructeur de la classe, la fonction cnx() : -- 1°) Ouvre (ou réutilise) une connexion à la base de données et la met à disposition dans son attribut $link, en fonction des valeurs enregistrées dans ses attributs $host (l?emplace- ment du serveur SQL), $login_mysql (l?identifiant) et $password_mysql (le mot de passe). --2°) exécute la fonction prédéfinie mysql_select_db() identifiant sur le serveur, la base de données ciblée, en l?occurrence celle de Thélia. 228 La sous-classe intermédiaire : Requete 2. Cette sous-classe intermédiaire abstraite, a un fonctionnement similaire : appelée par „ „ le constructeur de l?objet Baseobj, son propre constructeur appelle le constructeur de Cnx, pour accéder à une connexion à la base. La fonction requete() s?approprie les attributs de la classe Cnx et les initialise avec une valeur vide. Elle dispose ainsi de ces attributs en plus de son propre attribut $table, initialisé lui aussi avec une valeur vide. La classe Requete définit aussi un ensemble de méthodes génériques, les requêtes „ „ générales sur la base de données Thélia : elles pourront être invoquées par les classes filles de Requete, soit la classe Baseobj et ses classes affiliées. Concrètement ces méthodes de la classe Requête seront mises à contribution par les fonctions assurant l?exécution des actions du visiteur sur le FO ou les actions du marchand sur le BO : elles sont donc princi- palement appelées par les fonctions du fichier fonctions/actions.php (FO) et des fichiers du dossier "admin" (BO). Charger() sera le plus souvent surchargée par une méthode charger() de la classe concrète sollicitée. -- charger() pour extraire des données de la base de données -- maj() pour modifier des données existantes de la base de données. -- add() pour ajouter de nouvelles données dans la base de données. -- delete() pour effacer des données de la base. Prenons l?exemple de la méthode add() : function add(){ $query = "insert into $this->table(" . $this->getListVarsSql() . ") values(" . $this- >getListValsSql() . ")"; $resul = mysql_query($query, $this->link); return mysql_insert_id(); } La méthode maj() : function maj(){ $listv = ""; $varid = $this->bddvars[0]; for($i=0; $i<count($this->bddvars); $i++){ $varn = $this->bddvars[$i]; if(get_magic_quotes_gpc()) $this->$varn = stripslashes($this->$varn); $this->$varn = mysql_real_escape_string($this->$varn, $this->link); $listv.= $this->bddvars[$i] . "=\"" . $this->$varn . "\", "; } $query = "update $this->table set " . substr($listv, 0, strlen($listv)-2) . " where $varid=\"" . $this->$varid . "\""; $resul = mysql_query($query, $this->link); return $query; } 229 Ou encore la méthode charger() (même si celle-ci est systématiquement supplantée par la méthode de la classe concrète) function charger(){ $varid = $this->bddvars[0]; return $this->getVars("select * from $this->table where $varid=\"" . $this->$varid . "\""); } Ces requêtes, comme celles définies dans les classes affiliées à Baseobj font appel à des méthodes définies dans la classe principale Baseobj que nous allons croiser maintenant. La sous-sous-classe principale : Baseobj 3. L?objet baseobj est doté d?un attribut $bddvars, un tableau vide à l?initialisation. La classe Basobj prévoit également une batterie de méthodes importantes, que les classes affiliées ou mères appellerons en sous-fonctions de leurs propres méthodes. -- Prenons par exemple la méthode getVars() que nous croisons un peu partout dans les dif- férentes classes de Thélia : function getVars($query){ if(! $resul = mysql_query($query, $this->link)) return 0; $row = mysql_fetch_object($resul); if($row){ for($i=0; $i<count($this->bddvars); $i++){ $tempvar = $this->bddvars[$i]; $this->$tempvar = $row->$tempvar; } return 1; } else return 0; return mysql_num_rows($resul); } Cette méthode est appelée principalement par les classes concrètes, comme sous-fonction de leurs propres méthodes définies pour manipuler et plus précisément extraire les données de la table à laquelle elles sont dédiées (les méthodes charger() surchargeant la méthode charger() de Requete). Les fonctionnalités de classement (concernant les produits, rubri- ques, contenus et dossiers) font pareillement appel à getVars(). -- Prenons ensuite le cas de la méthode getListVarSql() : 230 function getListVarsSql(){ $listvars=""; for($i=0; $i<count($this->bddvars); $i++){ $listvars .= $this->bddvars[$i] . ","; } return substr($listvars, 0, strlen($listvars)-1); } -- Puis celui de getListValsSql() : function getListValsSql(){ $listvals= ""; for($i=0; $i<count($this->bddvars); $i++){ $tempvar = $this->bddvars[$i]; if(get_magic_quotes_gpc()) $this->$tempvar = stripslashes($this->$tempvar); $this->$tempvar = mysql_real_escape_string($this->$tempvar, $this->link); $listvals .= "\"" . $this->$tempvar . "\","; } return substr($listvals, 0, strlen($listvals)-1); } Ces deux méthodes sont exécutées par add(), c?est-à-dire principalement lors d?actions sur le FO ou le BO, comme nous l?avons déjà signalé plus haut. -- La première génère une liste des noms des champs de l?attribut $bddvars de l?objet l?exé- cutant. Ces noms sont séparés par une virgule. -- La seconde génère une liste de valeurs à partir des valeurs stockées dans les attributs de l?objet exécutant la méthode. Ces valeurs sont séparées par une virgule et sécurisées pour éviter les injections SQL. -- La fonction add() créé une requête de type INSERT sur la table $this->table : une nouvelle ligne est créée, les champs identifiés par getListVarsSql() sont renseignés par les valeurs re- tournées par getListValsSql(). -- Enfin, la fonction add() retourne l?ID de la nouvelle ligne créée dans la table. Les classes concrètes : synthèse d?une connexion à la base 4. de données dans Thélia Reprenons le cheminement en sens inverse, depuis l?instanciation dans le moteur d?un objet d?une classe affiliée à Baseobj. Nous continuerons de prendre un objet "accessoire" comme exemple mais ce cheminement est identique pour tous les objets affiliés à Baseobj. Nous commençons sur le squelette produit.html du template de base : nous croisons „ „ une boucle accessoire de ce type : <THELIA_PRODUITSACC type="ACCESSOIRE" produit= "#PRODUIT_ID"> <a href= "produit.php?ref=#PRODREF"> </THELIA_PRODUITSACC> Cette boucle va lister sous forme de liens l?ensemble des produits attachés en tant qu?acces- soires au produit en cours. Le moteur prévoit donc l?exécution de la fonction „ „ boucleAccessoire() au passage du serveur sur le squelette. En voici une version simplifiée (voir lignes 628 et suivantes du fi- chier fonctions/boucles.php) : function boucleAccessoire($texte, $args){ $accessoire = new Accessoire(); $query = "select * from $accessoire->table where 1 $search $order $limit"; $resul = mysql_query($query, $accessoire->link); $nbres = mysql_num_rows($resul); if(!$nbres) return ""; while( $row = mysql_fetch_object($resul)){ $prod = new Produit(); $prod->charger_id($row->produit); $temp = str_replace("#ACCESSOIRE", "$row->accessoire", $texte); $temp = str_replace("#PRODID", "$row->produit", $temp); $temp = str_replace("#PRODREF", $prod->ref, $temp); $res .= $temp; } return $res; } Ce code est typique d?une boucle Thélia comme nous l?avons vu lors de la première partie de cette étude. Nous nous intéressons ici à la signification des attributs $accessoire->table et $accessoire->link (en violet) de l?objet $accessoire instancié quelques lignes plus haut. Le serveur a besoin de transformer la boucle du squelette en données de la base. comment y accède-t-il ? L?instanciation d? „ „ $accessoire ouvre une connexion à la base de données par appels successifs aux constructeurs Basobj(), Requete() et Cnx() -- Dans $query est définie la requête qui s?exécutera sur la table ?accessoire? conformément à la valeur de $accessoire->table. Les paramètres de la requête ont été définis plus haut dans la fonction (non représentés) et sont stockés dans $search, $order et $limit. 231 -- mysql_query() exécute la requête proprement dite à partir de $query et de $link, l?attribut de Cnx dont nous avons défini le contenu supra (le canal entre la base SQL et le script php). Le résultat est stocké dans $resul. -- Nous voyons qu?au niveau du script php, l?appel aux données de la base est simplifié : 2 lignes de code après l?instanciation d?un objet suffisent à rapatrier dans le script les données de la table relative à cet objet. Classe concrète Classes abstraites Accessoire BaseObj Requete Cnx accessoire() $this->Baseobj() $query [définition de la table et de la re- quête] $resul [exécution de la $query via $link] $row [récupération du résultat dans des objets] baseobj() $this->Requete() Requete() $this->Cnx() Cnx() $link [identification et ouverture (ou réutilisation) d?un canal vers la base de données] Si maintenant nous nous intéressons au paramètre de sortie #PRODREF de la boucle „ „ "accessoire", nous constatons que la fonction boucleAccessoire() a besoin de données stoc- kées non plus dans la table ?accessoire? mais dans la table ?produit? (REF) : par l?instanciation d?un objet ?produit?, la fonction boucleAccessoire() bénéficie immédiatement de la méthode charger_id() dont elle a besoin. Celle-ci est donc définie dans le fichier classes/Produit.class. php : function charger_id($id){ return $this->getVars("select * from $this->table where id=\"$id\""); } Les méthodes des classes concrètes concernées par l?extraction de données de la base font un usage systématique de la méthode getVars() définie dans la classe Baseobj. Cette méthode : -- Exécute la requête (de type SELECT) via le canal défini dans $link -- Récupère sous forme d?objets les résultats de la requête. -- Comptabilise le nombre de champs de l?attribut $bddvars de chaque objet ainsi instancié. -- Retourne une valeur =0 si la requête n?a donné aucun résultat. -- Retourne une valeur =1 dans le cas contraire. 232 Produit BaseObj Requete Cnx Produit() $this->Baseobj() charger_id() [défini- tion de la table et de la requête, soumise à getVar()] $prod [récupération du résultat de char- ger_id() dans des objets] baseobj() $this->Requete() getVar() [exécution de la requête via $link retourne 0 ou 1 en fonction du résultat] Requete() $this->Cnx() Cnx() $link [identification et ouverture (ou réutilisation) d?un canal vers la base de données] Classe concrète Classes abstraites Enfin si nous isolons quelques lignes d?une fonction du BO (par exemple sur produit_ „ „ modifier.php lignes 436 et suivantes) ou du fichier fonctions/actions.php nous croisons bon nombre d?instructions de ce type : function ajouter(){ $produit = new Produit(); $lastid = $produit->add(); Comme précisé précédemment, les actions font généralement appel aux méthodes de Re- quete, ce qui se traduit ici par l?appel à la méthode add() de la classe abstraite Requete : j?ai déjà détaillé le fonctionnement de cette méthode et nous savons donc en résumer le fonc- tionnement : Produit BaseObj Requete Cnx Produit() $this->Baseobj() $produit->add() [exécution de la méthode add() sur l?objet $produit] Cnx() $link [identification et ouverture (ou réutilisation) d?un canal vers la base de données] Classe concrète Classes abstraites baseobj() $this->Requete() getListVarsSql() [liste des champs de $bddvars] getListValsSql() [liste des valeurs de chaque attribut] Requete() $this->Cnx() add() [ Définition de la table et de la requête, Exécution de la re- quête, Retourne l?ID de la nouvelle ligne] 233 Schéma général de la base de données 4. Thélia 234 235 Venteadr id # raison T nom T prenom T adresse1 T adresse2 T adresse3 T cpostal T ville T tel # pays Promo id T code # type # valeur # mini # utilise # illimite 17 datefin Racmodule id T module 236 Commande id # client # adrfact # adrlivr 17 date 17 datefact T ref T transaction T livraison T facture # transport # port 17 datelivraison # remise T colis # paiement # statut # lang Ventedeclidisp id # venteprod # declidisp Client id T ref # raison T entreprise T siret T intracom T nom T prenom T adresse1 T adresse2 T adresse3 T cpostal T ville # pays T telfixe T telport T email T motdepasse # parrain # type #pourcentage Adresse id T libelle # client # raison T nom T prenom T adresse1 T adresse2 T adresse3 T cpostal T ville # pays T tel Statut id Venteprod id T ref T titre T chapo T description # quantite # prixu # tva # commande Module id T nom # type # actif # classement Statutdesc id # statut # lang T titre T chapo T description Zone id T nom # unite Transzone id # transport # zone Moduledesc id T plugin # lang T titre T chapo T description # devise Langue id T description -> docu -> 237 Produit id T ref 17 datemodif # prix # ecotaxe # promo # prix2 # rubrique # nouveauté # perso # stock # ligne # garantie # poids # tva # classement Accessoires id # produit # accessoire # classement Produitdesc id T produit T titre T chapo T description T postscriptum T lang Caracdisp id # caracteristique Caracdispdesc id # caracdisp # lang T titre Caracval id # produit # caracteristique # caracdisp T valeur Rubdeclinaison id # rubrique # declinaison Declinaison id # classement Declinaisondesc id # declinaison # lang T titre T chapo T description Declidisp id # declinaison Declidispdesc id # declidisp # lang T titre Exdecprod id # produit # declidisp Rubriquedesc id # rubrique # lang T titre T chapo T description T postscriptum Caracteristiquedesc id # caracteristique # lang T titre T chapo T description Rubcaracteristique id # rubrique # caracteristique Caracteristique id # affiche # classement -> pays -> -> docu -> -> image -> -> image -> Contenuassoc id # objet # type T contenu T classement -> devise -> Stock id # declidisp # produit # valeur # surplus Rubrique id # parent T lien # ligne # classement Pays id # lang # zone # defaut # tva Image id # produit # rubrique # contenu # dossier T fichier # classement Message id T nom # protege Paysdesc id # pays # lang T titre T chapo T description Dossierdesc id # dossier # lang T titre T chapo T description T postscriptum Contenu id 17 datemodif # dossier # ligne # classement contenudesc id # contenu T titre T chapo T description T postsriptum # lang Dossier id # parent T lien # ligne # classement Documentdesc id # document # lang T titre T chapo T description Document id # produit # rubrique # contenu # dossier T fichier # classement Devise id T nom T code T symbole # taux Imagedesc id # image # lang T titre T chapo T description Messagedesc id # message # lang T intitule T titre T chapo T description T descriptiontext Administrateur id T identifiant T motdepasse T nom T prenom # niveau Cache id T session T texte T arg T variables T type T res 17 date -> clé primaire zone -> clé primaire produit -> clé primaire rubrique Variable id T nom T valeur # protege # cache -> contenuassoc -> moduledesc Langue Livre 6 Fonctionnaliés avancées Comprendre Agir Modifier Enrichir Adapter Voyage au centre d?une boucle 239 Livre 7 Les classes, les tables Les boucles : Synthèse générale Comprendre Agir Modifier Enrichir Adapter Voyage au centre d?une boucle DEFINITION Gestion des accessoires d?un produit (produit attaché à un autre produit) FILIATION basobj SQUELETTE produit.html COMMENTAIRES Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie $ID ID (AUTO_INCREMENT) $PRODUIT PRODUIT T produit ->ID PRODUIT T #PRODID T $ACCESSOIRE ACCESSOIRE T produit ->ID #ACCESSOIRES T $CLASSEMENT CLASSEMENT V CLASSEMENT T DEB V NUM V ALEATOIRE V #PRODREF T produit ->REF $TABLE $BDDVARS Classe Substitutions METHODES DEFINITION SUBSTITUTION ATTRIBUTS 242 Accessoire Objet classes/accessoire.class.php DEFINITION Gestion des accès sécurisés au Back Office FILIATION basobj SQUELETTE COMMENTAIRES La variable de session $_SESSION[?util?] est un objet de classeAdministrateur. Les pages du BO sont sécurisées : elles imposent qu?une session [?util?] soit ouverte. Une session s?initialise si les valeurs soumises pour identifiant et motdepasse sont conformes aux valeurs des champs "identifiant" et "motdepasse" d?une ligne de la table administrateur. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie $ID ID (AUTO_INCREMENT) $IDENTIFIANT IDENTIFIANT V $MOTDEPASSE MOTDEPASSE V $PRENOM PRENOM V $NOM NOM V $NIVEAU NIVEAU V $TABLE $BDDVARS Classe Substitutions METHODES DEFINITION SUBSTITUTION ATTRIBUTS Classe classes/administrateur.class.php 243 Administrateur DEFINITION Gestion des produits ajoutés au panier FILIATION SQUELETTE COMMENTAIRES La variable de session $_SESSION[?navig?] dispose d?un attribut ?panier?, objet disposant d?un attribut ?article?, objet instancié par cette classe. L?action ajouter au panier créé un nouvel objet article que le constructeur de la classe alimente de valeurs. l?objet Article est composé d?attributs eux-même des objets. La boucle ?panier? dispose d?un paramètre de sortie #ARTICLE ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie N PRODUIT T produit N PRODUITDESC T produitdesc QUANTITE V N PERSO[] Substitutions Classe Table Boucle Article 244 Objet classes/article.class.php DEFINITION Gestion des accessoires d?un produit (produit attaché à un autre produit) FILIATION basobj SQUELETTE produit.html COMMENTAIRES Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie $ID ID (AUTO_INCREMENT) #ID T $LIBELLE LIBELLE V $CLIENT CLIENT V CLIENT T $RAISON RAISON V #RAISON1F C #RAISON2F C #RAISON3F C $NOM NOM V #NOM T $PRENOM PRENOM V #PRENOM T $ADRESSE1 ADRESSE1 V #ADRESSE1 T $ADRESSE2 ADRESSE2 V #ADRESSE2 T $ADRESSE3 ADRESSE3 V #ADRESSE3 T $CPOSTAL CPOSTAL V #CPOSTAL T $VILLE VILLE V #VILLE T $TEL TEL V #TEL T $PAYS PAYS V #PAYS T DEFAUT V #URL A #SUPPRURL A $TABLE $BDDVARS Classe Substitutions METHODES DEFINITION #ADRESSE_ SUBSTITUTIONS adresse() ID charger(ID) ACTIVE Liens A #URL A #SUPPRURL Adresse 245 Objet classes/adresse.class.php DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle Attibuts Champs Paramètres d?entrée Paramètres de sortie $bddvars [] Classe Substitutions Méthodes Substitutions baseobj() getListVarsSql() getListValsSql() getVars($query) serialise_js() Liens Baseobj 246 DEFINITION Gestion de la fonction cache de l?application FILIATION baseobj SQUELETTE COMMENTAIRES Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie $ID ID (AUTO_INCREMENT) $SESSION $TEXTE $ARG $VARIABLE $TYPE_BOUCLE $RES $DATE NOM VALEUR PROTEGE CACHE $TABLE $BDDVARS Classe Substitutions Méthodes Substitutions Cache() charger_id($id) charger($texte, $args, $variables, $type_bou- cle) charger_ session($session, $texte, $args, $varia- bles, $type_boucle) vider($type_bou- cle, $variables) vider_ session($session, $type_boucle, $va- riables) Liens Cache 247 DEFINITION Gestion de la valeur prédéfinie d?une caractéristique FILIATION baseobj SQUELETTE produit.html COMMENTAIRES Utilité du paramètre d?entrée et de sortie rubrique ? Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie $ID ID (AUTO_INCREMENT) ID T #ID T #IDC T $CARACTERISTIQUE CARACTERISTIQUE T caracteristique->ID CARACTERISTIQUE T #CARACTERISTIQUE T ETCARACTERISTIQUE T # CARACTERISTIQUEC T ETCARACDISP T STOCKMINI V COURANTE V RUBRIQUE T #RUBRIQUE T CLASSEMENT V DEB V NUM V #TITRE T caracdispdesc->titre #SELECTED C $TABLE $BDDVARS Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS caracdisp() charger($id) delete($requete) supprimer() Liens Caracdisp Objet classes/caracdisp.class.php 248 249 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Caracdispdesc Objet classes/caracdispdesc.class.php DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Caracteristique 250 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Caracteristiquedesc 251 252 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Caracval 253 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Client 254 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Cnx 255 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Commande 256 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Contenu 257 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Contenuassoc DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Contenudesc DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Contrib DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Declidisp DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Declidispdesc 262 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Declinaison DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Declinaisondesc DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Devise DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Document DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Documentdesc DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Dossier DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Dossierdesc 268 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Exdecprod 269 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Image 270 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Imagedesc 271 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Lang 272 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Mail 273 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Message 274 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Messagedesc 275 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Module 276 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Moduledesc 277 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Navigation 278 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Panier 279 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Pays 280 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Paysdesc 281 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Perso 282 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens PluginsClassiques 283 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens PluginsPaiements 284 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens PluginsTransports 285 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Port 286 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Produit 287 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Produitdesc 288 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Promo 289 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Racmodule 290 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Requete 291 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Rubcaracteristique 292 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Rubdeclinaison 293 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Rubrique 294 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Rubriquedesc 295 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Smtp 296 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Statut 297 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Statutdesc 298 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Stock 299 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Transzone 300 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Variable 301 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Venteadr 302 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Ventedeclidisp 303 DEFINITION Gestion de la connexion à la base de données pour les objets FILIATION requete SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Venteprod 304 DEFINITION Gestion des données client (hors adresse livraison) FILIATION basobj SQUELETTE COMMENTAIRES Toutes les classes dont les objets doivent accéder à la base de données sont affiliées à cette classe. Classe Table Boucle ATTRIBUTS Champs Paramètres d?entrée Paramètres de sortie Classe Substitutions METHODES DEFINITION SUBSTITUTIONS SUBSTITUTIONS Liens Zone 305 Index 1. AJOUTER : decval quantite transport THELIA BOOK Thélia comme vous ne l?aviez jamais vu ! Découvrez l?application E-commerce Open Source dans ses moindres recoins : des centaines de lignes de code commentées en détail, les principales fonctions expliquées et décrites, des dizaines d?illustrations synthétiques, des tableaux récapitulatifs, un tour exhaustif des tables, des classes et de boucles Thélia. Développez dix fois plus vite de puissantes fonctionnalités pour votre site en apprivoisant la méthodes des plugins.... Un livre de chevet pour tous les utilisateurs avancés de Thélia, à lire du début à la fin ou inver- sement, en diagonal ou à l'horizontal, pour comprendre, modifier, enrichir ou encore adapter à votre sauce le moteur de Thélia. ThéliaBook, le premier manuel de référence sur Thélia, pour les marchands, les Webdesigners et les développeurs. - Livre 1 : Le moteur du Front Office - Livre 2 : Le moteur du Back Office - Livre 3 : Le catalogue Thélia - Livre 4 : Le client - Livre 5 : La commande - Livre 6 : La base de données - Livre 7 : Les fonctionnalités avancées de Thélia - Livre 8 : Les classes, les boucles, les tables : synthèse générale Les auteurs : Jean-Baptiste BILLOT : à 37 ans, l?auteur découvre Thélia et dans la foulée la programmation. Formateur, chef de projet technique, spécialiste des interfaces applicatives il tombe amoureux du programme et s?attache à lui fournir un mode d?emploi digne de ce nom. xxx 312

PARTAGER SUR

Envoyer le lien par email
3673
READS
174
DOWN
9
FOLLOW
5
EMBED
DOCUMENT # TAGS
#thelia  #cms  #open source ecommerce  #ecommerce open source solution 

CC BY-ND


DOCUMENT # INDEX
e-commerce 
img

Partagé par  thelia-addict

 Suivre

Auteur:Jean-Baptiste BILLOT
Source:Non communiquée