PHP

Installation
Lancement
Configuration d'Apache
Configuration de php
Configuration de mysql
Le langage
Fonctions prédéfinies
Exemples simples
Utilisation de mysql
Dictionnaire pédagogique Japonais-Anglais
Remarques ultérieures (janvier 2002)

Installation

J'ai installé apache-1.3.19 (la version 2 ne marchait pas avec le reste), php-4.0.4pl1 et mysql-3.23, de la manière suivante

http://httpd.apache.org/
http://www.php.net/
http://fr.php.net/docs.php
http://fr.php.net/manual/en/html/
http://mysql.mtesa.net/

cd mysql
./configure --prefix=$HOME/spool/WWW
make
make install

cd ../apache
./configure --prefix=$HOME/spool/WWW
cd ../php
./configure --prefix=$HOME/spool/WWW --with-mysql=$HOME/spool/WWW --with-apache=../apache_1.3.19/ --enable-track-vars
make
make install
cp php.ini-dist $HOME/spool/WWW/lib/php.ini

cd ../apache
./configure --prefix=$HOME/spool/WWW --activate-module=src/modules/php4/libphp4.a
make
make install
#cp php.ini-dist $HOME/spool/WWW/lib/php.ini
vi $HOME/spool/WWW/conf/httpd.conf
  AddType application/x-httpd-php .php

On peut ensuite lancer apache

cd $HOME/spool/WWW/bin
apachectl start

Il faut initialiser la base de données

mysql_install_db

Preparing db table
Preparing host table
Preparing user table
Preparing func table
Preparing tables_priv table
Preparing columns_priv table
Installing all prepared tables
010430 23:12:10  /home/zoonek/spool/WWW/libexec/mysqld: Shutdown Complete
  
To start mysqld at boot time you have to copy support-files/mysql.server
to the right place for your system
  
PLEASE REMEMBER TO SET A PASSWORD FOR THE MySQL root USER !
This is done with:
/home/zoonek/spool/WWW/bin/mysqladmin -u root -p password 'new-password'
/home/zoonek/spool/WWW/bin/mysqladmin -u root -h localhost.localdomain -p password 'new-password'
See the manual for more instructions.
  
You can start the MySQL daemon with:
cd /home/zoonek/spool/WWW ; /home/zoonek/spool/WWW/bin/safe_mysqld &
  
You can test the MySQL daemon with the benchmarks in the 'sql-bench' directory:
cd sql-bench ; run-all-tests
  
Please report any problems with the /home/zoonek/spool/WWW/bin/mysqlbug script!
  
The latest information about MySQL is available on the web at
http://www.mysql.com
Support MySQL by buying support/licenses at https://order.mysql.com

Et on peut la lancer

safe_mysqld &

Vérifions maintenant que tout fonctionne correctement. Le serveur Web a bien été lancé :

lynx http://localhost:8080/

La base de donnée a bien été lancée :

mysqladmin version

mysqladmin  Ver 8.19 Distrib 3.23.37, for pc-linux-gnu on i586
Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL license
  
Server version          3.23.37
Protocol version        10
Connection              Localhost via UNIX socket
UNIX socket             /tmp/mysql.sock
Uptime:                 3 min 3 sec
  
Threads: 1  Questions: 1  Slow queries: 0  Opens: 6  Flush tables: 1  Open tables: 0 Queries per second avg: 0.005

Elle contient les bases de données mysql (utilisée pour gérer les différents utilisateurs des bases de données et leurs droits respectifs) et test :

mysqlshow

+-----------+
| Databases |
+-----------+
| mysql     |
| test      |
+-----------+

On peut voir le contenu de la base mysql

mysqlshow -u root mysql

+--------------+
|    Tables    |
+--------------+
| columns_priv |
| db           |
| func         |
| host         |
| tables_priv  |
| user         |
+--------------+

Et voici un morceau du contenu de la table db de la bse de données mysql :

mysql -e "select host,db,user from db" mysql

+------+---------+------+
| host | db      | user |
+------+---------+------+
| %    | test    |      |
| %    | test\_% |      |
+------+---------+------+

Lancement

Si les serveurs ne sont pas lancés automatiquement, taper :

cd $HOME/spool/WWW/bin
PATH=$PATH:.
apachectl start
safe_mysqld &

Configuration d'Apache

httpd.conf

Je me contente de lire et de commenter le fichier httpd.conf. Il y a aussi des fichiers srm.conf et access.conf, mais ils sont vides car tout a été mis dans httpd.conf. On peut aussi donner un autre fichier de configuration, en lançant

httpd -f myhttpd.conf

Les premières lignes du fichier donnent le numéro du port (généralement 80, si le serveur est lancé par root), l'identité (utilisateur et groupe) qu'il doit prendre après avoir ouvert ce port, ainsi que le chemin correspondant à la racine de l'arborescence HTML.

Port 8080
User nobody
Group nobody
ServerAdmin zoonek@localhost.localdomain
ServerName localhost.localdomain
DocumentRoot "/home/zoonek/spool/WWW/htdocs"

