Valid XHTML     Valid CSS2    

Développement Web avancé L2 (PHP et MySQL)

TP numéro 2

                     gilles.hunault "at" univ-angers.fr

 

Remarque : vous pouvez désormais utiliser Windows ou Linux pour écrire vos fichiers. De plus, il est possible d'utiliser le serveur devel.info-ua au lieu de forge.info.univ-angers.fr pour exécuter vos pages PHP. Cet autre serveur affiche les erreurs PHP alors que forge renvoie une page blanche. Si vous êtes dans une des salles locales de TP, vous pouvez utiliser la forme courte du serveur, à savoir forge.info-ua au lieu de forge.info.univ-angers.fr.

 

Table des matières cliquable

  1. Ecriture de fonctions et procédures

  2. PHP conceptuel et utilisation de bibliothèques de fonctions

  3. Expressions régulières et chaines de caractères

  4. Fichiers, expressions régulières et fichiers-URL

  5. La structure de données "pile" version 2

  6. PHP et tables MySQL (1)

  7. Analyse d'une application (1)

  8. Parcours de fichier et documentation

  9. Parcours de fichier et comptages d'attributs

10. PHP et tables MySQL (2)

11. PHP, tables, éléments et Javascript

12. Analyse d'une application (2)

 

Il est possible d'afficher toutes les solutions via ?solutions=1.

 

1. Ecriture de fonctions et procédures

En programmation, on distingue classiquement fonction et procédure. Comment fait-on la différence en PHP ?

Est-ce qu'une fonction PHP peut renvoyer plusieurs valeurs ou plusieurs variables ?

Application : écrire une fonction min_max qui renvoie la plus petite valeur et la plus grande valeur d'un "array", qu'on testera en ligne de commandes. Peut-on utiliser try pour s'assurer que la variable passée est bien un tableau ?

Au passage, quelles sont toutes les fonctions liées aux tableaux en PHP ? Et comment vérifier la syntaxe d'un programme PHP, puisqu'une erreur de syntaxe comme oublier un point-virgule ou un guillemet fait que tout la page HTML est blanche (vide) comme sur tout serveur de production ?

Solution :  

En PHP, tout est fonction. Mais l'instruction return() est facultative. Donc une fonction PHP sans return() peut être considérée comme une procédure.

Une fonction PHP ne peut pas renvoyer plus d'une variable ni plus d'une valeur directement. Par contre, la variable peut être un tableau, ce qui permet de renvoyer plusieurs valeurs. En voici un exemple avec le renvoi du min et du max d'un tableau :


     <?php
     
     function min_max( $unTableau ) {
     
        return( array( min($unTableau), max($unTableau) ) ) ;
     
     } ; # fin de fonction min_max
     
     # -- tests unitaires
     
     print_r( min_max( array(3,8,2,5,8) ) ) ;
     print_r( min_max( array("oui","a","abcd","non","fgh") ) ) ;
     print_r( min_max( array(3,"oui",8,2,"5","8",37) ) ) ;
     print_r( min_max( array("oui",3,8,2,"5","8",37) ) ) ;
     
     ?>
     

Il y a un peu moins d'une centaine de fonctions directement liées aux tableaux en PHP, comme le montre la page ref.array du manuel de référence. C'est sans doute trop, certaines fonctions auraient pu être regroupées à l'aide d'options, comme en PERL.

On ne peut pas utiliser try pour s'assurer que la variable passée est bien un tableau parce que les fonctions que nous avons utilisées ne renvoient pas des erreurs, mais seulement des warnings. Donc le programme suivant ne "capte" rien :


     <?php
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT) ;
     
     ############################################################
     #                                                          #
     # attention, ceci est un programme qui ne "capte" pas      #
     # l'erreur de typage car PHP n'émet qu'un warning          #
     #                                                          #
     ############################################################
     
     function min_max( $unTableau ) {
     
        try {
           return( array( min($unTableau), max($unTableau) ) ) ;
        } catch (Exception $e) {
           echo "Votre variable n'est pas un tableau.\n" ;
        } # fin de try
     
     } ; # fin de fonction min_max
     
     # -- tests unitaires
     
     print_r( min_max( array(3,8,2,5,8) ) ) ;
     print_r( min_max( "pomme") ) ;
     
     ?>
     

Il faut lire le manuel php pour trouver qu'il existe une fonction is_array qui permet de résoudre notre problème :


     <?php
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT) ;
     
     
     function min_max( $unTableau ) {
     
        if (is_array( $unTableau )) {
           return( array( min($unTableau), max($unTableau) ) ) ;
        } else {
           echo "Le paramètre passé n'est pas un tableau.\n" ;
           return("Erreur dans la fonction min_max( ): le paramètre passé n'est pas un tableau.\n") ;
        } # fin de si
     
     } ; # fin de fonction min_max
     
     # -- tests unitaires
     
     print_r( min_max( array(3,8,2,5,8) ) ) ;
     print_r( min_max( "pomme") ) ;
     
     ?>
     

On remarquera au passage l'incohérence de nommage des fonctions en PHP avec isset et is-array et l'incohérence de nommage de la page Web : is-array.php pour is_array.

Pour vérifier la syntaxe d'un programme PHP, le mieux est d'utiliser php -l fichier en ligne de commande, lorsqu'on a accès à une session en mode terminal sur le serveur. Sinon, on peut utiliser php lint, phpcodechecker ou php syntax check. Mais ceci ne vous dispense pas de XHTML validator parce que le code PHP suivant est valide, mais pas le code XHTML produit à cause du deuxième > en fin d'instruction et de l'absence d'apostrophe fermante pour l'attribut class.


     <?php
     
     
     echo "<h1 class='erreur >Dommage Eliane !</h1>>" ;
     
     ?>
     

 

2. PHP conceptuel et utilisation de bibliothèques de fonctions

Réécrire le code PHP suivant en mode "conceptuel", c'est-à-dire sans aucune balise apparente, à l'aide de std.php (archive std.zip) dans une page Web valide au sens XHTML strict.


     <?php
          echo "<ul>\n" ;
          echo "<li><p> Voir le fichier <a href=\"montresource.php?nomfic=combien.php\">combien.php</a>";
          echo "La commande <b>mysqldump</b> fournit le code-source pour la recréation " ;
          echo "de la table et du remplissage avec les données, soit ici : " ;
          echo "<a href='elf.mysql.txt'>elf.mysql</a>." ;
          echo "</p></li>\n" ;
     
     
          echo "<li><p> Voir le fichier <a href=\"montresource.php?nomfic=combien.php\">combien.php</a>";
          echo " dont l'<em>exécution</em> est <a href='combien.php'>ici</a>." ;
          echo "</p>" ;
          echo "<p> " ;
          echo "</p></li>" ;
          echo "</ul>" ;
     ?>
     

Il est possible de rapatrier l'archive et de la décompresser en ligne de commandes (dans le bon répertoire !) via :


     > cd ~/forge_html/
     > wget http://forge.info.univ-angers.fr/~gh/internet/std.zip
     > unzip std.zip
     

Dans une autre page, écrire une fonction para(nbl) qui produit nbl lignes de texte répétitif. Utiliser ensuite la classe d'objets tdm du fichier std.php pour afficher une page qui implémente une table des matières cliquable avec deux ou trois "chapitres" qui contiennent chacun deux ou trois paragraphes générés par la fonction para().

Comment est définie la fonction h1() dans std.php ? Comment sait-on si une fonction de std.php fait un echo ou un return ? Comment sont nommées les fonctions de std.php ?

Expliquer ensuite comment est généré et où (client ? serveur ?) le code XHTML produit en PHP et Javascript dans la section identifiée par sectdyn si on utilise le texte suivant qui correspond à la page jsdyn.php.


     <?php
     
     #    (gH)   -_-  jsdyn.php  ;  TimeStamp (unix) : 17 Mars 2013 vers 10:16
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT) ;
     include("std.php") ;
     debutPage("Elément h1 généré","strict","","jsdyn.js") ;
     sdl(5) ;
     
     ## -------------------------------------------------------------------------------------------
     
     div("","sectdyn") ;
     h1("Bonjour") ;
     findiv() ;
     
     ## -------------------------------------------------------------------------------------------
     
     sdl(5) ;
     finPage() ;
     
     ?>
     

Voici le fichier Javascript utilisé :


     //    (gH)   -_-  jsdyn.js  ;  TimeStamp (unix) : 14 Mars 2013 vers 11:40
     
     function h1dyn(x) {
     
     var elt   = document.createElement("h1")         ;
     var h1txt = document.createTextNode("Bonsoir !") ;
     
     elt.appendChild(h1txt) ;
     window.document.getElementById("sectdyn").appendChild(elt) ;
     
     } ; // fin de fonction h1dyn
     
     // ###############################################
     
     function addEvent(obj, evType, fn){
      if (obj.addEventListener){
        obj.addEventListener(evType, fn, false);
        return true;
      } else if (obj.attachEvent){
        var r = obj.attachEvent("on"+evType, fn);
        return r;
      } else {
        return false;
      }
     } // # fin de function addEvent
     
     // ###############################################
     
     addEvent(window, 'load', h1dyn);
     
     

Que se passe-t-il si Javascript n'est pas actif ? Y a-t-il un message d'erreur ?

Après avoir lu le fichier montre.js écrire en PHP conceptuel une page qui utilise un bouton Afficher/Masquer qui sert à montrer ou cacher le texte préformaté mis dans un élement dont l'id est surprise comme dans surprise.php.


     // ceci est le  fichier montre.js
     
     
     function montre(sens) {
     
     if (sens==1) {
       window.document.getElementById("surprise").setAttribute("class","cadre visible") ;
       window.document.getElementById("dor").innerHTML = "&nbsp;cliquer ici pour masquer le texte&nbsp;" ;
       window.document.getElementById("dor").setAttribute("onclick","montre(0)") ;
     } else {
       window.document.getElementById("surprise").setAttribute("class","cadre invisible") ;
       window.document.getElementById("dor").innerHTML = "&nbsp;cliquer ici pour afficher le texte&nbsp;" ;
       window.document.getElementById("dor").setAttribute("onclick","montre(1)") ;
     } // fin si
     
     } // fin de fonction montre
     

Solution :  

Il s'agit en fait de l'exercice 11 de notre cours sur la technologie internet dont la solution est ici. On en profitera bien sûr pour regarder les autres exercices...

Le code conceptuel est donc :


     <?php
     
     ul() ;
     
     debutli() ;
        p() ;
          echo " Voir le fichier ".href("montresource.php?nomfic=combien.php","combien.php")."." ;
          echo " La commande ".b("mysqldump")." fournit le code-source pour la recréation " ;
          echo " de la table et du remplissage avec les données, soit ici : " ;
          echo href('elf.mysql.txt',"elf.mysql")."." ;
        finp()  ;
     finli() ;
     
     debutli() ;
       p() ;
          echo "  Voir le fichier ".href("montresource.php?nomfic=combien.php","combien.php");
          echo "  dont l'".em("exécution")." est ".href('combien.php')."." ;
       finp()  ;
       pvide() ;
     finli() ;
     
     finul() ;
     
     ?>
     

Pour une démonstration de table des matières avec la fonction para(), voir tdm_demo.php dont le code-source est ici et ci-dessous.


     <?php
     include "std.php" ;
     debutPage("Demo TDM","strict") ;
     debutSection() ;
     
     function para($nbl=1) {
       p("texte") ;
       for ($idl=1;$idl<=$nbl;$idl++) {
         echo "Ceci est un texte répétitif (ligne $idl). " ;
       } # fin pour idl
       finp() ;
     } # fin de fonction para
     
     $tableauDesRubriques = array() ;
     $idr = 0 ;
     $idr++; $tableauDesRubriques[$idr] = "Chapitre 1" ;
     $idr++; $tableauDesRubriques[$idr] = "Chapitre 2" ;
     $idr++; $tableauDesRubriques[$idr] = "Chapitre 3" ;
     
     $tdmCRLM = new tdm($tableauDesRubriques) ;
     
     # on ne doit pas écrire
     #    $tdmCRLM = new tdm( array("non utilisé","Chapitre_1","Chapitre_2","Chapitre_3")) ;
     
     $tdmCRLM->titre() ;
     $tdmCRLM->menu("oui","oui","nou") ;
     
     $tdmCRLM->afficheRubrique("oui") ; # Chapitre 1
     
     para(30) ;
     para(25) ;
     
     $tdmCRLM->afficheRubrique("oui") ; # Chapitre 2
     
     para(28) ;
     para(15) ;
     para(12) ;
     
     $tdmCRLM->afficheRubrique("oui") ; # Chapitre 3
     
     para(9) ;
     para(7) ;
     para(11) ;
     
     finSection() ;
     finPage() ;
     ?>
     