On précise certaines options : ici, on autorise les liens symboliques (peut-être pas une très bonne idée : on peut autoriser les liens syboloques uniquement quand l'utilisateur est le même, on même les interdire complètement). La commande « AllowOverride None » interdit de modifier les options d'un répertoire (par exemple, pour autoriser l'exécution de scripts CGI) à l'aide de fichiers .htaccess.

<Directory />
    Options FollowSymLinks
    AllowOverride None
</Directory>

On peut pr&ciser cela répertoire par répertoire.

<Directory "/home/zoonek/spool/WWW/htdocs">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>

L'option MultiViews demande à Apache de reconnaire les fichiers présents sous plusieures formes différentes, par exemple un fichier index.html.en (en anglais) et un fichier index.html.fr (en français). Le client envoie une liste de préférences et le serveur s'efforce de le satisfaire. On peut faire la même chose avec le format des images. Voir mod_negotiation un peu plus loin.

On pourrait lancer le serveur Web en mode « parano », en interdisant les connections depuis l'extérieur (ne pas oublier de modifier de la même manière les autres section « Options »). [D'ailleurs, ce n'est pas une mauvaise idée : on interdit l'accès à tous les fichiers comme suit, et dans les sections suivantes on l'autorise pour certains répertoires.]

<Directory "/home/zoonek/spool/WWW/htdocs">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Order deny,allow
    Deny from all
</Directory>

On peut autoriser les utilisateurs à avoir leur propre page Web.

<IfModule mod_userdir.c>
    UserDir public_html
</IfModule>
<Directory /home/*/public_html>
    AllowOverride FileInfo AuthConfig Limit
    Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
    <Limit GET POST OPTIONS PROPFIND>
        Order allow,deny
        Allow from all
    </Limit>
    <LimitExcept GET POST OPTIONS PROPFIND>
        Order deny,allow
        Deny from all
    </LimitExcept>
</Directory>

Quand un URL correspond à un répertoire, on cherche un fichier index.html dans ce répertoire. Pour le cas où il n'y aurait pas de tel fichier, voir autoindex, plus loin.

<IfModule mod_dir.c>
    DirectoryIndex index.html
</IfModule>

On peut être plus général :

DirectoryIndex index.cgi index.pl index.shtml index.html

Si on n'a pas précisé « AllowOverride None », il est possible de changer les options (reconnaissances des CGI, des SSI, des liens symboliques, etc.) d'un répertoire donné en mettant une ligne du genre

Options All MultiViews

dans un fichier .htaccess dans le répertoire en question.

AccessFileName .htaccess
<Files ~ "^\.ht">
    Order allow,deny
    Deny from all
</Files>

Quand le serveur renvoie un document, il précise aussi quel est son type (oui, c'est le serveur qui fait ça, par le client), pour que le client sache comment le visualiser. Le fichier mimes.types contient la correspondance entre l'extension d'un fichier et son type. Le fichier magic explique à Apache comment trouver le type d'un fichier à l'aide de ses premiers octets. Si on ne trouve pas le type d'un fichier, on dira que c'est text/plain.

<IfModule mod_mime.c>
    TypesConfig /home/zoonek/spool/WWW/conf/mime.types
</IfModule>
DefaultType text/plain
<IfModule mod_mime_magic.c>
    MIMEMagicFile /home/zoonek/spool/WWW/conf/magic
</IfModule>

On précise dans quel fichier les messages d'erreur et les « logs » seront stockés et sous quel format. On peut avoir plusieurs fichiers de log, indiquant par exemple quel est le butineur utilisé ou de quelle page on vient.

ErrorLog /home/zoonek/spool/WWW/logs/error_log
LogLevel warn

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
CustomLog /home/zoonek/spool/WWW/logs/access_log common
CustomLog /home/zoonek/spool/WWW/logs/referer_log referer
CustomLog /home/zoonek/spool/WWW/logs/agent_log agent
#CustomLog /home/zoonek/spool/WWW/logs/access_log combined

???

<IfModule mod_alias.c>
  Alias /icons/ "/home/zoonek/spool/WWW/icons/"
  <Directory "/home/zoonek/spool/WWW/icons">
      Options Indexes MultiViews
      AllowOverride None
      Order allow,deny
      Allow from all
  </Directory>

On précise dans quelrépertoire se trouvent les fichiers CGI. On peut aussi (voir plus loin) dire que tous les fichiers *.cgi sont des CGI.

  ScriptAlias /cgi-bin/ "/home/zoonek/spool/WWW/cgi-bin/"
  <Directory "/home/zoonek/spool/WWW/cgi-bin">
      AllowOverride None
      Options None
      Order allow,deny
      Allow from all
  </Directory>
</IfModule>

Quand un site Web a changé d'emplacement, par exemple si http://www.math.jussieu.fr/~zoonek/ devient http://zoonek.free.fr/, on peut demander au serveur de rediriger automatiquement les requêtes sur ce nouveau site.

Redirect permanent /~zoonek/ http://zoonek.free.fr/

On peut utiliser cela lors de la réécriture d'un site, pour qu'il ne soit pas accessible pendant la modification

RedirectMatch temp .* http://www.example.com/startpage.html

Quand l'URL correspond à un répertoire et qu'il n'y a pas de fichier index.html, le serveur en crée un automatiquement, en concaténant un éventuel fichier HEADER à la liste des fichiers (avec des icônes correspondant à leur type, pour faire joli) et à un éventuel fichier README.

<IfModule mod_autoindex.c>
  IndexOptions FancyIndexing
  AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip

  AddIconByType (TXT,/icons/text.gif) text/*
  AddIconByType (IMG,/icons/image2.gif) image/*
  AddIconByType (SND,/icons/sound2.gif) audio/*
  AddIconByType (VID,/icons/movie.gif) video/*

  AddIcon /icons/binary.gif .bin .exe
  AddIcon /icons/binhex.gif .hqx
  AddIcon /icons/tar.gif .tar
  AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
  AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
  AddIcon /icons/a.gif .ps .ai .eps
  AddIcon /icons/layout.gif .html .shtml .htm .pdf
  AddIcon /icons/text.gif .txt
  AddIcon /icons/c.gif .c
  AddIcon /icons/p.gif .pl .py
  AddIcon /icons/f.gif .for
  AddIcon /icons/dvi.gif .dvi
  AddIcon /icons/uuencoded.gif .uu
  AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
  AddIcon /icons/tex.gif .tex
  AddIcon /icons/bomb.gif core

  AddIcon /icons/back.gif ..
  AddIcon /icons/hand.right.gif README
  AddIcon /icons/folder.gif ^^DIRECTORY^^
  AddIcon /icons/blank.gif ^^BLANKICON^^

  DefaultIcon /icons/unknown.gif

  #AddDescription "GZIP compressed document" .gz
  #AddDescription "tar archive" .tar
  #AddDescription "GZIP compressed tar archive" .tgz

  ReadmeName README
  HeaderName HEADER

  IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t
</IfModule>

On peut fournir plusieures versions d'un même fichier HTML, en différentes langues, par exemple index.html.fr, index.html.en, index.html.ja, etc. (c'est ce qui se passe par exemple avec la page d'exemple d'apache, lors de la première installation). Le butineur donne les langues qu'il préfère dans l'en-tête de la négociation HTTP et le serveur essaye de satisfaire sa requète. Il en va de même pour les codages (que l'on appelle improprement, ici, jeux de caractères : ucs2, ucs4, utf8 sont différents codages d'un même jeu de caractères).

<IfModule mod_mime.c>
  AddEncoding x-compress Z
  AddEncoding x-gzip gz tgz
  AddLanguage da .dk
  AddLanguage nl .nl
  AddLanguage en .en
  AddLanguage et .ee
  AddLanguage fr .fr
  AddLanguage de .de
  AddLanguage el .el
  AddLanguage he .he
  AddCharset ISO-8859-8 .iso8859-8
  AddLanguage it .it
  AddLanguage ja .ja
  AddCharset ISO-2022-JP .jis
  AddLanguage kr .kr
  AddCharset ISO-2022-KR .iso-kr
  AddLanguage no .no
  AddLanguage pl .po
  AddCharset ISO-8859-2 .iso-pl
  AddLanguage pt .pt
  AddLanguage pt-br .pt-br
  AddLanguage ltz .lu
  AddLanguage ca .ca
  AddLanguage es .es
  AddLanguage sv .se
  AddLanguage cz .cz
  AddLanguage ru .ru
  AddLanguage zh-tw .tw
  AddLanguage tw .tw
  AddCharset Big5         .Big5    .big5
  AddCharset WINDOWS-1251 .cp-1251
  AddCharset CP866        .cp866
  AddCharset ISO-8859-5   .iso-ru
  AddCharset KOI8-R       .koi8-r
  AddCharset UCS-2        .ucs2
  AddCharset UCS-4        .ucs4
  AddCharset UTF-8        .utf8
  <IfModule mod_negotiation.c>
      LanguagePriority en fr ja de da nl et el it kr no pl pt pt-br ru ltz ca es sv tw
  </IfModule>

Important : on autorise les fichiers php. Ici, il s'agit de php4.

  AddType application/x-httpd-php .php
  AddType application/x-httpd-php-source .phps

  AddType application/x-tar .tgz

On peut aussi autoriser les scripts CGI (fichier exécutables *.cgi) ou les SSI (server side include, fichiers *.shtml). (voir plus loin)

  #AddHandler cgi-script .cgi
  #AddType text/html .shtml
  #AddHandler server-parsed .shtml

???

  #AddHandler send-as-is asis
  #AddHandler imap-file map
  #AddHandler type-map var
</IfModule>

La balise META dans l'en-tête d'un fichier HTML correspond à des choses que le serveur peut envoyer directement, avant le document HTML lui-même. On peut préciser, comme suit, que ces information sont dans des fichiers *.meta dans un répertoire .web/

#MetaDir .web
#MetaSuffix .meta

On peut modifier les messages d'erreur

#ErrorDocument 500 "The server made a boo boo.
#ErrorDocument 404 /missing.html
#ErrorDocument 404 /cgi-bin/missing_handler.pl
#ErrorDocument 402 http://some.other_server.com/subscription_info.html

<IfModule mod_setenvif.c>
  BrowserMatch "Mozilla/2" nokeepalive
  BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
  BrowserMatch "RealPlayer 4\.0" force-response-1.0
  BrowserMatch "Java/1\.0" force-response-1.0
  BrowserMatch "JDK/1\.0" force-response-1.0
</IfModule>

J'ai l'impression qu'on peut contrôler le fonctionnement du serveur à distance...

# Allow server status reports, with the URL of http://servername/server-status
<Location /server-status>
  SetHandler server-status
  Order deny,allow
  Deny from all
  Allow from .your_domain.com
</Location>

# Allow remote server configuration reports http://servername/server-info 
<Location /server-info>
  SetHandler server-info
  Order deny,allow
  Deny from all
  Allow from .your_domain.com
</Location>

Voilà ce que ça donne :

<0_server-status.html>

Il est aussi possible d'utiliser Apache comme proxy :

<IfModule mod_proxy.c>
  ProxyRequests On
  <Directory proxy:*>
      Order deny,allow
      Deny from all
      Allow from .your_domain.com
  </Directory>
  ProxyVia On
  CacheRoot "/home/zoonek/spool/WWW/proxy"
  CacheSize 5
  CacheGcInterval 4
  CacheMaxExpire 24
  CacheLastModifiedFactor 0.1
  CacheDefaultExpire 1
  NoCache a_domain.com another_domain.edu joes.garage_sale.com
</IfModule>

Apache permet d'avoir plusieurs sites Web, avec des noms différens sur une même machine.

<http://www.apache.org/docs/vhosts/>

Par défaut, il n'y en a qu'un.

NameVirtualHost *

<VirtualHost *>
  ServerAdmin webmaster@dummy-host.example.com
  DocumentRoot /www/docs/dummy-host.example.com
  ServerName dummy-host.example.com
  ErrorLog logs/dummy-host.example.com-error_log
  CustomLog logs/dummy-host.example.com-access_log common
</VirtualHost>

Mais si nous sommes sur la machine 111.22.33.44, qui s'appelle à la fois www.domain.tld et www.otherdomain.tld, on peut configurer les choses ainsi (la documentation donne des exemples plus détaillés) :

<NameVirtualHost 111.22.33.44

<VirtualHost 111.22.33.44>
  ServerName www.domain.tld
  DocumentRoot /www/domain
</VirtualHost>

<VirtualHost 111.22.33.44>
  ServerName www.otherdomain.tld
  DocumentRoot /www/otherdomain
</VirtualHost>

Voyons comment ça marche. Lorsqu'un butineur se connecte à un site, Que lui dit-il ? Pour le voir, j'ai juste recopié quelques lignes de la page de manuel de Perl.

#!/usr/bin/perl -Tw
use strict;
use Socket;

my $port = 2345;
my $proto = getprotobyname('tcp');

socket(Server, PF_INET, SOCK_STREAM, $proto)        || die "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR,
                   pack("l", 1))   || die "setsockopt: $!";
bind(Server, sockaddr_in($port, INADDR_ANY))        || die "bind: $!";
listen(Server,SOMAXCONN)                            || die "listen: $!";

my $paddr = accept(Client,Server)
my($port,$iaddr) = sockaddr_in($paddr);
my $name = gethostbyaddr($iaddr,AF_INET);

while(<Client>){ print }
close Client;

Quand on demande au butineur d'aller chercher la page http://localhost:2345/, il se connecte sur le port en question et envoie le message

GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/4.6 [en] (X11; I; Linux 2.2.9-27mdk i586)
Host: localhost:2345
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*
Accept-Encoding: gzip
Accept-Language: en
Accept-Charset: iso-8859-1,*,utf-8

En particulier, il indique le nom de la machine. C'est ainsi q'apache sait sous quel non il a été appelé.

Il est aussi possible de faire tout cela dynamiquement (c'est utilie si on a des centaines de sites Web derrière le même serveur, par exemple si on est un ISP --- ce n'est pas mon cas).

Les règles de réécriture permettent de systématiser ce genre de choses, et bien plus.

SSI

La principale utilisation consiste à inclure le début (et la fin) du fichier HTML de manière à avoir des pages homogènes.

<!--#include virtual="/header.html" -->
...
<!--#include virtual="/footer.html" -->

Voici d'autres exemples, tirés de la documentation.

<!--#echo var="DATE_LOCAL" -->

<!--#config timefmt="%A %B %d, %Y" -->
Today is <!--#echo var="DATE_LOCAL" -->

This document last modified <!--#flastmod file="index.html" -->

<!--#config timefmt="%D" -->
This file last modified <!--#echo var="LAST_MODIFIED" -->

<!--#include virtual="/cgi-bin/counter.pl" -->

<!--#config errmsg="[It appears that you don't know how to use SSI]" -->

<!--#set var="name" value="Rich" -->
<!--#set var="modified" value="$LAST_MODIFIED" -->
<!--#set var="cost" value="\$100" -->
<!--#set var="date" value="${DATE_LOCAL}_${DATE_GMT}" -->

<!--#if expr="test_condition" -->
<!--#elif expr="test_condition" -->
<!--#else -->
<!--#endif -->

<!--#if expr="${Mac} && ${InternetExplorer}" -->
   Apologetic text goes here
<!--#else -->
  Cool JavaScript code goes here
<!--#endif -->

Il est même possible d'exécuter des commandes (on peut autoriser les SSI en interdisant ce genre de chose).

<pre>
<!--#exec cmd="ls" -->
</pre>

Modules

A FAIRE

Configuration de php

On peut éditer le fichier $PREFIX/lib/php.ini, en particulier ce qui 
concerne le « safe mode ». Le fichier étant abndemment documenté, je 
ne détaille pas.

Configuration de mysql

Il est bon de donner un mot de passe à root. Ce sera désormais « foobar ».

mysqladmin -u root -h localhost.localdomain -p password 'foobar'
Enter password: RET

On peut vérifier que la connection sans mot de passe ne fonctionne pas.

mysql -u root
ERROR 1045: Access denied for user: 'root@localhost' (Using password: NO)

Par contre, avec un mot de passe, ça marche.

mysql -u root -p 
Enter password: foobar

Lors d'une connection à distance, le mot de passe est transmis crypté. Néanmoins :

Note that even if the password is stored
'scrambled', and knowing your 'scrambled' password is enough to be
able to connect to the *MySQL* server!
(...)
Knowing
the encrypted password for a user makes it possible to login as
this user.  The passwords are only scrambled so that one shouldn't
be able to see the real password you used (if you happen to use a
similar password with your other applications).
(...)
The encrypted password is
then used when the client/server is checking if the password is correct
(This is done without the encrypted password ever traveling over the
connection.)

Si jamais on a oublié le mot de passe de root, on peut quand même s'en tirer en lançant :

mysqladmin --no-defaults -u root ver

On peut vérifier le contenu de la base de données :

mysql -u root mysql -p  
Enter password: foobar
mysql> SELECT Host, User, Password FROM user;
+-----------------------+------+------------------+
| Host                  | User | Password         |
+-----------------------+------+------------------+
| localhost             | root | 4655c05b05f11fab |
| localhost.localdomain | root |                  |
| localhost             |      |                  |
| localhost.localdomain |      |                  |
+-----------------------+------+------------------+
4 rows in set (0.01 sec)

L'utilisateur sans nom (ni mot de passe), c'est juste l'utilisateur annonyme.

Il faut rajouter un mot de passe à root@localhost.localdomain (la commande PASSWORD permet de crypter le mot de passe, qui n'est pas stocké en clair dans la base de données).

SET PASSWORD FOR root@localhost=PASSWORD('foobar');
SET PASSWORD FOR root@localhost.localdomain=PASSWORD('foobar');
FLUSH PRIVILEGES;

SELECT Host, User, Password FROM user;
SHOW GRANTS FOR root@localhost.localdomain;
SHOW GRANTS FOR root@localhost;

Voyons un peu le contenu de cette table.

Host 
User 
Password

On peut préciser si un utilisateur a le droit d'exécuter des commandes SELECT, INSERT, etc.

Select_priv 
Insert_priv 
Update_priv 
Delete_priv 
Create_priv 
Drop_priv 
Index_priv       Créer ou effacer des indexes
Alter_priv

On précise aussi quelques autres droits

Reload_priv      Droit de relancer le serveur
Shutdown_priv    Droit  d'arréter le serveur
Process_priv     Droit de regarder quelles sont les requètes en
                 cours (processlist) et de les arréter (kill)
File_priv        Droit d'écrire des fichiers (par exemple, avec 
                 SELECT ... INTO OUTFILE)
Grant_priv       Droit de donner des droits (on peut juste donner 
                 les droits que l'on a déjà)
References_priv

On peut maintenant rajouter un utilisateur, lui donner un mot de passe et lui accorder des droits raisonnables sur une seule base de donnée, celle qui porte son nom. On poet aussi préciser des droits sur certaines tables, ou certaines colonnes de certaines tables.

GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP
  ON zoonek.* TO zoonek@localhost IDENTIFIED BY 'azerty';

On peut modifier ces privilèges par la suite.

REVOKE INSERT,UPDATE,DELETE,CREATE,DROP ON zoonek.foo FROM zoonek@localhost;

Le langage

Php est un langage de programmation interprété, comme perl, python ou ruby. Il est plus particulièrement utilisé pour créer des pages Web, mais peut-être utilisé à d'autres fins.

Le code php doit être compris entre les balises <?php et ?>.

Les commentaires sont comme en C, en C++ ou en Perl.

/* Ceci est un commentaire */
/* /* Ceci est une erreur */ */
// Ceci est un commentaire
# Ceci est un commentaire

Messages d'erreur

Je conseille vivement (à moins que vous soyez capable de programmer sans jamais commettre la moindre d'erreur) de rajouter

error_reporting(E_ALL);

au début de chacun de vos programmes : ainsi, php vous signalera toutes les erreurs et vous inondera d'avertissements, comme l'utilisation d'une variable sans qu'elle soit réalablement définie. Malheurersement, si votre programme est réparti dans plusieurs fichiers, php est incapable de vous dire dans lequel l'erreur se trouve (il donne toutefois la ligne, c'est déjà ça).

Structures de contrôle

Les conditionnelles if/elseif/else

<?php
  if(strstr($HTTP_USER_AGENT,"MSIE")) {
    print "You are using Internet Explorer";
  } elseif(strstr($HTTP_USER_AGENT,"Mozilla")) {
    print "You are using Netscape";
  } else {
   print "You are using neither Internet Explorer nor Netscape";
  }
?>

Il y a une autre syntaxe :

if ($a == 5):
  print "a equals 5";
  print "...";
elseif ($a == 6):
  print "a equals 6";
  print "!!!";
else:
  print "a is neither 5 nor 6";
endif;

La boucle for

for ($i=1; $i<=10; $i++) { print $i; }

La boucle while

$i = 1;
while ($i <= 10) { print $i++; }

La boucle do...while

$i = 0;
do { print $i; } while ($i>0);

Il est possible d'arrêter une boucle ou de oasser à l'itération suivante à l'aide des commandes

break continue

Il y a aussi une commande switch, qui présente les mêmes défauts que celle du C : oublions-la.

La commande each permet d'avoir l'« élément suivant » d'un tableau (on fera attention de revenir au début du tableau à l'aide de la commande reset).

reset ($arr);
while (list($key, $value) = each ($arr)) {
   echo "Key: $key; Value: $value<br>\n";
}

Il n'est donc visiblement pas prévu de pouvoir imbriquer ces boucles (par exemple, pour pouvoir obtenir tous les couples ($a, $b), avec $a et $b dans un certain tableau $tab). C'est quand même possible, mais il faut faire une copie du tableau...

Il est possible d'intercepter les erreurs en mettant un @ devant une commande (sinon, les erreurs s'affichent dans la page HTML, ce qui n'est pas toujours voulu).

@this_may_fail() || echo "It failed";

@this_may_fail() || die "It failed";

Il y a aussi une commande eval, je pense qu'elle intercepte aussi les erreurs fatales (non testé).

Variables

Les variables doivent être précédées d'un $. Php est un langage faiblement typé (comme python, contrairement à C++). On peut les déclarer préalablement à l'aide de la commande var, mais ce n'est pas nécessaire.

var $foo;

Les variables globales ne sont pas visibles partout (en d'autres termes, elles ne sont pas globales). Si on veut en utiliser dans une fonction, il faut les déclarer explicitement comme telles. Le tableau GLOBALS contient toutes les variables globales.

function foo($a,$b) {
  global $c, $d;
  ...
}

À l'intérieur d'une fonction, on peut avoir des variables statiques (ie, elles sont initialisées une fois et à chaque appel de la fonction, elles retrouvent la valeur qu'elles avaient à la fin de l'appel précédent.

function foobar () {
  static $a = 0;
  ...
  $a++;
}

La commande gettype permet d'obtenir le type d'une variable

<?php 
  function element($a) {
    print "  <td bgcolor=\"#ffffaa\">$a</td>\n";
  }
  function line ($a, $b, $c) {
    print " <tr>\n";
    element($a);
    element($b);
    element($c);
    print " </tr>\n";
  }
  print "<table cellspacing=1>\n";
  line("Variable", "type", "value");
  reset ($GLOBALS);
  while (list($key, $value) = each ($GLOBALS)) {
    line($key, gettype($value), $value);
  }
  print "</table>\n";
?>

<3_global_variables.php>
<3_global_variables.html>

Il y a aussi des commandes

is_long is_double is_string is_array is_object

Les casts se font comme en C (mais il y a aussi une commande settype).

$a = (int) $b 
$a = (integer) $b // the same

$a = (real) $b 
$a = (double) $b // the same
$a = (float) $b  // the same

$a = (string) $b 
$a = (array) $b

Références, pointeurs

À éviter.

Constantes

define("FOOBAR", "Hello world.");
echo FOOBAR;

Nombres

Les nombres s'utilisent ainsi.

$a = 1234; # decimal number
$a = -123; # a negative number
$a = 0123; # octal number (equivalent to 83 decimal)
$a = 0x12; # hexadecimal number (equivalent to 18 decimal)
$a = 1.234; 
$a = 1.2e3;

Il y a plein de comstantes mathématiques (M_PI, M_E, etc.) et de fonctions : voir la documentation en cas de besoin.

Chaines de caractères

Les chaines de caractères sont délimitées par des "..." (auquel cas les variables contenues dans la chaine seront remplacées par leur valeur) ou par des '...'.

$str = "This is a string"; $str = $str . " with some more text"; $str .= " and a newline at the end.\n"; $num = 9; $str = "<p>Number: $num</p>";

Le point (.) est utilisé pour la concaténation (surtout pas de +, car contrairement à Perl, php ne râlera pas).

$str = 'This is a test.'; $first = $str[0];

On peut récupérer les différents caractères d'une chaine avec des crochets.

$str = 'This is still a test.'; $last = $str[strlen($str)-1];

Tableaux

Les tableaux de php sont indexés indifféremment par des nombres ou par des chaines de caractères.

On peut créer des tableaux à l'aide de crochets, sans rien déclarer préalablement.

$a[1]     = $f;
$a["foo"] = $f;   

$a[1][0]     = $f;
$a["foo"][2] = $f;
$a[3]["bar"] = $f;

On peut aussi utiliser la commande array

$a = array(
           "color" => "red",
           "taste" => "sweet",
           "shape" => "round",
           "name"  => "apple",
           3       => 4
          );

Il ne faut pas utiliser de tableaux dans une chaine délimitée par des "...", car php ne comprendrait pas.

$b = "foo". $a[1][0] ."bar";

On peut trier un tableau à l'aide des fonctions suivantes :

asort arsort ksort rsort sort uasort usort uksort

La commande count donne le nombre d'éléments d'un tableau.

print "Le tableau \$a a ". count($a) ." éléments.";

On peut énumérer les éléments d'un tableau à l'aide d'une boucle

reset ($arr);
while (list($key, $value) = each ($arr)) {
   echo "Key: $key; Value: $value<br>\n";
}

Voir aussi les commandes next et prev.

Une dernière remarque à propos des tableaux : ce sont des tableaux associatifs qui se souviennent de l'ordre dans lequel ils ont été remplis (et dans une boucle, c'est cet ordre que l'on prendra) : ainsi, si on remplit un tableau comme suit :

$a = array();
$a[1] = 'dsfg';
$a[2] = 'kjsdvk';
$a[3] = 'jhfd';
$a[0] = 'jkhkj';

une boucle itérera sur ces éléments dans l'ordre

$a[1], $a[2], $a[3], $a[0].

Variables prédéfinies

Toutes les variables d'environement, toutes les variables passées en argument au script, par exemple s'il est appelé par un URL de la forme

foo.php?a=1;toto=2

se retrouvent sous forme de variables globales. Dans le cas où le script est appelé depuis le shell (et pas depuis un serveur Web), il y a bien sûr les variables argc et argv. Ces variables sont accessibles soit sous forme de variables globales $a, $toto, soit dans le tableau $HTTP_GET_VARS : $HTTP_GET_VARS["a"], $HTTP_GET_VARS["toto"].

if($HTTP_GET_VARS){
  reset($HTTP_GET_VARS);
  while( list($var, $value) = each($HTTP_GET_VARS)){
    ...
  }
}

Il y a aussi des constantes TRUE et FALSE.

La variable PHP_SELF contient le nom du script qui est en train d'être exécuté.

La commande phpinfo permet d'obtenir la liste de toutes ces variables et plein d'autres choses.

<?php phpinfo(); ?>

Voici le résultat (c'est la commande phpinfo qui s'est chargée de tout présenter bien comme il faut).

<0_phpinfo.html>

Opérateurs

Les voici (le % est le reste de la division euclidienne, les `...` sont un racourci pour la commande system, le reste est clair)

== != % & | ~ ^ << >> ?: @ ` and or not && || ! xor

Inclusion

La commande include permet d'inclure un fichier. (Il y a aussi une commande require, mais généralement, c'est plutôt include que l'on veut).

include("foo.inc");

Les fonctions

On définit une fonction de la manière suivante (une fonction sans argument, une fonction à trois arguments, une autre fonction à trois arguments dont seul le premier est obligatoire).

function foo () { ... return $a; }
function bar ($a, $b, $c) { ... }  
function foobar ($a, $b=1, $c="toto") { ... }

Rappelons que les variables globales ne sont accessibles à l'intérieur d'une fonction que si on les a déclarées comme telles.

function foo () {
  global $a, $b;
  ...
}

Les objets

On définit et utilise une classe ainsi. (On accède aux champs d'un objets à l'aide de $this->.)

class Foo {
  var $a=0;
  function bar ($x) {
    $this->a += $x;
  }
}

$f = new Foo;
$f->(3);
$f->(4);
print $f->a;

On peut rajouter une constructeur : c'est juste une fonction qui porte le même nom que la classe.

class Foo {
  var $a;
  function Foo { $a=0; }
  function bar ($x) { $this->a += $x; }
}

Il y a aussi de l'héritage (on prendra garde que quand on appelle le constructeur d'une clase, celui-ci n'appelle pas automatiquement le constructeur de son père).

class FooBar extends Foo {
  var $b;
  function FooBar { $a=0; $b=0; }
  ...
}

Divers

On peut inclure du code php (avec de jolies couleurs) dans un fichier html :

<?php show_source("9.php"); ?>
<?php show_source($PHP_SELF); ?>

Voire même tous les fichiers *.php du répertoire courrant :

<html>
<body bgcolor="white">
<?php
$rep = preg_replace( "/[^\\/]*$/", "", $SCRIPT_FILENAME);
if ($dir = @opendir("$rep")) {
  while($file = readdir($dir)) {
    if( preg_match( "/\\.php3?$/", $file ) ){
      echo "<hr><p align=\"center\"><b>$file</b></p>\n";
      show_source("$rep/$file");
    } else { echo "<hr><p align=\"center\">$file</p>\n"; }
  }
  closedir($dir);
}
?>
</body>

Fonctions prédéfinies

Les fonctions de php sont TRÈS nombreuses (un peu comme si on rajoutait à perl toutes les fonctions qui sont dans tous les modules existants), je me contente donc de donner quelques exemples.

Tableaux

La commande array permet de définir un tableau dont les entrées sont indexées par des entier consécutifs ou des chaines de caractères. La commande print_r permet de les afficher.

$a = array(0,1,2,3);
print $a[0];

$b = array( 0 => 'a', 1 => 'b', 2 => 'c' );
print $b[0];

$c = array( 'a' => "abc", 'b' => "def", 'lskdhglksd' => 'ksqjf' );
print $c['a'];

$d = array (
       'a' => array( 1, 2, 3 ),
       'b' => "abc" );

print_r($d);

On dispose, entre autres des commandes suivantes, que je ne détaille pas.

array_pop
array_push
array_shift
array_unshift
array_slice
array_splice
array_reverse
count, sizeof
array_rand
shuffle

Contrairement à Perl, les tableaux associatifs sont _aussi_ munis d'un ordre, qui apparait quand on fait des boucles.

reset ($arr);
while (list($key, $value) = each ($arr)) {
   echo "Key: $key; Value: $value<br>\n";
}

On dispose dont de plusieures fonctions de tri : asort, arsort, uasort trient en fonction de la valeur, en gardant un tableau associatif ; ksort, krsort, uksort trient en fonction de la clef ; sort, rsort, usort trient en fonction de la valeur et on obtient un tableau indexé par des entiers consécutifs. (Le r signifie que l'on trie à l'envers, le u signifie que l'on fournit une fonction de comparaison pour effectuer le tri.)

La commande list permet d'assigner plusieures variables en même temps.

list( $a, $b, $c ) = (1,2,3);

Il est possible de couper une chaine en morceaux pour obtenir un tableau (la commande explode permet de faire cela avec une chaine de caractères au lieu d'une expression régulière).

$keywords = preg_split ("/[\s,]+/", "hypertext language, programming");

La commande join permet de faire le contraire : obtenir une chaine de caractères à l'aide d'un tableau (il y a aussi une commande implide qui fait la même chose).

$fcontents = join ('', file ('http://www.php.net'));

Expressions régulières

Ce qui choque le plus, au niveau des expressions régulières en php ou python (mais pas en ruby, il me semble), quand on est habitué à perl, c'est que les expressions régulières sont contenues dans des chaines : or, quand on veut mettre un antislash dans une chaine, il faut en mettre deux, mais dans les expressions régulières, il n'est pas rare de vouloir mettre deux antislashes, il en faudra donc quatre --- et dans certains cas intermédiaires, il en faut trois. À croire que ces langages cherchent à voler à Perl son qualificatif de « langage en écriture seul ».

En php, il y a deux bibliothèques manipulant des expressions régulières : une première compatible avec Perl et une autre POSIX. J'utilise celle compatible avec Perl. On notera que, contrairement à Perl, le délimiteur des expressions régulières est nécessairement /. Les deux commandes sont preg_match et preg_replace.

$s = "PHP is the web scripting language of choice.")){...}
if (preg_match ("/php/i", $s)     { ...; }
if (preg_match ("/\bweb\b/i", $s) { ...; }
if (preg_match ("/\bweb\b/i", $s) { ...; }

preg_match("/^(http:\/\/)?([^\/]+)/i",
  "http://www.php.net/index.html", $matches);
$host = $matches[2];
// get last two segments of host name
preg_match("/[^\.\/]+\.[^\.\/]+$/",$host,$matches);
echo "domain name is: ".$matches[0]."\n";

La commande preg_replace ne modifie pas son argument : il faut donc souvent écrire $a = preg_replace("...", "...", $a).

$patterns = array ("/(19|20)(\d{2})-(\d{1,2})-(\d{1,2})/",
                   "/^\s*{(\w+)}\s*=/");
$replace = array ("\\3/\\4/\\1\\2", "$\\1 =");
print preg_replace ($patterns, $replace, "{startDate} = 1999-5-27");

preg_replace ("/(<\/?)(\w+)([^>]*>)/e",
              "'\\1'.strtoupper('\\2').'\\3'",
              $html_body);

J'ai essayé d'utiliser des tableaux, comme ci-dessus, mais sans succès.

function hiragana_to_katakana_single_character ($a) {
  $hiragana = array("¤¢","¤¤","¤¦","¤¨","¤ª","¤«","¤­","¤¯","¤±","¤³",
  "¤¬","¤­","¤°","¤²","¤´","¤µ","¤·","¤¹","¤»","¤½","¤¶","¤¸","¤º","¤¼",
  "¤¾","¤¿","¤Á","¤Ä","¤Ã","¤Æ","¤È","¤À","¤Â","¤Å","¤Ç","¤É","¤Ê","¤Ë",
  "¤Ì","¤Í","¤Î","¤Ï","¤Ò","¤Õ","¤Ø","¤Û","¤Ñ","¤Ô","¤×","¤Ú","¤Ý","¤Ð",
  "¤Ó","¤Ö","¤Ù","¤Ü","¤Þ","¤ß","¤à","¤á","¤â","¤ä","¤æ","¤è","¤é","¤ê",
  "¤ë","¤ì","¤í","¤ï","¤ò","¤ó","¤ç","¤å","¤ã","¤Ã","¤¡","¤£","¤¥","¤§",
  "¤©");
  $katakana = array("¥¢","¥¤","¥¦","¥¨","¥ª","¥«","¥­","¥¯","¥±","¥³",
  "¥¬","¥®","¥°","¥²","¥´","¥µ","¥·","¥¹","¥»","¥½","¥¶","¥¸","¥º","¥¼",
  "¥¾","¥¿","¥Á","¥Ä","¥Ã","¥Æ","¥È","¥À","¥Â","¥Å","¥Ç","¥É","¥Ê","¥Ë",
  "¥Ì","¥Í","¥Î","¥Ï","¥Ò","¥Õ","¥Ø","¥Û","¥Ñ","¥Ô","¥×","¥Ú","¥Ý","¥Ð",
  "¥Ó","¥Ö","¥Ù","¥Ü","¥Þ","¥ß","¥à","¥á","¥â","¥ä","¥æ","¥è","¥é","¥ê",
  "¥ë","¥ì","¥í","¥ï","¥ò","¥ó","¥ç","¥å","¥ã","¥Ã","¥¡","¥£","¥¥","¥§",
  "¥©");
  $b = $a;
  // D'après le manuel, il semblerait que l'on puisse donner 
  // des listes en argument de str_replace, mais ca ne marche pas.
  reset($hiragana);
  while(list($i,$j) = each($hiragana)){
    $b = str_replace($hiragana[$i], $katakana[$i], $b);
  }
  return $b;
}

Plus simplement, on peut effectuer des remplacements sans utiliser d'expression régulière, à l'aide de la commande str_replace (comme dans l'exemple précédent).

Objets

Il est possible d'obtenir certaines informations sur un objet à l'aide des fonction

get_object_class
get_parent_class
get_objet_vars
get_class_methods

Fichiers

Changer le répertoire courrant

chdir('/tmp');
$pwd = getcwd();
mkdir ("/path/to/my/dir", 0700);
rmdir ("/path/to/my/dir");

Effacer un fichier

unlink('foo.txt');
rename('bar.txt', 'foo.txt');

Liste des fichiers dans un répertoire

if ($dir = @opendir("/tmp")) {
  while($file = readdir($dir)) {
    echo "$file\n";
  }
  closedir($dir);
}

Liste des fichiers dans un répertoire, sauf . et ..

$handle=opendir('.');
while (false!==($file = readdir($handle))) {
  if ($file != "." && $file != "..") {
    echo "$file\n";
  }
}
closedir($handle);

Idem avec des objets

$d = dir("/etc");
echo "Handle: ".$d->handle."<br>\n";
echo "Path: ".$d->path."<br>\n";
while($entry=$d->read()) {
  echo $entry."<br>\n";
}
$d->close();

Lecture d'un fichier ligne par ligne (ici, des lignes qui font au maximum 4095 caractères : si elles sont plus longues, elles seront coupées en morceaux).

$fd = fopen ("/tmp/inputfile.txt", "r");
while (!feof ($fd)) {
  $buffer = fgets($fd, 4096);
  echo $buffer;
}
fclose ($fd);

On peut mettre un fichier dans un tableau (ligne par ligne).

$fcontents = file ('http://www.php.net');
while (list ($line_num, $line) = each ($fcontents)) {
  echo "<b>Line $line_num:</b> " . htmlspecialchars ($line) . "<br>\n";
}

On peut aussi le mettre dans une seule grande chaine

$fcontents = join ('', file ('http://www.php.net'));

Ouverture d'un fichier (r: lecture, w: écriture, a:append)

$fp = fopen ("/home/rasmus/file.txt", "r");
$fp = fopen ("/home/rasmus/file.gif", "w");
$fp = fopen ("http://www.php.net/", "r");
$fp = fopen ("ftp://user:password@example.com/", "w");

Lecture d'un fichier binaire

$filename = "/usr/local/something.txt";
$fd = fopen ($filename, "r");
$contents = fread ($fd, filesize ($filename));
fclose ($fd);

Écrire dans un fichier

$filename = "/usr/local/something.txt";
$fd = fopen ($filename, "a");
$contents = fwrite ($fd, "abc\n");
fclose ($fd);

Créer un fichier temporaire

$fd = tmpfile();

Connections HTTP et autres

On peut accéder à un URL exactement comme si c'était un fichier (en Perl, ce n'est pas encore aussi transparent, mais c'est prévu pour Perl 6).

Dates

/* Today is March 10th, 2001, 5:16:18 pm */
$today = date("F j, Y, g:i a");                 // March 10, 2001, 5:16 pm
$today = date("m.d.y");                         // 03.10.01
$today = date("j, g, Y");                       // 10, 3, 2001
$today = date("Ymd");                           // 20010310
$today = date('h-i-s, j-m-y, it is w Day z ');  // 05-16-17, 10-03-01, 1631 1618 6 Fripm01
$today = date('\i\t \i\s \t\h\e jS \d\a\y.');   // It is the 10th day.
$today = date("D M j G:i:s T Y");               // Sat Mar 10 15:16:08 MST 2001
$today = date('H:m:s \m \i\s\ \m\o\n\t\h');     // 17:03:17 m is month
$today = date("H:i:s");                         // 17:16:17

$today = getdate();
$month = $today['month'];
$mday = $today['mday'];
$year = $today['year'];
echo "$month $mday, $year";

echo date ("M d Y H:i:s", mktime (0,0,0,1,1,1998));
echo gmdate ("M d Y H:i:s", mktime (0,0,0,1,1,1998));

echo date ("M-d-Y", mktime (0,0,0,12,32,1997));
echo date ("M-d-Y", mktime (0,0,0,13,1,1997));
echo date ("M-d-Y", mktime (0,0,0,1,1,1998));
echo date ("M-d-Y", mktime (0,0,0,1,1,98));

setlocale ("LC_TIME", "C");
print (strftime ("%A in Finnish is "));
setlocale ("LC_TIME", "fi_FI");
print (strftime ("%A, in French "));
setlocale ("LC_TIME", "fr_CA");
print (strftime ("%A and in German "));
setlocale ("LC_TIME", "de_DE");
print (strftime ("%A.\n"));

La date du fichier courrant :

$SELF = preg_replace("/\\/zoonek\.free.fr\\//", "", $PHP_SELF);
$date = date( "D M j G:i:s T Y", filemtime($SELF) );

Sockets

Exemple de client (tiré du manuel) :

$fp = fsockopen ("www.php.net", 80, $errno, $errstr, 30);
if (!$fp) {
  echo "$errstr ($errno)<br>\n";
} else {
  fputs ($fp, "GET / HTTP/1.0\r\n\r\n");
  while (!feof($fp)) {
    echo fgets ($fp,128);
  }
  fclose ($fp);
}

On peut passer du nom d'une machine à son adresse IP et réciproquement :

$free = gethostbyaddr('213.228.0.42');
$free_ip = gethostbyname('www.free.fr');

Le manuel ne donne pas d'exemple de client (mais php b'est pas a priori fait pour ça).

XML, DOM

La documentation nous dit :

This documentation is not finished yet. Don't start to translate it or
use it as a programming reference (steinm@php.net).
   
These functions are only available if PHP was configured with
--with-dom=[DIR], using the GNOME xml library. You will need at least
libxml-2.2.7 These functions have been added in PHP 4.

Images, PDF

Il est possible de créer des fichiers GIF ou PNG à la volée.

Animations Flash

http://www.opaque.net/ming/

Divers

Il y a d'autres bibliothèques, que je ne mentionne pas ici. Elles ne sont généralement pas installées par défaut, il faut le préciser lors de la compilation de php.

Exemples simples

Inutilité de php

Une première utilisation de php consiste à uniformiser un site, de manière que chaque page ait la même présentation, les mêmes couleurs, la même notice de copyright, la même barre de navigation, etc.

<?php
  
global $bgcolor, $fgcolor, $link, $vlink, $alink;
$bgcolor = "#FFFFFF";
$fgcolor = "#000000";
$link    = "#0000FF";
$vlink   = "#000080";
$alink   = "#FF0000";
  
global $title_bgcolor, $title_fgcolor;
$title_bgcolor = "#eecc99";
$title_fgcolor = $fgcolor;
  
global $section_fgcolor, $section_bgcolor;
$section_fgcolor = "#ffffff";
$section_bgcolor = "#3030a0";
  
global $foot_fgcolor;
$foot_fgcolor = "#c0c0c0";
  
function head($title, $author="", $email="", $url="",
              $encoding="iso-8859-1", 
              $keywords="", $description="") {
  
  $GLOBALS[author] = $author;
  $GLOBALS[email] = $email;
  $GLOBALS[url] = $url;
  
  global $bgcolor, $fgcolor, $link, $vlink, $alink;
  global $title_bgcolor, $title_fgcolor;
  
  print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n";
  print "<html>\n";
  print " <head>\n";
  print "  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=$encoding\">\n";
  if( $author != "" ){
    print "  <meta name=\"Author\" content=\"$author <$email>\">\n";
  }
  if( $keywords != "" ){
    print "  <meta name=\"keywords\" content=\"$keywords\">\n";
  }
  if( $description != "" ){
    print "  <meta name=\"description\" content=\"$description\">\n";
  }
  
  print "  <title>$title</title>\n";
  print " </head>\n";
  print " <body\n";
  print "    text=\"$fgcolor\"\n";
  print "    bgcolor=\"$bgcolor\"\n";
  print "    link=\"$link\"\n";
  print "    alink=\"$alink\"\n";
  print "    vlink=\"$vlink\"\n";
  print " >\n\n";
  print "<div align=\"center\"><table cellpadding=10><tr><td bgcolor=\"$title_bgcolor\"><font size=\"+1\" color=\"$title_fgcolor\"><strong>$title</strong></font></td></tr></table></div>\n";
}
  
function foot() {
  global $author, $email, $url;
  global $foot_fgcolor;
  print "\n\n<p align=\"right\" style=\"color: $foot_fgcolor\">\n";
  print "<A HREF=\"$url\"\n";
  print "   STYLE=\"text-decoration: none; color: $foot_fgcolor\" >$author</A><br>\n";
  print "<A HREF=\"mailto:$email\"\n";
  print "   STYLE=\"text-decoration: none; color: $foot_fgcolor\" >&lt;$email></A><br>\n";
  print Date("H:i:s d/m/Y");
  print "</p>\n";
  print "\n\n </body>\n";
  print "</html>\n";
}
  
function section ($a) {
  global $section_fgcolor, $section_bgcolor;
  print "\n\n<p></p>\n";
  print "<table cellpadding=5 width=\"100%\"><tr><td bgcolor=\"$section_bgcolor\"><font size=\"+1\" color=\"$section_fgcolor\">$a</font></td></tr></table>\n";
}
  
?>

<!-- Ce qui précède devrait être dans un fichier séparé, que l'on 
     charge à l'aide de la commande include --!>
  
<?php head("Essai", 
           "Vincent Zoonekynd", "zoonek@math.jussieu.fr", 
           "http://www.math.jussieu.fr",
           "iso-8859-1", "essai", "useless use of php") ?>
  
<?php section("Introduction") ?>
  
Bla bla bla bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla
bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla
bla. Bla bla bla bla. Bla bla bla bla.  Bla bla bla bla. 
  
<?php section("Suite") ?>
  
Bla bla bla bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla
bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla
bla. Bla bla bla bla. Bla bla bla bla.  Bla bla bla bla. 
  
<?php section("Conclusion") ?>
  
Bla bla bla bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla
bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla bla. Bla bla bla
bla. Bla bla bla bla. Bla bla bla bla.  Bla bla bla bla. 
  
<?php foot() ?>

<2_useless_use_of_php.php>
<2_useless_use_of_php.html>

Ce n'est pas une utilisation pertinente, car les pages devront être recalculées à chaque fois et le résultat sera toujours le même. Il aurait été beaucoup plus judicieux d'utiliser un outil permettant de créer des pages Web statiques, par exemple WML (http://www.engelschall.com/sw/wml/).

Formulaires

Un formulaire HTML ressemble à cela.

<html><body>
<form action="3.php" method=post>
  
  <!-- du texte à saisir, plus ou moins long -->
  A: <input type="text" name="A"><br>
  B: <input type="text" name="B" size=40><br>
  
  code:<br>
  <textarea name="code" cols=50 rows=5>
  </textarea>
  <br>
  
  <!-- Un seul choix possible -->
  C: <select name="C">
       <option selected>1
       <option> 2
       <option> 3
       <option> 4
     </select><br>
  
  <!-- Plusieurs choix possibles -->
  D: <select multiple name="D[]">
       <option selected>1
       <option> 2
       <option> 3
       <option> 4
     </select><br>
  
  <!-- Oui ou non-->
  E: <input type="checkbox" name="E"><br>
  
  <!-- un seul choix possible -->
  F: 
  <input type="radio" name="F" value="1">1
  <input type="radio" name="F" value="2">2
  <input type="radio" name="F" value="3">3
  <input type="radio" name="F" value="4" checked>4
  <br>
  
  <!-- Une image : on peut cliquer n'importe où et on récupère les
       coordonnées -->
  I: <input type="image" src="/icons/icon.sheet.gif" name="sub"><br>

  <!-- Un bouton, qui ne sert pas à RIEN -->
  G: <input type="button" name="G" value="Essayer"><br>
  
  <!-- Un champ caché, pour garder la valeur d'une variable entre
       deux appels -->
  <input type="hidden" name="foobar" value="46">
  
  <!-- Le bouton pour envoyer le formulaire -->
  <input type="submit" value="Valider" name="Z">
</form>
</body></html>

<1_form.php>

Si l'on tape un peu n'importe quoi dans ces divers champs, on peut récupérer leur contenu de la manière suivante .

<?php
  function element($a) {
    print "  <td bgcolor=\"#ffffaa\">$a</td>\n";
  }
  function line ($a, $b) {
    print " <tr>\n";
    element($a);
    element($b);
    print " </tr>\n";
  }
  print "<table cellspacing=1>\n";
  line("Variable", "value");
  line("A", $A);
  line("B", $B);
  line("C", $C);
  
  $D_text = "";
  reset($D);
  while (list($key, $value) = each ($D)) {
    $D_text .= "$value, ";
  }
  line("D", "[ $D_text ]");
  
  line("E", $E);
  line("F", $F);
  line("sub_x", $sub_x);
  line("sub_y", $sub_y);
  line("G", $G);
  print "</table>\n";
?>
</body></html>

Ce qui donne des choses du genre

$A= "aaaa a a";
$B= "bbbb b";
$C= 4;
$D= list( 1, 2, 4 );
$E= "on";
$F= 3;
$sub_x = 289;
$sub_y = 437;

Cookies

J'ai horreur de ça, je n'en utilise donc pas.

Utilisation de mysql

Commandes SQL

http://www.dcs.napier.ac.uk/~andrew/gisq/

SQL est un « langage » conçu pour que les non informaticiens puissent dialoguer avec une base de données.

Création d'une nouvelle base de données (une base de données, c'est un ensemble de tables ; on peut faire des recherches en mélangeant les diférentes tables d'une même base de données)

DROP DATABASE IF EXISTS essai;
CREATE DATABASE essai;

Création d'une nouvelle table

DROP TABLE IF EXISTS toto
CREATE TABLE toto (date date);

On utilise souvent un « identificateur unique » pour pouvoir accéder rapidement à un champ.

DROP TABLE IF EXISTS essai;
CREATE TABLE essai (id INT NOT NULL, name CHAR(200), value INT,
                    PRIMARY KEY (id));

D'un point de vue sémantique, cela ne change absolument rien, c'est juste une contrainte suppléméntaire sur la colonne « id ». On peut d'ailleurs demander à MySQL de la remplir tout seul, à l'aide du mot-clef AUTO_INCREMENT.

DROP TABLE IF EXISTS essai;
CREATE TABLE essai (id INT NOT NULL AUTO_INCREMENT, 
                    name CHAR(200), 
                    value INT,
                    PRIMARY KEY (id));  
INSERT INTO essai (name, value) VALUES ("foo", 100);
INSERT INTO essai (name, value) VALUES ("bar", 500);
INSERT INTO essai (name, value) VALUES ("foobar", -10);
SELECT * FROM essai;

Pour pouvoir accéder rapidement à une ligne du tableau à partir du contenu d'une colonne, on peut demander à mySQL d'indexer cette colonne.

DROP TABLE IF EXISTS essai;
CREATE TABLE essai (name CHAR(200), 
                    rank INT UNSIGNED NOT NULL,
                    INDEX (rank));

Dans l'exemple suivant, l'indexation se fait selon last_name et selon (last_name, first_name). (Note : la commande KEY est un synonyme de INDEX.)

DROP TABLE IF EXISTS essai;
CREATE TABLE essai (id INT NOT NULL,
                    last_name CHAR(30) NOT NULL,
                    first_name CHAR(30) NOT NULL,
                    PRIMARY KEY (id),
                    INDEX name (last_name,first_name));

Dans l'exemple suivant, l'indexation se fait suivant les dix premiers caractères de

DROP TABLE IF EXISTS essai;
CREATE TABLE essai (name CHAR(200) NOT NULL,
                   INDEX index_name (name(10)));

Il est possible d'indexer une table selon plusieurs colonnes.

DROP TABLE IF EXISTS essai;
CREATE TABLE essai (one CHAR(200) NOT NULL,
                    two CHAR(200) NOT NULL,
                    INDEX one_index (one(10)),
                    INDEX two_index (two(10)));

La commande INSERT permet d'ajouter une ligne dans une table.

DROP TABLE IF EXISTS essai;
CREATE TABLE essai (id INT NOT NULL AUTO_INCREMENT, 
                    name CHAR(200), 
                    value INT,
                    PRIMARY KEY (id));  
INSERT INTO essai (name, value) VALUES ("foobar", -10);

Il est possible de rajouter plusieures lignes à la fois.

INSERT INTO essai (name, value) VALUES 
  ("foo", 100), 
  ("bar", 500);

Modification d'une ligne dans une table

INSERT INTO pron (id, pron, count) VALUES (4, 'a', 1);
(...)
UPDATE pron SET count=2 WHERE id=4;

Effacement d'une ligne dans une table

DELETE FROM user WHERE Host='localhost' AND User=''

Ajout d'une colonne (ou d'un index) à une table

ALTER TABLE t2 ADD INDEX (d), ADD PRIMARY KEY (a);
ALTER TABLE t2 ADD c INT UNSIGNED NOT NULL AUTO_INCREMENT,
               ADD INDEX (c);

Changement du nom d'une colonne

ALTER TABLE t CHANGE COLUMN old_name new_name

Changement du nom d'une table

ALTER TABLE old_name RENAME TO new_name

Effacement d'une colonne d'une table

ALTER TABLE t2 DROP COLUMN c;

Effacement d'une table

DROP TABLE IF EXISTS essai;

Effacement d'une base de données (ie, un ensemble de tables)

DROP DATABASE IF EXISTS essai;

Une fois que l'on a construit une ou plusieures tables, on peut commencer à travailler avec, à faire des recherches dedans. C'est là que les choses peuvent devenir compliquées.

La ligne suivante affiche le contenu de la table essai.

SELECT * FROM essai

La ligne suivante sélectionne les pays (dans la base de donnée « cia ») dont la population excède 2 millions.

SELECT name, gdp FROM cia 
WHERE population > 200000000

Il est possible de faire des opérations mathématiques entre les champs que l'on récupère.

SELECT name, gdp/population FROM cia 
WHERE population > 200000000

Il est possible d'utiliser des opérateurs logiques AND OR NOT (avec des parenthèses si nécessaire)

SELECT name, population FROM cia
WHERE name="France" OR name="Italy" OR name="Germany"

Il est possible de faire des recherches approchées : le caractère % joue le rôle du * pour le shell (et donc du .* pour perl), le caractère _ celui du ? pour le shell (et donc du . pour perl).

SELECT name FROM cia 
WHERE name LIKE "%United%"

SELECT title, score FROM movie 
WHERE title LIKE "%police academy%"

On peut aussi utiliser des expression régulières (penser à doubler les antislashes).

SELECT * FROM pet WHERE name REGEXP "^b"

.      Un caractère
[a-z]  Une lettre minuscule
[0-9]  Un chiffre
.*     N'importe quoi (éventuellement rien)
.+     N'importe quoi, mais quelque chose
[0-9]* Rien ou des chiffres
[0-9]+ Un ou plusieurs chiffres
[0-9]? Rien ou un chiffre
[^0-9] un caractère qui n'est pas un chiffre
^      Début
$      Fin
abc|de "abc" ou "de"
^.{5}$ Exactement cinq caractères
^.{5,10}$ Entre cinq et dix caractères
[[:<:]]  Début d'un mot
[[:>:]]  Fin d'un mot
[:alnum:]
[:digit:]
[:punct:]
[:alpha:]
[:graph:]
[:space:]
[:blank:]
[:lower:]
[:upper:]
[:cntrl:]
[:print:]
[:xdigit:]

Le mot-clef IN permet de savoir si un champ est dans une certaine liste.

SELECT id, title FROM movie WHERE id IN (1,2,3)

On peut aussi vérifier si un champ existe ou pas.

SELECT code, name FROM party
WHERE leader IS NULL;

SELECT name, leader FROM party
WHERE leader IS NOT NULL;

SQL permet d'imbriquer des commandes SELECT, mais pas mySQL. La commande suivante, parfaitement légale, ne marche donc pas.

SELECT city, country 
FROM location 
WHERE city IN (SELECT city 
               FROM weather 
               WHERE condition = 'CLOUDY');

Il est possible d'appliquer les fonctions SUM COUNT AVG MAX MIN à l'ensemble des éléments d'une colonne.

SELECT SUM(population) FROM cia;

SELECT SUM(population), SUM(gdp)
FROM cia
WHERE region = 'Europe';

La commande GROUP BY permet de regrouper certaines des lignes et de leur appliquer l'une des fonctions ci-dessus.

SELECT region, SUM(population) FROM cia
GROUP BY region;

SELECT region, COUNT FROM cia
GROUP BY region;

SELECT region, COUNT FROM cia
WHERE population>10000000
GROUP BY region

La commande ORDER BY permet de classer les résultats.

SELECT name, population
FROM cia
WHERE population > 100000000
ORDER BY population DESC;

SELECT region, SUM(population), SUM(area)
FROM cia
GROUP BY region ORDER BY 2 DESC;

Il est possible de regrouper plusieures tables pour faire des recherches.

SELECT * FROM movie, casting, actor
WHERE movie.title="Alien" 
AND casting.movieid=movie.id
AND actor.id=casting.actorid
ORDER BY casting.ord;

SELECT year, SUM(1) FROM movie, actor, casting
WHERE movie.id=casting.movieid 
AND actor.id=casting.actorid
AND actor.name='John Travolta'
GROUP BY year;

Lorsque l'on réunit deux tables à l'aide de la commande

FROM table1, table2 WHERE table1.a = table2.a,

on ne récupère que les éléments de la table1 qui correspondent à un élément de la table2. On peut aussi vouloir récupérer tous les éléments de la table1, même si ils ne correspondent à aucun des éléments de la table 2. C'est une « réunion à gauche ».

FROM table1 LEFT JOIN table2 ON table1.a = table2.b

Il y a de même une réunion à droite.

FROM table1 RIGHT JOIN table2 ON table1.a = table2.b

Il est aussi possible de réunir une table avec elle-même : il faut alors la renommer de deux manières différentes.

SELECT * FROM route R1, route R2
WHERE R1.stop=R2.stop;

Conception de la base de données

Ça peut paraître trivial, mais en fait il faut bien réfléchir à la manière dont on va décomposer l'information en plusieures tables. Nous verrons par la suite que j'ai initialement commis plusieures erreurs.

A DETAILLER

Rappelons au passage qu'il est primordial d'indexer tout ce par rapport à quoi on va effectuer une recherche (pour des questions de rapidité : sinon, il faudrait parcourrir toute la table)

Interface php à une base de données.

La page suivante permet de taper des commandes SQL

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  <meta name="Author" content="Vincent Zoonekynd <zoonek@math.jussieu.fr>">
  <meta name="keywords" content="mySQL">
  <title>Livres</title>
 </head>
 <body
    text="#000000"
    bgcolor="#FFFFFF"
    link="#0000FF"
    alink="#FF0000"
    vlink="#000080"
 >
<p>Enter some SQL commands below</p>
<p><form action="8.php" method=post>
<textarea name="code" cols=50 rows=10">
SELECT * FROM essai
</textarea><br>
<input type="submit" value="Envoyer">
</form></p>
</body>

et de les envoyer à un script php qui les exécute et en affiche le résultat.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  <meta name="Author" content="Vincent Zoonekynd <zoonek@math.jussieu.fr>">
  <meta name="keywords" content="mySQL">
  <title>Livres</title>
 </head>
 <body
    text="#000000"
    bgcolor="#FFFFFF"
    link="#0000FF"
    alink="#FF0000"
    vlink="#000080"
 >

<?php
   // Il y a un gros problème : il y a des antislashes qui
   // apparaissent un peu partout...
   // Quand je tape « "é" », il ressort « \"é\" »...
   $code = ereg_replace( "\\\\'", "'", $code);  // This ain't perl
   $code = ereg_replace( "\\\\\"", "\"", $code);
?>

<p><table border=1><tr><td>
<?php print $code ?>
</td></tr></table></p>

<?php
  $db = mysql_connect("localhost", "root");
  mysql_select_db("test", $db);
  $result = mysql_query($code, $db);

  // Il faut récupérer une éventuelle erreur de mySQL
  if( mysql_errno() ){  
    print "<p>Erreur numéro " . mysql_errno().": ".mysql_error()."</p>";
  } else {

    print "<p>Il y a ". mysql_affected_rows() ." lignes concernées.</p>";
    print "<p>Le résultat comporte ". mysql_num_rows($result) ." lignes\n";
    print "et ". mysql_num_fields($result) ." colonnes.</p>\n";

    print "<TABLE BORDER=1 CELLPADDING=5>\n";

    // Titre des colonnes
    print " <TR>\n";
    $i = 0;
    while ($i < mysql_num_fields ($result)) {
      $meta = mysql_fetch_field ($result);
      print "  <TH>";
      if (!$meta) { print "?"; }
      else { print $meta->name ."<br>(". $meta->type .")"; } 
      print "</TH>\n";
      $i++;
    }
    print " </TR>";
    while( $myrow = mysql_fetch_row($result) ){
      print " <TR>\n";
      $i = 0;
      while ($i < mysql_num_fields ($result)) {
        print "  <TD>". $myrow[$i]."</TD>\n";
        $i++;
      }
      print " </TR>\n";
    }
    print "</TABLE>\n";
  }
  mysql_free_result($result);
  mysql_close($db);
  ?>
</body>

Base de donnée bibliographique

Créons par exemple une base de données qui contient la référence des livres que j'ai lu récemment, avec quelques commentaires, ainsi qu'une page Web pour visualiser/modifier cette base de données.

Pour chaque livre on aura

son titre
le nom de son auteur
le prénon de son auteur
le genre (roman/nouvelles/policier/SF/essai)
un bref résumé ou une courte critique
une note (non/bof/oui/super)

(Note rajoutée par la suite : la base est mal conçue, car un même nom d'auteur sera répété plusieures fois : il est préférable de faire une table avec les auteurs et une autre avec leurs livres, en utilisant des numéros pour passer de l'une à l'autre).

Nous créons la table (dans la base de données « test ») directement au shell.

mysql test

DROP TABLE IF EXISTS livres;

CREATE TABLE livres (
  id INT NOT NULL AUTO_INCREMENT, 
  title VARCHAR(255),
  author_last_name VARCHAR(255),
  author_first_name VARCHAR(255),
  genre ENUM('roman', 'nouvelles', 'policier', 'science fiction', 'essai'),
  abstract TEXT,
  mark ENUM('non', 'bof', 'oui', 'super'),
  PRIMARY KEY (id)
);

INSERT INTO livres 
  (title, author_last_name, author_first_name, genre, abstract, mark) 
VALUES 
  ("Effroyables jardins", "QUINT", "Michel", "roman", 
   "Un très court livre, écrit dans un patois universel et
    littéraire, montrant l'inanité de la guerre, la contingence des hostilités
    et l'humanité de certains des belligérants --- belligérants malgrès eux.", 
   "oui");

INSERT INTO livres
  (title, author_last_name, author_first_name, genre, abstract, mark) 
VALUES 
  ("Hygiène de l'assassin", "NOTHOMB", "Amélie", "roman", 
   "L'idée qui sous-tend le livre est très bonne, mais elle est
    traitée avec trop de manichéisme et de vulgarité. Vraiment
    décevant.",  
   "non");

Je remarque avec surprise et effroi que les accents disparaissent. Heureusement, c'est juste l'interface shell de mysql...

On peut vérifier, toujours au shell, l'état de notre table.

SELECT * FROM livres;

La page suivante permet de la visualiser.

<?php 
  // On se connecte à mysql
  $db = mysql_connect("localhost", "root");
  
  // On choisit la base de donnée qui contient la table qui nous intéresse
  mysql_select_db("test", $db);
  
  // On récupère cette table
  $result = mysql_query("SELECT * FROM livres", $db);
  
  // On l'affiche
  table_start();
  while( $myrow = mysql_fetch_array($result) ){
    table_row( $myrow["title"], 
               $myrow["author_last_name"] . " " . $myrow["author_first_name"], 
               $myrow["genre"],
               $myrow["mark"] );
    table_full_row($myrow["abstract"]);
    table_space();
  }
  table_end();  
  
  // On se déconnecte
  mysql_close($db);
?>

Voici le résultat

<4_livres.php>
<4_livres.html>

On veut maintenant quelque chose de plus complet, avec la possibilité de faire des recherches (sur le nom, le titre, etc.), avec le résultat trié selon l'auteur ou le titre, la possibilité d'ajouter une entrée, la possibilité de modifier une entrée.

<html>
<head>
  <title>Livres</title>
</head>
<body>
  
<?php // COULEURS
  $td = 'td bgcolor="white"';
?>
  
<?php // INITIALISATION DE LA BASE DE DONNÉES
  $init_done = FALSE;
  global $init_done, $db;
  function init_db () {
    global $init_done, $db;
    if( ! $init_done ){
      $init_done = TRUE;
      $db = mysql_connect("localhost", "root");
      mysql_select_db("test", $db);
    }
  }
?>
  
<?php // MODIFICATION D'UNE CHAINE DE CARACTERES LONGUE
  function string_remove_spaces ($a) {
    return preg_replace( '/\\s+/', ' ', $a);
  }
  function string_add_new_lines ($a) {
    $b = string_remove_spaces($a);
    $b = preg_replace( '/(.{1,50})\s+/', "\\1\n", $a);
    return $b;
  }
?>
  
<?php // NETTOYAGE D'UNE CHAINE DE CARACTERES PASSEE EN ARGUMENT
  function clean_string ($a) {
    return $a; // A FAIRE
  }
?>
  
<?php // AFFICHAGE DU FORMULAIRE
  function print_form ($a="", $b="", $t="", $g="", $m="", $r="", $i="") {
    function print_form_line_text ($u, $v, $w) {
      print "<tr>\n";
      print " <td align=\"right\">$u:</td>\n";
      print " <td><input type=\"text\" name=\"$v\" value=\"$w\" size=40></td>\n";
      print "</tr>\n";
    }
    function print_form_line_radio($name, $value, $values){
      reset ($values);
      while (list($key, $v) = each ($values)) {
        print "<input type=\"radio\" name=\"$name\" value=\"$v\"";
        if( $v == $value ){ print " checked"; }
        print ">$v\n";
      }
      print "<br>\n";
    }
    print "<p><form action=\"9.php?type=submit\" method=post>\n";
    print "<table>\n";
    print_form_line_text( "Author's last name", "author_last_name", $a );
    print_form_line_text( "Author's first name", "author_first_name", $b );
    print_form_line_text( "Book title", "title", $t );
    print "</table><br>\n";
    print_form_line_radio("genre", $g, 
                          array("roman", "nouvelles", "policier", 
                                "science fiction", "essai")
                         );
    print_form_line_radio("mark", $m, 
                          array("non", "bof", "oui", "super") );
    print "Abstract:<br>\n";
    print "<textarea name=\"abstract\" cols=50 rows=10>";
    print string_add_new_lines($r);
    print "</textarea><br>\n";
    if($i!=""){print "<input type=\"hidden\" name=\"id\" value=\"$i\">\n";}
    print "<input type=\"submit\" value=\"Valider\">\n</p>\n";
  }
?>
  
<?php // AFFICHAGE D'UNE FICHE DE LECTURE
  function print_entry ($id, $title, $author_last_name,
                        $author_first_name, $genre, $abstract, 
                        $mark) {
  global $td;
  $a = "a href=\"9.php?type=modif&id=$id\" style=\"color:black; text-decoration:none\"";
  if($mark == 'nul'){ $mark="o"; }
  elseif($mark == 'bof'){ $mark='*'; }
  elseif($mark == 'oui'){ $mark='**'; }
  elseif($mark == 'super'){ $mark='***'; }
  else{ $mark ="<table width=1><tr><td></td></tr></table>"; }
    
  print "<p><table width=100%><tr><td bgcolor=black><table width=100% cellspacing=1 cellpadding=5>
    <tr>
     <$td width=10%><$a>$mark</a></td>
     <$td rowspan=2 width=45%><b><$a>$author_last_name</a></b><br><b><$a>$author_first_name</a></b></td>
     <$td rowspan=2 width=45%><b><$a>$title</a></b></td>
    </tr>
    <tr><$td><$a>$genre</a></td></tr>
    <tr><$td colspan=3><$a>$abstract</a></td></tr>
    </table></td></tr></table></p>\n";
  }
?>
  
<?php // AFFICHAGE DU RÉSULTAT D'UNE REQUÉTE SQL
  function print_sql_result ($r) {
    if( mysql_errno() ){  
      print "<p>Error " . mysql_errno().": ".mysql_error()."</p>";
    } else {
      print "<p>". mysql_num_rows ($r) ." réponses</p>\n";
      while( $myrow = mysql_fetch_array($r) ){
        print_entry($myrow["id"], $myrow["title"], $myrow["author_last_name"],
                    $myrow["author_first_name"], $myrow["genre"],
                    $myrow["abstract"], $myrow["mark"]);
      }
/* 
      print "<TABLE BORDER=1 CELLPADDING=5>\n";
      print " <TR>\n";
      $i = 0; while ($i < mysql_num_fields ($r)) {
        $meta = mysql_fetch_field ($r);
        print "  <TH>";
        if (!$meta) { print "?"; }
        else { print $meta->name; } 
        print "</TH>\n";
        $i++;
      }
      print " </TR>";
      while( $myrow = mysql_fetch_row($r) ){
        print " <TR>\n";
        $i = 0;
        while ($i < mysql_num_fields ($r)) {
          if($i==0){
            print "  <TD><A HREF=\"9.php?type=modif&id=$myrow[0]\">". $myrow[$i]."</A></TD>\n";
          } else { print "  <TD>". $myrow[$i]."</TD>\n"; }
          $i++;
        }
        print " </TR>\n";
      }
      print "</TABLE>\n";
*/
    }
  }
?>
  
<?php // QUERY RESULTS
  function print_query () {
    global $db;
    global $author, $name, $genre, $order;
    $q = array();
    if($author) { array_push($q, "author_last_name LIKE \"$author\""); }
    if($title){ array_push ($q, "title LIKE \"$title\""); }
    if($genre){ array_push ($q, "genre in (\"". join('", "', $genre) ."\")"); }
    $query = "SELECT * FROM livres";
    if($q){ $query .= " WHERE ". join(' AND ', $q); }
    $query .= " ORDER BY ";
    if( $order=='title' ){ $query .= "title"; }
    else{ $query .= "author_last_name"; }
    init_db();
    $result = mysql_query($query, $db);
    print_sql_result($result);
    print "<p>SQL Query was:<br> $query</p>\n";
  }
?>
  
<?php // SEARCH: A QUERY IS ABOUT TO BE SUBMITTED
  function print_search () {
    print "<form action=\"9.php?type=query\" method=post>
      <table>
	<tr>
	  <td>Author</td>
	  <td><input type=\"text\" size=50 name=\"author\"></td>
	</tr>
	<tr>
	  <td>Title</td>
	  <td><input type=\"text\" size=50 name=\"title\"></td>
	</tr>
	<tr><td colspan=2>
	    <table width=\"100%\">
              <tr>
		<td>
		  Genre<br>
		  <select multiple name=\"genre[]\">
		    <option selected>roman
		    <option selected>nouvelles
		    <option selected>essai
		    <option selected>policier
		    <option selected>science fiction
		  </select><br>
		</td>
		<td>
		  order by<br>
		  <input type=\"radio\" name=\"order\"
		    value=\"author_last_name\" checked> author<br> 
		  <input type=\"radio\" name=\"order\"
		    value=\"title\"> title<br>
		</td>
		</tr>
	    </table>
	  </td>
	</tr>
        <tr>
          <td><input type=\"submit\" value=\"Search\"></td>
          <td align=\"right\"><a href=\"9.php?type=new\">Add a new entry</a></td>
        </tr> 
      </table>
      </form>\n";
  }
?>
  
<?php // NEW ENTRY
  function print_new () {
    print_form();
  }
?>
  
<?php // A NEW ENTRY HAS BEEN SUBMITTED
  function print_submit () {
    global $author_last_name, $author_first_name, $title, $genre, $abstract, $mark, $id, $db;
  
    // Vérifions qu'il ne manque rien
    $ok = TRUE;
    if(! $abstract){ print "Missing abstract<br>\n"; $ok=FALSE; }
    if(! $title){ print "Missing title<br>\n"; $ok=FALSE; }
    if(! $author_last_name){ print "Missing author?<br>\n"; }
    if(! $mark){ print "Missing mark<br>\n"; }
  
    if($ok){
  
      // Vérifions que les arguments ne contiennt rien de bizarre.
      $author_last_name=clean_string($author_last_name);
      $author_first_name=clean_string($author_first_name);
      $title=clean_string($title);
      $genre=clean_string($genre);
      $abstract=clean_string($abstract);
      $mark=clean_string($mark);
      $id=clean_string($id);
      $db=clean_string($db);
  
      print "You have submitted the following entry :\n<br>\n";
      print "Author : $author_last_name $author_first_name\n<br>\n";
      print "Title : $title\n<br>\n";
      print "Genre : $genre\n<br>\n";
      print "Abstract : \n<br>\n$abstract\n<br>\n";
      init_db();
      if( $id) {
        $query="UPDATE livres SET 
          author_last_name='$author_last_name',
          author_first_name='$author_first_name', 
          title='$title',
          abstract='$abstract',
          mark='$mark',
          genre='$genre'
        WHERE id=$id";
      } else {
        $query="INSERT INTO livres
          (author_last_name, author_first_name,
          title, abstract, mark, genre) VALUES
          ('$author_last_name', '$author_first_name', 
          '$title', '$abstract', '$mark', '$genre')";
      }
      print "<p>Performing query:</p><pre>$query</pre>\n";
      $r = mysql_query($query, $db);
      if( mysql_errno() ){  
        print "<p>Error " . mysql_errno().": ".mysql_error()."</p>";
      } else {
        print "<p>". mysql_affected_rows() ." rows affected</p>\n";
      }
      // On affiche le résultat
      if( $id ){
        $query = "SELECT * FROM livres WHERE id=$id";
      } else {
        $query = "SELECT * FROM livres WHERE title = '$title'";
        }
      print "<p>Performing query: $query</p>\n";
      $r = mysql_query($query, $db);
      print_sql_result($r);
    } else {
      print "<p><b>Do it again.</b></p>\n";
    }
  }
?>
  
<?php
  function print_remove () {
    global $id, $db;
    $query = "DELETE FROM livres WHERE id='$id'";
    print "<p>Performing Query: $query</p>\n";
    init_db();
    $r = mysql_query($query, $db);
    if( mysql_errno() ){  
      print "<p>Error " . mysql_errno().": ".mysql_error()."</p>";
    }
  }
?>
  
<?php // MODIFYING AN EXISTING ENTRY
  function print_modif () {
    global $id, $db;
    init_db();
    $result = mysql_query("SELECT * FROM livres WHERE id=$id", $db);
    if( mysql_errno() ){  
      print "<p>Error " . mysql_errno().": ".mysql_error()."</p>";
    } else {
      $myrow = mysql_fetch_array($result);
      print_form( $myrow["author_last_name"], 
                  $myrow["author_first_name"],
                  $myrow["title"],
                  $myrow["genre"],
                  $myrow["mark"],
                  $myrow["abstract"], 
                  $myrow["id"] );
      print "<p><a href=\"9.php?type=remove&id=$id\">Remove this entry</a></p>\n";
    }
  }
?>
  
<?php // MENU
  function print_menu () {
      
    print '<A HREF="9.php">Menu</A><br>' ."\n";
    print '<A HREF="9.php?type=query">View</A><br>' ."\n";
    print '<A HREF="9.php?type=new">Submit a new entry</A><br>' ."\n";
    print '<A HREF="9.php?type=query">Search</A><br>' ."\n";
    print '<A HREF=""></A><br>' ."\n";
  }
?>
  
<?php // CORPS DU PROGRAMME
  
  // La page affichée dépend de la variable type
//  print "<p>TYPE IS '$type'.</p>\n";
  if(     $type == 'query'  ){ print_query(); }
  elseif( $type == 'search' ){ print_search(); }
  elseif( $type == 'new'    ){ print_new(); }
  elseif( $type == 'submit' ){ print_submit(); }
  elseif( $type == 'modif'  ){ print_modif(); }
  elseif( $type == 'remove'  ){ print_remove(); }
  else                      { print_search(); }
  
?>
  
<?php // Link to the main page
  print "<p><a href=\"9.php\">Main page</a></p>\n";
?>
  
<?php // DEBUG
  print "\n<p></p><hr><p align=\"center\"><strong>Debugging information</strong></p>\n\n";
  print "<p><table>\n";
  reset ($GLOBALS);
  while (list($key, $value) = each ($GLOBALS)) {
     print "<tr><td>$key</td><td>" . $GLOBALS[$key] ."</td></tr>\n";
  }
  print "</table></p>\n";
  
  
  print "<table width='100%'><tr><td bgcolor='white'>\n";
  show_source("9.php");
  print "</td></tr></table>\n";
?>
  
</body>

Voici le résultat

<5_livres_main.html>
<5_livres_query.html>
<5_livres_search_q.html>
<5_livres_modif.html>
<5_livres_after_modif.html>
<5_livres_new.html>
<5_livres_submit_incomplete.html>

Dictionnaire pédagogique Japonais-Anglais

Attaquons-nous à un projet plus ambicieux : une interface au dictionnaire Japonais-Anglais Edict.

Ce qu'il devrait y avoir sur les pages web :

- liste des caractères par ordre de fréquence

- liste des caractères par « niveau »

- liste des caractères dans l'ordre du livre de Heisig

- Liste des caractères par ordre de prononciation

- Liste des caractères par nombre de traits

- Liste des caractères par clef (bushu)

- liste des caracères par radicaux

Pour chaque caractère :

- Dessin

- Traductions

- Liste de kanji (ou de mots) synonymes

- Prononciations

- Liste de kanji homophones

- Liste des « radicaux »

- Liste de caractères avec des radicaux semblables

- exemples de mots

Exercices:

- Donner une liste de mots, avec juste la traduction et la prononciation : l'utilisateur doit trouver les kanjis utilisés pour l'écrire ; il peut s'agir de kanjis ayant la même prononciation, ayant le même sens, ayant le même bushu, ayant les même radicaux.

On part des données suivantes :

- edict (liste de mots, avec pour chacun, sa prononciation et ses traductions)

- kanjidic (liste de caractère, avec pour chacun, son nombre de traits, son bushu, sa fréquence, son numéro de Heisig, son niveau, ses prononciations, ses traductions)

- radkfile (liste des radicaux, avec pour chacun, la liste des kanjis qui le contiennent)

Mon premier essai définissait les tables comme suit :

      kanji: id/kanji/freq/grade/heisig/strokes/bushu
       word: id/k1/k2/k3/k4/k5/k6/word/freq
     pron_k: id/pron
     pron_w: id/pron
      trans: id/trans
 kanji_pron: kanji_id/pron_id
kanji_trans: kanji_id/trans_id
   word_dic: word_id/pron_id/trans_id/p1/p2/p3/p4/p5/p6
        rad: kanji_id/rad_id
     phrase: id/phrase
   kanji_ex: phrase_id/kanji_id
    word_ex: phrase_id/word_id

Mais la répétition « k1/k2/k3/k4/k5/k6 » pour les différents kanjis d'un mot et « p1/p2/p3/p4/p5/p6 » pour leur prononciations respectives est très maladroite : beaucoup de caractères n'ont qu'un ou deux kanjis, et certains (rares) en ont plus de six. Il est plus simple et économique de rajouter une table contenant le numéro d'un mot, la position du kanji, le kanji et sa prononciation.

Un autre problème vient du fait qu'une entrée du dictionnaire peut avoir plusieures prononciations, ie, un mot aura plusieures prononciations et pour chacune de ces prononciations il y aura plusieures traductions. Je me retrouve donc avec une table pour les mots, une table pour les prononciations, une table pour les traductions, une table pour les entrés du dictionnaire (reliant un mot et une traduction) et une table pour les traductions de ces entrées de dictionnaire (chaque ligne reliant une entrée et une traduction)

Une autre problème : il y a des commentaires grammaticaux pour certaines des traductions, qu'il convient de traiter séparément si on veut pouvoir rechercher des synonymes. Comme il peut y avoir plusieurs commentaires pour une même traduction, on pourrait procéder comme dans la remarque précédente (ce qui rajouterait deux tables), mais j'ai choisi de concaténer les commentaires afin de faire comme s'il n'y en avait qu'un seul (je ne prévois pas de faire de recherches sur ces commentaires). [Note rajoutée ultérieurement : j'ai d'ailleurs commis une erreur : le commentaire n'est pas attaché à une traduction mais à une entrée du dictionnaire.]

Finalement, la structure des tables est la suivante :

         kanji: id/kanji/grade/freq/heisig/bushu/strokes
          pron: id/pron/count 
         trans: id/trans/count
       comment: id/comment
     kanji_rad: kanji_id/rad_id
   kanji_trans: kanji_id/trans_id
    kanji_pron: kanji_id/pron_id/type
         words: id/word/freq
           dic: id/word_id/pron_id
     dic_trans: dic_id/trans_id/comment_id
dic_pron_kanji: dic_id/kanji_id/pron_id/position

(Si les phrases d'exemple ont disparu des tables, c'est tout simplement parce que je n'en ai pas :)

Réalisation : accès distant à mysql, « serveur »

Maintenant, il faut remplir notre base de données, à diatance. Les pages se trouveront chez mon fournisseur d'accès internet (free.fr) : je commence par lui demander de créer l'adresse zoonek.free.fr (ici, « zoonek », c'est juste mon login chez free.fr), puis de me créer une base de données.

http://support.free.fr/web/pagesperso.html

Le remplissage du site se fera par ftp

ncftp -u zoonek ftpperso.free.fr

(on peut demander à ncftp de se souvenir du mot de passe, pour ne pas avoir à le retaper à chaque fois), mais celui de la base de données

sql.free.fr 
login: zoonek 
password:********

va s'avérer un peu plus problèmatique.

Commençons par écrire un petit programme qui nous permette de dialoguer directement avec mysql (uniquement à partir de certaines machines, pour d'évidentes raisons de sécurité)

<html><body>
<?php
  if( ! ( $HTTP_X_FORWARDED_FOR == "134.157.13.112" // borel3
    or $HTTP_X_FORWARDED_FOR == "134.157.13.111" // borel2
    or $HTTP_X_FORWARDED_FOR == "134.157.13.110" // borel1
    or $REMOTE_ADDR == "134.157.13.110"
    or $REMOTE_ADDR == "134.157.13.111"
    or $REMOTE_ADDR == "134.157.13.112"
  )){ 
    print "Forbidden";
    phpinfo();
    exit(1); 
  }
  
  // Fonction pour afficher une éventuelle erreur après une requète SQL
  function my_sql_error(){
    if( mysql_errno() ){
      print "<p>  ERROR:\n            <b>". mysql_error() ."\n</b></p>\n";
    } else {
      print "<p><b>  OK  </b></p>\n";
    }
  }
  
  // Fonction pour afficher un résultat de requète (ou une erreur)
  function my_sql_print($result){
    if( mysql_errno() ){
      print "<p>  ERROR:\n             <b>". mysql_error() ."</b></p>\n";
    } else {
      print "<p><b>  OK  </b></p>\n";
      print "<p>Affected rows(s): ". mysql_affected_rows() .".</p>";
      print "<p>Answer has ". mysql_num_rows($result) ." rows\n";
      print "and ". mysql_num_fields($result) ." columns.</p>\n";
      print "<TABLE BORDER=1 CELLPADDING=5>\n";
      // Titre des colonnes
      print " <TR>\n";
      $i = 0;
      while ($i < mysql_num_fields ($result)) {
        $meta = mysql_fetch_field ($result);
        print "  <TH>";
        if (!$meta) { print "?"; }
        else { print $meta->name ."<br>(". $meta->type .")"; } 
        print "</TH>\n";
        $i++;
      }
      print " </TR>";
      while( $myrow = mysql_fetch_row($result) ){
        print " <TR>\n";
        $i = 0;
        while ($i < mysql_num_fields ($result)) {
          print "  <TD>". $myrow[$i]."&nbsp;</TD>\n";
          $i++;
        }
        print " </TR>\n";
      }
    print "</TABLE>\n";
    }
  }
  
  // Fonction pour effectuer une requète SQL, 
  // avec affichage de l'éventuelle erreur.
  function my_sql_ask($q) {
    global $db;
    print "<p>Query:<pre>$q</pre></p>\n";
    $r = mysql_query($q, $db);
    my_sql_print($r);
  }
  
  print "<p>Connecting to sql.free.fr</p>\n";
  $db = mysql_connect("sql.free.fr", "zoonek", "********");
  my_sql_error();
  
  print "<p>Opening database</p>\n";
  mysql_select_db("zoonek", $db);
  my_sql_error();
  
  if($query){ 
    // Il faut enlever les \ ****************
    print "<p>Unescaped query:<br>\n<pre>$query</pre></p>\n";
    $query = preg_replace( '/\\\\\\\'/', '\'', $query);
    my_sql_ask($query); 
  }
  
  print "<hr><form action=\"mysql.php3\" method=post>
  mysql code:<br>
  <textarea name=\"query\" cols=50 rows=5>
  </textarea>
  <br>
  <input type=\"submit\" value=\"Exécuter\">
</form>";
    
?>
</body></html>

Je constate que j'ai tous les droits sur ma base de données :

SHOW GRANTS FOR zoonek@localhost

GRANT FILE ON *.* TO 'zoonek'@'localhost' 
  IDENTIFIED BY PASSWORD '22e6167c032c1245'
GRANT ALL PRIVILEGES ON zoonek.* TO 'zoonek'@'localhost'

Je peux donc m'autoriser à me connecter depuis ici :

GRANT ALL PRIVILEGES ON zoonek.* 
  TO 'zoonek@%.math.jussieu.fr'
  IDENTIFIED BY PASSWORD '22e6167c032c1245'

Eh non !

Access denied for user: 'zoonek@php2-1.free.fr' to database 'zoonek'

Il aurait fallu pour cela que la commande

SHOW GRANTS FOR zoonek@localhost

me réponde

GRANT ALL PRIVILEGES ON zoonek.* TO 'zoonek'@'localhost' WITH GRANT OPTION

Accès distant à mysql, « client »

Voici un script en Perl, qui lit des instructions SQL dans un fichier et les donne au script php précédent.

#! perl -w
use strict;
use HTTP::Request::Common;
use LWP::UserAgent;
  
my $file = shift @ARGV || die "I need an argument";
print `date`;
  
open(SQL, $file);
my $query = "";
while(<SQL>){
  next if m/^\#/;
  $query .= $_;
  next unless m/\;\s*$/gsm;
  $query =~ s/\;\s*$//gsm;
  $query =~ s/^\s*//gsm;
  $query =~ s/\s*$//gsm;
  
  print "*** QUERY:\n $query\n";
  my $ua = LWP::UserAgent->new;
  my $answer = $ua->request(POST 'http://zoonek.free.fr/mysql.php3',
			    [ query => $query ],
			   )->as_string;;
  chomp($answer);
  if($answer =~ m/error/ism){
    print "Answer: $answer\n\n";
    print `date`;
    die;
  } else {
    print "OK\n";
  }
  
  $query = "";
}
  
unless( $query =~ m/^\s*$/sm ){
  print STDERR "Remaining characters: $query\n";
}
  
print `date`;

Comme il y a à peu près 379133 requètes SQL, à raison d'une par seconde, cela devrait durer quelques 105 heures...

On peut penser à les regrouper, mais la limite de temps d'un script php est réglée sur 5 secondes...

phpMyAdmin

Je viens de découvrir qu'il y a beaucoup plus simple et que c'est déjà prévu ! Il suffit d'envoyer (par HTTP/POST) un fichier texte contenant les instructions à exécuter. C'est relativement rapide.

http://phpmyadmin.free.fr/phpMyAdmin/

Le fichier HTML obtenu contient

<form method="post" action="db_readdump.php" enctype="multipart/form-data">
<input type="hidden" name="server" value="sql-z.free.fr">
<input type="hidden" name="db" value="zoonek">
<input type="hidden" name="goto" value="db_details.php">
<input type="hidden" name="zero_rows" value="Votre requête SQL a été exécutée avec succès">
Exécuter une ou des requêtes sur la base zoonek [<a href="http://www.tcx.se/Manual_chapter/manual_Syntax.html#Select">Documentation</a>]:<br>
<textarea name="sql_query" cols="40" rows="3" wrap="VIRTUAL" style="width: 300px"></textarea><br>
<i>ou</i> Emplacement du fichier texte:<br>
<input type="file" name="sql_file"><br>
<input type="submit" name="SQL" value="Exécuter">
</form>

On devrait donc pouvoir envoyer un fichier comme suit :

#! perl -w
use strict;
use HTTP::Request::Common;
use LWP::UserAgent;
  
my $file = shift @ARGV || die "I need an argument";
print `date`;
  
my $ua = LWP::UserAgent->new;
my $answer = $ua->request(
  POST 'http://sql.free.fr/phpMyAdmin/db_readdump.php',
       Content_Type => 'form-data',
       Content      => [ server => "sql-z.free.fr",
                         db     => "zoonek",
                         goto   => "db_details.php",
                         zero_rows => "Votre requête SQL a été exécutée avec succès",     
                         sql_file => [$file],
                       ],
  )->as_string;;
chomp($answer);
print "Answer: $answer\n\n";
print `date`;

Mais il manque bien sûr le login et le mot de passe. On peut procéder ainsi.

#! perl -w
use strict;
use HTTP::Request::Common;
use LWP::UserAgent;
  
my $file = shift @ARGV || die "I need an argument";
print `date`;
  
sub LWP::UserAgent::get_basic_credentials { return 'zoonek', '********' }
my $ua = LWP::UserAgent->new;
my $answer = $ua->request(
  POST 'http://sql.free.fr/phpMyAdmin/db_readdump.php',
       Content_Type => 'form-data',
       Content      => [ server => "sql-z.free.fr",
                         db     => "zoonek",
                         goto   => "db_details.php",
                         zero_rows => "Votre requête SQL a été exécutée avec succès",
                         sql_file => [$file],
                       ],
  )->as_string;;
chomp($answer);
print "Answer: $answer\n\n";
print `date`;

Mais ce n'est pas très propre : Perl râle car on redéfinit une fonction qui existe déjà. Pour faire les choses proprement, on peut définir une sous-classe de LWP::UserAgent dans laquelle on redéfinira la fonction get_basic_credentials.

#! perl -w
use strict;
use HTTP::Request::Common;
use LWP::UserAgent;
  
BEGIN {
  package RequestAgent;
  use LWP::UserAgent;
  our @ISA = qw(LWP::UserAgent);
  sub get_basic_credentials { return 'zoonek', '********' }
}
  
my $file = shift @ARGV || die "I need an argument";
print `date`;
  
my $ua = RequestAgent->new;
my $answer = $ua->request(
  POST 'http://sql.free.fr/phpMyAdmin/db_readdump.php',
       Content_Type => 'form-data',
       Content      => [ server => "sql-z.free.fr",
                         db     => "zoonek",
                         goto   => "db_details.php",
                         zero_rows => "Votre requête SQL a été exécutée avec succès",
                         sql_file => [$file],
                       ],
  )->as_string;;
chomp($answer);
print "Answer: $answer\n\n";
print `date`;

Je peux donc maintenant couper mon gros fichier contenant toutes les commandes SQL destinées à créer les tables et à peupler la base. Par exemple en morceaux de 1000 lignes.

#! perl -w
  
use strict;
use constant MAX => 1000;
  
my $n=0;
my $file=0;
  
open(A, '>', "MYSQL_part.$file");
while(<>){
  $n++;
  print A $_;
  if( $n>MAX ){
    $n=1;
    close A;
    $file++;
    print STDERR "$file\n";
    open(A, '>', "MYSQL_part.$file");
  }
}
close A;

Maintenant, il suffit d'une boucle

perl 1.pl >MYSQL 2>/dev/null
perl -n -i -e 'print unless m/^\#/' MYSQL
perl decoupe.pl MYSQL
for i in MYSQL_part.*
do
  date
  perl post_phpmyadmin.pl $i | head -100 > $i.RESULT
  if grep succ.s $i.RESULT
  then
    echo $i done          
  else
    echo PROBLEME $i
  fi
done

Estimation du temps : 1000 fichiers, à raison de 20 secondes chacun, cela fait à peu près 5 heures (finalement : 3h45).

Il y a un problème : ça plante si il y a des \' dans les chaines de caractères...

En attendant, on peut récupérer toutes les lignes qui posent problème

perl -n -e "print if m/\\\\'/" MYSQL > MYSQL_PROBLEME
perl -n -e "print unless m/\\\\'/" MYSQL > MYSQL_OK

Il suffit en fait de mettre deux fois plus d'antislashes (ça doit être naturel, pour les utilisateurs de php...). Comme il y en avait déjà un, je me contente d'en rajouter un autre.

perl -p -i -e 's/\\/\\\\/g' MYSQL_PROBLEME
perl post_phpmyadmin.pl MYSQL_PROBLEME

Il y a quand même certains problèmes qui restent, que je ne comprends pas, et qui disparaissent quand on met une seule requète par fichier (un peu moins de 1500 fichiers, donc).

perl -n -e 'BEGIN{$i=1} open(A,">MYSQL_PROBLEME_$i"); print A $_; close A; $i++ ' MYSQL_PROBLEME
for i in MYSQL_PROBLEME_*[0-9]
do
  perl ../post_phpmyadmin.pl $i > $i.RESULT
  if grep succ.s $i.RESULT
  then
    echo $i done          
  else
    echo PROBLEME $i
  fi
done

On vérifie ensuite qu'il n'y a pas eu de problèmes (généralement parce que le serveur ne marche pas --- il semble planté une heure toutes les deux heures) :

for i in MYSQL_PROBLEME_*[0-9]
do
  if grep -i succ.s $i.RESULT >/dev/null
  then
    echo -n
  else
    echo PROBLEME $i
  fi
done

Création de la base de données

Il s'agit maintenant d'écrire un petit programme qui écrive toutes les requètes SQL pour remplir la base de données. Il s'agit essentiellement de reprendre les informations situées dans les fichiers edict, kanjidic et radkfile et de les mettre sous une autre forme, mais certains détails ne sont pas immédiats.

Tout d'abord, je veux pouvoir trouver la correspondance entre prononciation d'un mot et prononciation des kanjis qui le composent --- il faut pour cela mélanger les informations contenues dans edict et kanjidic.

D'autre part, pour chaque kanji, je veux pouvoir donner les mots les plus courrants le contenant, or rien n'indique la fréquence des mots --- il va falloir la trouver tout seul. Pour cela, j'aurai besoin de deux fichiers supplémentaires :

vconj :      Liste de règles pour déconjuguer les verbes et adjectifs

corpus.euc : Plein de textes en japonais, ramassés un peu 
             partout sur le Web.

Pour créer ce fichier corpus.euc à partir de fichiers aux codages variés, j'ai utilisé le script suivant, qui permet de convertir du japonais en un codage à peu près quelconque en EUC.

#! /share/nfs/users1/umr-tge/zoonek/gnu/Linux/bin/perl -w
  
#############################################################################
##
## jp2utf8
##
## Convertit un fichier contenant du texte en UTF8, Latin1 ou ISO-2022-2
## (le codage n'est pas toujours le meme, mais il ne change pas en plein
## milieu d'une ligne) en un fichier EUC.
##
## (c) 2001 Vincent Zoonekynd <zoonek@math.jussieu.fr>
##
#############################################################################
  
# Je n'utilise pas Convert::Recode à cause du message d'erreur suivant
#   recoding latin1 to u8 too complex, use recode directly
  
use strict;
use IPC::Open2;
  
sub guess {
  my $string = shift;
  
  # ASCII
  return "ISO-2022-JP-2" if $string =~ m/^[A-Za-z0-9<>%\/\".\s]*$/;
  
  my($utf, $iso, $euc, $sjis);
  $utf  = not check($string, "UTF-8",  "ISO-2022-JP-2");
  $euc  = not check($string, "EUC-JP", "ISO-2022-JP-2");
  $sjis = not check($string, "SJIS",   "ISO-2022-JP-2");
  $iso  = not check($string, "ISO-2022-JP-2", "UTF-8");
  
  return "UTF-8" if $utf and not $euc and not $sjis and not $iso;
  return "EUC-JP" if $euc and not $utf and not $sjis and not $iso;
  return "SJIS" if $sjis and not $euc and not $utf and not $iso;
  return "ISO-2022-JP-2" if $iso and not $euc and not $sjis and not $utf;
  
  return "ISO-2022-JP-2" if $iso and $string =~ m/\e(\$|\()B/;
  
  print STDERR "Line: $string";
  print STDERR "(Ambiguous) ";
  return "EUC-JP" if $euc and $string =~ m/¤/;
  return "UTF-8" if $utf and $string =~ m/Ã[\x80-\x9F]/;
  
  print STDERR "(unable to guess";
  print STDERR " not UTF-8" unless $utf;
  print STDERR " not EUC-JP" unless $euc;
  print STDERR " not SJIS" unless $sjis;
  print STDERR " not ISO-2022-JP-2" unless $iso;
  print STDERR ") ";
  return "ISO-2022-JP-2" if $iso;
  
  print STDERR "WARNING ";
  return "EUC-JP" if $euc;
  return "SJIS" if $sjis;
  return "UTF-8" if $utf;
  
  print STDERR "UNKNOWN ";
  return "UTF-8";
}
  
sub check {
  my ($s, $a, $b) = @_;
  my $D;
  my $p = open($D, "|recode $a..$b >/dev/null 2>&1");
  print $D $s;
  close $D;
  my $z = $?;
  waitpid $p, 0;
  return $z;
}
  
while(<>){
  
  my $codage = guess( $_ );
  print STDERR "$codage\n";
  
  # On ouvre quelques processus...
  my ($B,$C);
  my $pid1 = open2( $C, $B, 'recode', "$codage..EUC-JP" );
  
  # Conversion en EUC
  print $B $_;
  close $B;
  
  # On lit le résultat
  while(<$C>){ 
    #print STDERR "Line: $_";
    print "\\bigskip\n" if m/^From/;
    s/^([A-Z][-A-Za-z]*:)/\\textbf\{$1\}/;
    print $_;
  }
  close $C;
  
  # Ne pas oublier les lignes suivantes : sinon, la table 
  # de processus se remplit et ça finit par planter (un message
  # du genre « Cannot fork: not enough memory ».
  waitpid $pid1, 0;
}
  
exit 0;

Je commence par créer un fichier contenant la fréquence des mots

#! perl -w
use strict;
  
## Créer un fichier de fréquence de mots
  
use constant TRUE  => (0==0);
use constant FALSE => (0==1);
  
our $edict = "/share/nfs/users1/umr-tge/zoonek/gnu/Linux/lib/Nihon/edict";
our $vconj = "/share/nfs/users1/umr-tge/zoonek/gnu/Linux/lib/Nihon/vconj";
our $corpus = $ARGV[0] || "corpus.euc";
our %edict;
  
######################################################################
  
## Lecture de vconj
print STDERR "Reading vconj\n";
my @vconj = ();
open(VCONJ, $vconj) || die "Cannot open $vconj for reading: $!";
while(<VCONJ>){
  next if m/^\#/;
  next if m/^[0-9]/;
  next if m/^\$/;
  chomp;
  m/^([^\s]+)\s+([^\s]+)/;
  push @vconj, [$1, $2];
#  print STDERR "  RULE \"$1\" --> \"$2\"\n";
}
close VCONJ;
  
######################################################################
  
sub vconj {
  my $w = shift;
#  print STDERR "Trying to unconjugate $w\n";
  my %result = ();
  $result{$w} = TRUE;
  foreach my $t (@vconj){
    my ($a, $b) = @$t;
    foreach my $v (keys %result) {
      if( $v =~ s/$a$/$b/ ){
	print STDERR "  Rule $a --> $b yielded $v from $w\n";
	$result{$v} = TRUE
	  unless $result{$v};
      }
    }
  }
  return (keys %result);
}
  
######################################################################
  
## Lecture de edict
print STDERR "Reading edict\n";
open(EDICT, "$edict") || die "Cannot open $edict for reading: $!";
while(<EDICT>){
  next if m/^\#/;
  chomp;
  my ($word, $pron, $engl);
  if( m§^([^\s]+)\s+(/.*)§ ){
    $word = $pron = $1;
    $engl = $2; 
  } elsif( m§^([^\s]+)\s+([^\s]+)\s+(/.*)§ ){
    $word = $1;
    $pron = $2;
    $engl = $3;
  } else {
    print STDERR "  Strange entry: $_\n";
  }
  $pron =~ s/^\[//; $pron =~ s/\]$//;
  $edict{$word} = {COUNT => 0, ENTRY => [] } unless exists $edict{$word};
  push @{ $edict{$word}->{ENTRY} },
    { PRON => $pron, ENGL => $engl};
}
close EDICT;
  
######################################################################
  
## Lecture du corpus
print STDERR "Reading $corpus\n";
open(CORPUS, $corpus) || die "Cannot open $corpus for reading: $!";
while(my $line = <CORPUS>){
  chomp($line);
#  print STDERR "  Processing $line\n";
  while( not $line =~ m/^$/ ){
    chomp($line);
    # On enlève les caractères ascii au début de la chaine
    $line =~ s/^[\s\x00-\x79]*//;
    # On regarde s'il y a un mot au début.
    my $w = $line;
    $w =~ s/^(........).*/$1/; # Les quatre premiers caractères
    print STDERR "Looking for $w\n";
    my $nothing = TRUE;
    while(not( $w =~ m/^$/ )){
      foreach my $v (vconj($w)){
	if( exists $edict{$v} ){
	  $nothing = FALSE;
	  $edict{$v}->{COUNT} ++;
	  print STDERR "Found $v\n";
	}
      }
      last unless $nothing;
      $w =~ s/.$//;
    }
    if( $nothing ) {
      $line =~ s/^.{1,2}// ;
    } else {
      $line =~ s/^$w//;
    }
  }
}
close CORPUS;
  
######################################################################
  
## Affichage des résultats
foreach my $w (keys %edict){
  print "$edict{$w}->{COUNT} $w\n"
    if $edict{$w}->{COUNT} >=1;
}

À l'aide de ce fichier et de kanjidic/radkfile/edict, je peux maintenant créer la base de données.

#! perl -w
use strict;
use POSIX qw(floor);
  
## Fichiers
my $edict = "/share/nfs/users1/umr-tge/zoonek/gnu/Linux/lib/Nihon/edict";
my $kanjidic = "/share/nfs/users1/umr-tge/zoonek/gnu/Linux/lib/Nihon/kanjidic";
my $radkfile = "/share/nfs/users1/umr-tge/zoonek/gnu/Linux/lib/Nihon/radkfile";
my $corpus = $ARGV[1] || "corpus.euc";
my $vconj = "/share/nfs/users1/umr-tge/zoonek/gnu/Linux/lib/Nihon/vconj";
my $wordsfreq = "WORDS_FREQ";
  
# Variables globales
our $max_voc = 3000;
our $kanji_n = 0;
our $trans_id = 0;
our $pron_id = 0;
our $word_id = 0;
our $comment_id = 0;
our $pron_length = 0;
our $trans_length = 0;
our $comment_length = 0;
our $word_length = 0;
our $dic_fail = 0;
our $dic_number = 0;
our %kanji = ();
our %kanji_pron = ();
our %trans = ();
our %trans_count = ();
our %pron = ();
our %pron_count = ();
our %word = ();
our %comment = ();
  
# Constantes
use constant TRUE  => (0==0);
use constant FALSE => (0==1);
  
##############################################################
  
## Fonctions diverses
sub max ($$) {
  my($a, $b) = @_;
  return ($a>$b) ? $a : $b;
}
sub kata2hira {
  my $sauvegarde = $_;
  $_ = shift;
s/¥¢/¤¢/g;
s/¥¤/¤¤/g;
s/¥¦/¤¦/g;
s/¥¨/¤¨/g;
s/¥ª/¤ª/g;
s/¥«/¤«/g;
s/¥­/¤­/g;
s/¥¯/¤¯/g;
s/¥±/¤±/g;
s/¥³/¤³/g;
s/¥¬/¤¬/g;
s/¥®/¤­/g;
s/¥°/¤°/g;
s/¥²/¤²/g;
s/¥´/¤´/g;
s/¥µ/¤µ/g;
s/¥·/¤·/g;
s/¥¹/¤¹/g;
s/¥»/¤»/g;
s/¥½/¤½/g;
s/¥¶/¤¶/g;
s/¥¸/¤¸/g;
s/¥º/¤º/g;
s/¥¼/¤¼/g;
s/¥¾/¤¾/g;
s/¥¿/¤¿/g;
s/¥Á/¤Á/g;
s/¥Ä/¤Ä/g;
s/¥Ã/¤Ã/g;
s/¥Æ/¤Æ/g;
s/¥È/¤È/g;
s/¥À/¤À/g;
s/¥Â/¤Â/g;
s/¥Å/¤Å/g;
s/¥Ç/¤Ç/g;
s/¥É/¤É/g;
s/¥Ê/¤Ê/g;
s/¥Ë/¤Ë/g;
s/¥Ì/¤Ì/g;
s/¥Í/¤Í/g;
s/¥Î/¤Î/g;
s/¥Ï/¤Ï/g;
s/¥Ò/¤Ò/g;
s/¥Õ/¤Õ/g;
s/¥Ø/¤Ø/g;
s/¥Û/¤Û/g;
s/¥Ñ/¤Ñ/g;
s/¥Ô/¤Ô/g;
s/¥×/¤×/g;
s/¥Ú/¤Ú/g;
s/¥Ý/¤Ý/g;
s/¥Ð/¤Ð/g;
s/¥Ó/¤Ó/g;
s/¥Ö/¤Ö/g;
s/¥Ù/¤Ù/g;
s/¥Ü/¤Ü/g;
s/¥Þ/¤Þ/g;
s/¥ß/¤ß/g;
s/¥à/¤à/g;
s/¥á/¤á/g;
s/¥â/¤â/g;
s/¥ä/¤ä/g;
s/¥æ/¤æ/g;
s/¥è/¤è/g;
s/¥é/¤é/g;
s/¥ê/¤ê/g;
s/¥ë/¤ë/g;
s/¥ì/¤ì/g;
s/¥í/¤í/g;
s/¥ï/¤ï/g;
s/¥ò/¤ò/g;
s/¥ó/¤ó/g;
  
s/¥ç/¤ç/g;
s/¥å/¤å/g;
s/¥ã/¤ã/g;
  
s/¥Ã/¤Ã/g;
s/¥¡/¤¡/g;
s/¥£/¤£/g;
s/¥¥/¤¥/g;
s/¥§/¤§/g;
s/¥©/¤©/g;
  
  my $resultat = $_;
  $_ = $sauvegarde;
  return $resultat;
}
  
sub kana_ka_to_ga {
  $_ = shift;
s/^¤«/¤¬/;
s/^¤­/¤®/;
s/^¤¯/¤°/;
s/^¤±/¤²/;
s/^¤³/¤´/;
  return $_;
}
sub kana_ha_to_ba {
  $_ = shift;
s/^¤Ï/¤Ð/;
s/^¤Ò/¤Ó/;
s/^¤Õ/¤Ö/;
s/^¤Ø/¤Ù/;
s/^¤Û/¤Û/;
  return $_;
}
sub kana_ha_to_pa {
  $_ = shift;
s/^¤Ï/¤Ñ/;
s/^¤Ò/¤Ô/;
s/^¤Õ/¤×/;
s/^¤Ø/¤Ú/;
s/^¤Û/¤Ý/;
  return $_;
}
sub kana_ta_to_da {
  $_ = shift;
s/^¤¿/¤À/;
s/^¤Á/¤Â/;
s/^¤Ä/¤Å/;
s/^¤Æ/¤Ç/;
s/^¤È/¤É/;
  return $_;
}
sub kana_sa_to_za {
  $_ = shift;
s/^¤µ/¤¶/;
s/^¤·/¤¸/;
s/^¤¹/¤º/;
s/^¤»/¤¼/;
s/^¤½/¤¾/;
  return $_;
}
sub kana_tsu_to_little_tsu {
  $_ = shift;
  s/¤Ä$/¤Ã/;
  return $_;
}
sub kana_ku_to_little_tsu {
  $_ = shift;
  s/¤¯$/¤Ã/;
  return $_;
}
sub remove_doubles {
  my %a = ();
  foreach my $b (@_) { $a{$b}=1; }
  return (keys %a);
}
sub get_euc_code {
  my $a = shift;
  my $b = $a;
  $b =~ s/^(.).*/ord($1)/e;
  my $c = $a;
  $c =~ s/^.(.).*/ord($1)/e;
  return 256*$b+$c;
}
  
sub get_pron_id {
  my $p = shift;
  $p = kata2hira($p);
  my $id;
  if( exists $pron{$p} ){
    $id = $pron{$p};
    $pron_count{$p} = ($pron_count{$p}>254) ? 255 : $pron_count{$p}+1;
    print "UPDATE pron SET count=$pron_count{$p} WHERE id=$id;\n";
  } else {
    $id = ++$pron_id;
    $pron{$p} = $id;
    $pron_count{$p} = 1;
    $pron_length = max( $pron_length, length($p) );
    print "INSERT INTO pron (id, pron, count) VALUES ($id, '$p', 1);\n";
  }
  return $id;
}
  
sub get_trans_id {
  my $e = shift;
  my $id;
  if( exists $trans{$e} ){
    $id = $trans{$e};
    $trans_count{$e} = ($trans_count{$e}>254) ? 255 : $trans_count{$e}+1;
    print "UPDATE trans SET count=$trans_count{$e} WHERE id=$id;\n";
  } else {
    $id = ++$trans_id;
    $trans{$e} = $id;
    $trans_count{$e} = 1;
    $trans_length = max( $trans_length, length($e) );
    # Si jamais il y a des apostrophes...
    $e =~ s/\'/\\\'/g;
    print "INSERT INTO trans (id, trans, count)  VALUES ($id, '$e', 1);\n";
  }
  return $id;
}
  
our @comments_list = ("abbr", "adj", "adv", "an", "a-no", "arch",
"aux", "aux v", "col", "fam", "fem", "gikun", "gram", "hon", "hum",
"I", "IV", "id", "iK", "ik", "io", "MA", "male", "m-sl", "neg", "neg
v", "obs", "obsc", "oK", "ok", "pol", "pref", "qv", "sl", "suf", "uK",
"uk", "vi", "vs", "vt", "vulg", "X", );
our $comments_list = join('|',@comments_list);
  
sub strip_comment {
  my $a = shift;
  $a =~ s/(\(($comments_list)\))//g;
  $a =~ s/^\s*//;
  return $a;
}
sub get_comment {
  my $a = shift;
  my $c = "";
  while( $a =~ m/(\(($comments_list)\))/g ){ $c .= $1; }
  $c =~ s/\)\(/\) \(/g;
  return $c;
}
sub get_comment_id {
  my $c = shift;
  my $id;
  if( exists $comment{$c} ){
    $id = $comment{$c};
  } else {
    $id = ++$comment_id;
    $comment{$c} = $id;
    $comment_length = max( $comment_length, length($c) );
    print "INSERT INTO comment (id, comment) VALUES ($id, '$c');\n";
  }
  return $id;
}
  
##############################################################
  
## Création des tables
  
print <<EOF;
DROP TABLE IF EXISTS kanji;
CREATE TABLE kanji (
  id      SMALLINT UNSIGNED NOT NULL,
  kanji   CHAR(2),
  grade   TINYINT UNSIGNED,
  freq    SMALLINT UNSIGNED,
  heisig  SMALLINT UNSIGNED,
  bushu   SMALLINT UNSIGNED,
  strokes TINYINT UNSIGNED,
  PRIMARY KEY (id),
  INDEX (grade),
  INDEX (freq),
  INDEX (heisig),
  INDEX (bushu),
  INDEX (strokes) );
  
DROP TABLE IF EXISTS pron;
CREATE  TABLE pron (
  id    SMALLINT UNSIGNED NOT NULL,
  pron  VARCHAR(50),
  count TINYINT UNSIGNED,
  PRIMARY KEY (id) );
  
DROP TABLE IF EXISTS trans;
CREATE TABLE trans (
  id         MEDIUMINT UNSIGNED NOT NULL,
  trans      VARCHAR(100),
  count      TINYINT UNSIGNED,
  PRIMARY KEY (id) );
  
DROP TABLE IF EXISTS comment;
CREATE TABLE comment (
  id      TINYINT UNSIGNED NOT NULL,
  comment VARCHAR(21),
  PRIMARY KEY (id) );
  
DROP TABLE IF EXISTS kanji_rad;
CREATE TABLE kanji_rad (
  kanji_id SMALLINT UNSIGNED,
  rad_id   SMALLINT UNSIGNED,
  INDEX (kanji_id),
  INDEX (rad_id) );
  
DROP TABLE IF EXISTS kanji_trans;
CREATE TABLE kanji_trans (
  kanji_id SMALLINT UNSIGNED,
  trans_id MEDIUMINT UNSIGNED,
  INDEX (kanji_id),
  INDEX (trans_id) );
  
DROP TABLE IF EXISTS kanji_pron;
CREATE TABLE kanji_pron (
  kanji_id SMALLINT UNSIGNED,
  pron_id  SMALLINT UNSIGNED,
  type     ENUM('ON', 'kun', 'T1', 'T2'),
  INDEX (kanji_id),
  INDEX (pron_id) );
  
DROP TABLE IF EXISTS words;
CREATE TABLE words (
  id   MEDIUMINT UNSIGNED NOT NULL,
  word VARCHAR(50),
  freq MEDIUMINT UNSIGNED,
  PRIMARY KEY (id) );
  
DROP TABLE IF EXISTS dic;
CREATE TABLE dic (
  id  MEDIUMINT UNSIGNED NOT NULL,
  word_id MEDIUMINT  UNSIGNED,
  pron_id SMALLINT  UNSIGNED,
  PRIMARY KEY (id),
  INDEX (word_id),
  INDEX (pron_id) );
  
DROP TABLE IF EXISTS dic_trans;
CREATE TABLE dic_trans (
  dic_id   MEDIUMINT UNSIGNED,
  trans_id MEDIUMINT UNSIGNED,
  comment_id TINYINT UNSIGNED,
  INDEX (dic_id),
  INDEX (trans_id) );
  
DROP TABLE IF EXISTS dic_pron_kanji;
CREATE TABLE dic_pron_kanji (
  dic_id   MEDIUMINT UNSIGNED,
  kanji_id SMALLINT  UNSIGNED,
  pron_id  MEDIUMINT UNSIGNED,
  position TINYINT   UNSIGNED,
  INDEX (dic_id),
  INDEX (kanji_id),
  INDEX (pron_id) );
  
EOF
  
##############################################################
  
## Lecture de Kanjidic
##
## Les lignes sont de la forme
## KANJI ... B8 ... S9 ... G8 ... F1652 ... L401 ... kana {meaning1} {meaning2} ...
##
print STDERR "Opening kanjidic\n";
my %kanjidic = ();
open(KANJIDIC, "$kanjidic") 
  || die "Cannot open $kanjidic for reading: $!";
while(<KANJIDIC>){
  next if m/^\#/;
  chomp;
  print "# $_\n";
  my ($kanji, $english, @english, $freq, $grade, $heisig,
      $pron, @pron, $t1, @t1, $t2, @t2, $bushu, $strokes);
  $heisig="NULL";
  $grade=10;
  $freq=666666;
  
  # Kanji
  s/^(..) ....// || next;
  $kanji = $1;
  $kanji{$kanji} = get_euc_code($kanji);
  $kanji_n++;
  
  # English meaning
  s/ \{(.*)\}//;
  $english = $1;
  @english = split( /\}\s+\{/, $english);
  $english =~ s/\}\s+\{/; /g;
  
  # Bushu
  m/\sB([0-9]+)/;
  $bushu = $1;
  
  # Strokes
  m/\sS([0-9]+)/;
  $strokes = $1;
  
  # Frequency
  if( s/\sF([0-9]+)/ / ){ $freq = $1 }
  
  # Grade (JouYou)
  if( s/\sG([0-9]+)/ / ){ $grade = $1 }
  
  # Heisig
  if( s/\sL([0-9]+)/ / ){ $heisig = $1 }
  
  # T2 pronuncuation
  if( s/\sT2\s(.*)// ){
    $t2=$1;
    $t2 =~ s/^\s*//gsm;
    $t2 =~ s/\s*$//gsm;
    @t2 = split(/\s+/, $t2);
    @t2 = remove_doubles(@t2);
    foreach my $p (@t2){
      $p = kata2hira($p);
      my $id = get_pron_id($p);
      print "INSERT INTO kanji_pron (kanji_id, pron_id, type) ";
      print "VALUES ($kanji{$kanji}, $id, 'T2');\n";
    }
  }
  
  # T1 pronuncuation
  if( s/\sT1\s(.*)// ){
    $t1=$1;
    $t1 =~ s/^\s*//gsm;
    $t1 =~ s/\s*$//gsm;
    @t1 = split(/\s+/, $t1);
    @t1 = remove_doubles(@t1);
    foreach my $p (@t1){
      $p = kata2hira($p);
      my $id = get_pron_id($p);
      print "INSERT INTO kanji_pron (kanji_id, pron_id, type) ";
      print "VALUES ($kanji{$kanji}, $id, 'T1');\n";
    }
  }
  
  # Prononciations
  s/[A-Z][-.a-zA-Z0-9:^]+\s*//g;
  $pron = $_;
  s/^\s*//gsm;
  s/\s*$//gsm;
  @pron = split( /\s+/, $pron );
#  shift @pron if $pron[0] =~ m/^\s*$/;
  foreach(@pron){ s/\..*//; s/^-//; s/-$//; }
  @pron=remove_doubles(@pron);
  $kanji_pron{$kanji} = [];
  
  ## On affiche l'entrée du Kanji
  print "INSERT INTO kanji (id, kanji, grade, freq, heisig, bushu, strokes)";
  print " VALUES ($kanji{$kanji}, '$kanji', $grade, $freq, $heisig, $bushu, $strokes);\n";
  
  ## On affiche les entrées correspondant à ses traductions
  foreach my $e (@english) {
    my $id;
    $id = get_trans_id($e);
    print "INSERT INTO kanji_trans (kanji_id, trans_id) VALUES ($kanji{$kanji}, $id);\n";
  }
  
  ## On affiche les entrées correspondant à ses prononciations
  foreach my $p (@pron) {
    next if $p =~ m/^\s*$/;
    my $hira = kata2hira($p);
    my $type = ( $hira eq $p ) ? "kun" : "ON";
    push @{ $kanji_pron{$kanji} }, $hira;
    my $id = get_pron_id($p);
    print "INSERT INTO kanji_pron (kanji_id, pron_id, type) ";
    print "VALUES ($kanji{$kanji}, $id, '$type');\n";
  }
  
  print "\n";
  
}
close KANJIDIC;
print STDERR "Closing kanjidic\n";
  
############################################################
  
## Lire radkfile
  
print STDERR "Opening radkfile\n";
open(RADKFILE, "$radkfile") || die "Cannot open $radkfile for reading: $!";
{
  my $rad_id;
  while(<RADKFILE>){
    next if m/^\#/;
    chomp;
    if( m/^\$\s+(..)/ ){
      print "# $_\n";
      my $k = $1;
      if( exists $kanji{$k} ){ $rad_id = $kanji{$k}; }
      else {
	$kanji{$k} = get_euc_code($k);
	$kanji_n++;
	$rad_id = $kanji{$k};
	my $strokes = "NULL";
	if( m/^\$\s+..\s+([0-9]+)/ ){ $strokes = $1; }
	print "INSERT INTO kanji (id, kanji, grade, freq, heisig, bushu, strokes)";
	print " VALUES ($kanji{$k}, '$k', 10, 666666, NULL, NULL, $strokes);\n";
      }
      next;
    }
    while(s/^(..)//){
      next unless exists $kanji{$1};
      my $k = $1;
      print "INSERT INTO kanji_rad (kanji_id, rad_id) VALUES ($kanji{$k}, $rad_id);\n";
    }
  }
}
close RADKFILE;
print STDERR "Closing radkfile\n";
  
############################################################
  
## Fonction pour obtenir la prononciation de chaque caractère d'un mot.
## Le résultat sera sous la forme suivante :
##   "FAIL"
##   [ ["NULL", "o"], ["cha (kanji meaning tea)", "cha"] ]
##   [ ["hito (kanji for people)","hito"], ["(kanji doubling sign)", "bito"] ]
  
sub did_fail {
  my ($result) = @_;
  return ($result eq "FAIL");
}
  
sub separate_kanji_pron {
  my($word, $pron, $latest_kanji) = @_;
  unless( $word =~ s/^(..)// ){
    if($pron =~ m/^$/){ return []; }
    else{ return "FAIL"; }
  }
  my $k = $1;
  print STDERR "  1Searching pronunciation of '$k' followed by '$word' ($pron).\n";
  if( $k eq '¡¹' ){
    print STDERR "   2It is a repetition symbol\n";
    if( defined $latest_kanji ){
      return separate_kanji_pron_try($word, $pron, $latest_kanji, $k);
    } else {
      print STDERR "Initial ¡¹\n";
      print STDERR "Returning FAIL1\n";
      return "FAIL";
    }
  } elsif (exists $kanji_pron{$k}){
    print STDERR "    2It is a kanji\n";
    return separate_kanji_pron_try($word, $pron, $k, $k);
  } else {
    print STDERR "    2It does not seem to be a kanji\n";
    my $kk = kata2hira($k);
    if( $pron =~ s/^$kk// ){
      my $next = separate_kanji_pron($word, $pron, $k);
      if( did_fail($next) ){
	print STDERR "Returning FAIL2\n";
	return "FAIL";
      }
      else { return [["NULL", $k], @$next]; }
    } else {
      print STDERR "  Returning FAIL3\n";
      return "FAIL";
    }
  }
}
sub separate_kanji_pron_try {
  my($word, $pron, $k, $kk) = @_;
  print STDERR "    3Searching pronunciation of '$k' (written '$kk') followed by '$word' ($pron)\n";
  my %among = ();
  foreach my $a (@{ $kanji_pron{$k} }){
    $among{$a} = $a;
    $among{kana_ka_to_ga($a)} = $a unless $among{kana_ka_to_ga($a)};
    $among{kana_ha_to_pa($a)} = $a unless $among{kana_ha_to_pa($a)};
    $among{kana_ta_to_da($a)} = $a unless $among{kana_ta_to_da($a)};
    $among{kana_sa_to_za($a)} = $a unless $among{kana_sa_to_za($a)};
    $among{kana_ha_to_ba($a)} = $a unless $among{kana_ha_to_ba($a)};
    $among{kana_tsu_to_little_tsu($a)} = $a
      unless $among{kana_tsu_to_little_tsu($a)};
    $among{kana_ku_to_little_tsu($a)} = $a
      unless $among{kana_ku_to_little_tsu($a)};
  }
  print STDERR "      among: ". join('/', (keys %among))."\n";
  foreach my $p (keys %among){
    my $pron2 = $pron;
    if( $pron2 =~ s/^$p// ){
      print STDERR "    4Searching pronunciation of '$k' (written '$kk') followed by '$word' ($pron = $p / $pron2).\n";
      my $next = separate_kanji_pron($word, $pron2, $k);
      unless( did_fail($next) ){ return [ [$kk, $among{$p}], @$next ]; }
    }
  }
  print STDERR "  Returning FAIL4\n";
  return "FAIL";
}
  
############################################################
  
## Lire wordfreq
  
my %wordfreq;
{
  my %w;
  print STDERR "Reading $wordsfreq\n";
  open(WORDFREQ, "$wordsfreq");
  while(<WORDFREQ>){
    chomp;
    next unless m/^([0-9]+)\s+(.*)/;
    $w{$2}=$1;
  }
  close WORDFREQ;
  my $i=1;
  foreach (sort {$w{$b}<=>$w{$a}} keys %w){
    $wordfreq{$_}=$i++;
  }
}
  
######################################################################
  
## Lire edict
  
print STDERR "Reading edict\n";
open(EDICT, "$edict") || die "Cannot open $edict for reading: $!";
while(<EDICT>){
  next if m/^\#/;
  next if m/^¡©¡©¡©¡©/; # Première ligne du fichier..
  chomp;
  print "\n# $_\n";
  my ($word, $pron, $engl);
  if( m§^([^\s]+)\s+(/.*)§ ){
    $word = $1;
    $pron = kata2hira($1);
    $engl = $2; 
  } elsif( m§^([^\s]+)\s+([^\s]+)\s+(/.*)§ ){
    $word = $1;
    $pron = kata2hira($2);
    $engl = $3;
  } else {
    print STDERR "  Strange entry: $_\n";
  }
  $pron =~ s/^\[//; $pron =~ s/\]$//;
  
  # On regarde si le mot existe déjà (a priori, avec une autre prononciation)
  my $id;
  if( exists $word{$word} ){ $id = $word{$word}; }
  else {
    $word_id++;
    $word{$word} = $word_id;
    my $f = (exists $wordfreq{$word}) ? $wordfreq{$word} : 666666;
    print "INSERT INTO words (id, word, freq) VALUES ($word_id, '$word', $f);\n";
    $word_length = max( $word_length, length($word) );
    $id = $word_id;
  }
  
  # On regarde si la prononciation existe
  my $p_id = get_pron_id($pron);
  
  $dic_number++;
  print "INSERT INTO dic (id, word_id, pron_id) ";
  print "VALUES ($dic_number, $id, $p_id);\n";
  
  # On regarde toutes les traductions possibles
  $engl =~ s#^/##;
  $engl =~ s#/\s*$##;
  my @trad = split( '/', $engl);
  foreach my $t (@trad) {
    # On regarde si la traduction existe
    my $c = get_comment($t);
    $t = strip_comment($t);
    my $c_id = get_comment_id($c);
    my $t_id = get_trans_id($t);
    # On ajoute l'entrée
    print "INSERT INTO dic_trans (dic_id, trans_id, comment_id)";
    print " VALUES ($dic_number, $t_id, $c_id);\n";
  
  }
  
  # Décomposer le mot en ses différents kanjis
  print STDERR "Decomposing '$word' ($pron)\n";
  my $result = separate_kanji_pron($word, $pron);
  if( did_fail($result) ){
    print STDERR "FAILED TO FIND PRONUNCIATION OF $word/$pron\n";
    $dic_fail++;
    for(my $i=0; $i<length($word)/2; $i++){
      my $kanji = substr($word, 2*$i, 2);
      $kanji = get_euc_code($kanji);
      print "INSERT into dic_pron_kanji(dic_id, kanji_id, pron_id, position) ";
      print "VALUES ($dic_number, $kanji, NULL, $i);\n";
    }
  } else {
    my $i=-1;
    foreach my $e (@$result) {
      $i++;
      print "# dic_pron_kanji: '$e->[0]', '$e->[1]'\n";
      next if $e->[0] eq "NULL"; # S'il n'y a pas de kanji
      next if $e->[0] eq '¡¹'; # Repetition symbol
      my $kanji = $kanji{$e->[0]};
      my $pron = $pron{$e->[1]};
      print "INSERT INTO dic_pron_kanji (dic_id, kanji_id, pron_id, position) ";
      print "VALUES ($dic_number, $kanji, $pron, $i);\n";
    }
  }
  
  # Vieux
  #$edict{$word} = {COUNT => 0, ENTRY => [] } unless exists $edict{$word};
  #push @{ $edict{$word}->{ENTRY} },
  #  { PRON => $pron, ENGL => $engl};
}
close EDICT;
print STDERR "Closing edict\n";
  
############################################################
  
## Lire un fichier de fréquence des mots...
  
############################################################
  
## Statistiques
  
print STDERR "Number of kanji: $kanji_n\n";
print STDERR "Number of pronunciations: $pron_id\n";
print STDERR "Maximal length of pronunciations: $pron_length\n";
print STDERR "Number of translations: $trans_id\n";
print STDERR "Maximal length of translations: $trans_length\n";
print STDERR "Number of grammatical comments: $comment_id\n";
print STDERR "Maximal length of a grammatical comment: $comment_length\n";
print STDERR "Number of words: $word_id\n";
print STDERR "Maximal length of words: $word_length\n";
print STDERR "Failed extraction of kanji pronunciations: $dic_fail/$dic_number\n";

config.inc

Ce fichier contient les variables utilisées dans tout le site, comme le nom de l'auteur, les couleurs, ou l'instruction de débugguage error_reporting(E_ALL).

<?php
  
//error_reporting(E_ALL);
//$debug=true;
  
if($CONFIG_INC == ""){
  $CONFIG_INC=TRUE;
  if($debug){ print "<pre>config.inc start</pre>\n"; }
  
// Nom du programme et arguments
$SELF = preg_replace("/\\/zoonek\.free.fr\\//", "", $PHP_SELF);
// Les arguments sont dans $CURRENT_ARG, défini dans variables.inc
  
// Coordonnées de l'auteur, copyright
$author          = "Vincent Zoonekynd";
$author_email    = "zoonek@math.jussieu.fr";
$author_homepage = "http://www.math.jussieu.fr/~zoonek/";
$date            = date( "D M j G:i:s T Y", filemtime($SELF) );
$credits         = "Automatically generated from 
<a href=\"http://www.csse.monash.edu.au/~jwb/j_edict.html\" 
   style=\"text-decoration: none\"
>edict</a>";
  
  
// Paramètres pour accéder à la base de données mysql
$username        = "zoonek";
$password        = "********";
  
// Couleurs et autres paramètres HTML
$charset = "iso-8859-1";
$charset = "UTF-8";
$charset = "ISO-2022-JP";
$charset = "EUC-JP";
  
$bgcolor = '#FFFFFF';
$text    = '#000000';
$alink   = '#FFFFFF';
$link    = '#6D8ADA';
$vlink   = '#415383';
  
$title_bgcolor   = '#ffdb43';
$title_fgcolor   = $text;
  
$section_bgcolor = '#6D8ADA';
$section_fgcolor = '#FFFFFF';
$section_face    = "Arial,Helvetica";
  
$code_bgcolor = '#FFFFAA';
$code_fgcolor = $text;
  
$tailer_fgcolor = '#c8c8c8';
  
$debug_fgcolor = $text;
$debug_bgcolor = '#ffdddd';
  
$td_bgcolor = '#dddddd';
  
  if($debug){ print "<pre>config.inc end</pre>\n"; }
} // $CONFIG_INC
  
?>

form.inc

Chaque page se termine sur un formulaire permettant d'étendre la recherche, en spécifiant la fréquence des mots ou caractères que l'on souhaite voir s'afficher.

<?php
if(!$FORM_INC){
  $FORM_INC=TRUE;
  if($debug){ print "<pre>form.inc start</pre>\n"; }
  
  require("config.inc");
  require("variables.inc");
  
  function html_form_limitations () {
    global $limit_grade_max,
      $limit_grade_min,
      $limit_freq_min,
      $limit_freq_max,
      $limit_max,
      $limit_words_freq_max,
      $SELF, $CURRENT_ARG;
    print "\n\n<!-- Formulaire -->\n\n";
    section("You may limit or extend the search by modifying the following parameters");
    print "<form action=\"redirect.php3?url=$SELF&$CURRENT_ARG\" method=post>\n";
    print "  <table>\n";
    print "    <tr>\n";
    print "      <td>minimal kanji frequency rank</td>\n";
    print "      <td><input type=\"text\"\n";
    print "             name=\"limit_freq_min\"\n";
    print "             value=\"$limit_freq_min\"></td>\n";
    print "    </tr>\n";
    print "    <tr>\n";
    print "      <td>maximal kanji frequency rank</td>\n";
    print "      <td><input type=\"text\"\n";
    print "             name=\"limit_freq_max\"\n";
    print "             value=\"$limit_freq_max\"></td>\n";
    print "    </tr>\n";
    print "    <tr>\n";
    print "      <td>minimal kanji grade</td>\n";
    print "      <td><input type=\"text\"\n";
    print "             name=\"limit_grade_min\"\n";
    print "             value=\"$limit_grade_min\"></td>\n";
    print "    </tr>\n";
    print "    <tr>\n";
    print "      <td>maximal kanji grade</td>\n";
    print "      <td><input type=\"text\"\n";
    print "             name=\"limit_grade_max\"\n";
    print "             value=\"$limit_grade_max\"></td>\n";
    print "    </tr>\n";
    print "    <tr>\n";
    print "      <td>maximal number of results</td>\n";
    print "      <td><input type=\"text\"\n";
    print "             name=\"limit_max\"\n";
    print "             value=\"$limit_max\"></td>\n";
    print "    </tr>\n";
    print "    <tr>\n";
    print "      <td>maximal word frequency rank</td>\n";
    print "      <td><input type=\"text\"\n";
    print "             name=\"limit_words_freq_max\"\n";
    print "             value=\"$limit_words_freq_max\"></td>\n";
    print "    </tr>\n";
    print "    <tr><td colspan=2><input type=\"submit\"\n";
    print "                             value=\"Validate\"></td></tr>\n";
    print "    <tr>\n";
    print "      <td>\n";
    print "        <a href=\"$SELF?limit_freq_max=666667&limit_grade_max=10&limit_max=999999&limit_words_freq_max=666667&$CURRENT_ARG\">limitless search</a>\n";
    print "      </td>\n";
    print "      <td>\n";
    print "        <a href=\"$SELF?$CURRENT_ARG\">default limits</a>\n";
    print "      </td>\n";
    print "    </tr>\n";
    print "  </table>\n";
    print "</form>\n";
  }
  
  if($debug){ print "<pre>form.inc end</pre>\n"; }
} // $FORM_INC
?>

html.inc

Les fonctions permettant de créer une page HTML, avec un découpage en sections, des tableaux, etc.

<?php
  
require('config.inc');
if(!$HTML_INC){
  $HTML_INC=TRUE;
  if($debug){ print "<pre>html.inc start</pre>\n"; }
  
////////////////////////////////////////////////////////////
  
// Formatage des données en HTML
/////////////////////////////////
  
function html_start($title) {
  global $bgcolor, $text, $alink, $vlink, $link;
  global $charset;
  global $title_bgcolor, $title_fgcolor;
  global $author;
  print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
  print "<html>\n";
  print "  <head>\n";
  print "    <title>$title</title>\n";
  print "    <meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\n";
  print "    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=$charset\">\n";
  print "    <meta name=\"author\" content=\"$author\">\n";
  print "    <style> A { text-decoration: none} </style>\n";
  print "  </head>\n";
  print "  <body bgcolor=\"$bgcolor\"\n";
  print "        text=\"$text\"\n";
  print "        alink=\"$alink\"\n";
  print "        vlink=\"$vlink\"\n";
  print "        link=\"$link\"\n";
  print "  >\n";
  print "<div align=\"center\">\n";
  print "<table cellpadding=\"10\">\n";
  print "  <tr>\n";
  print "    <td bgcolor=\"$title_bgcolor\">\n";
  print "      <font color=\"$title_fgcolor\"\n"; 
  print "            face=\"Arial,Helvetica\"\n";
  print "      >$title</font>\n";
  print "    </td>\n";
  print "  </tr>\n";
  print "</table>\n";
  print "</div>\n";
}
  
function html_end() {
  global $author, $author_email, $author_homepage, $credits, $date;
  global $tailer_fgcolor;
  print "\n<!-- End of html file -->\n";
  print "<p align =\"RIGHT\">\n";
  print "  <font color=\"$tailer_fgcolor\">\n";
  print "    <a href=\"$author_homepage\"\n";
  print "       style=\"text-decoration: none\"\n";
  print "    >$author</a><br>\n";
  print "    <a href=\"mailto:$author_email\"\n";
  print "       style=\"text-decoration: none\"\n";
  print "    >&lt;$author_email></a><br>\n";
  print "    $date<br>\n";
  print "    $credits\n";
  print "  </font>\n";
  print "</p>\n";
  print "\n\n";
  print "  </body>\n";
  print "</html>\n";
}
  
function section($title) {
  global $section_bgcolor, $section_fgcolor, $section_face;
  print "\n<!-- Section title -->\n";
  print "<p></p>\n";
  print "<table width=\"100\%\"\n";
  print "       cellpadding=\"2\"\n";
  print "       cellspacing=\"3\"\n";
  print "       border=\"0\">\n";
  print "  <tr>\n";
  print "    <td bgcolor=\"$section_bgcolor\">\n";
  print "      <font color=\"$section_fgcolor\" face=\"Arial,Helvetica\">\n";
  print "        $title\n";
  print "      </font>\n";
  print "    </td>\n";
  print "  </tr>\n";
  print "</table>\n";
  print "\n";
}
  
function html_debug($message){
  global $debug_fgcolor, $debug_bgcolor;
  print "<table>\n";
  print "  <tr>\n";
  print "    <td bgcolor=\"$debug_bgcolor\">\n";
  print "      <font size=\"-1\" color=\"$debug_fgcolor\">\n";
  print "        $message\n";
  print "      </font>\n";
  print "    </td>\n";
  print "  </tr>\n";
  print "</table>\n";
}
  
function html_table_start() {
  print "<table cellspacing=1>\n";
}
function html_table_end() {
  print "</table>\n";
}
function html_table_row($row){
  global $td_bgcolor;
  print "  <tr>\n";
  reset ($row);
  while (list($key, $value) = each ($row)) {
     print "    <td bgcolor=\"$td_bgcolor\">$value</td>\n";
  }
  print "  </tr>\n";
}
  
  if($debug){ print "<pre>html.inc end</pre>\n"; }
} // $HTML_INC
?>

js.inc

Pour les exercices, j'utilise un peu de Javascript (inspiré de http://www.rikai.com/).

<?php
  
function define_js_functions () {
  
print "
    <script>
function show_solution(aref,div) {
  if (window.document.layers[\"X\"+div]) {
    var ax=aref.x
    if (ax+window.document.layers[\"X\"+div].clip.width > 
        window.innerWidth+window.pageXOffset-20) {
      ax=window.innerWidth+window.pageXOffset - 20
          - window.document.layers[\"X\"+div].clip.width 
    }
    var ay=aref.y; // was aref.y+30
    if (ay+window.document.layers[\"X\"+div].clip.height > 
         window.innerHeight+window.pageYOffset-20) {
      ay=aref.y-10-window.document.layers[\"X\"+div].clip.height
      if (ay < window.pageYOffset) {
        ay=aref.y+30;
      }  
    }
    window.document.layers[\"X\"+div].x=ax;
    window.document.layers[\"X\"+div].y=ay;
    window.document.layers[\"Y\"+div].visibility=false;
    window.document.layers[\"X\"+div].visibility=true;
  }
}
function hide_solution(div) {
  if (window.document.layers[\"X\"+div]) {
    window.document.layers[\"X\"+div].visibility=false;
    window.document.layers[\"Y\"+div].visibility=true;
  }
}
window.oldInnerWidth=window.innerWidth;
window.oldInnerHeight=window.innerHeight;
window.onresize=function(e) {
  if ((e.width!=window.oldInnerWidth)||(e.height!=window.oldInnerHeight)) {
    window.oldInnerWidth=e.width;
    window.oldInnerHeight=e.height;
    window.history.go(0);
  }
}
  
document.solution_number=1;
function print_solution(question, solution){
  document.solution_number=document.solution_number+1;
  document.write('<layer id=\"X');
  document.write(document.solution_number);
  document.write('\" class=\"solution\" visibility=\"hide\" ');
  document.write('onMouseOut=\"hide_solution(');
  document.write(document.solution_number);
  document.write(')\">');
  document.write(solution);
  document.write('</layer>');
  document.write('<ilayer id=\"Y');
  document.write(document.solution_number);
  document.write('\" class=\"question\" visibility=\"show\">');
  document.write('<a href=\"javascript:\" ');
  document.write('onMouseOver=\"show_solution(this,');
  document.write(document.solution_number);
  document.write(')\" ');
  document.write('class=\"question\">');
  document.write(question);
  document.write('</a>');
  document.write('</ilayer>');
  d1 = \"X\" + document.solution_number;
  d2 = \"Y\" + document.solution_number;
  if( window.document.layers[d1].clip.width > 
      window.document.layers[d2].clip.width  ){
    window.document.layers[d2].clip.width = 
    window.document.layers[d1].clip.width;
  } else {
    window.document.layers[d1].clip.width = 
    window.document.layers[d2].clip.width;
  }
  window.document.layers[d2].visibility=true;
}
</script>
";
}
  
function js_choice($a,$b) {
  return "<script>print_solution('" . $a . "', '" . $b . "')</script>";
}
  
?>

kanji.inc

Les fonctions relatives à la base de donnée (connection, recherche) et aux kanji (conversion hiragana -> katakana)

<?php
  
if(!$KANJI_INC){
  $KANJI_INC=TRUE;
  
// Variables globales
$db="";
  
////////////////////////////////////////////////////////////
  
// Requète mysql
function my_sql($q){
  global $db;
  global $debug;
  $result = mysql_query($q, $db);
  if( mysql_errno() ){
    html_debug("QUERY:<PRE>$q</PRE>\nERROR: ". mysql_error());
  } else {
    if($debug){
      html_debug("QUERY:<PRE>$q</PRE>\n".
                 "Affected rows: ". mysql_affected_rows()."<br>\n".
                 "Rows: ". mysql_num_rows($result)."<br>\n".
                 "Columns: ". mysql_num_fields($result)
                );
      html_table_start();
      // titre des colonnes
      $a = array();
      $i = 0;
      while ($i < mysql_num_fields ($result)) {
        $meta = mysql_fetch_field ($result);
	if (!$meta) { $a[$i] = "?"; }
        else { $a[$i] = $meta->name ."<br>(". $meta->type .")"; } 
        $i++;
      }
      html_table_row($a);
      // valeurs
      while( $myrow = mysql_fetch_row($result) ){
	html_table_row($myrow);
      }
      html_table_end();
      mysql_data_seek($result,0);
    }
  }
  return $result;
}
  
////////////////////////////////////////////////////////////
  
// Initialisations diverses
/////////////////////////////
  
// Connection au serveur
require("config.inc");
$db = mysql_connect("sql.free.fr", $username, $password);
if( mysql_errno() ){
    print "<p>  ERROR:\n            <b>". mysql_error() ."\n</b></p>\n";
  }
  
// Choix de la base de données
mysql_select_db("zoonek", $db);
if( mysql_errno() ){
    print "<p>  ERROR:\n            <b>". mysql_error() ."\n</b></p>\n";
  }
  
// Nombres aléatoires
///////////////////////
srand ((double) microtime() * 10000000);
  
  
// Requètes fréquentes
///////////////////////
  
// Obtenir un bushu
function get_bushu($bushu) {
  $query = "SELECT id, kanji, strokes
FROM kanji
WHERE bushu=$bushu
ORDER BY strokes
LIMIT 1";
  $result = my_sql($query);
  list ($foobar, $k) = mysql_fetch_row ($result);
  mysql_free_result($result);
  return $k;
}
  
function get_kanji($id){
  $query = "SELECT kanji FROM kanji WHERE id=$id";
  $result = my_sql($query);
  list($k) = mysql_fetch_row($result);
  mysql_free_result($result);
  return $k;
}
  
function hiragana_to_katakana ($a) {
  $b = "";
  for($i=0; $i<strlen($a); $i+=2){
    $b .= hiragana_to_katakana_single_character(substr($a,$i,2));
  }
  return $b;
}
  
function hiragana_to_katakana_single_character ($a) {
  $hiragana = array("¤¢","¤¤","¤¦","¤¨","¤ª","¤«","¤­","¤¯","¤±","¤³",
"¤¬","¤­","¤°","¤²","¤´","¤µ","¤·","¤¹","¤»","¤½","¤¶","¤¸","¤º","¤¼",
"¤¾","¤¿","¤Á","¤Ä","¤Ã","¤Æ","¤È","¤À","¤Â","¤Å","¤Ç","¤É","¤Ê","¤Ë",
"¤Ì","¤Í","¤Î","¤Ï","¤Ò","¤Õ","¤Ø","¤Û","¤Ñ","¤Ô","¤×","¤Ú","¤Ý","¤Ð",
"¤Ó","¤Ö","¤Ù","¤Ü","¤Þ","¤ß","¤à","¤á","¤â","¤ä","¤æ","¤è","¤é","¤ê",
"¤ë","¤ì","¤í","¤ï","¤ò","¤ó","¤ç","¤å","¤ã","¤Ã","¤¡","¤£","¤¥","¤§",
		    "¤©");
  $katakana = array("¥¢","¥¤","¥¦","¥¨","¥ª","¥«","¥­","¥¯","¥±","¥³",
"¥¬","¥®","¥°","¥²","¥´","¥µ","¥·","¥¹","¥»","¥½","¥¶","¥¸","¥º","¥¼",
"¥¾","¥¿","¥Á","¥Ä","¥Ã","¥Æ","¥È","¥À","¥Â","¥Å","¥Ç","¥É","¥Ê","¥Ë",
"¥Ì","¥Í","¥Î","¥Ï","¥Ò","¥Õ","¥Ø","¥Û","¥Ñ","¥Ô","¥×","¥Ú","¥Ý","¥Ð",
"¥Ó","¥Ö","¥Ù","¥Ü","¥Þ","¥ß","¥à","¥á","¥â","¥ä","¥æ","¥è","¥é","¥ê",
"¥ë","¥ì","¥í","¥ï","¥ò","¥ó","¥ç","¥å","¥ã","¥Ã","¥¡","¥£","¥¥","¥§",
"¥©");
  $b = $a;
  // D'après le manuel, il semblerait que l'on puisse donner 
  // des listes en argument de str_replace, mais ca ne marche pas.
  reset($hiragana);
  while(list($i,$j) = each($hiragana)){
    $b = str_replace($hiragana[$i], $katakana[$i], $b);
  }
  return $b;
}
  
} // $KANJI_INC
  
?>

misc.inc

Les fonctions qui n'ont pas trouvé leur place ailleurs. Par exemple, une fonction qui prend la liste des radicaux d'un caractère, la considère comme un ensemble et en renvoie tous les sous-ensembles non vides.

<?php
  
if(!$MISC_INC){
  $MISC_INC=TRUE;
  if($debug){ print "<pre>misc.inc start</pre>\n"; }
  
  require("html.inc");
  
function n_set($n, $a){
  $r = array();
  $i = 0;
  if($n==1){
    reset($a);
    while( list($foobar, $x) = each($a) ){
      $t = array();
      $t[0] = $x;
      $r[$i++] = $t;
    }
    return $r;
  }
  $b = n_set($n-1, $a);
  reset($b);
  while( list($foobar, $c) = each($b) ){
    $d = $c[sizeof($c)-1];
    $found = FALSE;
    reset($a);
    while( list($foobar, $x) = each($a) ){
      if($found){
	$t = $c;
	$t[sizeof($t)] = $x;
	$r[$i++] = $t;
      } elseif($x==$d) {
	$found = TRUE;
      }
    }
  }
  return $r;
}
  
function n_set_debug($n, $a){
  $b = n_set($n, $a);
  $message = "";
  $message .= "$n-sets in (";
  reset($a);
  while(list($foobar, $x) = each($a)){ $message .= "$x, "; }
  $message .= ") are: \n";
  reset($b);
  while( list($foobar, $c) = each($b) ){
    $message .= "(";
    reset($c);
    while( list($foobar, $x) = each($c) ){
      $message .= "$x, ";
    }
    $message .= ")\n";
  }
  html_debug($message);
  return $b;
}
  
} // $MISC_INC
  
?>

order.inc

Les commandes de tri usuelles ne permettent pas de classer les hiragana comme dans les dictionnaires : j'ai donc écrit ma propre fonction de comparaison.

<?php
  
if(!$ORDER_INC){
  $ORDER_INC=TRUE;
  if($debug){ html_debug("order.inc: Start"); }
  
$order = array(
  array("¤¢", "¤¡"),
  array("¤¤", "¤£"),
  array("¤¦", "¤¥"),
  array("¤¨", "¤§"),
  array("¤ª", "¤©"),
  array("¤«","¤¬"),
  array("¤­","¤®"),
  array("¤¯","¤°"),
  array("¤±","¤²"),
  array("¤³","¤´"),
  array("¤µ","¤¶"),
  array("¤·","¤¸"),
  array("¤¹","¤º"),
  array("¤»","¤¼"),
  array("¤½","¤¾"),
  array("¤¿","¤À"),
  array("¤Á","¤Â"),
  array("¤Ä","¤Å", "¤Ã"),
  array("¤Æ","¤Ç"),
  array("¤È","¤É"),
  array("¤Ê"),
  array("¤Ë"),
  array("¤Ì"),
  array("¤Í"),
  array("¤Î"),
  array("¤Ï","¤Ð","¤Ñ"),
  array("¤Ò","¤Ó","¤Ô"),
  array("¤Õ","¤Ö","¤×"),
  array("¤Ø","¤Ù","¤Ú"),
  array("¤Û","¤Ü","¤Ý"),
  array("¤Þ"),
  array("¤ß"),
  array("¤à"),
  array("¤á"),
  array("¤â"),
  array("¤ä", "¤ã"),
  array("¤æ", "¤å"),
  array("¤è", "¤ç"),
  array("¤é"),
  array("¤ê"),
  array("¤ë"),
  array("¤ì"),
  array("¤í"),
  array("¤ï"),
  array("¤ò"),
  array("¤ó"));
  
$rank = array();
$i=1;
reset($order);
while( list($foobar, $l) = each($order) ){
  reset($l);
  while( list($foobar, $x) = each($l) ){
    $rank[$x] = $i;
  }
  $i++;
}
  
$subrank = array();
reset($order);
while( list($foobar, $a) = each($order) ){
  $i=1;
  reset($a);
  while( list($foobar, $x) = each($a) ){
    $subrank[$x] = $i++;
  }
}
  
function compare_simple($a, $b){
  global $rank;
  if    ( $a == "" and $b == "" ){ return 0; }
  elseif( $a == "" and $b != "" ){ return -1; }
  elseif( $a != "" and $b == "" ){ return +1; }
  else{
    $left  = $rank[ $a[0].$a[1] ];
    $right = $rank[ $b[0].$b[1] ];
    if    ( $left<$right ){ return -1; }
    elseif( $left>$right ){ return +1; }
    else{ 
      return compare_simple( substr($a, 2, strlen($a)-2), 
                             substr($b, 2, strlen($b)-2) ); 
    }
  }
}
  
function compare_complex($a, $b){
  global $subrank;
  $r = compare_simple($a, $b);
  if( $r != 0 ){ return $r; }
  for( $i=0; $i<strlen($a)/2; $i++ ){ 
    $left  = $subrank[ $a[2*$i] . $a[2*$i+1] ];
    $right = $subrank[ $b[2*$i] . $b[2*$i+1] ];
    if    ( $left<$right ){ return -1; }
    elseif( $left>$right ){ return +1; }
  }
  return 0;
}
  
function print_array($txt, $a){
  print "$txt<br>\n";
  reset($a);
  while( list($foobar, $x) = each($a) ){
    print "$x<br>\n";
  }
}
  
 if($debug){ html_debug("order.inc: End"); }
} // $ORDER_INC
  
/*
$test = array("¤¬", "¤¢", "¤«", "¤­", "¤­¤®", "¤®", "¤®¤®", "¤®¤­", "¤­¤­");
print_array("start", $test);
usort($test, "compare_simple");
print_array("simple", $test);
usort($test, "compare_complex");
print_array("complex", $test);
*/
  
?>

redirect.inc

Pour que l'utilisateur du site puisse garder (« boukmarquer ») une page, il est parfois nécessaire de transformer des variables passées par un formulaire (POST) en des variables passées dans l'URL lui-même (GET).

Même chose pour le « kanji aléatoire » : on arrive à une page bien définie ; si on y revient, ce sera le même kanji.

<?php
  require('variables.inc');
  // On redéfinit la variable $CURRENT_ARG;
  $CURRENT_ARG = "";
  
  if($HTTP_GET_VARS){
    reset($HTTP_GET_VARS);
    while( list($var, $value) = each($HTTP_GET_VARS)){
      if( (!$variables[$var]) and ($var != "url") ){
        $CURRENT_ARG .= "&$var=$value";
      }
    }
  }
  
  if($HTTP_POST_VARS){
    reset($HTTP_POST_VARS);
    while( list($var, $value) = each($HTTP_POST_VARS)){
      if( (!$variables[$var]) and ($var != "url") ){
        $CURRENT_ARG .= "&$var=$value";
      }
    }
  }
  
  function redirect($url, $v="") {
    global $CURRENT_ARG, $ARG;
    $a = "$url?$CURRENT_ARG&$ARG&$v";
    $a = str_replace('&&', '&', $a);
    $a = str_replace('?&', '?', $a);
    $a = preg_replace('/&$/', '', $a);
    header("location: $a");
    exit;
  }
?>

redirect.php3

Utilisé pour transformer les arguments passés par un formulaire en morceaux d'URL.

<?php
  require("redirect.inc");
  redirect($HTTP_GET_VARS["url"]);
?>

variables.inc

Définition des variables globales correspondant aux restrictions sur la recherche prévues dans le formulaire, et qu'il faudra rappeler à chaque URL.

<?php
  
// Variables globales définies dans ce fichier :
//   $CURRENT_ARG 
//   $ARG
//   $SQL_LIMIT_KANJI
//   $SQL_LIMIT_WORDS
//   $SQL_LIMIT
  
if(!$VARIABLES_INC){
  $VARIABLES_INC=TRUE;
  if($debug){ print "<pre>variables.inc start</pre>\n"; }
  
// Variables pour limiter la recherche
///////////////////////////////////////
  
$variables = array("limit_grade_max" => 1, 
                   "limit_grade_min" => 1, 
                   "limit_freq_min"  => 1, 
                   "limit_freq_max"  => 1, 
                   "limit_words_freq_max" => 1,
                   "limit_max"       => 1,
                   "debug"           => 0);
$CURRENT_ARG = "";
reset($HTTP_GET_VARS);
while( list($var, $value) = each($HTTP_GET_VARS)){
  if(!$variables[$var]){
    $CURRENT_ARG .= "&$var=$value";
  }
}
  
// Valeurs par défaut
$limit_grade_max_default=6;
$limit_grade_min_default=1;
$limit_freq_min_default=1;
$limit_freq_max_default=3000;
$limit_words_freq_max_default=6000;
$limit_max_default=3000;
if( $limit_grade_max == "" )
  { $limit_grade_max = $limit_grade_max_default; }
if( $limit_grade_min == "" )
  { $limit_grade_min = $limit_grade_min_default; }
if( $limit_freq_max  == "" )
  { $limit_freq_max  = $limit_freq_max_default; }
if( $limit_freq_min  == "" )
  { $limit_freq_min  = $limit_freq_min_default; }
if( $limit_words_freq_max == "" )
  { $limit_words_freq_max = $limit_words_freq_max_default; }
if( $limit_max       == "" )
  { $limit_max       = $limit_max_default; }
  
// Chaine de caractères à rajouter à la fin des URLs
$ARG="";
if( $limit_grade_max != $limit_grade_max_default )
  { $ARG .= "&limit_grade_max=$limit_grade_max"; }
if( $limit_grade_min != $limit_grade_min_default )
  { $ARG .= "&limit_grade_min=$limit_grade_min"; }
if( $limit_freq_max != $limit_freq_max_default )
  { $ARG .= "&limit_freq_max=$limit_freq_max"; }
if( $limit_freq_min != $limit_freq_min_default )
  { $ARG .= "&limit_freq_min=$limit_freq_min"; }
if( $limit_max != $limit_max_default )
  { $ARG .= "&limit_max=$limit_max"; }
if( $limit_words_freq_max != $limit_words_freq_max_default )
  { $ARG .= "&limit_words_freq_max=$limit_words_freq_max"; }
if( $debug ){ $ARG .= "&debug=1"; }
  
// Pour les requètes SQL
$SQL_LIMIT_KANJI  = "kanji.freq >= $limit_freq_min\n";
$SQL_LIMIT_KANJI .= "AND kanji.freq <= $limit_freq_max\n";
$SQL_LIMIT_KANJI .= "AND kanji.grade >= $limit_grade_min\n";
$SQL_LIMIT_KANJI .= "AND kanji.grade <= $limit_grade_max\n";
$SQL_LIMIT_WORDS  = "words.freq <= $limit_words_freq_max";
$SQL_LIMIT = "LIMIT $limit_max\n";
  
 if($debug){ print "<pre>variables.inc end</pre>\n"; }
} // $VARIABLES_INC
?>

words.inc

Fonction pour afficher le vocabulaire.

<?php
if(!$WORDS_INC){
  $WORDS_INC=TRUE;
  
  require("html.inc");
  
  function add_kanji_link_to_word($w, $links=true) {
    if(! $links){ return $w; }
    global $ARG;
    $printable_word="";
    for($i=0; $i<strlen($w)/2; $i++){
      $j = 256* ord($w[2*$i]) + ord($w[2*$i+1]);
      $k = $w[2*$i] .  $w[2*$i+1];
      $printable_word .= "<A HREF=\"kanji.php3?id=$j&$ARG\">$k</A>";
    }
    return $printable_word;
  }
  function add_pron_link($pron, $pron_id, $count, $links=true) {
    global $ARG;
    if($links and ($count>1)){
      return "<A HREF=\"list.php3?type=word_pron&pron=$pron_id&$ARG\">$pron</A>";
    } else {
      return $pron;
    }
  }
  function add_trans_link($trans, $trans_id, $count, $links=true) {
    global $ARG;
    if($links and ($count>1)){
      return "<A HREF=\"list.php3?type=word_trans&trans=$trans_id&$ARG\">$trans</A>";
    } else {
      return $trans;
    }
  }
  
  function result_to_list($result, $links=true) {
    $w = array();
    $i = 0;
  
    $preceding_word="";
    $preceding_pron="";
    $preceding_pron_id=0;
    $preceding_freq="";
    $preceding_trans="";
    $preceding_trans_id=0;
    $preceding_pron_count  = 1;
    $preceding_trans_count = 1;
    $preceding_position = "";
    $preceding_kanji_id = "";
  
    while( $myrow = mysql_fetch_array($result) ){
      if( ($myrow["word"] == $preceding_word) and 
	  ($myrow["pron"] == $preceding_pron) ){
	// Case 1 : different translation for the same word
	if( $preceding_trans != "" ){ $preceding_trans.="; "; }
	$preceding_trans_id=$myrow["trans_id"];
	$preceding_trans_count=$myrow["trans_count"];
	$preceding_trans.=add_trans_link($myrow["trans"] ." ". $myrow["comment"], 
					 $preceding_trans_id, 
					 $preceding_trans_count, 
					 $links);
      } else {
	// Case 2 : new word
	// We put the preceding word in the list 
	// (except if it is the first word)
	// and remember the current word
	$t = array();
	$t["freq"] = $preceding_freq;
	$t["word"] = add_kanji_link_to_word($preceding_word, $links);
	$t["pron"] = add_pron_link($preceding_pron, 
				   $preceding_pron_id, 
				   $preceding_pron_count, 
				   $links);
	$t["trans"] = $preceding_trans;
	$t["position"] = $preceding_position;
	$t["kanji_id"] = $preceding_kanji_id;
	if($preceding_word != ""){ $w[$i++]=$t; }
  
	$preceding_trans_id=$myrow["trans_id"];
	$preceding_trans_count=$myrow["trans_count"];
	$preceding_trans=add_trans_link($myrow["trans"] ." ".  $myrow["comment"],
					 $preceding_trans_id, 
					 $preceding_trans_count, 
					 $links);
	$preceding_kanji_id = $myrow["kanji_id"];
	$preceding_position = $myrow["position"];
	$preceding_word=$myrow["word"];
	$preceding_pron=$myrow["pron"];
	$preceding_pron_id=$myrow["pron_id"];
	$preceding_freq=$myrow["freq"];
	$preceding_pron_count = $myrow["pron_count"];
      }
    }
    $t = array();
    $t["freq"] = $preceding_freq;
    $t["word"] = add_kanji_link_to_word($preceding_word, $links);
    $t["pron"] = add_pron_link($preceding_pron, 
			       $preceding_pron_id, 
			       $preceding_pron_count, 
			       $links);
    $t["trans"] = $preceding_trans;
    $t["position"] = $preceding_position;
    $w[$i++]=$t;
    return $w;
  }
  
  function html_words($result) {
    $w = result_to_list($result); 
    html_table_start();
    reset($w);
    while( list(,$t) = each($w) ){
      html_table_row(array( $t["freq"], 
			    $t["word"],
			    $t["pron"],
			    $t["trans"] ));
    }
    html_table_end();
  }
  
} //$WORDS_INC
?>

main.php3

La page principale, qui ne contient que quelques liens.

<?php
  require("config.inc");
  require("variables.inc");
  require("html.inc");
  require("form.inc");
  html_start("Kanji");
  
  function li($php, $arg, $txt, $rem){
    global $ARG;
    print "<li>\n";
    print "  <a href=\"$php.php3?$ARG&$arg\">$txt</a>\n";
    if( $rem != "" ){ print "  $rem\n"; }
    print "</li>\n";
  }
?>
  
<ul>
<?php li("list", "type=grade", 
         "Kanji sorted by grade", "") ?>
<?php li("list", "type=freq", 
         "Kanji sorted by frequency", "") ?>
<?php li("list", "type=heisig", 
         "Kanji in Heisig order", "") ?>
<?php li("list", "type=bushu", 
         "Kanji sorted by bushu", "") ?>
<?php li("list", "type=rad", 
         "Kanji sorted by radicals", "") ?>
<?php li("list", "type=strokes", 
         "Kanji sorted by stroke count", "") ?>
<?php li("list", "type=pron", 
         "Kanji sorted by pronunciation", "") ?>
<?php li("kanji", "", 
         "Random kanji", "") ?>
<?php li("list", "type=words", 
         "Word list", "") ?>
<li><a href="http://phpmyadmin.free.fr/phpMyAdmin/">Modify the database</a> (for me)</li>
</ul>
  
<?php 
  html_form_limitations();
  html_end(); 
?>

list.php3

Listes de kanji ayant une certaine prononciation, une certaine traduction, un certain nombre de traits, un certain radical, un certain bushu, etc.

<?
  require("config.inc");
  require("html.inc");
  require("kanji.inc");
  require("variables.inc");
  require("form.inc");
  require("words.inc");
  
  // Variables globales (HTTP_GET_VARS) : 
  // type
  // pron
  
  // On recherche d'éventuelles variables limitant la recherche
  // A FAIRE (dans kanji.inc)
  
  // Liste par fréquence
  if( $type=="freq" or $type=="" ){
  
    html_start("Kanji sorted by frequency");
    $query = "SELECT id, kanji, freq 
FROM kanji 
WHERE $SQL_LIMIT_KANJI 
ORDER BY freq
";
    $result = my_sql($query);
    while( $myrow = mysql_fetch_array($result) ){
      print "<A HREF=\"kanji.php3?$ARG&id=". $myrow["id"] .
            "\">". $myrow["kanji"] ."</A>\n";
    }
    mysql_free_result($result);
  
  } elseif( $type == "grade" and $grade ){
    html_start("Kanji sorted by grade");
  
      if( $grade <= 6 ){ section("Jouyou Grade $grade"); }
      elseif( $grade == 8 ) { section("General-use characters"); }
      elseif( $grade == 9 ) { section("Jinmeiyou characters"); }
      elseif( $grade == 10 ) { section("Other characters"); }
      else{ print "\n<!-- there is no grade 8 -->\n\n"; }
      $query = "SELECT id, kanji 
FROM kanji 
WHERE grade=$grade 
AND $SQL_LIMIT_KANJI
ORDER BY freq
";
      $result = my_sql($query);
      while( $myrow = mysql_fetch_array($result) ){
        print "<A HREF=\"kanji.php3?$ARG&id=". $myrow["id"] .
            "\">". $myrow["kanji"] ."</A>\n";
      }
      mysql_free_result($result);
  
  // By grade
  } elseif( $type == "grade" ){
    html_start("Kanji sorted by grade");
    for($grade=1; $grade<=10; $grade++){
  
      if( $grade <= 6 ){ section("Jouyou Grade $grade"); }
      elseif( $grade == 8 ) { section("General-use characters"); }
      elseif( $grade == 9 ) { section("Jinmeiyou characters"); }
      elseif( $grade == 10 ) { section("Other characters"); }
      else{ print "\n<!-- there is no grade 8 -->\n\n"; }
      $query = "SELECT id, kanji 
FROM kanji 
WHERE grade=$grade 
AND $SQL_LIMIT_KANJI
ORDER BY freq
";
      $result = my_sql($query);
      while( $myrow = mysql_fetch_array($result) ){
        print "<A HREF=\"kanji.php3?$ARG&id=". $myrow["id"] .
            "\">". $myrow["kanji"] ."</A>\n";
      }
      mysql_free_result($result);
  
    }
  
  // Heisig
  } elseif( $type == "heisig" ){
  
    html_start("Kanji in Heisig order");
    // (Kanji with no heisig number have heisig=NULL)
    $query = "SELECT id, kanji, heisig 
FROM kanji 
WHERE $SQL_LIMIT_KANJI
AND heisig>0
ORDER BY heisig
";
    $result = my_sql($query);
    $i=0; $j=1; // row and column number
    $width=20; // width of a column
    $row=array(1,        '&nbsp;', '&nbsp;', 
               '&nbsp;', '&nbsp;', '&nbsp;',
               '&nbsp;', '&nbsp;', '&nbsp;',
               '&nbsp;' );
    html_table_start();
    while( $myrow = mysql_fetch_array($result) ){
  
      while( $myrow["heisig"] > $width*$i+$j ){
        $j++;
        if($j>$width){ $j=1; $i++; 
          html_table_row($row); 
          $row=array();
          $row[0] = "(". ($width*$i+$j) .")";
          $f=1; while($f<=$width){ $row[$f]= '&nbsp;'; $f++; }
        }
      }
  
      $row[$j]= "<A HREF=\"kanji.php3?$ARG&id=". $myrow["id"] .
            "\">". $myrow["kanji"] ."</A>";
  
    }
    html_table_row($row); 
    html_table_end();
    mysql_free_result($result);
  
    // Heisig
    section("Heisig-less characters");
    $query = "SELECT id, kanji, freq
FROM kanji 
WHERE $SQL_LIMIT_KANJI
AND heisig=NULL
ORBER BY freq
";
    $result = my_sql($query);
    while( $myrow = mysql_fetch_array($result) ){
      print "<A HREF=\"kanji.php3?$ARG&id=". $myrow["id"] .
            "\">". $myrow["kanji"] ."</A>\n";
    }
    mysql_free_result($result);
  
  } elseif( $type == "strokes" and $strokes ){
    html_start("Kanji sorted by stroke count");
  
      $i=$strokes;
      if($i==1){ section("1 stroke"); } else { section("$i strokes"); }
      $query="SELECT kanji, id 
FROM kanji 
WHERE $SQL_LIMIT_KANJI
AND strokes=$i 
ORDER BY bushu
";
      $result=my_sql($query);
      while( $myrow = mysql_fetch_array($result) ){
        print "<A HREF=\"kanji.php3?$ARG&id=". $myrow["id"] .
              "\">". $myrow["kanji"] ."</A>\n";
      }
      mysql_free_result($result);
  
  // By stroke count
  } elseif( $type == "strokes" ){
    html_start("Kanji sorted by stroke count");
    // Rechercher le nombre de traits maximal
    $query="SELECT MAX(strokes) FROM kanji WHERE $SQL_LIMIT_KANJI";
    $result=my_sql($query);
    $max_strokes = mysql_fetch_row($result);
    $max_strokes = $max_strokes[0];
    mysql_free_result($result);
    // Boucle
    for($i=1; $i<=$max_strokes; $i++){
  
      if($i==1){ section("1 stroke"); } else { section("$i strokes"); }
      $query="SELECT kanji, id 
FROM kanji 
WHERE $SQL_LIMIT_KANJI
AND strokes=$i 
ORDER BY bushu
";
      $result=my_sql($query);
      while( $myrow = mysql_fetch_array($result) ){
        print "<A HREF=\"kanji.php3?$ARG&id=". $myrow["id"] .
              "\">". $myrow["kanji"] ."</A>\n";
      }
      mysql_free_result($result);
  
    }
  
  // kanji with a given bushu
  } elseif( $type == "bushu" and $bushu){
  
    // picture of the bushu
    $k = get_bushu($bushu);
    html_start("Kanji with bushu $k");
    section("$k ($bushu)");
  
      $b=$bushu;
      $query = "SELECT id, kanji, strokes
FROM kanji 
WHERE $SQL_LIMIT_KANJI
AND bushu=$b 
ORDER BY strokes
";
      $result = my_sql($query);
      while( list ($i, $k) = mysql_fetch_row ($result) ){
        print "<A HREF=\"kanji.php3?$ARG&id=$i\">$k</A>\n";
      }
      mysql_free_result($result);
  
  // By bushu
  } elseif( $type == "bushu" ){
    html_start("kanji sorted by bushu");
    // bushu = entier de 1 à 214
    for($b=1; $b<=214; $b++){
      $k = get_bushu($b);
      section("$k ($b)"); 
      $query = "SELECT id, kanji 
FROM kanji 
WHERE $SQL_LIMIT_KANJI
AND bushu=$b 
ORDER BY strokes";
      $result = my_sql($query);
      while( list ($i, $k) = mysql_fetch_row ($result) ){
        print "<A HREF=\"kanji.php3?$ARG&id=$i\">$k</A>\n";
      }
      mysql_free_result($result);
    }
  
  // By pronunciation
  } elseif( ($type == "pron") and $pron){
      
    $query="SELECT pron 
FROM pron 
WHERE id=$pron";
    $result=my_sql($query);
    list($p) = mysql_fetch_row($result);
    mysql_free_result($result);
  
    html_start("Kanji pronunced $p");
    $query="SELECT id, kanji, freq
FROM kanji, kanji_pron 
WHERE $SQL_LIMIT_KANJI
AND kanji_pron.kanji_id=kanji.id
AND pron_id=$pron
ORDER BY freq";
    $result = my_sql($query);
    while( list ($id, $k) = mysql_fetch_row ($result) ){
      print "<A HREF=\"kanji.php3?$ARG&id=$id\">$k</A>\n";
    }      
    mysql_free_result($result);
  
  } elseif( $type=="pron" and $start != ""){
    html_start("Kanji sorted by pronunciation");
    require("order.inc");
    section("Pronunciations starting by ". join('/', $order[$start]));
      
    $query="SELECT DISTINCT pron.id, pron.pron 
FROM pron, kanji_pron, kanji
WHERE kanji_pron.pron_id=pron.id
AND kanji_pron.kanji_id=kanji.id
AND $SQL_LIMIT_KANJI
AND type IN ('ON', 'kun')
AND ( \n";
    $subquery=array();
    reset( $order[$start] );
    while( list($i,$j) = each( $order[$start] ) ){
      $subquery[$i]= "pron LIKE '$j%'";
    }
    $query .= join("\nOR ", $subquery) .")";
    $result = my_sql($query);
    // $pron_to_id: hash table
    // $pron: list of pronunciations
    $i=0;
    $pron=array();
    $pron_to_id=array();
    while( list ($pron_id, $p) = mysql_fetch_row ($result) ){
      $pron_to_id[$p] = $pron_id;
      $pron[$i++]=$p;
    }
    mysql_free_result($result);
    usort($pron, "compare_complex");
    $begin=0;
    reset($pron);
    while( list ($foobar, $p) = each($pron) ){
      $pron_id=$pron_to_id[$p];
      if($begin != $rank[ substr($p,2,2) ]){
        $begin = $rank[ substr($p,2,2) ];
        print "<br>\n"; 
      }
      print "<A HREF=\"list.php3?$ARG&type=pron&pron=$pron_id\">$p</A>\n";
    }
  
  // By pronunciation
  } elseif( $type=="pron" ){
    html_start("Kanji sorted by pronunciation");
    require("order.inc");
    section("Choose the first letter of the pronunciation");
    reset($order);
    while( list($i,$j) = each($order) ){
      print "<a href=\"list.php3?type=pron&start=$i\">$j[0]</a>\n";
    }
  
  // List of words
  } elseif( $type == "words" ){
  
    html_start("Word list");
    $query="SET OPTION SQL_BIG_SELECTS=1";
    my_sql($query);
    $query = "SELECT 
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  comment, freq
FROM words, dic_trans, dic, pron, trans, comment
WHERE $SQL_LIMIT_WORDS
AND freq>=1 
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND dic_trans.comment_id = comment.id
ORDER BY freq, pron.id
$SQL_LIMIT";
    $result = my_sql($query);
    html_words($result);
    mysql_free_result($result);
  
  } elseif( $type == "word_pron" ){
    html_start("Homonyms");
    $query="SELECT pron FROM pron WHERE id=$pron";
    $result = my_sql($query);
    $myrow = mysql_fetch_array($result);
    $pron_kanji=$myrow["pron"];
    mysql_free_result($result);
    section("Words pronounced $pron_kanji");
  
    $query = "SELECT 
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  freq, comment
FROM words, dic_trans, dic, pron, trans, comment
WHERE $SQL_LIMIT_WORDS
AND freq>=1 
AND dic_trans.dic_id=dic.id
AND pron.id=$pron
AND dic.pron_id=$pron
AND dic_trans.trans_id=trans.id
AND words.id=word_id
AND dic_trans.comment_id = comment.id
ORDER BY freq, pron.id
$SQL_LIMIT";
    $result = my_sql($query);
    html_words($result);
    mysql_free_result($result);
  
    section("Kanji pronounced $pron_kanji");
    $query="SELECT kanji, id, freq
FROM kanji, kanji_pron
WHERE $SQL_LIMIT_KANJI
AND kanji_pron.kanji_id=kanji.id
AND kanji_pron.pron_id=$pron
ORDER BY freq
$SQL_LIMIT";
    $result=my_sql($query);
    while( $myrow = mysql_fetch_array($result) ){
      $k = $myrow["kanji"];
      $i = $myrow["id"];
      print "<A HREF=\"kanji.php3?id=$i&$ARG\">$k</A>\n";
    }
    mysql_free_result($result);
  
  } elseif( $type == "word_trans" ){
    html_start("Synonyms");
    $query="SELECT trans FROM trans WHERE id=$trans";
    $result = my_sql($query);
    $myrow = mysql_fetch_array($result);
    $trans_text=$myrow["trans"];
    mysql_free_result($result);
    section("Words meaning \"$trans_text\"");
  
    $query = "SELECT dic.id
FROM dic, dic_trans, words
WHERE dic_trans.dic_id = dic.id
AND dic_trans.trans_id = $trans
AND dic.word_id=words.id
AND $SQL_LIMIT_WORDS
$SQL_LIMIT";
    $result = my_sql($query);
      
    $dic_ids="";
    while( list ($i) = mysql_fetch_row ($result) ){
      $dic_ids="$dic_ids,$i";
    }
    $dic_ids = preg_replace('/^,/', '', $dic_ids);
    mysql_free_result($result);
  
    $query = "SELECT 
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  freq, comment
FROM words, dic_trans, pron, trans, dic, comment
WHERE $SQL_LIMIT_WORDS
AND freq>=1 
AND dic_trans.dic_id=dic.id
AND dic.id IN ($dic_ids)
AND pron.id=dic.pron_id
AND trans_id=trans.id
AND words.id=word_id
AND dic_trans.comment_id = comment.id
ORDER BY freq, pron.id
$SQL_LIMIT";
    $result = my_sql($query);
    html_words($result);
    mysql_free_result($result);
  
    section("Kanji meaning \"$trans_text\"");
    $query="SELECT kanji, id, freq
FROM kanji, kanji_trans
WHERE $SQL_LIMIT_KANJI
AND kanji_trans.kanji_id=kanji.id
AND kanji_trans.trans_id=$trans
ORDER BY freq
$SQL_LIMIT";
    $result=my_sql($query);
    while( $myrow = mysql_fetch_array($result) ){
      $k = $myrow["kanji"];
      $i = $myrow["id"];
      print "<A HREF=\"kanji.php3?id=$i&$ARG\">$k</A>\n";
    }
    mysql_free_result($result);
  
  } elseif( $type == "rad" and !$rad ){
    html_start("Kanji sorted by radical");
  
    $query = "SELECT DISTINCT kanji, rad_id, strokes
FROM kanji_rad, kanji
WHERE rad_id=kanji.id
ORDER BY strokes
";
    $result = my_sql($query);
    section("Choose a radical");
    $s=1;
    print "$s\n";
    while( $myrow = mysql_fetch_array($result) ){
      $k = $myrow["kanji"];
      $i = $myrow["rad_id"];
      if( $s < $myrow["strokes"] ){ 
        $s=$myrow["strokes"]; 
        print "$s\n";
      }
      print "<a href=\"list.php3?$ARG&type=rad&rad=$i\">$k</a>\n";
    }
    mysql_free_result($result);
  
  } elseif( $type == "rad" ){
  
    // liste des radicaux passés en argument
    $rads = split(',', $rad);
    $kanjis = array();
    reset($rads);
    while( list($i,$r) = each($rads) ){
      $kanjis[$i]=get_kanji($r);
    }
    if(sizeof($rads)==1){
      html_start("Kanji with radical $kanjis[0]");
    } else {
      html_start("Kanji with radicals ". join(',', $kanjis));
    }
  
    $query="SELECT kanji, id, freq
FROM kanji";
    reset($rads);
    while( list($i,$r) = each($rads) ){
      $query .= ", kanji_rad R$i";
    }
    $query .= "\nWHERE $SQL_LIMIT_KANJI\n";
    reset($rads);
    while( list($i,$r) = each($rads) ){
      $query .= "AND R$i.kanji_id = kanji.id AND R$i.rad_id=$r\n";
    }
    $query .= "ORDER BY freq ";
    $result = my_sql($query);
    while( list($k, $i, $foobar) = mysql_fetch_row($result) ){
      print "<a href=\"kanji.php3?$ARG&id=$i\">$k</a>\n";
    }
    mysql_free_result($result);
  
    section("You may wish to add a radical");
    $query = "SELECT DISTINCT kanji, rad_id, strokes
FROM kanji_rad, kanji
WHERE rad_id=kanji.id
ORDER BY strokes
";
    $result = my_sql($query);
    $s=1;
    print "$s\n";
    while( $myrow = mysql_fetch_array($result) ){
      $k = $myrow["kanji"];
      $i = $myrow["rad_id"];
      if( $s < $myrow["strokes"] ){ 
        $s=$myrow["strokes"]; 
        print "$s\n";
      }
      print "<a href=\"list.php3?$ARG&type=rad&rad=$rad,$i\">$k</a>\n";
    }
    mysql_free_result($result);
  
  } else {
    html_start("error");
    print "Invalid type: $type";
    $credits="";
  }
  
?>
  
<?php 
  html_form_limitations(); 
  html_end(); 
?>

kanji.php3

Informations relatives à un kanji donné.

<?php
  require("config.inc");
  require("html.inc");
  require("kanji.inc");
  require("variables.inc");
  require("form.inc");
  require("words.inc");
  require("misc.inc");
  
  // GLOBAL VARIABLES
  // $id: ID of the current kanji
  // $kanji: current kanji (EUC)
  // $trans: translations of the current kanji
  // $trans_id: ids of the translations of the current kanji
  // $pron: ids of the pronunciations of the current kanji
  // $radicals: radicals of the current kanji
  // $radicals_id: ids of the radicals of the current kanji
  
  // If no character was specified, choose a random one
  // definition of $id
  if( ! $id ){
    $query="SELECT id 
FROM kanji 
WHERE $SQL_LIMIT_KANJI
";
    $result = my_sql($query);
    mysql_data_seek($result, rand(0,mysql_num_rows($result)-1));
    list ($id) = mysql_fetch_row ($result);
    mysql_free_result($result);
    require('redirect.inc');
    redirect("kanji.php3", "id=$id");
  }
  
  // Information about this character
  // definition of $kanji
  $query = "SELECT kanji, grade, strokes, heisig, bushu, freq 
FROM kanji 
WHERE id=$id
";
  $result = my_sql($query);
  if(mysql_num_rows($result)<1){
    html_start("No such kanji");
    print "<a href=\"main.php3?$ARG\">Return to main menu</a>\n";
    html_end();
    exit;
  }
  list ($kanji, $grade, $strokes, $heisig, $bushu, $freq) = 
    mysql_fetch_row ($result);
  mysql_free_result($result);
  
  // Print thins information
  html_start($kanji);
  section("General information");
  print "<br><a href=\"list.php3?$ARG&type=grade\">Grade</a>:\n";
  print "<a href=\"list.php3?$ARG&type=grade&grade=$grade\">$grade</a> \n";
  print "<br><a href=\"list.php3?$ARG&type=strokes\">Strokes</a>:\n";
  print "<a href=\"list.php3?$ARG&type=strokes&strokes=$strokes\">$strokes \n";
  print "<br><a href=\"list.php3?$ARG&type=heisig\">Heisig</a>:\n";
  print "$heisig\n";
  if($bushu){
    $bushu_pic=get_bushu($bushu);
    print "<br><a href=\"list.php3?$ARG&type=bushu\">Bushu</a>: ";
    print "<a href=\"list.php3?$ARG&type=bushu&bushu=$bushu\">$bushu_pic</a>";
    print " ($bushu)\n";
  }
  print "<br><a href=\"list.php3?$ARG&type=freq\">Frequency</a>: ";
  if($freq>10000){ print "rare\n"; }
  else{ print $freq ."\n"; }
  print "</p>\n";
  
  // Translations
  // definition of the arrays $trans and $trans_id
  section("Translations");
  $query = "SELECT trans, trans_id
FROM kanji_trans, trans
WHERE kanji_id=$id 
AND trans.id=trans_id
";
  $result = my_sql($query);
  $trans = array();
  $trans_id = array();
  $i=0;
  while( $myrow = mysql_fetch_array($result) ){
    print $myrow["trans"] ."<br>\n";
    $trans[$i]=$myrow["trans"];
    $trans_id[$i]=$myrow["trans_id"];
    $i++;
  }
  print "</p>\n";
  mysql_free_result($result);
  
  // Synonyms
  $first = TRUE;
  reset($trans_id);
  while (list($i, $t_i) = each ($trans_id)) { 
    $query="SELECT id, kanji
FROM kanji, kanji_trans
WHERE trans_id=$t_i
AND id=kanji_id
AND $SQL_LIMIT_KANJI
";
    $result = my_sql($query);
    if( mysql_num_rows($result)>1 ){
      if( $first ){
	section("Synonyms");
	html_table_start();
	$first = FALSE;
      }
      $right="";
      while( $myrow = mysql_fetch_array($result) ){
	$a = $myrow["id"];
	$b = $myrow["kanji"];
	$right .= "<A HREF=\"kanji.php3?$ARG&id=$a\">$b</a>\n";
      }
      $left = $trans[$i];
      html_table_row(array( $left, $right ));
    }
    mysql_free_result($result);
  }
  if(!$first){ html_table_end(); }
  
  // look also for similar words
  
  // Pronunciations
  // definition of the arrays $on, $kun, $t1, $t2
  $query = "SELECT pron, type, pron_id
FROM kanji_pron, pron 
WHERE kanji_id=$id 
AND pron.id=pron_id
";
  $result = my_sql($query);
  $on=array();  $on_id=array();
  $kun=array(); $kun_id=array();
  $t1=array();  $t1_id=array();
  $t2=array();  $t2_id=array();
  $i_on=0; $i_kun=0; $i_t1=0; $i_t2=0;
  while( $myrow = mysql_fetch_array($result) ){
    if( $myrow["type"] == "ON"      ){ 
      $on[$i_on]     = $myrow["pron"]; 
      $on_id[$i_on]  = $myrow["pron_id"]; 
      $i_on++;
    }
    elseif( $myrow["type"] == "kun" ){ 
      $kun[$i_kun]    = $myrow["pron"]; 
      $kun_id[$i_kun] = $myrow["pron_id"]; 
      $i_kun++;
    }
    elseif( $myrow["type"] == "T1"  ){ 
      $t1[$i_t1]    = $myrow["pron"]; 
      $t1_id[$i_t1] = $myrow["pron_id"]; 
      $i_t1++;
    }
    elseif( $myrow["type"] == "T2"  ){ 
      $t2[$i_t2]    = $myrow["pron"]; 
      $t2_id[$i_t2] = $myrow["pron_id"]; 
      $i_t2++;
    }
  }
  mysql_free_result($result);
  section("ON pronunciations");
  if( sizeof($on) == 0 ){
    print "<p>There seems to be none.</p>\n";
  } else {
    reset ($on);
    print "<p>\n";
    while (list($a, $b) = each ($on)) { 
      $kata = hiragana_to_katakana($b);
      print "  <a href=\"list.php3?$ARG&type=pron&pron=$on_id[$a]\">$kata</a><br>\n"; 
    }
    print "</p>\n";
  }
  
  section("kun pronunciations");
  if( sizeof($kun) == 0 ){
    print "<p>There seems to be none.</p>\n";
  } else {
    reset ($kun);
    print "<p>\n";
    while (list($a, $b) = each ($kun)) { 
      print "  <a href=\"list.php3?$ARG&type=pron&pron=$kun_id[$a]\">$b</a><br>\n"; 
    }
    print "</p>\n";
  }
  
  if( sizeof($t1)>0 ){
    section("Pronunciations used in proper names");
    reset ($t1);
    print "<p>\n";
    while (list($a, $b) = each ($t1)) { 
      print "  <a href=\"list.php3?$ARG&type=pron&pron=$t1_id[$a]\">$b</a><br>\n"; 
    }
    print "</p>\n";
  }
  
  if( sizeof($t2)>0 ){
    section("Bushu name");
    reset ($t2);
    print "<p>\n";
    while (list($a, $b) = each ($t2)) { 
      print "  <a href=\"list.php3?$ARG&type=pron&pron=$t2_id[$a]\">$b</a><br>\n"; 
    }
    print "</p>\n";
  }
  
  // Kanji with the same pronunciation
  
  // kanji with the same ON pronunciation
  if( sizeof($on)>0 ){
    $first = TRUE;
    reset($on_id);
    while (list($i, $p_i) = each ($on_id)) { 
      $query="SELECT id, kanji, freq
FROM kanji, kanji_pron
WHERE pron_id=$p_i
AND kanji_id=id
AND type='ON'
AND $SQL_LIMIT_KANJI
ORDER BY freq
";
      $result = my_sql($query);
      if( mysql_num_rows($result)>1 ){
	if( $first ){
	  section("Kanji with the same ON pronunciation");
	  html_table_start();
	  $first = FALSE;
	}
	$right="";
	while( $myrow = mysql_fetch_array($result) ){
	  $a = $myrow["id"];
	  $b = $myrow["kanji"];
	  $right .= "<A HREF=\"kanji.php3?$ARG&id=$a\">$b</a>\n";
	}
	$left = hiragana_to_katakana($on[$i]);
	$left = "<A HREF=\"exercise.php3?type=pron&pron=$p_i\">$left</A>";
	html_table_row(array( $left, $right ));
      }
      mysql_free_result($result);
    }
    if(!$first){ html_table_end(); }
  }
  
  // kanji with the same kun pronunciation
  if( sizeof($kun)>0 ){
    $first = TRUE;
    reset($kun_id);
    while (list($i, $p_i) = each ($kun_id)) { 
      $query="SELECT id, kanji, freq
FROM kanji, kanji_pron
WHERE pron_id=$p_i
AND kanji_id=id
AND type='kun'
AND $SQL_LIMIT_KANJI
ORDER BY freq
";
      $result = my_sql($query);
      if( mysql_num_rows($result)>1 ){
	if( $first ){
	  section("Kanji with the same kun pronunciation");
	  html_table_start();
	  $first = FALSE;
	}
	$right="";
	while( $myrow = mysql_fetch_array($result) ){
	  $a = $myrow["id"];
	  $b = $myrow["kanji"];
	  $right .= "<A HREF=\"kanji.php3?$ARG&id=$a\">$b</a>\n";
	}
	$left = $kun[$i];
	$left = "<A HREF=\"exercise.php3?type=pron&pron=$p_i\">$left</A>";
	html_table_row(array( $left, $right ));
      }
      mysql_free_result($result);
    }
    if(!$first){ html_table_end(); }
  }
  
  // Radicals
  section("radicals");
  $radicals = array();
  $query = "
SELECT kanji, rad_id
FROM kanji_rad, kanji
WHERE kanji_id=$id 
AND rad_id=kanji.id
";
  $result = my_sql($query);
  print "<p>\n";
  $n=0;
  while( $myrow = mysql_fetch_array($result) ){
    $radicals[$n] = $myrow["kanji"];
    $radicals_id[$n] = $myrow["rad_id"];
    print "  ". $myrow["kanji"] ."\n";
    $n++;
  }
  print "</p>\n";
  mysql_free_result($result);
  
// Les n-uplets de radicaux
for($n=sizeof($radicals); $n>0; $n--){
  $first = TRUE;
  $a = array();
  
  //$message="";
  //reset($radicals_id);
  //while(list($foobar, $x)=each($radicals_id)){ $message.="$foobar => $x, "; }
  //html_debug("radical ids: ($message)");
  
  for($i=0; $i<sizeof($radicals); $i++){ $a[$i] = $i; }
  //$n_tuples = n_set_debug($n, $a);
  $n_tuples = n_set($n, $a);
  reset($n_tuples);
  while( list($foobar, $a) = each($n_tuples) ){
    $query="SET OPTION SQL_BIG_SELECTS=1";
    my_sql($query); 
    $query = "SELECT kanji, id, freq
FROM kanji";
    for($i=0; $i<$n; $i++){ $query .= ", kanji_rad R$i"; }
    $query .="\nWHERE $SQL_LIMIT_KANJI";
    for($i=0; $i<$n; $i++){ 
      $query .= " AND R$i.rad_id=". $radicals_id[$a[$i]];
      $query .= " AND kanji.id=R$i.kanji_id\n";
    }
    $query .= "ORDER BY freq";
    $result=my_sql($query);
    if( mysql_num_rows($result)>1 ){
      if( $first ){
	section("Kanji with $n of these radicals");
	html_table_start();
	$first = FALSE;
      }
      $right="";
      while( $myrow = mysql_fetch_array($result) ){
	$right .= "<a href=\"kanji.php3?$ARG&id=". $myrow["id"] 
	  ."\">". $myrow["kanji"] . "</a> ";
      }
      $left = "<a href=\"exercise.php3?type=rad&rad=" . $radicals_id[$a[0]];
      for($i=1; $i<$n; $i++){ $left .= "," . $radicals_id[$a[$i]]; }
      $left .= "\">";
      for($i=0; $i<$n; $i++){ $left .= $radicals[$a[$i]]; }
      $left .= "</A>";
      html_table_row(array( $left, $right ));
    }
    mysql_free_result($result);
  }
  if(!$first){ html_table_end(); }
}
  
  
  // Kanji avec le même bushu
  section("Kanji with the same bushu");
  $query="SELECT kanji, id
FROM kanji
WHERE bushu=$bushu
AND $SQL_LIMIT_KANJI
ORDER BY freq
$SQL_LIMIT
";
  $result=my_sql($query);
  html_table_start();
  $right="";
  while( $myrow = mysql_fetch_array($result) ){
    $right.="<a href=\"kanji.php3?$ARG&id=". $myrow["id"] 
            ."\">". $myrow["kanji"] . "</a> ";
  }
  $left = "<A HREF=\"exercise.php3?type=bushu&bushu=$bushu\">$bushu_pic</A>";
  html_table_row(array($left, $right));
  html_table_end();
  mysql_free_result($result);
  
  // Confusable kanji ///////// A FAIRE
  section("Kanji with the same pronunciation and some radicals in common");
  html_debug("Not implemented");
    
  // Words
  section("Words");
  $query = "SELECT 
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  comment,
  freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans, comment
WHERE kanji_id=$id 
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND dic_trans.trans_id=trans.id
AND words.id=word_id
AND dic_trans.comment_id = comment.id
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
  $result = my_sql($query);
  html_words($result);
  mysql_free_result($result);
  
  // Words (2)
  section("Words, classified according to the pronunciation of $kanji");
  // On commence par reprendre la liste des prononciations.
  // (si possible, avec celles correspondant aux mots les plus courrants 
  // en premier)
  
  // ON
  reset($on);
  reset($on_id);
  while (list($foobar, $p_id) = each($on_id)) {
    list($foobar, $p) = each($on);	
  
    $query = "SELECT
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  comment,
  freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans, comment
WHERE kanji_id=$id 
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND dic_pron_kanji.pron_id=$p_id
AND dic_trans.comment_id = comment.id
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
    section("$kanji pronounced ". hiragana_to_katakana($p) ." (ON)");
    $result = my_sql($query);
    html_words($result);
    mysql_free_result($result);
  
  }
  
  // kun
  reset($kun);
  reset($kun_id);
  while (list($foobar, $p_id) = each($kun_id)) {
    list($foobar, $p) = each($kun);	
  
    $query = "SELECT 
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  comment, freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans, comment
WHERE kanji_id=$id 
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND dic_pron_kanji.pron_id=$p_id
AND dic_trans.comment_id = comment.id
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
    section("$kanji pronounced $p (kun)");
    $result = my_sql($query);
    html_words($result);
    mysql_free_result($result);
  
  }
  
  // Other pronunciations
  $i=0;
  // $pron =  all the pronunciations already treated
  $pron=array();
  reset($kun_id);
  while (list($foobar, $p_id) = each($kun_id)) { $pron[$i++]=$p_id; }
  reset($on_id);
  while (list($foobar, $p_id) = each($on_id))  { $pron[$i++]=$p_id; }
  
  $pron_list=join(', ', $pron);
  $query = "SELECT
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  comment, freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans, comment
WHERE kanji_id=$id 
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND dic_pron_kanji.pron_id=NULL
AND dic_trans.comment_id = comment.id
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
  # Pourquoi est-ce que 
  #  « NOT dic_pron_kanji.pron_id IN ($pron_list) »
  # ne donne aucun résultat ?
  section("Other pronunciations of $kanji");
  $result = my_sql($query);
  html_words($result);
  mysql_free_result($result);
  
  html_form_limitations();
  html_end();
?>

exercise.php3

La page d'exercices.

<?php
  
require('html.inc');
require('js.inc');
require('kanji.inc');
require('variables.inc');
require('words.inc');
require("form.inc");
  
function exercise_words($result) {
  $w = result_to_list($result, false);
  shuffle($w);
  html_table_start();
  reset($w);
  while( list(,$t) = each($w) ){
    $word = $t["word"];
    $p = $t["position"];
    $word = substr($word, 0, 2*$p) . 
      js_choice( "¡©", substr($word, 2*$p, 2) ) .
      substr($word, 2*$p+2);
    html_table_row(array( $t["freq"], 
			  $word,
			  $t["pron"],
			  $t["trans"] ));
  }
  html_table_end();
}  
  
html_start("Exercises");
define_js_functions();
  
if($type == "pron"){
  // pronunciation
  $query="SELECT pron 
FROM pron 
WHERE id=$pron";
  $result=my_sql($query);
  list($p) = mysql_fetch_row($result);
  mysql_free_result($result);
  
  // kanji
  $query="SELECT id, kanji, freq
FROM kanji, kanji_pron 
WHERE $SQL_LIMIT_KANJI
AND kanji_pron.kanji_id=kanji.id
AND pron_id=$pron
ORDER BY freq";
  $result = my_sql($query);
  $kanji = array();
  $kanjis = "";
  while( list ($id, $k) = mysql_fetch_row ($result) ){
    $kanji[$id] = $k;
    $kanjis .= $k ." ";
  }      
  mysql_free_result($result);
  
  section("In the following words, find the correct kanji (pronounced $p) among $kanjis");
  
  $kanji_ids = "";
  reset($kanji);
  while( list($id,) = each($kanji) ){
    $kanji_ids .= $id .",";
  }
  $kanji_ids = preg_replace('/,$/', '', $kanji_ids);
  $query = "SELECT
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  kanji_id, position,
  freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans
WHERE kanji_id IN ($kanji_ids)
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND dic_pron_kanji.pron_id=$pron
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
  $result = my_sql($query);
  exercise_words($result);
  mysql_free_result($result);
  
} elseif($type == "bushu"){
  
  $bushu_pic = get_bushu($bushu); // VERIFIER QU'IL Y A BIEN UN RESULTAT
  $query="SELECT id, kanji, freq
FROM kanji
WHERE $SQL_LIMIT_KANJI
AND kanji.bushu=$bushu
ORDER BY freq
$SQL_LIMIT";
  $result = my_sql($query);
  $kanji = array();
  $kanjis = "";
  while( list ($id, $k) = mysql_fetch_row ($result) ){
    $kanji[$id] = $k;
    $kanjis .= $k ." ";
  }      
  mysql_free_result($result);
  
  section("In the following words, find the correct kanji (with bushu $bushu_pic) among $kanjis");
  
  $kanji_ids = "";
  reset($kanji);
  while( list($id,) = each($kanji) ){
    $kanji_ids .= $id .",";
  }
  $kanji_ids = preg_replace('/,$/', '', $kanji_ids);
  $query = "SELECT
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  kanji_id, position,
  freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans
WHERE kanji_id IN ($kanji_ids)
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
  $result = my_sql($query);
  exercise_words($result);
  mysql_free_result($result);
  
} elseif($type == "trans"){
  
  $query="SELECT trans FROM trans WHERE id=$trans";
  $result = my_sql($query);
  list($eng) = mysql_fetch_row($result);
  mysql_free_result($result);
  
  $query="SELECT id, kanji, freq
FROM kanji, kanji_trans
WHERE $SQL_LIMIT_KANJI
AND kanji.id = kanji_trans.kanji_id
AND kanji_trans.trans_id=$trans
ORDER BY freq
$SQL_LIMIT";
  $result = my_sql($query);
  $kanji = array();
  $kanjis = "";
  while( list ($id, $k) = mysql_fetch_row ($result) ){
    $kanji[$id] = $k;
    $kanjis .= $k ." ";
  }      
  mysql_free_result($result);
  
  section("In the following words, find the correct kanji (one of whose translations is `$eng') among $kanjis");
  
  $kanji_ids = "";
  reset($kanji);
  while( list($id,) = each($kanji) ){
    $kanji_ids .= $id .",";
  }
  $kanji_ids = preg_replace('/,$/', '', $kanji_ids);
  $query = "SELECT
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  kanji_id, position,
  freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans
WHERE kanji_id IN ($kanji_ids)
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
  $result = my_sql($query);
  exercise_words($result);
  mysql_free_result($result);
  
  html_debug("Not implemented");
  
} elseif($type == "rad"){ // RADICAL
  
  $rads = split(',', $rad);
  $rad_pic = "";
  reset($rads);
  while( list($i,$r) = each($rads) ){
    $rad_pic .= get_kanji($r) . " ";
  }
  if(sizeof($rads)==1){ $plural = "s"; }
  else { $plural = ""; }
  
  $query="SELECT id, kanji, freq
FROM kanji ";
  for($i=0; $i<sizeof($rads); $i++){ 
    $query .= ",\n  kanji_rad R$i";
  }
  $query .= "\nWHERE $SQL_LIMIT_KANJI";
  for($i=0; $i<sizeof($rads); $i++){ 
    $query .= "\nAND kanji.id = R$i.kanji_id";
    $query .= "\nAND R$i.rad_id = $rads[$i]";
  }
  $query .= "\nORDER BY freq\n$SQL_LIMIT";
  $result = my_sql($query);
  $kanji = array();
  $kanjis = "";
  while( list ($id, $k) = mysql_fetch_row ($result) ){
    $kanji[$id] = $k;
    $kanjis .= $k ." ";
  }      
  mysql_free_result($result);
  
  section("In the following words, find the correct kanji (with radical$plural $rad_pic) among $kanjis");
  
  $kanji_ids = "";
  reset($kanji);
  while( list($id,) = each($kanji) ){
    $kanji_ids .= $id .",";
  }
  $kanji_ids = preg_replace('/,$/', '', $kanji_ids);
  $query = "SELECT
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  kanji_id, position,
  freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans
WHERE kanji_id IN ($kanji_ids)
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
  $result = my_sql($query);
  exercise_words($result);
  mysql_free_result($result);
  
  html_debug("Not implemented");
  
} elseif($type == "kanji"){ // A LIST OF KANJI
  
  $kanjis = split(',', $kanji);
  $kanji_pic = "";
  reset($kanjis);
  while( list($i,$k) = each($kanjis) ){
    $kanji_pic .= get_kanji($k) . " ";
  }
  if(sizeof($kanjis)==1){ $plural = "s"; }
  else { $plural = ""; }
  
  section("In the following words, find the correct kanji among $kanji_pic");
  
  $query = "SELECT
  word, 
  pron, pron.id pron_id, pron.count pron_count, 
  trans, trans.id trans_id, trans.count trans_count, 
  kanji_id, position,
  freq
FROM dic_pron_kanji, dic_trans, words, dic, pron, trans
WHERE kanji_id IN ($kanji)
AND dic_pron_kanji.dic_id=dic.id
AND dic_trans.dic_id=dic.id
AND dic.pron_id=pron.id
AND trans_id=trans.id
AND words.id=word_id
AND $SQL_LIMIT_WORDS
ORDER BY freq, pron.id
$SQL_LIMIT
";
  $result = my_sql($query);
  exercise_words($result);
  mysql_free_result($result);
  
} elseif($type == ""){
  html_debug(" &lt;empty> Not implemented");
} else {
  html_debug("Unknown type $type");
}
  
  
html_form_limitations();
html_end();
  
?>

Le site

La page d'accueil

http://zoonek.free.fr/kanji/kanji/

Différentes listes de kkanji

http://zoonek.free.fr/kanji/list.php3?type=grade
http://zoonek.free.fr/kanji/list.php3?type=freq
http://zoonek.free.fr/kanji/list.php3?type=heisig
http://zoonek.free.fr/kanji/list.php3?limit_freq_max=666667&limit_grade_max=10&limit_max=999999&limit_words_freq_max=666667&type=heisig
http://zoonek.free.fr/kanji/list.php3?type=bushu
http://zoonek.free.fr/kanji/list.php3?type=rad
http://zoonek.free.fr/kanji/list.php3?type=strokes

Recherche d'un caractère par sa prononciation

http://zoonek.free.fr/kanji/list.php3?type=pron
http://zoonek.free.fr/kanji/list.php3?type=pron&start=11
http://zoonek.free.fr/kanji/list.php3?type=pron&pron=67

Liste de mots

http://zoonek.free.fr/kanji/list.php3?type=words

Informations sur un kanji donné, ou sur un kanji au hasard

http://zoonek.free.fr/kanji/kanji.php3?id=48861
http://zoonek.free.fr/kanji/kanji.php3

Exercices : distinguer (dans des mots) des kanjis ayant la même prononciation, le même bushu, le même sens, les même radicaux.

http://zoonek.free.fr/kanji/exercise.php3?type=pron&pron=65
http://zoonek.free.fr/kanji/exercise.php3?type=bushu&bushu=96
http://zoonek.free.fr/kanji/exercise.php3?type=trans&trans=2741
http://zoonek.free.fr/kanji/exercise.php3?type=rad&rad=55238
http://zoonek.free.fr/kanji/exercise.php3?type=rad&rad=55238,51621

Exercices : distinguer des kanjis dans une liste donnée (il n'y a aucun lien vers cette page).

http://zoonek.free.fr/kanji/exercise.php3?type=kanji&kanji=48082,52923

(août 2001)

Remarques ultérieures (janvier 2002)

Lorsque l'on accède à une base de données, on met généralement le mot de passe dans un fichier séparé, pour pouvoir difuser le code plus facilement. Il faut prendre garde à ce que ce fichier ne soit pas lisible. J'avais l'habitude d'utiliser un fichier pass.inc, auquel on peut accéder directement, en demandant l'URL correspondant. Il vaut mueix prendre pass.php3 qui, si on cherche à le télécharger, s'exécutera et ne renverra rien.

Remarque générale : php est utilie si la conception du site Web est séparée entre un programmeur et un non programmeur : si c'est la même personne qui fait tout, il ne sera pas très content. Si c'est un programmeur, il sera obligé de mélanger di HTML à son code, si c'est un graphiste (ou autre, je ne sais pas vraiment comment ça s'appelle), il sera contraint de programmer. Pour les programmeurs, je conseillerais plutôt Perl et son module CGI (même s'il ne s'agit pas de fichiers CGI), qui permet d'écrire des choses du genre :

print p, "Your name is ", b(escapeHTML($name));
print h2("Error"), "Hmm, I can't process your input.  Please ";
print a({href => script_name()}, "start over"),".";

Pour les non-programmeurs, je conseillerais plutôt quelque chose du genre Zope, où tout ressemble à du HTML, même les requètes SQL ou les boucles.

Je rappelle encore une fois (dans le manuel, ils n'insistent pas suffisemment) que tout programme php DOIT commencer par la ligne

error_reporting(E_ALL);

C'est l'équivalent du -w et du use strict de Perl : si on programme sans jamais commettre la moindre erreur, c'est complètement inutile, mais si le programmeur est un être humain, c'est une aide énorme lors du débugguage. Toutefois, ces messages d'erreurs (et de manière plus générale, tous les messages de débugguage) peuvent constituer une faille de sécurité en donnant des informations sur le fonctionnement du script et ses bugs possible. Une fois que le programme fonctionne, on peut comprendre que certains les enlèvent.

(Il me semble que j'avais autre chose à dire, mais j'ai oublié quoi.)

Voici un autre exemple de programme en php, permettant à tout un chacun de contribuer à une base de données.

http://crookshanks.free.fr/dvdhk/

<?php
  
error_reporting(E_ALL);
  
////////////////////////////////////////////////////////////
  
// Fonction pour afficher une fiche
function results($result, $myrow) {
  print start_pretty_table();
  for($i=0; $i<mysql_num_fields($result); $i++){
    $meta = mysql_fetch_field($result, $i);
    $name = $meta->name;
    $text = $myrow[$name];
    $text = escapeHTML($text);
    if( $name == "titre" ){ $text = b($text); }
    print Tr( td($name) . td($text.'&nbsp;') );
  }
  print end_table();
  print p();
}
  
// Fonctions pour créer du HTML (comme avec perl)
  
function start_html ($title) { 
  return '<html><head><title>' . $title . '</title></head><body>'; 
}
function end_html () { return '</body>'; }
function p ($arg = "") { return '<p>'.$arg.'</p>'."\n"; }
function b ($arg = "") { return '<b>'.$arg.'</b>'; }
function pre ($arg) { return '<pre>'.$arg.'</pre>'; }
function start_table() { return "\n".'<table>'; }
function start_pretty_table() { return '<table border=1>'; }
function end_table() { return '</table>'."\n"; }
function row_table ($row) {
  $r = "";
  reset ($row);
  while (list($key, $value) = each ($row)) { $r .= td($value); }
  return Tr($r);
}
function table ($arg) { return start_table() . $arg . end_table(); } 
function Tr ($arg) { return '<tr>' . $arg . '</tr>'; }
function td ($arg) { return '<td>' . $arg . '</td>'; }
function a ($url, $text) { return '<a href="' . $url . '">' . $text . '</a>'; }
function start_form ($url) { return '<form action="'.$url.'" method="post">'; }
function end_form () { return '</form>'; }
function textarea ($name, $default, $rows, $cols) {
  return '<textarea name="'.$name.'" rows="'.$rows.'" cols="'.$cols.'">'.$default.'</textarea>';
}
function textfield ($name, $default, $width) {
  return '<input type="text" name="'.$name.'" value="'.$default.'" size="'.$width.'">'; 
}
function popup_menu ($name, $values) {
  $result = '<select name="'.$name.'">';
  reset($values);
  while( list(,$entry) = each($values) ){
    $result .= '<option  value="'.$entry.'">'.$entry.'</option>';
  }
  $result .= '</select>';
  return $result;
}
function submit ($text) { return '<input type="submit" value="'.$text.'">'; }
function h1 ($arg) { return '<h1>' . $arg . '</h1>'; }
  
function escapeHTML($text) {
  $text = preg_replace("/\\&/", "\\&amp;", $text);
  $text = preg_replace("/\\</", "\\&lt;", $text);
  $text = preg_replace("/\\n/", "<br>", $text);
  return $text;
}
  
////////////////////////////////////////////////////////////
  
$jenesaispas = "je ne sais pas";
  
////////////////////////////////////////////////////////////
  
// Base de données
  
function my_sql($q){
  global $db;
  // Lors du premier appel, on se connecte
  if(!isset($db)){
    include("pass.php3"); // login et mot de passe
    $db = mysql_connect($sql_host, $username, $password);
    if(mysql_errno()){ print pre("ERROR: ".mysql_error()); exit(); }
    mysql_select_db($username, $db);
    if(mysql_errno()){ print pre("ERROR: ".mysql_error()); exit(); }
  }
  
  $result = mysql_query($q, $db);
  if(mysql_errno()){print pre("QUERY:<PRE>$q</PRE>\nERROR: ". mysql_error()); exit();}
  return $result;
}
  
////////////////////////////////////////////////////////////
  
  
print start_html("DVD HK");
  
if(!isset( $HTTP_GET_VARS["action"] ) ){ 
  $HTTP_GET_VARS["action"] = "view"; 
}
  
if( $HTTP_GET_VARS["action"] == "add" ){
  
  print h1("Ajouter un titre à la base de données");
  print start_form("index.php3?action=post");
  print table(Tr(td("Pseudo").td(textfield('pseudo', '', 60))).
	      Tr(td("Titre de l'animé").td(textfield('titre', '', 60))).
	      Tr(td("Qualité des sous-titres anglais").
		 td(popup_menu('soustitres',array($jenesaispas,
						  "fansub",
						  "bon",
						  "inégaux",
						  "mauvais")))).
	      Tr(td("référence").td(textfield('ref', 'DVD-', 60))).
	      Tr(td("marque").
		 td(popup_menu('marque',array($jenesaispas,
					      "Anime Manga International", 
					      "Anime Cartoon International",
					      "Kinda",
					      "Anime Studio")))).
	      Tr(td("commentaires").td(textarea('commentaires', '', 10, 60)))
	      );
  print submit("Ajouter la fiche");
  print end_form();
    
} elseif( $HTTP_GET_VARS["action"] == "post" ){
    
  // Valeurs postées
  $pseudo       = $HTTP_POST_VARS["pseudo"];
  $titre        = $HTTP_POST_VARS["titre"];
  $soustitres   = $HTTP_POST_VARS["soustitres"];
  $ref          = $HTTP_POST_VARS["ref"];
  $marque       = $HTTP_POST_VARS["marque"];
  $commentaires = $HTTP_POST_VARS["commentaires"];
  $date         = date("Y-m-d G:i:s"); // 2001-10-25 17:49:56
    
  // Vérifications
  if( !isset($pseudo) ){ $pseudo = "anonyme"; }
  if( !isset($titre) or $titre==""){ 
    print pre("Il faut un titre"); 
    print end_html();
    exit(); 
  }
  if( !isset($soustitres) or ($soustitres=="" or $soustitres==$jenesaispas)){ 
    print pre("Il manque la qualité des sous-titres"); 
    print end_html();
    exit(); 
  }
  if( !isset($ref) ){ $ref = "???"; }
  if( !isset($marque) or $marque == ""){ $marque = $jenesaispas; }
  if( !isset($commentaires) ){ $commentaires = ""; }
    
  // Nouvel id
  $query = "SELECT MAX(id) FROM dvdhk";
  $result = my_sql($query);
  list ($id) = mysql_fetch_row ($result);
  if(!isset($id)){ $id=1; } // table vide
  else{ $id++; }
  mysql_free_result($result);
    
  my_sql('INSERT INTO dvdhk 
(id, pseudo, titre, soustitres, ref, marque, commentaires, date) 
VALUES ("'.$id.'", "'.$pseudo.'", "'.$titre.'", "'.$soustitres.'", 
        "'.$ref.'", "'.$marque.'", 
        "'.$commentaires.'",
        "'.$date.'")');//';
  
  print p("Base de données mise à jour");
  print p(a('index.php3', 'Retour à la page principale'));
  
} elseif( $HTTP_GET_VARS["action"] == "view" ){ // On affiche la base de données
  $result = my_sql("SELECT pseudo, titre, soustitres, ref, marque, commentaires, date FROM dvdhk ORDER BY date DESC");
  print h1("Votre avis sur la qualité des sous-titre anglais des DVD HK");
  print p("Il y a ".mysql_num_rows($result)." titres dans la base de données");
  print p("Cliquez ".a('index.php3?action=add','ici')." pour ajouter un titre");
  while( $myrow = mysql_fetch_array($result) ){ 
    print results($result, $myrow);
  }
} else { print pre('Unknown action '. $HTTP_GET_VARS["action"]); }
  
print end_html();
  
?>

Vincent Zoonekynd
<zoonek@math.jussieu.fr>
latest modification on mar mai 14 10:34:19 CEST 2002