La fonction h1() est définie comme la fonction h2() et la fonction h3() par un appel à la fonction h() avec le niveau (1, 2, 3...) passé en paramètre. On peut consulter son code source ici. La plupart des éléments XHTML «courts» sont implémentés comme des fonctions avec des echo. Par contre les fonctions comme s_span(), b() et href() utilisent return. Les fonctions sont nommées comme les éléments. Ainsi il y a p() pour <p>, br() pour <br />. Pour raccourcir la frappe, les divers <input> d'un formulaire ont leur propre fonction.

Pour la page jsdyn.php le code PHP génère (coté serveur) et transmet un élément <h1> dans une division identifiée par "jsdyn". Lorsque la page est entièrement chargée (évènement onLoad), le navigateur crée en mémoire via Javascript (coté client) un deuxième <h1> puis le texte Bonsoir ! qui est inséré dans le <h1> et Javascript ajoute ensuite cet élément <h1> à la division.

Si Javascript n'est pas actif, il n'y a aucun message d'erreur, mais Bonsoir ! ne sera pas affiché.

 

3. Expressions régulières et chaines de caractères

Il faudrait aider la jej (jeune et jolie) Abby -- Abigail Scituo -- à identifier des suspects à l'aide de numéros de plaques minéralogiques. Voici ce qu'on sait des plaques des suspects, grâce au NCIS :

  • le numéro de plaque d'une voiture se compose de 1 à 4 chiffres, un espace, deux lettres, un espace et enfin deux chiffres, et rien d'autre.

  • si toutes les lettres sont des consonnes, il s'agit du suspect nommé Ari Haswari.

  • si le numéro de plaque comporte un chiffre répété, il s'agit du tueur de port en port.

  • si le numéro de plaque comporte avant les lettres exactement 4 chiffres avec une alternance pair/impair, il s'agit de "la grenouille".

Ecrire un document PHP qui reproduit le premier formulaire de la page ncis.php via du code valide pour XHTML strict. On affichera l'image abby.jpg et on mettra un lien vers la page Wiki associée.

A partir du formulaire ncis.php écrire le programme défini par l'attribut action de façon à afficher les suspects possibles quand on saisit un numéro de plaque. On utilisera, quand c'est possible les expressions régulières PCRE pour avoir un code PHP concis et notamment preg_match et preg_match_all.

Reprendre l'exercice si on admet que certaines lettres ou certains chiffres ne sont pas exploitables. On notera par un tiret toute lettre ou chiffre non su à condition qu'il n'y ait pas plus de 3 tirets en tout (indication fournie par Mac Gee).

Solution :  

Pour le formulaire, consulter le code-source de la page.

Le mieux est sans doute d'écrire des fonctions spécialisées pour chaque suspect, d'où la solution nommée ncis_res_sol testable via ncis_sol.php. On remarquera les exemples de plaques ajoutés dans la page, utiles pour des tests unitaires et d'intégration.

 

4. Fichiers, expressions régulières et fichiers-URL

Rapatriez le fichier l2grps_2013.txt et reproduisez le tableau fourni dans la page du controle continu.

On voudrait connaitre la popularité de certains chanteurs ou artistes en consultant le nombre de "hits" renvoyés par Google quand on entre le nom du chanteur ou de l'artiste dans le moteur de recherches. Ecrire une page Web qui affiche la popularité de plusieurs chanteurs artistes et qui renvoie les résultats comme dans la page motsinternet.php. On utilisera jphistopct pour générer les graphiques. Si vous avez le temps, écrire un formulaire qui permet de saisir le nom des chanteurs ou des artistes.

Remarque : il est possible de rapatrier le fichier l2grps_2013.txt en ligne de commandes via 


  $forge_html> wget http://forge.info.univ-angers.fr/~gh/internet/l2grps_2013.txt

Solution :  

Pour l2grps_2013.txt il suffit de consulter montresource.php?nomfic=l2a_cc.php.

Dans la mesure où PHP peut ouvrir le fichier correspondant à une URL comme si c'était un fichier local, il suffit de repérer le texte comme Environ 6 840 000 résultats dans la page renvoyée par Google, d'où la solution chanteurs.php.

 

5. La structure de données "pile" version 2

Reprendre l'exercice 10 de la série 1 (la structure de données "pile") en mode démonstration dans une page Web. Indication : on utilisera une session. Pour un exemple de ce qu'on veut réaliser, cliquer ici.

Solution :  

Voir pilev2.php dont le code-source est ci-dessous et ici.


     <?php
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT ) ;
     
     session_start() ;
     
     include "std.php" ;
     
     debutPage("Exemple de pile","strict") ;
     debutSection() ;
     
     if (!isset($_SESSION["laPile"])) {
       $_SESSION["laPile"] =  creerPile() ;
     } ; # fin si
     
     # -------------------------------------------------------------------------------------
     
     h1("Implémentation d'une \"pile\" via un tableau") ;
     
     blockquote() ;
     
     form("pilev2.php") ;
     div("bgcolor_slate_encadre") ;
     blockquote() ;
       pvide() ;
       p() ;
         input_submit("Afficher la pile","afficher") ;
       finp() ;
     
       p() ;
         input_submit("Dépiler","depiler") ;
       finp() ;
     
       p() ;
         input_submit("Empiler la valeur ","empiler") ;
         input_text("valeur","") ;
         echo " (de préférence numérique) " ;
         input_submit("Exécution !","empiler2","vert_pastel") ;
       finp() ;
     
       p() ;
         input_submit("Réinitialiser la pile","reinitialiser") ;
       finp() ;
     
       p() ;
         if (isset($_GET["dbg"])) {
           input_checkbox("dbg"," Activer le mode debug ","","chk") ;
         } else {
           input_checkbox("dbg"," Activer le mode debug ") ;
         } ; # fin si
       finp() ;
       pvide() ;
     finblockquote() ;
     findiv() ;
     finform() ;
     
     form("pilev2.php") ;
       p() ;
         input_submit("Réinitialiser le formulaire") ;
       finp() ;
     finform() ;
     
     # -------------------------------------------------------------------------------------
     
     if (isset($_GET["reinitialiser"])) {
        h3("La pile a été réinitialisée.") ;
        $_SESSION["laPile"] = creerPile() ;
     } ; # fin si
     
     if (isset($_GET["afficher"])) {
        if (pileVide($_SESSION["laPile"])) {
          h3("La pile est vide, il n'y a rien à afficher.") ;
        } else {
          h3("Vous avez demandé d'afficher la pile, la voici&nbsp;: ") ;
          montrerPile($_SESSION["laPile"]) ;
        } ; # fin si
     } ; # fin si
     
     if (isset($_GET["depiler"])) {
       list($enleve,$_SESSION["laPile"]) = depiler($_SESSION["laPile"]) ;
       if ($enleve=="") {
         h3("La pile est vide, il n'y a rien à dépiler.") ;
       } else {
         h3("On a oté $enleve de la pile.") ;
       } ; # finsi
     } ; # fin si
     
     if (isset($_GET["empiler"]) or (isset($_GET["empiler2"]))) {
       if ((!isset($_GET["valeur"])) or (isset($_GET["valeur"]) and ($_GET["valeur"]=="")) ){
         h3("Vous n'avez fourni aucune valeur à empiler.") ;
       } else {
         $_SESSION["laPile"] = empiler($_GET["valeur"],$_SESSION["laPile"]) ;
         h3("On a empilé la valeur ".$_GET["valeur"]) ;
       } ; # fin si
     } ; # fin si
     
     if (isset($_GET["dbg"])) {
       h2("Mode debug actif","grouge") ;
       blockquote() ;
       h3("Voici le contenu du tableau qui implémente la pile :") ;
       pre("plusgrand cadre") ;
         print_r($_SESSION["laPile"]) ;
       finpre() ;
       finblockquote() ;
     } ; # fin si
     
     finblockquote() ;
     
     # -------------------------------------------------------------------------------------
     
     finSection() ;
     finPage() ;
     
     # ----------------------------------
     
     function creerPile() {
     
       return( array() ) ;
     
     } # fin de fonction creerPile
     
     # ----------------------------------
     
     function pileVide($p) {
     
       return( count($p)==0 ) ;
     
     } # fin de fonction pileVide
     
     # ----------------------------------
     
     function empiler($valeur,$pile) {
     
       $pile[ count($pile) ] = $valeur ;
     
       return( $pile ) ;
     
     } # fin de fonction empiler
     
     # ----------------------------------
     
     function depiler($pile) {
     
       $idd = count($pile) - 1 ; # idd : indice du dernier
     
       if ($idd>=0) {
          $dep = $pile[ $idd ] ;
          unset( $pile[ $idd ]  ) ;
       } else {
          $dep = "" ;
       } # finsi
     
       return( array($dep,$pile) ) ;
     
     } # fin de fonction depiler
     
     # ----------------------------------
     
     function montrerPile($pile) {
     
       pre("plusgrand") ;
       $nbe = count($pile) ;
       if (pileVide($pile)) {
          echo "la pile est vide.\n" ;
       } else {
         echo "Contenu de la pile :\n" ;
         for ($ide=$nbe-1;$ide>=0;$ide--) {
           echo "  valeur numéro ".sprintf("%2d",$ide)." : ".sprintf("%4d",$pile[$ide]) ;
           if ($ide==$nbe-1) { echo " (haut de la pile)" ; } ;
           if ($ide==0)      { echo " (bas  de la pile)" ; } ;
           echo "\n" ;
         } ; # fin pour ide
       } ; # finsi
       finpre() ;
     
     } # fin de fonction montrerPile
     
     # ----------------------------------
     
     ?>
     

 

6. PHP et tables MySQL (1)

On dispose dans fa.txt des instructions MySql qui ont permis de construire les tables films et artistes dans la base statdata. On pourra consulter la page filmsartistes pour voir de façon plus agréable le contenu des tables.

Ecrire un programme qui affiche dans une page Web les 15 premiers films (où est le piège ?). On fournira le nom et l'année du film. On pourra essayer de trouver le code SQL en ligne de commande sur forge en mode terminal via


       $forge> mysql --host=localhost --user=anonymous --password=anonymous
     
       mysql> USE statdata ;
     
       mysql> SELECT ...
     

Afficher ensuite le nom du film, l'année et le metteur en scène pour ces quinze films.

Ecrite ensuite un "jeu" qui tire au hasard un film et demande l'année de sortie. On utilisera astucieusement un formulaire en mode POST pour avoir la solution (cachée) dans le formulaire afin de pouvoir vérifier facilement la solution et gérer la réponse en Javascript. On indiquera que le jeu est impossible si Javascript n'est pas actif. On écrira une version 1 qui utilise un champ texte pour saisir l'année (c'est donc difficile pour l'utilisateur et simple pour le programmeur) puis une version 2 qui fournit les années de sortie possibles dans une liste de sélection (c'est donc plus facile pour l'utilisateur et moins simple pour le programmeur). Voici en deux liens ce qu'on veut obtenir : quizz1.php et quizz2.php.

Ecrire ensuite un formulaire qui demande de choisir entre films et artistes, puis qui demande combien on veut de lignes résultats et à partir de quelle ligne. Pour la table films, on utilisera les champs idFilm annee genre pays mes titre et on triera par annee alors que pour la table artistes, on utilisera les champs idArtiste artnom artprenom anneenaiss et on triera par artnom. Voir fa.php version 2 comme implémentation de ce qu'on veut.

Pour tous ces programmes, on utilisera std.php pour produire du PHP conceptuel. On en profitera pour trouver quelles fonctions de std.php utilisent MySql et celles qui affichent des tableaux.

Le lien phpmysql fournit un rappel de la syntaxe des fonctions et objets PHP pour effectuer des requêtes MySQL.

Solution :  

Le code MySQL pour trouver les quinze premiers films par ordre de date est simple :


      SELECT annee, titre FROM films ORDER BY annee LIMIT 15 ;
     
      SELECT annee, titre, artnom, artprenom
          FROM films, artistes
          WHERE films.mes=artistes.idArtiste
          ORDER BY annee
          LIMIT 15 ;
     

Le piège est le fait que «les quinze premiers» n'a aucun sens si on ne précise pas le critère de tri. On prend ici la date comme critère de tri.

Il ne reste plus qu'à inclure les résultats de la requête MySQL dans un tableau XHTML. Consulter quinzefilms comme page-solution, dont le code est ci-dessous. Au passage, on a trouvé le metteur en scène grâce au numéro d'artiste.


     <?php
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT ) ;
     include "std.php" ;
     debutPage("Quinze films","strict") ;
     debutSection() ;
     
     ##############################################################################
     
     h1("Les quinze premiers films") ;
     
     anonymousConnect("statdata") ;
     
     table(1,15,"collapse") ;
     
     entetesTableau("Numéro Date Film","jaune_pastel") ;
     
     $ch1   = "annee" ;
     $ch2   = "titre" ;
     
     $requete  = " SELECT $ch1, $ch2 FROM films ORDER BY $ch2 LIMIT 15 " ;
     $resultat = mysql_query($requete) ;
     $num      = 0 ;
     while ($ligneResultat=mysql_fetch_array($resultat)) {
       $num++ ;
       tr() ;
         td("R") ; echo $num                 ; fintd() ;
         td("R") ; echo $ligneResultat[$ch1] ; fintd() ;
         td()    ; echo $ligneResultat[$ch2] ; fintd() ;
       fintr() ;
     } ; # fin tant que
     
     fintable() ;
     
     # ----------------------------------------------
     
     h1("Les quinze premiers films avec metteur en scène") ;
     
     anonymousConnect("statdata") ;
     
     table(1,15,"collapse") ;
     
     entetesTableau("Numéro Date Film Metteur_en_scène","jaune_pastel") ;
     
     $ch1   = "annee"  ;
     $ch2   = "titre"  ;
     $ch3   = "artprenom" ;
     $ch4   = "artnom" ;
     
     $requete  = " SELECT $ch1, $ch2, $ch3, $ch4             " ;
     $requete .= "        FROM  films, artistes              " ;
     $requete .= "        WHERE films.mes=artistes.idArtiste " ;
     $requete .= "        ORDER BY $ch2                      " ;
     $requete .= "        LIMIT 15                           " ;
     $resultat = mysql_query($requete) ;
     $num      = 0 ;
     while ($ligneResultat=mysql_fetch_array($resultat)) {
       $num++ ;
       $mes = $ligneResultat[$ch3]." ".strtoupper($ligneResultat[$ch4])  ;
       tr() ;
         td("R") ; echo $num                 ; fintd() ;
         td("R") ; echo $ligneResultat[$ch1] ; fintd() ;
         td()    ; echo $ligneResultat[$ch2] ; fintd() ;
         td()    ; echo $mes                 ; fintd() ;
       fintr() ;
     } ; # fin tant que
     
     fintable() ;
     
     ###########################################################
     
     p() ;
     echo href("montresource.php?nomfic=quinzefilms.php","Code-source php de cette page","orange_stim nou").". " ;
     finp() ;
     
     ##############################################################################
     
     finSection() ;
     finPage() ;
     ?>
     

Le premier quizz utilise simplement un champ texte et un champ caché issu de MySql pour que Javascript puisse comparer la réponse utilisateur et la solution :


     <?php
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT ) ;
     include "std.php" ;
     debutPage("Quizz films v1","strict","","quizz1.js") ;
     debutSection() ;
     
     ##############################################################################
     
     h1("Trouvez l'année du film, version 1 ") ;
     anonymousConnect("statdata") ;
     $nbfilms = comptageSqlSimple("titre","films") ;
     
     # tirage aléatoire entre 2 et nbfilms
     
     $numfilm = rand(2,$nbfilms) ;
     $idfilm  = $numfilm - 1 ;
     
     # on récupère le film et le metteur en scène
     
     $ch1   = "annee"  ;
     $ch2   = "titre"  ;
     
     $requete  = " SELECT $ch1, $ch2       " ;
     $requete .= "        FROM films       " ;
     $requete .= "        ORDER BY $ch2    " ;
     $requete .= "        LIMIT $idfilm, 1 " ;
     $resultat = mysql_query($requete) ;
     $num      = 0 ;
     $ligneResultat=mysql_fetch_array($resultat) ;
     $annee    = $ligneResultat[$ch1] ;
     $titre    = $ligneResultat[$ch2] ;
     
     # le questionnaire
     
     form("non.php","post","onsubmit='reponse(this); return false '") ;
       p() ;
         echo "En quelle année est sorti le film " ;
       finp() ;
       p() ;
         nbsp(10 ) ; sdl() ;
         echo s_span("$titre","gvertf")." &nbsp;? " ;
         nbsp(5) ; sdl() ;
         input_text("annee","   ") ;
         sdl() ;
         input_hidden("solution",$annee) ;
         nbsp(5) ;
         input_submit("réponse","envoi") ;
       finp() ;
     finform() ;
     
     
     ###########################################################
     
     p() ;
     echo href("montresource.php?nomfic=quizz1.php","Code-source php de cette page","orange_stim nou").". " ;
     finp() ;
     
     ##############################################################################
     
     finSection() ;
     finPage() ;
     ?>
     

Le code Javascript se réduit donc à un test et à un affichage 


     function reponse(formulaire) {
     
       reponseU = formulaire.annee.value.trim()
       if (reponseU=="") { reponseU = "(vide)" }
     
       if (reponseU==formulaire.solution.value) {
         alert("Bravo, c'était bien en "+ formulaire.solution.value)
       } else {
         alert("Eh non, ce n'était pas " + reponseU + " mais "+ formulaire.solution.value)
       } // fin si
     
     } // fin de fonction reponse
     

Le second quizz complète le premier quizz en l'incluant dans une division (élément <div>) avec un style invisible que Javascript rend visible si Javascript est actif. La réponse est affichée de la même façon, dans une autre division. L'élément <select> qui affiche les années possibles est construit à l'aide de notre fonction listeSelectFromChampMySql() de std.php.


     <?php
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT ) ;
     include "std.php" ;
     debutPage("Quizz films v2","strict","","quizz2.js") ;
     debutSection() ;
     
     ##############################################################################
     
     noscript() ;
       h2("Impossible d'afficher la page, Javascript n'est pas actif." ) ;
     finnoscript() ;
     
     div("invisible","tout") ;
     
     h1("Trouvez l'année du film, version 2 ") ;
     anonymousConnect("statdata") ;
     $nbfilms = comptageSqlSimple("titre","films") ;
     
     # tirage aléatoire entre 2 et nbfilms
     
     $numfilm = rand(2,$nbfilms) ;
     $idfilm  = $numfilm - 1 ;
     
     # on récupère le film et le metteur en scène
     
     $ch1   = "annee"  ;
     $ch2   = "titre"  ;
     
     $requete  = " SELECT $ch1, $ch2       " ;
     $requete .= "        FROM films       " ;
     $requete .= "        ORDER BY $ch2    " ;
     $requete .= "        LIMIT $idfilm, 1 " ;
     $resultat = mysql_query($requete) ;
     $num      = 0 ;
     $ligneResultat=mysql_fetch_array($resultat) ;
     $annee    = $ligneResultat[$ch1] ;
     $titre    = $ligneResultat[$ch2] ;
     
     # le questionnaire
     
     form("non.php","post","onsubmit='reponse(this); return false '") ;
       p() ;
         echo "En quelle année est sorti le film " ;
       finp() ;
       p() ;
         nbsp(10 ) ; sdl() ;
         echo s_span("$titre","gvertf")." &nbsp;? " ;
         nbsp(5) ; sdl() ;
         listeSelectFromChampMySql("annee","films","annee") ;
         sdl() ;
         input_hidden("solution",$annee) ;
         nbsp(5) ;
         input_submit("réponse","envoi") ;
       finp() ;
       p("invisible","affiche") ;
         nbsp() ;
         echo $annee ;
       finp() ;
     finform() ;
     
     findiv() ;
     
     ###########################################################
     
     p() ;
     echo href("montresource.php?nomfic=quizz2.php","Code-source php de cette page","orange_stim nou").". " ;
     finp() ;
     
     ##############################################################################
     
     finSection() ;
     finPage() ;
     ?>
     

Le code Javascript est donc un tout petit plus compliqué puisqu'il doit mettre la réponse dans un paragraphe :


     // ###############################################
     
     function reponse(formulaire) {
     
        reponseU = formulaire.annee.value.trim()
       if (reponseU=="") { reponseU = "(vide)" }
     
       if (reponseU==formulaire.solution.value) {
         phr = "Bravo, c'était bien en " + formulaire.solution.value + "."
       } else {
         phr = "Eh non, ce n'était pas " + reponseU + " mais "+ formulaire.solution.value + "."
       } // fin si
     
       window.document.getElementById("affiche").setAttribute("class","visible") ;
       window.document.getElementById("affiche").innerHTML = phr ;
     
     } // fin de fonction reponse
     
     // ###############################################
     
     function montreTout() {
     
       window.document.getElementById("tout").setAttribute("class","visible") ;
     
     } // fin de fonction montreTout
     
     // ###############################################
     
     function addEvent(obj, evType, fn){
      if (obj.addEventListener){
        obj.addEventListener(evType, fn, false);
        return true;
      } else if (obj.attachEvent){
        var r = obj.attachEvent("on"+evType, fn);
        return r;
      } else {
        return false;
      }
     } // # fin de function addEvent
     
     // ###############################################
     
     addEvent(window, 'load', montreTout);
     
     

Enfin, pour le questionnaire films ou artistes, voir fa.php dont le code-source est ici comme exemple de formulaire et fa_res.php dont le code-source est ici comme fichier implémentant l'attribut action.


     <?php
     
     #    (gH)   -_-  fa.php  ;  TimeStamp (unix) : 03 Mars 2013 vers 12:51
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT) ;
     include("std.php") ;
     
     include("resfa_inc.php") ; # contient la fonction listeDesChamps
     
     debutPage("Films et artistes","strict","","fa.js") ;
     debutSection() ;
     
     #############################################################################################
     
     h1("Consultation des tables films et artistes") ;
     
     #############################################################################################
     
     h2("Formulaire version 1") ;
     
     blockquote() ;
       form("fa_res.php") ;
        p() ;
          echo "Choisissez la table " ;
          listeSelectFromTxt("nomtable","films artistes") ;
        finp() ;
        p() ;
          echo " puis le nombre de lignes à renvoyer " ;
          echo input_text("nblr","10") ;
        finp() ;
        p() ;
          echo " à partir de l'enregistrement " ;
          echo input_text("debenr","35") ;
        finp() ;
        p() ;
          nbsp(15) ;
          echo input_submit("envoyer") ;
        finp() ;
       finform() ;
       p() ;
         anonymousConnect("statdata") ;
         echo s_span("Remarque","gvert") ;
         $nbf = comptageSqlSimple("titre","films") ;
         $nba = comptageSqlSimple("artnom","artistes") ;
         echo " : il y a $nbf films et $nba artistes en tout." ;
       finp() ;
     finblockquote() ;
     
     # ------------------------------------------------------------------------------------------
     
     h2("Formulaire version 2 ") ;
     
     noscript() ;
     p() ;
     echo "Attention, Javascript n'est pas actif, ce formulaire est incomplet." ;
     finp() ;
     finnoscript() ;
     
     blockquote() ;
       form("fa_res2.php") ;
        p("","seltable") ;
          echo "Choisissez la table " ;
          sdl() ;
          input_radio("nomtable2","films","films"   ,"","","films"   ,"onclick='montreChamps(\"films\")'") ;
          input_radio("nomtable2","films","artistes","","","artistes","onclick='montreChamps(\"artistes\")'") ;
          input_hidden("nomtableChoix","films") ;
        finp() ;
        div("invisible","pcrit") ;
          p() ;
          echo s_span(" et le critère de tri ","grouge") ;
          sdl() ;
          span("invisible","champsFilms") ;
          listeSelectFromTxt("criteref",join(" ",listeDesChamps("films"))) ;
          finspan() ;
          sdl() ;
          span("invisible","champsArtistes") ;
          listeSelectFromTxt("criterea",join(" ",listeDesChamps("artistes"))) ;
          finspan() ;
          finp() ;
        findiv() ;
        p() ;
          echo " puis le nombre de lignes à renvoyer " ;
          echo input_text("nblr2","10") ;
        finp() ;
        p() ;
          echo " à partir de l'enregistrement " ;
          echo input_text("debenr2","35") ;
        finp() ;
        p("invisible","envoi") ;
          nbsp(15) ;
          echo input_submit("envoyer") ;
        finp() ;
       finform() ;
     finblockquote() ;
     
     
     #############################################################################################
     
     finSection() ;
     finPage() ;
     ?>
     

     <?php
     
     #    (gH)   -_-  fa_res.php  ;  TimeStamp (unix) : 03 Mars 2013 vers 12:51
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT) ;
     include("std.php") ;
     debutPage("Films et artistes","strict") ;
     debutSection() ;
     
     #############################################################################################
     
     h1("Consultation des tables films et artistes") ;
     
     #############################################################################################
     
     # récupération des paramètres
     
     $nomtable = "???" ;
     if (isset($_GET["nomtable"])) {
       $nomtable = $_GET["nomtable"] ;
     } ; # fin si
     
     $nblr = "???" ;
     if (isset($_GET["nblr"])) {
       $nblr = $_GET["nblr"] ;
     } ; # fin si
     
     $debenr = "???" ;
     if (isset($_GET["debenr"])) {
       $debenr = $_GET["debenr"] ;
     } ; # fin si
     
     # test des paramètres
     
     $err = 0 ;
     
     if (!preg_match("/^[0-9]+$/",$nblr)) {
       $err++ ;
       h2("Vous n'avez pas fourni un entier comme nombre de lignes à afficher.","grouge") ;
     } # fin si
     
     if (!preg_match("/^[0-9]+$/",$debenr)) {
       $err++ ;
       h2("Vous n'avez pas fourni un entier comme numéro d'enregistrement de départ.","grouge") ;
     } # fin si
     
     if ($err==0) {
     
        anonymousConnect("statdata") ;
     
        if ($nomtable=="films") {
           $nbe       = comptageSqlSimple("titre","films") ;
           $lesChamps =  preg_split("/\s+/","idFilm annee genre pays mes titre") ;
           $critere   = "annee" ;
        } else {
           $nbe = comptageSqlSimple("artnom","artistes") ;
           $lesChamps =  preg_split("/\s+/","idArtiste artnom artprenom anneenaiss") ;
           $critere   = "artnom" ;
        } ; # finsi
     
        if (! (($nomtable=="films") or ($nomtable=="artistes")) ) {
          $err++ ;
          h2("Nom de table incorrect, ce devrait &ecirc;tre ".b("films")." ou ".b("artistes"),"grouge") ;
        } # fin si
     
        if ($debenr>$nbe) {
          $err++ ;
          h2("Rien à afficher, il n'y a que $nbe enregistrements dans la table $nomtable","grouge") ;
          h2(" alors que vous voulez commencer à l'enregistrement numéro $debenr.","grouge") ;
        } # fin si
     
        # affichage si tout va bien
     
        if ($err==0) {
     
          h2("Affichage de $nblr enregistrements à partir du numéro $debenr dans la table $nomtable","gvert") ;
          h3("(qui contient $nbe enregistrements)","gvert") ;
     
          $nbc       = count($lesChamps)  ;
     
          table(1,15,"collapse") ;
            # entetes
            tr() ;
              th() ; echo "Enregistrement" ; finth() ;
              foreach( $lesChamps as $col) {
                th() ; echo $col ; finth() ;
              } # fin pour chaque
            fintr() ;
            # données
            $que    = " SELECT * FROM $nomtable ORDER BY $critere LIMIT ".($debenr-1).", $nblr " ;
            $numenr = $debenr-1 ;
            $res    = mysql_query($que) ;
            while ($ldr=mysql_fetch_array($res)) {
              $numenr++ ;
              tr() ;
                td("R") ; echo $numenr ; fintd() ;
                foreach( $lesChamps as $col) {
                  td() ; echo $ldr[$col] ; fintd() ;
                } # fin pour chaque
              fintr() ;
            } # fin tant que
          fintable() ;
     
        } # fin si
     
     } # fin si
     
     
     #############################################################################################
     
     finSection() ;
     finPage() ;
     ?>
     

     
     //    (gH)   -_-  fa.js  ;  TimeStamp (unix) : 07 Mars 2013 vers 16:56
     
     function ucFirst (str) { // # à ne pas confondre avec ucwords...
     
       // http://kevin.vanzonneveld.net
       // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
       // +   bugfixed by: Onno Marsman
       // +   improved by: Brett Zamir (http://brett-zamir.me)
       // *     example 1: ucfirst('kevin van zonneveld');
       // *     returns 1: 'Kevin van zonneveld'
     
       // ajout (gH) ; test sur la longueur de str
     
       if (str.length==0) {
          return str
       } else {
         str += '' ;
         var f = str.charAt(0).toUpperCase();
         return f + str.substr(1).toLowerCase() ;
       } // fin si
     
     } // fin de fonction ucFirst
     
     // ##########################################################################
     
     String.prototype.ucfirst = function () {
     
       return ucFirst(this)
     
     } // fin de fonction ucfirst
     
     // ##########################################################################
     
     function montreChamps(laTable) {
     
       autreTable = "artistes" ;
       if (laTable=="artistes") { autreTable = "films" }
     
       window.document.getElementById("champs"+laTable.ucfirst()).setAttribute("class","visible")
       window.document.getElementById("champs"+autreTable.ucfirst()).setAttribute("class","invisible")
       window.document.getElementById("pcrit").setAttribute("class","visible")
       window.document.getElementById("envoi").setAttribute("class","visible")
     
       // pour transmettre à php le choix de la table
     
       window.document.getElementById("nomtableChoix").value = laTable
     
     } // fin de fonction montreChamps
     

 

7. Analyse d'une application (1)

Essayer de trouver tous les choix de programmation qui ont été faits dans l'implémentation d'un système d'exercices disponible sur la page senQuest. On rechargera plusieurs fois la page.

Essayer aussi de définir la ou les bases de données impliquées.

Solution :  

Une lecture attentive du code source du fichier sen_quests2.php devrait être suffisante. Vous devez maintenant savoir comment y accéder...

 

8. Parcours de fichier et documentation

Il est facile de repérer une fonction PHP dans le code source puisqu'elle commence par le mot function. En admettant que le début d'une fonction est toujours écrit en début de ligne (précédé éventuellement d'espaces), écrire un programme qui liste les fonctions d'un code-source php avec leur numéro de ligne, comme par exemple :


      Fonction(s) du fichier design.php
      ---------------------------------
     
            1.  affich_header                      33
            2.  affich_menu                        48
            3.  doctype_head                       11
            4.  fin_lea                           113
     

Est-ce beaucoup plus compliqué si on essaie de lister toutes les fonctions d'un projet à partir d'un fichier "maitre" qui contient des include et des require comme dans ldphp_xmp.txt ?

Expliquer comment les pages std et statgh.r fonctionnent. Quelles précautions faut-il prendre lorsqu'on écrit des fonctions pour profiter d'une telle interface ?

Solution :  

 

Solution volontairement non communiquée.

Ceux et celles qui sont venu(e)s en cours savent où est la solution. Pour les personnes qui ne sont pas inscrites dans mes cours, vous pouvez me contacter par mail.

 

9. Parcours de fichier et comptages d'attributs

On voudrait analyser une page Web en termes d'attributs et d'éléments. Ecrire un formulaire qui permet de tester une URL quelconque, y compris un fichier XML. On pourra tester la validité W3C du texte analysé ; on fournira des URL prêtes à l'emploi pour faciliter le test du programme PHP utilisé comme attribut action du formulaire.

Solution :  

Voir la page eltatt.php qui fournit des liens pour les codes-sources PHP utilisés.

 

10. PHP et tables MySQL (2)

Compléter le formulaire de la question 6 en ajoutant un critère de tri pour choisir l'affichage. On viendra construire automatiquement la liste de tous les champs possibles, et on utilisera des fonctions pour toute action répétée au moins deux fois. Afin d'éviter les tests, on utilisera des tableaux associatifs. Il est conseillé d'écrire une fonction listeDesChamps(table) dans un fichier nommé resfa_inc.php qu'on viendra inclure, aussi bien pour le formulaire que pour les résultats.

Il faudra certainement utiliser du Javascript dans le formulaire pour choisir quelle liste de champs afficher.

Solution :  

Voir fa.php comme formulaire d'entrée (version 2), dont le code-source est ici et fa_res2.php et son code source pour la gestion des résultats. Le code-source Javascript utilisé est fa.js.


     
     //    (gH)   -_-  fa.js  ;  TimeStamp (unix) : 07 Mars 2013 vers 16:56
     
     function ucFirst (str) { // # à ne pas confondre avec ucwords...
     
       // http://kevin.vanzonneveld.net
       // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
       // +   bugfixed by: Onno Marsman
       // +   improved by: Brett Zamir (http://brett-zamir.me)
       // *     example 1: ucfirst('kevin van zonneveld');
       // *     returns 1: 'Kevin van zonneveld'
     
       // ajout (gH) ; test sur la longueur de str
     
       if (str.length==0) {
          return str
       } else {
         str += '' ;
         var f = str.charAt(0).toUpperCase();
         return f + str.substr(1).toLowerCase() ;
       } // fin si
     
     } // fin de fonction ucFirst
     
     // ##########################################################################
     
     String.prototype.ucfirst = function () {
     
       return ucFirst(this)
     
     } // fin de fonction ucfirst
     
     // ##########################################################################
     
     function montreChamps(laTable) {
     
       autreTable = "artistes" ;
       if (laTable=="artistes") { autreTable = "films" }
     
       window.document.getElementById("champs"+laTable.ucfirst()).setAttribute("class","visible")
       window.document.getElementById("champs"+autreTable.ucfirst()).setAttribute("class","invisible")
       window.document.getElementById("pcrit").setAttribute("class","visible")
       window.document.getElementById("envoi").setAttribute("class","visible")
     
       // pour transmettre à php le choix de la table
     
       window.document.getElementById("nomtableChoix").value = laTable
     
     } // fin de fonction montreChamps
     

 

11. PHP, tables, éléments et Javascript

Après réflexion, on aimerait que les tableaux affichés lors d'une requête MySQL soient triables quand on clique sur l'en-tete de la colonne. Est-ce facile à implémenter ?

De même, si on pagine l'affichage avec un choix personnalisé (10 par page, 20 par page...), est-ce facile de disposer d'un bouton ou d'un lien voir les xx suivants ?

Essayez de réaliser une page en PHP et Javascript qui utilise les éléments de formulaire suivants pour afficher les films :

                             

     Paramétrisation :  films par page

Essayez ensuite de réaliser une page en PHP et Javascript pour afficher les films sans rechargement explicite de la page.

Solution :  

Avoir un tableau XHTML triable n'est pas du ressort de PHP mais de Javascript. Nous conseillons d'utiliser sorttable.js qui demande seulement à être inclus dans la page et à ajouter class='sortable' dans l'élément table. Une démonstration est fournie dans le texte du CC et ci-dessous : cliquez sur les noms de colonnes...

Nom usuel Prénom principal
BOND James
ARMA Line
ZOLA Emile

Pour la pagination, ce n'est pas très compliqué non plus, tout dépend d'où viennent les pages. Par exemple, pour un diaporama avec une liste de fichiers ou une liste d'URL, regardez diaporamas. Si les données viennent d'une base de données, il faut certainement jouer avec le SELECT ... LIMIT pour envoyer les "bonnes" informations. Voir pagination.php comme exemple d'implémentation. Pour comprendre la page pagination.php on pourra remplacer POST par GET et décommenter les lignes indiquées dans le code-source. L'heure est affichée à chaque fois afin de montrer que chaque action utilisateur recharge la page.

Pour une solution PHP et AJAX, sans recharger la page, le code est un peu plus compliqué puisqu'il faut effacer la zone du tableau et en reconstruire un autre, solution sans doute plus rapide que de réécrire dans les cases du tableau. Pour cela, on pourrait utiliser un programme PHP qui renvoie juste le tableau en XHTML et laisser à Javascript le soin de remplacer le tableau précédent avec ce tableau.

 

12. Analyse d'une application (2)

Essayer de trouver tous les choix qui ont été faits ainsi que leur motivation dans l'implémentation d'un système de rendez-vous d'exposé de stage disponible sur la page stages.

Essayer aussi de définir la ou les bases de données impliquées.

Essayer d'imaginer tout ce que peut représenter l'analyse et l'implémentation d'un site comme LEAdb qui est XHTML valide. On viendra parcourir les différents menus pour voir tout ce qu'il est possible de faire sur le site.

Solution :  

Solution volontairement non communiquée.

 

Code-source php de cette page ; code javascript utilisé. Retour à la page principale du cours.

 

 

retour gH    Retour à la page principale de   (gH)