Programmation PHP/Version imprimable3
Une version à jour et éditable de ce livre est disponible sur Wikilivres,
une bibliothèque de livres pédagogiques, à l'URL :
https://fr.wikibooks.org/wiki/Programmation_PHP
Exemples/Variables
Utilisation de variables
modifierUn exemple de programme
modifier<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Variables en PHP !</title>
</head>
<body>
<?php
for($i = 1; $i <= 10; $i++)
echo ' <p>Ligne numéro '.$i.'</p>'."\n";
?>
</body>
</html>
Explications
modifier- Une variable en php commence par le symbole $. Ici nous utilisons une variable d'identificateur $i.
- Il n'y a pas de déclaration ni de typage fixe : une variable peut changer dynamiquement de type, ce qui est parfois vu comme un atout, parfois comme une faiblesse !
- Ce programme comporte une boucle for qui a sa sémantique habituelle. La variable $i va donc prendre successivement les valeurs 1,2,... jusqu'à 10.
- Dans cet exemple les chaînes de caractères sont entre apostrophes.
- La concaténation des chaînes de caractères s'effectue grâce à l'opérateur ..
- Remarque : si on veut qu'une chaîne de caractères contienne une apostrophe droite il faut écrire \' à l'intérieur de la chaîne.
Exécution du programme
modifier<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Variables en php</title>
</head>
<body>
<p>Ligne numéro 1</p>
<p>Ligne numéro 2</p>
<p>Ligne numéro 3</p>
<p>Ligne numéro 4</p>
<p>Ligne numéro 5</p>
<p>Ligne numéro 6</p>
<p>Ligne numéro 7</p>
<p>Ligne numéro 8</p>
<p>Ligne numéro 9</p>
<p>Ligne numéro 10</p>
</body>
</html>
Les guillemets
modifierUne chaîne de caractère entre guillemet est assez particulière : si elle contient $a alors $a est remplacé par la valeur de la variable $a. Il y a automatiquement substitution. Si on écrit \$ alors il n'y a plus substitution. De la même manière, pour afficher le caractère guillemet on écrit \".
Exemple 2 : guillemets et variables
modifier<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Variables en PHP !</title>
</head>
<body>
<?php
$a=67+33;
echo "la variable \$a vaut $a";
?>
</body>
</html>
Explications
modifierDans ce programme la variable $a vaut 67+33 donc vaut 100. Dans la chaîne de caractères \$a affichera $a et le deuxième $a sera remplacé par la valeur 100. Il s'affichera donc :
la variable $a vaut 100
Exemples/Sommaire
Un sommaire simple
modifierImaginons un site Web composé de 4 pages entre lesquelles on peut naviguer grâce à un sommaire. Notre sommaire est par exemple à gauche de l'écran et contient 4 liens hypertextes : page 1, page 2, page 3 et page 4. À droite de l'écran, le contenu principal de la page change en fonction de la page affichée. Par contre notre sommaire apparaît lui sur chacune des pages.
Le programme en php
modifierFichier index.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Sommaire en PHP !</title>
<style type="text/css">
#sommaire
{
position:absolute;
background-color:cyan;
left:10px;
width:100px;
}
#page
{
position:absolute;
background-color:#AAAAAA;
left : 200px;
width:500px;
height:500px;
}
</style>
</head>
<body>
<div id="sommaire">
<h3>Sommaire</h3>
<?php for($i = 1; $i <= 4; $i++) :?>
<a href="index.php?page=<?php echo $i ?>">Page <?php echo $i ?></a><br/>
<?php endfor ?>
</div>
<div id="page">
<?php
if (isset($_GET['page'])) $numero=$_GET['page']; else $numero='1';
require 'page'.$numero.'.html';
?>
</div>
</body>
</html>
fichier page1.html
<h1>Page 1</h1>
bla bla bla
fichier page2.html
<h1>Page 2</h1>
ble ble ble
fichier page3.html
<h1>Page 3</h1>
bli bli bli
fichier page4.html
<h1>Page 4</h1>
blo blo blo
Explications
modifier- Dans ce programme, la page est découpée en 2 parties : à gauche une partie ayant comme id sommaire et à droite une page ayant comme id page.
- La partie « sommaire » utilise le style #sommaire de la feuille de style et la partie « page », le style #page.
- Le sommaire est constitué de 4 liens hypertextes appelant respectivement index.php?page=1, index.php?page=2, index.php?page=3 et index.php?page=4. Lorsqu'on met un point d'interrogation après l'URL d'une page, cela signifie qu'on donne une valeur à un paramètre et qu'on envoie cette information au serveur par la méthode GET. Lorsqu'on clique sur l'un des 4 liens hypertextes, on appelle à chaque fois la même page index.php mais à chaque fois la valeur du paramètre nommé
page
change : il vaut 1, 2, 3 ou 4 selon le lien cliqué. - Dans la partie « page » de index.php, la valeur du paramètre
page
est récupérée de l'url cliquée en écrivant$_GET['page']
. Ce paramètre peut très bien ne pas exister : ceci a lieu notamment la première fois qu'on appelle notre page index.php. Dans ce cas, la fonction isset() permet de savoir si une variable existe. La ligne
if (isset($_GET['page']))$numero=$_GET['page']; else $numero='1';
- récupère la valeur du paramètre page et la place dans la variable $numero. Si ce paramètre n'existe pas $numero vaut 1.
- La fonction
require'nom_du_fichier';
permet d'insérer un fichier à cet endroit dans le code. Le server insère donc page1.html ou page2.html ou page3.html ou page4.html en fonction de la valeur du paramètre page (et de la variable numero). - Notre sommaire est terminé.
Exemples/Formulaire
Interaction avec un formulaire
modifierLes principaux concepts
modifierL'interaction entre une application en PHP et un utilisateur peut s'effectuer par des liens hypertextes ou par l'envoi d'un formulaire. C'est ce cas dernier que nous allons étudier ici.
Le formulaire comporte une balise form qui précise que la méthode utilisée pour envoyer le contenu du formulaire au programme en PHP est la méthode POST. Elle précise également l'action du formulaire, c'est-à-dire à quelle adresse envoyer le contenu du formulaire pour son traitement. Dans notre exemple, après un clic sur le bouton d'envoi, le formulaire déclenchera l'exécution du programme go.php. Le formulaire est composé de 3 éléments graphiques : 2 champs de type texte nommés respectivement nom et prénom et un bouton sur lequel il est écrit envoyer le formulaire. Le formulaire invite donc l'utilisateur à entrer un nom et un prénom et à cliquer sur le bouton « envoyer le formulaire ».
Le programme go.php doit récupérer les valeurs contenues dans le formulaire : pour récupérer la valeur du champ nom, il faut écrire $_POST['nom']
. De la même manière, pour récupérer la valeur du champ prénom, il faut écrire $_POST['prenom']
. Après un clic sur le bouton « envoyer le formulaire », une autre page s'affiche. Elle contient un message contenant "Bienvenue à" suivi du prénom et du nom définis dans le formulaire rempli. Le programme a bien récupéré la valeur des différents champs du formulaire.
Le programme en php
modifierLe fichier index.html :
<!DOCTYPE html PUBLIC "-//DTD XHTML 2.0 Transitional//EN"
"http://www.org/TR/xhtml/xhtml-transitional.dtd">
<form action="go.php" method="post">
<p>Votre nom : <input type="text" name="nom" /></p>
<p>Votre prénom : <input type="text" name="prenom" /></p>
<p><input type="submit" value="envoyer le formulaire" /></p>
</form>
Le fichier go.php :
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 1.0 Transitional//EN"
"http://www.org/xhtml/html-transitional.dtd">
<?php
$nom = $_POST['nom'];
$prenom = $_POST['prenom'];
echo "<h3>Bienvenue à ".$prenom.' '.$nom,"</h3>";
echo "<p><a href='index.html'>Retour au formulaire</a></p>";
?>
Captures d'écran
modifier
Exemples/BD 1
Afficher le résultat d'une requête
modifierPrésentation
modifierDans cet exemple, on va créer une application qui extrait des données à partir d'une base de données et qui affiche le résultat à l'écran. Notre programme va afficher une liste d'employés d'une entreprise imaginaire : chaque employé est défini par un nom, un prénom et un salaire. La base de données utilisée sera une base mysql. Les données seront dans une table nommée employe. La table employé possède des champs NOM, PRENOM et SALAIRE. La table employé possède de plus un identifiant nommé ID qui est un entier (BIGINT) autoincrémenté et qui est une clé primaire de la table.
Nous étudierons une première version du programme et dans un second temps nous améliorerons le programme en utilisant la méthode GET lors d'une deuxième version.
Descriptif du site
modifier- La première page affiche 3 liens hypertextes :
- un lien pour afficher la liste complète des employés,
- un lien pour afficher la liste dans l'ordre alphabétique,
- un lien pour afficher par salaire décroissant.
- Sur chacune des pages, on affiche la liste et il y a un lien pour revenir à la première page.
Création de la base
modifierLes requêtes suivantes permettent la création et l'initialisation des données dans notre table :
Fichier BD.sql :
CREATE TABLE `employe` (
`ID` BIGINT NOT NULL AUTO_INCREMENT ,
`NOM` VARCHAR( 20 ) ,
`PRENOM` VARCHAR( 20 ) ,
`SALAIRE` DOUBLE DEFAULT '0',
PRIMARY KEY ( `ID` )
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Dupond', 'Marcel', '8000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Martin', 'Xavier', '4000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Gogol', 'Henri', '3000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Hugo', 'Victor', '2000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Gali', 'Daniel', '6000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Martin', 'Georges', '9000'
);
La table créée par ces requêtes est la suivante :
ID | NOM | PRENOM | SALAIRE |
1 | Dupond | Marcel | 8000 |
2 | Martin | Xavier | 4000 |
3 | Gogol | Henri | 3000 |
4 | Hugo | Victor | 2000 |
5 | Gali | Daniel | 6000 |
6 | Martin | Georges | 9000 |
Configuration de la base de données
modifierLa table employe appartient à la base de données toto. L'administrateur de cette base est root. Il n'a pas de mot de passe. Attention ceci est une configuration type utilisée par défaut pour la plateforme de développement EasyPHP : il est vivement recommandé de mettre un vrai mot de passe pour une plateforme en exploitation.
Les requêtes utilisées
modifier- Pour afficher la liste des employés, on utilisera la requête :
SELECT * FROM employe
- Pour afficher la liste des employés par ordre alphabétique, on utilisera la requête :
SELECT * FROM employe ORDER BY NOM, PRENOM
- Pour afficher la liste des employés par salaire décroissant, on utilisera la requête :
SELECT * FROM employe ORDER BY SALAIRE DESC
Comment accéder en php à une base mysql
modifierLes différentes étapes à respecter seront les suivantes :
- récupérer les informations suivantes :
- le nom de la machine qui héberge le serveur de la base de données (ici localhost)
- le nom de la base de données (ici toto)
- le nom de l'utilisateur de la base de données (ici root)
- le mot de passe de cet utilisateur (ici le mot de passe est vide).
- se connecter au serveur de base de données
- sélectionner la base de données (ici toto) sur ce serveur.
- créer la requête dans une chaîne de caractères
- envoyer la requête à la base de données et récupérer le résultat
- fermer la connexion avec le serveur de base de données
- transformer le résultat de la requête en HTML.
Étape 1 : les paramètres spécifiques au site
modifierPour réaliser la première étape nous allons créer un fichier params.php qui contient 4 variables $host (le nom de la machine qui héberge la base de données, $user (le nom de l'utilisateur de la base de données), $password (le mot de passe de cet utilisateur) et $base le nom de la base de données. Ces paramètres changent en fonction de la plateforme que vous utilisez. Il est intéressant d'isoler ces paramètres dans un fichier séparé, que ce soit au niveau de la sécurité ou pour porter l'application sur une autre plateforme. Il faut se renseigner au niveau de l'hébergeur sur les caractéristiques de la plateforme utilisée. Les paramètres suivants sont ceux par défaut si on utilise EasyPHP. On a juste créé une base de données appelée toto.
<?php
$host='localhost';
$user='root';
$password='';
$base='toto';
?>
Pour récupérer, ces paramètres il suffit d'inclure ce fichier en utilisant le mot clé require. On écrira require'params.php' . Le fichier sera alors inclus dans le programme en php et le programmeur utilisera les 4 variables.
Étape 2 : connexion à la base de données
modifierLa fonction mysql_connect($host,$user,$password) permet de se connecter au serveur de base de données mysql situé sur la machine nommée $host en utilisant le nom d'utilisateur $user ayant comme mot de passe $password. Cette fonction renvoie le booléen true si la connexion est établie et false sinon.
Étape 3 : sélectionner la base de données
modifierUn serveur de base de données peut héberger plusieurs bases de données (une base de données est un ensemble de tables). La fonction mysql_select_db($base) permet de sélectionner la base de données à laquelle on va envoyer les requêtes. Cette fonction renvoie true si tout s'est bien passé et false sinon.
Étape 4 : création de la requête
modifierIl faut ensuite créer la requête dans une chaîne de caractères. Ici la requête va être simple, par exemple $query='SELECT * FROM employe'; : on met la requête dans la variable $query. Parfois, il faudra concaténer des chaînes de caractères pour construire la requête--.
Étape 5 : envoyer la requête et récupérer le résultat
modifierPour envoyer la requête au serveur, il faut utiliser la fonction $r=mysql_query($query); qui envoie la requête $query et récupère le résultat dans $r.
Étape 6 : fermer la connexion avec le serveur de base de données
modifierPour fermer la connexion avec le serveur de base de données, il suffit d'appeler la fonction mysql_close();.
Étape 7 : générer du HTML à partir du résultat de la requête
modifierSupposons que le résultat d'une requête soit contenu dans la variable $r. Il est souvent intéressant de parcourir un à un tous les enregistrements contenus dans $r. L'appel de fonction $a=mysql_fetch_object($r) permet de récupérer un à un tous les enregistrements dans la variable $a. Lors du premier appel, on récupère le premier enregistrement dans $a, lors du second appel, on récupère le second enregistrement et ainsi de suite jusqu'au dernier enregistrement. Si on appelle alors une nouvelle fois cette fonction, elle va renvoyer le booléen false. Ceci permet donc de traiter un à un tous les enregistrements en écrivant
while ($a=mysql_fetch_object($r)) {
...
}
Lors de l'exécution de ce while $a va prendre toutes les valeurs des différents enregistrements contenus dans $r, du premier au dernier. À partir de l'objet $a, on pourra récupérer le champs NOM de cet enregistrement en écrivant $a->NOM. On peut ainsi récupérer la valeur de chaque champ d'un enregistrement.
Il suffit donc maintennant de parcourir chaque enregistrement et d'afficher du HTML en utilisant la commande echo
à partir des différents champs des enregistrements.
Les pages du site (version 1)
modifierFichier index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Afficher le résultat d'une requête</title>
</head>
<body>
<h1> Choisissez dans le menu ci-dessous</h1>
<a href="liste1.php">Afficher la liste des employés</a><br/>
<a href="liste2.php">Afficher la liste des employés par ordre alphabétique</a><br/>
<a href="liste3.php">Afficher la liste des employés par salaire décroissant</a><br/>
</body>
</html>
Fichier params.php
<?php
$host='localhost';
$user='root';
$password='';
$base='toto';
?>
Fichier liste1.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Afficher la liste des employés</title>
</head>
<body>
<h1>Afficher la liste des employés</h1>
<?php
require 'params.php';
mysql_connect($host,$user,$password) or die('Erreur de connexion au SGBD.');
mysql_select_db($base) or die('La base de données n\'existe pas');
$query='SELECT * FROM employe';
$r=mysql_query($query);
mysql_close();
echo'<table><tr><td>NOM</td><td>PRENOM</td><td>SALAIRE</td></tr>';
while ($a=mysql_fetch_object($r)) {
$nom=$a->NOM;
$prenom=$a->PRENOM;
$salaire=$a->SALAIRE;
echo"<tr><td>$nom</td><td>$prenom</td><td>$salaire</td></tr>";
}
echo '</table>';
?>
<br/>
<br/>
<a href="index.html">Retour à la page d'accueil</a>
</body>
</html>
Fichier liste2.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Afficher la liste des employés par ordre alphabétique</title>
</head>
<body>
<h1>Afficher la liste des employés par ordre alphabétique</h1>
<?php
require 'params.php';
mysql_connect($host,$user,$password) or die('Erreur le connexion au SGBD.');
mysql_select_db($base) or die('La base de données n\'existe pas');
$query='SELECT * FROM employe ORDER BY NOM, PRENOM';
$r=mysql_query($query);
mysql_close();
echo'<table><tr><td>NOM</td><td>PRENOM</td><td>SALAIRE</td></tr>';
while ($a=mysql_fetch_object($r)) {
$nom=$a->NOM;
$prenom=$a->PRENOM;
$salaire=$a->SALAIRE;
echo"<tr><td>$nom</td><td>$prenom</td><td>$salaire</td></tr>";
}
echo '</table>';
?>
<br/>
<br/>
<a href="index.html">Retour à la page d'accueil</a>
</body>
</html>
Fichier liste3.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Afficher la liste des employés par salaire décroissant</title>
</head>
<body>
<h1>Afficher la liste des employés par ordre alphabétique</h1>
<?php
require 'params.php';
mysql_connect($host,$user,$password) or die('Erreur le connexion au SGBD.');
mysql_select_db($base) or die('La base de données n\'existe pas');
$query='SELECT * FROM employe ORDER BY SALAIRE DESC';
$r=mysql_query($query);
mysql_close();
echo'<table><tr><td>NOM</td><td>PRENOM</td><td>SALAIRE</td></tr>';
while ($a=mysql_fetch_object($r)) {
$nom=$a->NOM;
$prenom=$a->PRENOM;
$salaire=$a->SALAIRE;
echo"<tr><td>$nom</td><td>$prenom</td><td>$salaire</td></tr>";
}
echo '</table>';
?>
<br/>
<br/>
<a href="index.html">Retour à la page d'accueil</a>
</body>
</html>
Les pages du site (version 2)
modifierLorsqu'on étudie les fichiers liste1.php, liste2.php et liste3.php, on s'aperçoit qu'ils sont extrêmement proches : on va donc réécrire l'application en écrivant un seul fichier liste.php et en le paramétrant grâce à la méthode GET.
Le nouveau fichier index.html devient donc :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Afficher le résultat d'un requête</title>
</head>
<body>
<h1> Choisissez dans le menu ci-dessous</h1>
<a href="liste.php?l=1">Afficher la liste des employés</a><br/>
<a href="liste.php?l=2">Afficher la liste des employés par ordre alphabétique</a><br/>
<a href="liste.php?l=3">Afficher la liste des employés par salaire décroissant</a><br/>
</body>
</html>
On constate donc que les liens hypertextes, par exemple liste.php?l=1 appelle le même fichier liste.php en lui donnant un paramètre par la méthode GET, ici l=1, ce qui permet de paramétrer le fichier.
Remarque importante : lorsqu'on utilise ce genre de liens hypertextes, il faut toujours se poser la question : que se passe-t-il si l'utilisateur accède à la page liste.php sans passer de paramètres ou alors en écrivant liste.php?l=4 ? Le programmeur doit garantir que dans un tel cas, le site reste cohérent et que ceci n'engendre pas de failles de sécurité.
Le fichier liste.php devient :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Afficher la liste des employés</title>
</head>
<body>
<h1>Afficher la liste des employés</h1>
<?php
require 'params.php';
mysql_connect($host,$user,$password) or die('Erreur le connexion au SGBD.');
mysql_select_db($base) or die('La base de données n\'existe pas');
if(isset($_GET['l']))
$l=$_GET['l'];
else $l=1;
if($l==1)$query='SELECT * FROM employe';
else if($l==2)$query='SELECT * FROM employe ORDER BY NOM, PRENOM';
else $query='SELECT * FROM employe ORDER BY SALAIRE DESC';
$r=mysql_query($query);
mysql_close();
echo'<table><tr><td>NOM</td><td>PRENOM</td><td>SALAIRE</td></tr>';
while ($a=mysql_fetch_object($r)) {
$nom=$a->NOM;
$prenom=$a->PRENOM;
$salaire=$a->SALAIRE;
echo"<tr><td>$nom</td><td>$prenom</td><td>$salaire</td></tr>";
}
echo '</table>';
?>
<br/>
<br/>
<a href="index.html">Retour à la page d'accueil</a>
</body>
</html>
Le programmeur teste en utilisant isset($_GET['l']) pour savoir si la variable l a été passée par la méthode GET et il récupère sa valeur dans la variable $l. Si ce paramètre n'existe pas, $l est fixé à la valeur 1. On construit ensuite la requête $query en fonction de la valeur de $l. Cette version du programme est plus simple mais îil faut toutefois faire très attention à la sécurité dans un tel cas.
Exemples/BD 2
Modifier une table
modifierLes fichiers de l'application
modifierLe fichier BD.sql
modifierCREATE TABLE `employe` (
`ID` BIGINT NOT NULL AUTO_INCREMENT ,
`NOM` VARCHAR( 20 ) ,
`PRENOM` VARCHAR( 20 ) ,
`SALAIRE` DOUBLE DEFAULT '0',
PRIMARY KEY ( `ID` )
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Dupond', 'Marcel', '8000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Martin', 'Xavier', '4000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Gogol', 'Henri', '3000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Hugo', 'Victor', '2000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Gali', 'Daniel', '6000'
);
INSERT INTO `employe` ( `ID` , `NOM` , `PRENOM` , `SALAIRE` )
VALUES (
'', 'Martin', 'Georges', '9000'
);
Le fichier index.php
modifier<?php
session_start();
require_once'Appli/appli.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Afficher le résultat d'un requête</title>
</head>
<body>
<h1> Choisissez dans le menu ci-dessous</h1>
<a href="liste.php?l=1">Afficher la liste des employés</a><br/>
<a href="liste.php?l=2">Afficher la liste des employés par ordre alphabétique</a><br/>
<a href="liste.php?l=3">Afficher la liste des employés par salaire décroissant</a><br/>
<a href="add.php">Ajouter un employé</a><br/>
<a href="delete.php">Supprimer un employé</a><br/>
<?php
if(is_admin())echo '<a href="disconnect.php">Se déconnecter</a><br/>';
?>
</body>
</html>
Le fichier liste.php
modifier<?php
session_start();
require_once'Appli/appli.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Afficher la liste des employés</title>
</head>
<body>
<h1>Afficher la liste des employés</h1>
<?php
if(isset($_GET['l'])) {
$l=$_GET['l'];
} else {
$l=1;
}
liste($l,false);
?>
<br/>
<br/>
<a href="index.php">Retour à la page d'accueil</a>
</body>
</html>
Le fichier add.php
modifier<?php
session_start();
require_once'Appli/appli.php';
$_SESSION['add_NOM']='';
$_SESSION['add_PRENOM']='';
$_SESSION['add_SALAIRE']='';
$_SESSION['add_ERROR']=0;
if (is_admin()) {
require 'Appli/addForm.php';
} else {
$_SESSION['connect_target']='Appli/addForm.php';
$_SESSION['connect_error']=0;
$_SESSION['connect_login']='';
require 'Appli/connectForm.php';
}
?>
Le fichier addAction.php
modifier<?php
session_start();
require_once'Appli/appli.php';
if (is_admin()) {
if(isset($_POST['add'])) {
$nom=$_POST['nom'];
$prenom=$_POST['prenom'];
$salaire=$_POST['salaire'];
$r=add_liste($nom,$prenom,$salaire);
if ($r==0) {
echo '<meta http-equiv="Refresh" content="0;URL=index.php">';
} else {
$_SESSION['add_NOM']=$nom;
$_SESSION['add_PRENOM']=$prenom;
$_SESSION['add_SALAIRE']=$salaire;
$_SESSION['add_ERROR']=$r;
require 'Appli/addForm.php';
}
} else {
echo '<meta http-equiv="Refresh" content="0;URL=index.php">';
}
}
?>
Le fichier connect.php
modifier<?php
session_start();
require_once'Appli/appli.php';
if (isset($_POST['connect'])) {
if (isset($_POST['login'])) {
$login = $_POST['login'];
} else {
$login = '';
}
if (isset($_POST['password'])) {
$password = $_POST['password'];
} else {
$password = '';
}
}
if (connect($login, $password)) {
$target=$_SESSION['connect_target'];
} else {
$target = 'Appli/connectForm.php';
$_SESSION['connect_login'] = $login;
$_SESSION['connect_error'] = 1;
} else {
$target='index.php';
}
require $target;
?>
Le fichier disconnect.php
modifier<?php
session_start();
require_once'Appli/appli.php';
disconnect();
?>
<meta http-equiv="Refresh" content="0;URL=index.php">
Le fichier delete.php
modifier<?php
session_start();
require_once'Appli/appli.php';
if (is_admin()) {
require 'Appli/deleteForm.php';
} else {
$_SESSION['connect_target']='Appli/deleteForm.php';
$_SESSION['connect_error']=0;
$_SESSION['connect_login']='';
require 'Appli/connectForm.php';
}
?>
Le fichier deleteAction.php
modifier<?php
session_start();
require_once'Appli/appli.php';
if (is_admin()) {
$ID = $_GET['ID'];
delete($ID);
require 'Appli/deleteForm.php';
} else {
echo '<meta http-equiv="Refresh" content="0;URL=index.php">';
}
?>
Le fichier Appli/.htaccess
modifierdeny from all
Le fichier Appli/params.php
modifier<?php
$host = 'localhost';
$user = 'root';
$password = '';
$base = 'toto';
$admin_login = 'admin';
$admin_password = 'aligator';
?>
Le fichier Appli/appli.php
modifier<?php
function is_admin()
{
return isset($_SESSION['admin']) and ($_SESSION['admin']==true);
}
function connect($login,$user_password)
{
$r=false;
require 'params.php';
if ($login==$admin_login and $user_password==$admin_password) {
$r=true;$_SESSION['admin']=true;}
return $r;
}
function disconnect()
{
$_SESSION['admin'] = false;
}
function liste($l,$editable)
{
require 'params.php';
mysql_connect($host,$user,$password) or die('Erreur le connexion au SGBD.');
mysql_select_db($base) or die('La base de données n\'existe pas');
if ($l==1) {
$query='SELECT * FROM employe';
} else if ($l==2) {
$query = 'SELECT * FROM employe ORDER BY NOM, PRENOM';
} else {
$query = 'SELECT * FROM employe ORDER BY SALAIRE DESC';
}
$r=mysql_query($query);
mysql_close();
if (editable==false) {
echo'<table><tr><td>NOM</td><td>PRENOM</td><td>SALAIRE</td></tr>';
} else {
echo'<table><tr><td>NOM</td><td>PRENOM</td><td>SALAIRE</td><td> </td></tr>';
}
while ($a=mysql_fetch_object($r)) {
$nom=$a->NOM;
$prenom=$a->PRENOM;
$salaire=$a->SALAIRE;
$ID=$a->ID;
if ($editable==false) {
echo"<tr><td>$nom</td><td>$prenom</td><td>$salaire</td></tr>";
} else {
echo"<tr><td>$nom</td><td>$prenom</td><td>$salaire</td><td><a href=\"deleteAction.php?ID=$ID\">SUPPRIMER</a></td></tr>";
}
echo '</table>';
}
function add_liste($nom,$prenom,$salaire)
{
$r = 0;
if ($nom == '') {
$r = 1;
} else if ($prenom == '') {
$r = 2;
} else if ($salaire == '') {
$r = 3;
} else {
require 'params.php';
mysql_connect($host,$user,$password) or die('Erreur le connexion au SGBD.');
mysql_select_db($base) or die('La base de données n\'existe pas');
$query="INSERT INTO employe (NOM, PRENOM, SALAIRE) VALUES ('$nom', '$prenom', '$salaire')";
mysql_query($query);
mysql_close();
}
return $r;
}
function delete($ID)
{
require 'params.php';
mysql_connect($host,$user,$password) or die('Erreur le connexion au SGBD.');
mysql_select_db($base) or die('La base de données n\'existe pas');
$query="DELETE FROM employe WHERE ID=$ID";
mysql_query($query);
mysql_close();
}
?>
Le fichier Appli/connectForm.php
modifier<?php
session_start();
require_once'Appli/appli.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Connexion</title>
</head>
<body>
<h1> Connexion</h1>
<form method="post" action="connect.php">
<table>
<?php
$value=$_SESSION['connect_login'];
echo "<tr><td><b>LOGIN</b></td> <td><input type=\"text\" name=\"login\" value=\"$value\"/></td></tr>";
?>
<tr><td><b>MOT DE PASSE</b></td> <td><input type="password" name="password"/></td></tr>
<tr><td colspan="2"><input type="submit" value="Se connecter" name="connect"><input type="submit" value="Annuler" name="cancel"></td></tr>
</table>
</form>
<br/>
<?php
$error= $_SESSION['connect_error'];
if($error==1)echo'Erreur de connexion';
$_SESSION['connect_error']=0;
?>
<p/>
<a href="index.php">Retour</a>
</body>
</html>
Le fichier Appli/addForm.php
modifier<?php
session_start();
require_once'Appli/appli.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Ajouter</title>
</head>
<body>
<h1>Ajouter un employé</h1>
<form method="post" action="addAction.php">
<table>
<?php
$nom=$_SESSION['add_NOM'];
$prenom=$_SESSION['add_PRENOM'];
$salaire=$_SESSION['add_SALAIRE'];
echo '<tr><td><b>NOM</b></td> <td><input type="text" name="nom" value="'.$nom.'"/></td></tr>';
echo '<tr><td><b>PRENOM</b></td> <td><input type="text" name="prenom" value="'.$prenom.'"/></td></tr>';
echo '<tr><td><b>SALAIRE</b></td> <td><input type="text" name="salaire" value="'.$salaire.'"/></td></tr>';
?>
<tr><td colspan="2"><input type="submit" value="Ajouter" name="add"><input type="submit" value="Annuler" name="cancel"></td></tr>
</table>
</form>
<br/>
<?php
$error=$_SESSION['add_ERROR'];
if ($error==1) echo'ERREUR : le nom est vide';
if ($error==2) echo'ERREUR : le prénom est vide';
if ($error==3) echo'ERREUR : le salaire est vide';
?>
<p/>
<a href="index.php">Retour</a>
</body>
</html>
Le fichier Appli/deleteForm.php
modifier<?php
session_start();
require_once'Appli/appli.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Suppression</title>
</head>
<body>
<h1> Suppression</h1>
<?php
liste(2,true);
?>
<p/>
<a href="index.php">Retour</a>
</body>
</html>
Exemples/Livre
Créer le livre
modifierTout d'abord, il faut créer le livre d'or, c'est-à-dire une table où seront stockés les messages.
Voici un exemple de table, avec un identifiant auto-incrémenté qui sera la clé (pour pouvoir identifier de manière unique chaque message), puis un champ pour le nom, courriel, date, ... et bien sûr le message.
CREATE TABLE `livre` (
`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
`nom` VARCHAR( 128 ),
`email` VARCHAR( 128 ),
`date` DATE,
`message` TEXT NOT NULL,
PRIMARY KEY( `id` )
);
Ajouter un message
modifierVoici la requête SQL qui permet d'insérer les données dans la table:
INSERT INTO `livre` ( `nom` , `email` , `date` , `message` )
VALUES ( `UnNom` , `Une@` , `xx/xx/xxxx` , `blalblablabla...` );
Saisie des données
modifierVoici un exemple de formulaire servant à récupérer les données saisies par l'utilisateur pour les faire suivre vers 'ajout_message.php' qui les traitera. La méthode utilisée est POST, bien que GET marche aussi, mais POST possède l'avantage de ne pas rendre visible dans l'URL les paramètres passés, ce qui rend cette méthode plus « propre » et plus claire.
fichier formulaire.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Formulaire</title>
</head>
<body>
<form name="formulaire" method="post" action="ajout_message.php">
<label for="nom">Nom :</label><input type="text" id="nom" name="nom" /><br />
<label for="email">Courriel :</label><input type="text" id="email" name="email" /><br />
<label for="message">Votre message :</label><br />
<textarea id="message" name="message" cols="50" rows="10">Message par défaut</textarea><br />
<input type="submit" />
</form>
</body>
</html>
Traitement des données
modifierLe fichier « ajout_message.php » va récupérer et traiter les données saisies par l'utilisateur puis effectuer une insertion dans la base (c'est-à-dire ajouter le message dans le livre d'or). Ces données portent le nom qui leur a été donné dans le formulaire, précédé d'un '$' (représente une variable en PHP). Il est fortement conseillé d'effectuer des tests de saisie à ce niveau, pour vérifier que l'utilisateur a rempli correctement les champs (champs non vides, adresse email correcte,...).
fichier params.php
<?php
$host='localhost';
$user='root';
$password='';
$base='toto';
?>
fichier ajout_message.php
<?php
isset($_POST['nom']) or die('Pas de paramètres en entrée');
//récupération des données entrées par l'utilisateur
$nom = $_POST['nom'];
$message = $_POST['message'];
$email= $_POST['email'];
//vérification des données entrées par l'utilisateur
if (($nom == "")||($message == ""))
die("Paramètres incorrects.
<a href='formulaire.html'>Cliquez ici pour revenir au formulaire</a>".mysql_error());
//connexion à la base
require 'params.php';
$link=mysql_connect($host,$user,$password) or die('Erreur de connexion au SGBD.');
mysql_select_db($base,$link) or die('La base de données n\'existe pas');
//insertion du message
$date=date("Y-m-d"); // on récupère la date
$message = htmlspecialchars($message,ENT_QUOTES); //convertit les caractères spéciaux
$sql="INSERT INTO `livre` ( `id` , `nom` , `email` , `date` , `message` ) VALUES ( NULL , '$nom', '$email', '$date', '$message' );";
$res=mysql_query($sql,$link);
mysql_close($link);
if ($res==NULL) die('Erreur lors de l\'écriture du message'.mysql_error());
print "Message correctement ajouté.<br />Merci d'avoir pris le temps de remplir ce livre d'or";
?>
La fonction htmlspecialchar()
permet de convertir les caractères spéciaux qui pourrait être interprétés comme du code HTML par un navigateur.
Il existe d'autres fonctions, comme htmlentities()
, qui permet en plus de spécifier les caractères de remplacement, ou n12br()
qui permet de remplacer les retours chariots par le caractère HTML de retour à la ligne (br
).
Récupérer les messages et les faire afficher
modifierCette partie est beaucoup plus simple à réaliser que la partie précédente.
Voici la requête SQL qui permet de récupérer toutes les informations de la table dans la base de donées:
SELECT * FROM `livre` ORDER BY `id`
Les messages seront alors triés en fonction du champ `id`, et, par défaut, par ordre croissant, donc plus du plus ancien au plus récent. Pour les classer dans l'ordre décroissant, il faut rajouter l'instruction DESC, ce qui donne la requête suivante :
SELECT * FROM `livre` ORDER BY `id` DESC
Après, il suffit d'afficher les informations obtenues grâce à la requête.
fichier affiche_message.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Livre d'or</title>
</head>
<body>
<?php
//connexion à la base
require 'params.php';
$link=mysql_connect($host,$user,$password) or die('Erreur de connexion au SGBD.');
mysql_select_db($base,$link) or die('La base de données n\'existe pas');
//récupération des messages
$sql="SELECT * FROM `livre` ORDER BY `id` DESC";
$rep=mysql_query($sql,$link);
mysql_close($link);
while ( $ligne = mysql_fetch_array($rep))
{
//on récupère les valeurs des diffèrent champs pour chaque message
$nom=$ligne['nom'];
$email=$ligne['email'];
$date=$ligne['date'];
$message=$ligne['message'];
/* on remplace les retours chariots '\n'
par le symbole de retour à la ligne en HTML : <br /> */
$message=nl2br($message);
//on affiche ses valeurs dans un tableau
print "<table>
<tr><th>Par :<a href=mailto:$email>$nom</a>/th>
<th>Le : $date</th></tr>
<tr><td colspan='2'>$message</td></tr>
</table>";
}
?>
</body>
</html>
Ceci n'est que le strict minimum pour faire un livre d'or. D'autres fonctionnalités, comme le nombre d'affichage par page, peuvent être ajoutées.
Exemples/MiniForum
Faire un mini forum de discussion
modifierBon nombre de forums sur internet ont repris des opensources et les ont customisé. Dans cet article, nous verrons comment simplement opposer de meilleures solutions, plus souples et personnelles. Ce chapitre traitera du programme à fournir pour cette solution sans proposer de sécurisation.
Introduction
modifierVoici un forum modulaire, universel, illimité, fonctionnel et très réactif mais à sécuriser avec MySQL en backend et HTML en frontend.
Architecture
modifierÀ bâtir en MVC, le forum est segmenté comme :
- ./index.php
- ./config.inc.php
// STATICAL MODULES
- ./Objects/
- ./Objects/frameset.inc.php // main frameset
- ./Objects/mainPage.inc.php // main pages content
// FUNCTIONNAL MODULES
- ./Libraries/
Déploiement
modifierLa méthodologie processus unifié fonctionne par itérations successives et augmentation, amélioration du code. On obtient donc pour chaque module différentes itérations représentatives de l'état d'avancement de l'application. L'état 0 ou incrément 0 étant l'ébauche et incrément 1, la première itération de l'applicatif.
<?php
/* */
define('OBJ', './Objects/');
define('LIB', './Libraries/');
$cnx = array('host'=>'localhost','user'=>'root','db'=>'miniForum','pass'=>'');
?>
increment 0 : index.php
<?php
/* Mini Forum - main entry
This is a mini forum
version : 1.0
date : 2009 07 28
author : zulul
*/
require_once "./config.inc.php";
require_once OBJ . "frameset.inc.php";
echo $_mainSet;
?>
increment 1 : index.php
<?php
/* Mini Forum - main entry
This is a mini forum
version : 1.0
date : 2009 07 28
author : zulul
*/
@session_start();
require_once "./config.inc.php";
require_once OBJ . "frameset.inc.php";
// lib
require_once LIB . "Connectivity.cla.php";
require_once LIB . "Request.cla.php";
require_once LIB . "utils.fnc.php";
// autho
if(chk_usr($cnx))
$_SESSION['autho'] = 1;
echo $_mainSet;
?>
increment 2 : index.php
<?php
/* Mini Forum - main entry
This is a mini forum
version : 1.0
date : 2009 07 28
author : zulul
*/
@session_start();
require_once "./config.inc.php";
// lib
require_once LIB . "Connectivity.cla.php";
require_once LIB . "Request.cla.php";
require_once LIB . "utils.fnc.php";
// page listener
require_once OBJ . "listener.inc.php";
// autho
if(chk_usr($cnx))
{
$_SESSION['autho'] = 1;
}
// controling application
require_once OBJ . "controler.inc.php";
// variing frameset call
require_once OBJ . "frameset.inc.php";
# OUTPUT
echo $_mainSet;
?>
Objects
modifier
<?php
/* main page
*/
require_once OBJ."mainPage.inc.php";
$_mainSet = <<<EOPAGE
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//FR" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html lang="fr" xml:lang="fr">
<head>
{$_content['title']}
{$_content['meta']}
{$_content['style']}
</head>
<body>
{$_content['main']}
</body>
</html>
EOPAGE;
?>
increment 3 : frameset.inc.php
<head>
{$_content['title']}
{$_content['meta']}
{$_content['script']}
{$_content['style']}
</head>
increment 0 : mainPage.inc.php
<?php
/* Main page content
*/
$title = ':: TITLE ::';
$meta = '';
$style = '';
// head content
$_content['title'] = '<title>'.$title.'</title>';
$_content['meta'] = '<meta>'.$meta.'</meta>';
$_content['style'] = '<style>
body
{
margin:0 0 0 0;
font-family:tahoma;
font-size:10px;
}
table td
{
border:1px solid;
padding:5px;
margin:5px;
}
.grey
{
background-color:#eee;
}
.creeper
{
border:1px solid;
height:200px;
}
</style>';
// content contents
$_content['authorize'] = '
<div id="cAuthorize">
<table align="center" width="100%">
<tr><td class="grey">pseudo <input type="text" value=""/></td></tr>
<tr><td class="grey">passwd <input type="text" value=""/></td></tr>
<tr><td class="grey"><input type="button" value="submit()"/></td></tr>
</table>
</div>
';
// content contents
$_content['skyCreeper'] = '
<div>
<div class="creeper">
<br/>this is the content
<br/>this is thec content
<br/>this is the content
</div>
</div>';
$_content['pager'] = <<<EOPAGE
<div id="cAuthorize">
<table width="100%">
<tr valign="top"><td width="50%" height="250" class="grey">:: 1 ::</td><td class="grey">:: 2 ::</td></tr>
<tr valign="top"><td width="50%" height="250" class="grey">:: 3 ::</td><td class="grey">:: 4 ::</td></tr>
</table>
</div>
EOPAGE;
// body contents
$_content['main'] = <<<EOPAGE
<table width="99%" height="600">
<!-- its still better to stay with table instead of divs -->
<tr>
<td height="18" colspan="2" width="99%" align="center" class="grey">
:: THIS IS TOP CONTENT ::
</td>
</tr>
<tr valign="top">
<td width="20%">
{$_content['authorize']}
{$_content['skyCreeper']}
</td>
<td width="79%">
{$_content['pager']}
</td>
</tr>
<tr>
<td height="18" colspan="2" width="99%" align="center" class="grey">:: THIS IS BOTTOM CONTENT ::</td>
</tr>
</table>
EOPAGE;
?>
increment 1 : listener.inc.php
<?php
/* application listener
*/
/* flag cases
*/
switch(true)
{
case(@$_REQUEST['aut']=='0'):
//action disconnect
unset($_SESSION['autho'],$_SESSION['userMod']);
break;
/*
case ():
break;
*/
}
?>
increment 1 : controler.inc.php
<?php
/* application controler
*/
# INIT
/* admin and mod status control
*/
if(@$_REQUEST['aut']!='0')
{
// moderating user
mod();
}
# PREPARE
/* cases
*/
if(@$_SESSION['userMod'])
{//
if(preg_match('/[0]/',$_SESSION['userMod']['adm']))
$_mod[0] = 'superAdmin';
if(preg_match('/[1]/',$_SESSION['userMod']['adm']))
$_mod[0] = 'Admin';
if(preg_match('/[0]/',$_SESSION['userMod']['mod']))
$_mod[1] = 'superModerator';
if(preg_match('/[1]/',$_SESSION['userMod']['mod']))
$_mod[1] = 'Moderator';
}
?>
increment 1 : mainPage.inc.php
- À cette itération, l'authentification du user est fonctionnelle.
- Le snippet content['authorize'] change de mode.
...
// content contents
$_content['authorize'] = (!@$_SESSION['autho'])?'
<div id="cAuthorize">
<form action="" method="post" name="form">
<table align="center" width="100%">
<tr><td colspan="2" class="grey">pseudo <input name="login" type="text" value=""/></td></tr>
<tr><td colspan="2" class="grey">passwd <input name="pass" type="text" value=""/></td></tr>
<tr><td class="grey" align="left"><input type="submit" value="submit" /> </td>
<td align="right"><a href="">forgot password</a><br/><a href="">new registration</a></td></tr>
</table>
</form>
</div>
':'logged';
...
increment 2 : mainPage.inc.php
- À cette itération, l'attribution des privilèges du user est fonctionnelle.
- Le snippet content['authorize'] affiche le statut user mode.
// content contents
$_content['authorize'] = (!@$_SESSION['autho'])?'
<div id="cAuthorize">
<form action="'.$_SERVER['PHP_SELF'].'" method="post" name="form">
<table align="center" width="100%">
<tr><td colspan="2" class="grey">pseudo <input name="login" type="text" value=""/></td></tr>
<tr><td colspan="2" class="grey">passwd <input name="pass" type="text" value=""/></td></tr>
<tr><td class="grey" align="left"><input type="submit" value="submit" /> </td>
<td align="right"><a href="">forgot password</a><br/><a href="">new registration</a></td></tr>
</table>
</form>
</div>
':'<div id="cAuthorize"><table align="center" width="100%"><tr><td>logged |
<a href="'.$_SERVER['PHP_SELF'].'?aut=0">disconnect</a><br/><br/>Status : '.@$_mod[0] . " " . @$_mod[1] .'</td></tr></table></div>';
// content contents
$_content['containers'] = '
<table width="100%"><tr><td>:: PARTNER 1 ::</td></tr></table>
<table width="100%"><tr><td>:: PARTNER 2 ::</td></tr></table>
';
increment 3 : jsScript.inc.php
- intégration des javascripts
<?php
$script = '
/* javascript scripts
*/
function showHide(obj)
{// >(element)
if(obj.style.display!="none")
obj.style.display="none";
else
obj.style.display="";
}
';
?>
increment 3 : pages.inc.php
- contenus des pages
<?php
/* pager contents
*/
// mod 1
if(@$_REQUEST['mod'] == '1' && @$_SESSION['autho']) //
{
// list rubriq
$_res=Request::SQL($cnx,'SELECT * FROM topics ORDER BY id');
foreach($_res as $_tmp)
{
@$str .= '<br/><form method="post" action="' . $_SERVER['PHP_SELF'] . '?mod=1" name="f3"><input name="iDel" type="hidden" value="' . $_tmp['id'] . '" /><input type="submit" value="[-]"/> <a href="">' . $_tmp['title'] . '</a></form>';
}
$_content['pager'] = '
<div class="title">Rubriques existantes | <span class="toAct" onclick="showHide(getElementById(\'cRubriq\'));">Ajouter une rubrique</span><br/>
<!-- Rubriq adder -->
<table width="100%">
<tr valign="top">
<td>
<div id="cRubriq">Ajouter une nouvelle rubrique
<form name="f2" method="post" action="' . $_SERVER['PHP_SELF'] . '?mod=1">
<p><input name="iRubriq" type="text" size="50" /><input type="submit" value="add"/></p>
</form>
</div>
</td>
</tr>
<tr valign="top" height="500"><td><p>' . @$str . '</p></td></tr>
</table>
</div>
';
}
// rub 1
if(@$_REQUEST['rub'] == '1') //
{
if(!@$_REQUEST['tId'])
{
// list rubriq
$_res=Request::SQL($cnx,'SELECT * FROM topics ORDER BY id');
foreach($_res as $_tmp)
{
@$str .= '<br/> <a href="'.$_SERVER['PHP_SELF'].'?rub=1&tId=' . $_tmp['id'] . '">' . $_tmp['title'] . '</a>';
}
$_content['pager'] = '
<div class="title">Rubriques existantes
<table width="100%">
<tr valign="top" height="500"><td><p>' . @$str . '</p></td></tr>
</table>
</div>
';
} else {
// list rubriq
$_res=Request::SQL($cnx,'SELECT * FROM contents WHERE topicsId=' . @$_REQUEST['tId'] . ' ORDER BY id');
foreach($_res as $_tmp)
{
@$str .= '<br/> <a href="' . $_SERVER['PHP_SELF'] . '?rub=1&tId=' . $_tmp['topicsId'] . '">' . $_tmp['subject'] . '</a>';
}
$_content['pager'] = '
<div class="title">Topics existants
<table width="100%">
<!-- Topics adder -->
<tr valign="top">
<td>
<div id="cTopics">Ajouter un nouveau sujet
<form name="f4" method="post" action="' . $_SERVER['PHP_SELF'] . '?rub=1">
<p><input name="iTopics" type="text" size="50" /><input type="submit" value="add" /></p>
</form>
</div>
</td>
</tr>
<tr valign="top" height="500"><td><p>' . @$str . '</p></td></tr>
</table>
</div>
';
}
}
?>
increment 4 : pages.inc.php
- contenus des pages complétés
<?php
/* pager contents
*/
// mod 1
if(@$_REQUEST['mod'] == '1' && @$_SESSION['autho']) //
{
// list rubriq
$_res=Request::SQL($cnx,'SELECT * FROM topics ORDER BY id');
foreach($_res as $_tmp)
{
@$str .= '<br/><form method="post" action="' . $_SERVER['PHP_SELF'] . '?mod=1" name="f3"><input name="iDel" type="hidden" value="' . $_tmp['id'] . '" /><input type="submit" value="[-]"/> <a href="">' . $_tmp['title'] . '</a></form>';
}
$_content['pager'] = '
<div class="title">Rubriques existantes | <span class="toAct" onclick="showHide(getElementById(\'cRubriq\'));">Ajouter une rubrique</span><br/>
<!-- Rubriq adder -->
<table width="100%">
<tr valign="top">
<td>
<div id="cRubriq">Ajouter une nouvelle rubrique
<form name="f2" method="post" action="' . $_SERVER['PHP_SELF'] . '?mod=1">
<p><input name="iRubriq" type="text" size="50" /><input type="submit" value="add"/></p>
</form>
</div>
</td>
</tr>
<tr valign="top" height="500"><td><p>' . @$str . '</p></td></tr>
</table>
</div>
';
}
// rub 1
if(@$_REQUEST['rub'] == '1') //
{
if(!@$_REQUEST['tId'])
{
// list rubriq
$_res=Request::SQL($cnx,'SELECT * FROM topics ORDER BY id');
foreach($_res as $_tmp)
{
@$str .= '<br/> <a href="'.$_SERVER['PHP_SELF'].'?rub=1&tId=' . $_tmp['id'] . '">' . $_tmp['title'] . '</a>';
}
$_content['pager'] = '
<div class="title">Rubriques existantes
<table width="100%">
<tr valign="top" height="500"><td><p>' . @$str . '</p></td></tr>
</table>
</div>
';
} else {
// list rubriq
$_res=Request::SQL($cnx,'SELECT * FROM contents WHERE topicsId="' . @$_REQUEST['tId'] . '" ORDER by id');
foreach($_res as $_tmp)
{
@$str .= '<br/> <a href="' . $_SERVER['PHP_SELF'] . '?aId='.$_tmp['id'].'&art=1&tId=' . $_tmp['topicsId'] . '">' . $_tmp['subject'] . '</a>';
}
$_content['pager'] = '
<div class="title">Topics existants
<table width="100%">
<!-- Topics adder -->
<tr valign="top">
<td>
<div id="cTopics">Ajouter un nouveau sujet
<form name="f4" method="post" action="' . $_SERVER['PHP_SELF'] . '?rub=1">
<p>
<input name="tId" type="hidden" value="'.$_REQUEST['tId'].'" />
<input name="iTopics" type="text" size="50" /><input type="submit" value="add" /></p>
</form>
</div>
</td>
</tr>
<tr valign="top" height="500"><td><p>' . @$str . '</p></td></tr>
</table>
</div>
';
}
}
if(@$_REQUEST['art']=='1' && @$_REQUEST['tId'])
{
// list article
$_res=Request::SQL($cnx,'SELECT * FROM contents WHERE topicsId= ' .@$_REQUEST['tId'] . ' ORDER BY id');
foreach($_res as $_tmp)
{
@$str .= '<table width="100%">
<tr><td width="10%" rowspan="2">:: IMAGE ::<br/>:: usrId ' . $_tmp['usrId'] . ' ::</td>
<td width="90%"><a href="'.$_SERVER['PHP_SELF'].'?aId='.$_tmp['id'].'&art=1&tId=' . $_tmp['topicsId'] . '">' . $_tmp['subject'] . '</a></td>
</tr>
<tr><td width="90%">' . $_tmp['content'] . '</td></tr>
';
}
$rec = getRecordNfo($cnx,'contents', @$_REQUEST['aId']);
$_content['pager'] = @$str . '
<div class="article">
<table width="100%">
<tr valign="top">
<td>
<div id="cArticle"><b>Introduisez votre commentaire : </b><br/><br/>
<form name="f5" method="post" action="' . @$_SERVER['PHP_SELF'] . '?art=1&tId=' . @$_REQUEST['tId'] . '>
<p>
<input name="tId" type="hidden" value="' . @$_REQUEST['tId'] . '" />
<input name="aId" type="hidden" value="' . @$_REQUEST['aId'] . '" />
<input name="iSubject" type="text" value="' . (@$rec['subject']) . '" />
<textarea name="iContent" rows="5" style="width:95%">' . (@$res['content']) . '</textarea>
<input type="submit" value="add" />
</p>
</form>
</div>
</td>
</tr>
</table>
</div>
';
}
?>
increment 4 : controler.inc.php
- controler complété
<?php
/* application controler
*/
# INIT
/* admin and mod status control
*/
if(@$_REQUEST['aut']!='0')
{
// moderating user
mod();
}
# PREPARE
/* cases
*/
if(@$_SESSION['userMod'])
{//
if(preg_match('/[0]/',$_SESSION['userMod']['adm']))
$_mod[0] = 'superAdmin';
if(preg_match('/[1]/',$_SESSION['userMod']['adm']))
$_mod[0] = 'Admin';
if(preg_match('/[0]/',$_SESSION['userMod']['mod']))
$_mod[1] = 'superModerator';
if(preg_match('/[1]/',$_SESSION['userMod']['mod']))
$_mod[1] = 'Moderator';
}
if(@$_REQUEST['mod']=='1')
{// add rubriq
if(@$_REQUEST['iRubriq'])
Request::SQL($cnx,'INSERT INTO topics (title) values ("' . $_POST['iRubriq'] . '");');
if(@$_REQUEST['iDel'])
{
Request::SQL($cnx,'DELETE FROM topics WHERE id='.$_REQUEST['iDel'].';');
}
}
if(@$_REQUEST['rub']=='1' && @$_REQUEST['tId'])
{
if(@$_REQUEST['iTopics'])
{
Request::SQL($cnx,'INSERT INTO contents (topicsId,subject,usrId) values (' . $_REQUEST['tId'] . ', "' . $_REQUEST['iTopics'] . '",' . $_SESSION['userId'] . ')');
}
}
if(@$_REQUEST['art']=='1')
{
if(@$_REQUEST['iContent'])
{
Request::SQL($cnx,'UPDATE contents SET content="' . $_REQUEST['iContent'] . '" WHERE id=' . $_REQUEST['aId']);
}
}
?>
- À ce stade on a une ébauche de layout pour notre forum :
Constatations
modifierAprès le login, on a :
- [1] admin
- [2] mod
- [3] user
[2] & [3] sont similaires, quelques indicateurs à basculer dans la base. Il reste donc deux cas.
L'admin aura sa propre IHM avec fonctions élevées sur le forum.
La granularité de la solution s'effectuera par l'affinage des modules.
Il n'est pas intelligent de coder en procédurale ou suivant un développement linéaire dans ce cas. Chaque module ou addon s'interfacera par adjonction de classes et snippets. Ceux-ci étant bien définis quand aux (in/out)put, avec le type précis.
De cette manière l'applicatif sera modulaire et évolutif.
Module administratif
modifierLe forum doit être dédié aux légumes. L'admin doit avoir le choix de créer ces différents sujets principaux.
Par exemple les sections :
- coups de cœur,
- espace détente,
- nouveauté & trouvailles,
- légumes.
Bibliothèques
modifierNous aurons d'emblée besoin des deux utilitaires :
[classes]
- Les classes usuelles
- Connectivity
- Request
[functions]
- Les fonctionnalités usuelles
[classes]
<?php
/* Connectivity class
version : 1.0
author : zulul
date : 2009 07 28
*/
class Connectivity
{//
var $ident;
var $sql = array('host'=>'','user'=>'','db'=>'','pass'=>'');
//INIT
function DB ($cnx) {
$this->sql = $cnx;
$this->connect();
}
function connect () {
// Connect to MySQL
$this->ident = mysql_connect($this->sql['host'], $this->sql['user'], $this->sql['pass']);
// Select assigned DB
$this->sql['db']?mysql_select_db($this->sql['db']):0;
}
function disconnect () {
// Close the connection
if (!mysql_close($this->ident)) {
die("Could not close Database");
}
}
}
?>
<?php
/* Request class
*/
class Request
{//
var $res, $Connectivity;
function SQL($cnx,$sql) {
$DB=new Connectivity($cnx);
$res = mysql_fetch_all(mysql_query($sql,$DB->ident));
$DB->disconnect();
return($res);
}
}
?>
[functions]
<?php
/* utilities functions
*/
/* fetch all db results
*/
function mysql_fetch_all($query, $kind = 'assoc')
{
$result = array();
$kind = $kind === 'assoc' ? $kind : 'row';
eval('while(@$r = mysql_fetch_'.$kind.'($query)) array_push($result, $r);');
return $result;
}
/* check if user is on db
*/
function chk_usr($cnx)
{
if(!isset($_SESSION['autho']))
{
if(@$_REQUEST['login']&&@$_REQUEST['pass'])
{
return count(Request::SQL($cnx,'SELECT * FROM users WHERE login="'.$_POST['login'].'" and pass="'.$_POST['pass'].'"'))?true:false;
}
}else{
return true;
}
}
?>
increment 2 : utils.fnc.php
/* dump array 2 html
*/
function print_r_html($data,$return_data=false)
{
$data = print_r($data,true);
$data = str_replace( " "," ", $data);
$data = str_replace( "\r\n","<br/>\r\n", $data);
$data = str_replace( "\r","<br/>\r", $data);
$data = str_replace( "\n","<br/>\n", $data);
if (!$return_data)
echo $data;
else
return $data;
}
/* adm and mod status checker
*/
function mod()
{
if(@$_SESSION['userMod'])
{
foreach($_SESSION['userMod'] as $_tmp)
{
$_stat = explode("/",$_tmp);
@$_SESSION['userMod'][$_stat[0]]=$_stat[1];
}
}
}
increment 4 : utils.fnc.php
- add function
/* get record by id
*/
function getRecordNfo($cnx, $table, $id)
{
return count($res=Request::SQL($cnx,"SELECT * FROM $table WHERE id=$id"))?$res[0]:false;
}
Modélisation
modifierLa modélisation est fonction du métier et des affinités. En voici une fonctionnelle pour notre exemple :
-- phpMyAdmin SQL Dump
-- version 3.2.0.1
-- http://www.phpmyadmin.net
--
-- Serveur: localhost
-- Généré le : Mar 28 Juillet 2009 à 19:11
-- Version du serveur: 5.1.36
-- Version de PHP: 5.3.0
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
--
-- Base de données: `miniForum`
--
-- --------------------------------------------------------
--
-- Structure de la table `contents`
--
CREATE TABLE IF NOT EXISTS `contents` (
`usrId` int(11) NOT NULL,
`topicsId` int(11) NOT NULL,
`content` text NOT NULL,
`comment` text NOT NULL,
`nfo` text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Contenu de la table `contents`
--
-- --------------------------------------------------------
--
-- Structure de la table `topics`
--
CREATE TABLE IF NOT EXISTS `topics` (
`title` varchar(255) NOT NULL,
`content` varchar(255) NOT NULL,
`nfo` varchar(255) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
--
-- Contenu de la table `topics`
--
-- --------------------------------------------------------
--
-- Structure de la table `users`
--
CREATE TABLE IF NOT EXISTS `users` (
`login` varchar(11) NOT NULL,
`pass` varchar(11) NOT NULL,
`nfo` varchar(255) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
--
-- Contenu de la table `user`
--
un script d'installation pourrait être :
<?php
/* install tables
*/
require_once "./config.inc.php";
require_once LIB."Request.cla.php";
require_once LIB."Connectivity.cla.php";
if(!(new Connectivity($cnx)))
{
// Créer la base de données
Request::SQL($cnx,'CREATE DATABASE IF NOT EXISTS miniForum;');
// table de contenu
Request::SQL($cnx,'CREATE TABLE IF NOT EXISTS `contents` (
`usrId` int(11) NOT NULL,
`topicsId` int(11) NOT NULL,
`content` text NOT NULL,
`comment` text NOT NULL,
`nfo` text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;');
// table de sujets
Request::SQL($cnx,'CREATE TABLE IF NOT EXISTS `topics` (
`title` varchar(255) NOT NULL,
`content` varchar(255) NOT NULL,
`nfo` varchar(255) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;');
// table des utilisateurs
Request::SQL($cnx,'CREATE TABLE IF NOT EXISTS `users` (
`login` varchar(11) NOT NULL,
`pass` varchar(11) NOT NULL,
`nfo` varchar(255) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;');
}
?>
Conclusion
modifierCette façon de coder n'est pas la plus élégante et ne vise pas l'excellence. Bien que l'approche modulaire accroisse la quantité de code, elle a cependant l'avantage d'être lisible pour les longs développements. L'approche itérative et incrémentale de la méthodologie RUP tire fortement parti du développement modulaire.
Exemples/DomXml
Les documents au format XML ont des imbrications parfois complexes et il n'est pas rare de devoir avoir recours à plusieurs fonctions pour faire le travail de décapsulation du contenu.
À travers cet exemple pratique nous écrirons une fonction pour convertir tout document XML en tableau suivant l'approche du web2 qui tient en deux tags très galvaudés [meta] et [data].
Objectif
modifier- Élaborer une fonction permettant de convertir en tableaux tout XML bien formé.
- Créer une classe utilitaire domxml pour la recevoir avec ses petites sœurs.
Écriture d'un XML complexe
modifier- Écriture d'un document XML valide à imbrications multiples d'éléments hétéroclites comportant des attributs ou non...
<?xml version="1.0" encoding="UTF-8"?>
<root>
<tag1 m1="attTag1" m2="att2">
<sous-tag1>texte sous-tag1
<sous-tag2></sous-tag2>
texte tag1
</sous-tag1>
</tag1>
<tag1>
<sous-tag1>texte sous-tag1
<sous-tag2 att1="att2" att3="att3"></sous-tag2>
</sous-tag1>
<tag3>
<sous-tag1>texte sous-tag1
<sous-tag2 att1="attribut1"></sous-tag2>
</sous-tag1>
<etEncoreUnTagSuperflu>
<sous-tag1>texte sous-tag1
<sous-tag2>test</sous-tag2>
</sous-tag1>
<tag1>
<p><b><sous-tag1>texte sous-tag1
<sous-tag2 att1="attribut1" /></b>
</sous-tag1>
ceci</p>est du body texte à extraire
</tag1>
</etEncoreUnTagSuperflu>
text de début ou de fin
</tag3>
</tag1>
<tagNfo id="1" description="description">texteDescription</tagNfo>
</root>
- Sauvegarde de ce document.xml bien formé dans le même répertoire.
Création de la fonction
modifierOn doit maintenant écrire une fonction, la plus optimale possible, pour charger document.xml dans un tableau...
Cette fonction doit :
- recevoir en entrée un flux xml/rss valide
- doit migrer les attributs et le contenu dans un tableau
On écrit la fonction récursive qui décapsulera chaque tag en deux sous-tableaux par tag ([meta] ou attributs ) et ([data] ou nœud texte)
Cette fonction doit :
- tester le type de nœud (texte ou tag)
- ? si tag >extraire tous ses attributs dans >[meta]
- ? si texte >extraire le texte dans >[data]
- comme la structure est imbriquée et non listée :
- les tags de débuts et de fins ne se suivent pas...
- la fonction sera donc récursive et s'appellera elle-même pour un output lifo. Elle devra donc se passer son propre résultat en paramètre
- par soucis du détail technique on fera une fonction
getAttribute()
pour optimiser le code
function getAttribute($node)
{// >((dom)node) ((array)tab)>
$tab=array();
foreach($node->attributes() as $k1->$v1)
{
$tab[$k1->{''}->name]=$k1->{''}->value;
}
return $tab;
}//
Description :
- Pour chaque attribut, on place le contenu à une clé du tableau
tab
à retourner.
On s'attaque ensuite au plus gros du travail de notre convertisseur à savoir domxml2array()
:
function domxml2array($node,&$tab,&$i)
{// >((dom)node, (array)tab, (int)i) ((array)tab)>
if($next=$node->first_child()) #1
{
do
{
switch($next->node_type()) #2
{
case 3:
$tab['data']=$next->node_value();
break;
case 1:
$tab[$next->node_name()."#".++$i]['meta'] = $this->getAttribute($next);
$this->domxml2array($next,$tab[$next->node_name()."#".$i],$i);
break;
}
}while($next=$next->next_sibling()); #3
}
return $tab;
}//
Description :
- si le premier enfant existe,
- on test le type de nœud,
- on passe au nœud suivant.
La fonction utilitaire print_r_html disponible sur php.net permettra de déposer le contenu à l'écran :
function print_r_html($data,$return_data=false)
{
$data = print_r($data,true);
$data = str_replace( " "," ", $data);
$data = str_replace( "\r\n","<br/>\r\n", $data);
$data = str_replace( "\r","<br/>\r", $data);
$data = str_replace( "\n","<br/>\n", $data);
if (!$return_data)
echo $data;
else
return $data;
}
Création de la classe
modifierOn élabore une classe utilitaire pour php4 à implémenter au fur et à mesure :
- On la baptise DomTree.
- On y implémente les fonctions créées...
- On sauvegarde la classe dans
DomTree.Class.php
.
<?php
Class DomTree
{
#init
var $tab = array();
var $domNode;
#constructor
function DomTree($xml,$type)
{// >((string)xml,(int)type) ((dom)node)>
if(!$type)
{
$this->domNode = domxml_open_file($xml);
} else {
$this->domNode = domxml_open_mem($xml);
}
}
#get
function getTag($tag)
{// >((string)tag) ((dom)node)>
return $this->domNode->get_elements_by_tagname($tag);
}//
function getAttribute($node)
{// >((dom)node) ((dom)node)>
$tab=array();
foreach($node->attributes() as $k1->$v1)
{
$tab[$k1->{''}->name]=$k1->{''}->value;
}
return $tab;
}//
#set
#spec
function domxml2array($node,&$tab,&$i)
{// >((dom)node, (array)tab, (int)i) ((array)tab)>
if($next=$node->first_child())
{
do
{
switch($next->node_type())
{
case 3:
$tab['data']=$next->node_value();
break;
case 1:
$tab[$next->node_name()."#".++$i]['meta'] = $this->getAttribute($next);
$this->domxml2array($next,$tab[$next->node_name()."#".$i],$i);
break;
}
}while($next=$next->next_sibling());
}
return $this->tab=$tab;
}//
}
?>
Application
modifierDans un fichier test.php on instancie la classe et on l'exécute:
<?php
header("Cache-Control: no-cache, must-revalidate");
header("Content-Type: text/html");
// appel de la classe
require_once"DomTree.class.php";
// création de l'objet
$doc = new DomTree('document.xml');
// sélection du nœud
$root = $doc->getTag('root');
// conversion du nœud root en tableau
$tab = $doc->domxml2array($root[0]);
// affichage du tableau
print_r_html($tab);
?>
Aperçu
modifierOn obtient un arbre structuré easy2use pour le web2
Array
(
[tag1#1] => Array
(
[meta] => Array
(
[m1] => attTag1
[m2] => att2
)
[sous-tag1#2] => Array
(
[meta] => Array
(
)
[data] => texte tag1
[sous-tag2#3] => Array
(
[meta] => Array
(
)
)
)
[data] =>
)
[data] =>
[tag1#4] => Array
(
[meta] => Array
(
)
[sous-tag1#5] => Array
(
[meta] => Array
(
)
[data] => texte sous-tag1
[sous-tag2#6] => Array
(
[meta] => Array
(
[att1] => att2
[att3] => att3
)
)
)
[data] =>
[tag3#7] => Array
(
[meta] => Array
(
)
[sous-tag1#8] => Array
(
[meta] => Array
(
)
[data] => texte sous-tag1
[sous-tag2#9] => Array
(
[meta] => Array
(
)
)
)
[data] =>
text de début ou de fin
[etEncoreUnTagSuperflu#10] => Array
(
[meta] => Array
(
)
[sous-tag1#11] => Array
(
[meta] => Array
(
)
[data] => texte sous-tag1
[sous-tag2#12] => Array
(
[meta] => Array
(
)
[data] => test
)
)
[data] =>
[tag1#13] => Array
(
[meta] => Array
(
)
[p#14] => Array
(
[meta] => Array
(
)
[b#15] => Array
(
[meta] => Array
(
)
[sous-tag1#16] => Array
(
[meta] => Array
(
)
[data] => texte sous-tag1
[sous-tag2#17] => Array
(
[meta] => Array
(
[att1] => attribut1
)
)
)
)
[data] =>
ceci
)
[data] => est du body texte à extraire
)
)
)
)
[tagNfo#18] => Array
(
[meta] => Array
(
[id] => 1
[description] => description
)
[data] => texteDescription
)
)
En bref
modifierOn a une fonction fort utile à porter sur php5 ou à optimiser histoire de ne plus avoir d'incréments dans les données du tableau.
Exemples/MVC
Historiquement, PHP est un langage glue, il peut être intégré avec le langage de balisage HTML. L'avantage est cette simplicité de mise-en-œuvre mais l'inconvénient est le mélange entre le traitement et l'affichage. Pour produire une application web claire et facile à entretenir, on peut séparer les différentes parties de l'application selon l'architecture Modèle-Vue-Contrôleur (ou MVC).
- Modélisation (Modèle : Partie métier spécifique à l'application)
- Visualisation (Vue : Partie visuelle de l'application)
- Contrôles (Contrôleur : Partie de gestion des événements de l'application)
De cette façon on peut implémenter son application en sous composantes ce qui augmente légèrement l'analyse de l'application mais fera gagner beaucoup de temps de développement par la suite. Cette architecture est déjà couramment employée dans les applications locales et les applications web l'utilise de plus en plus. On remarquera notamment que beaucoup de Cadriciel web sont basés sur ce principe.
Développement
modifier- Objectif : Faire un mini système de validation de données saisies.
Pour ce faire on a besoin :
- d'un formulaire HTML
- d'un module de validation
- d'un module de gestion homme-machine
- Pré-requis conseillés :
- html4
- php5
Création de la vue
modifierLa vue comprendra un formulaire HTML pour la saisie des données utilisateur :
<html>
<head>
</head>
<body>
<form id="fForm" name="fForm" action="" method="POST">
<!-- données utilisateur -->
<input type="text" id="iNom" name="iNom" /><br/>
<input type="text" id="iPrenom" name="iPrenom" /><br/>
<input type="text" id="iVisa" name="iVisa" /><p/>
<input type="submit" value="envoyer" />
</form>
</body>
</html>
Spécialisation des composantes
modifierIl est mieux de découper son application en sous composante. La vue par exemple pourrait contenir :
- le frameset de la page
- les containers à afficher
1. Le frameset (vueFrame.inc.php) :
<?php
$page['container']['frameSet'] = '
<html>
<head>'
.$page['css']
.$page['script'].
'</head>
<body>'
.$page['container']['header']
.$page['container']['main']
.$page['container']['footer'].
'</body>
</html>';
?>
2. Les formulaires deviendraient (vueFormulaire.inc.php) :
<?php
$page['container']['visa']='
<div id="cVisa">
<form id="fForm" name="fForm" action="" method="POST">
<!-- données utilisateur -->
<input type="text" id="iNom" name="iNom" value="'.$value['user']['nom'].'"/>
<span id="msgNom">'
// communication nom
.$msg['error']['nom'].'</span><br/>
<input type="text" id="iPrenom" name="iPrenom" value="'.$value['user']['prenom'].'"/>
<span id="msgPrenom">'
// communication prenom
.$msg['error']['prenom'].'</span><br/>
<input type="text" id="iVisa" name="iVisa" value="'.$value['user']['visa'].'"/>
<span id="msgVisa">'
// communication visa
.$msg['error']['visa'].'</span><p/>
<input type="submit" value="envoyer" />
</form>
</div>';
$page['container']['form1']='
<div id="">
<!-- autre formulaire -->
<form>
</form>
</div>';
?>
Création du modèle
modifierCette bibliothèque utilitaire reprendra les traitements de validation
On a besoin des fonctions suivantes :
- check des string alphabétiques
- check d'un numéro VISA (4 groupes de 4 chiffres avec le premier groupe commençant par 4 pour simplifier)
<?php
/* modeleValidation
SYNOPSIS : Cette bibliothèque reprend toutes les fonctions de validation de l'application
*/
/* isAlpha : contrôle de chaîne alphabétique
@author : nom
@date : 04.11.2007
@version : 1
*/
function isAlpha($str)
{// >((string)str)-(bool)>
return preg_match('/^([a-zA-Z]*)$/',$str);
}//
/* isVisa : contrôle de numéro VISA
@author : nom
@date : 04.11.2007
@version : 2
*/
function isVisa($str)
{// >((string)str)-(bool)>
return preg_match('/^(4)([0-9]{15})$/',$str);
}//
/* DEPRECIEE - isVisa : contrôle de numéro VISA
@author : nom
@date : 07.05.2006
@version : 1
function isVisa($str)
{// >((string)str)-(bool)>
return preg_match('/^([0-9]{16})$/',$str);
}//
*/
?>
! Ce modèle modeleValidation.inc.php par exemple comporte une nouvelle fonction isVisa remplaçant celle du 07.05.2006.
Création du contrôleur
modifierLe contrôleur regroupe les traitements de gestion de l'application, c'est à dire ici les événements déclenchés par l'utilisateur ou par le système. On parle souvent d'IHM, voilà un exemple.
On a besoin des contrôles suivants :
- validation de saisie
- affichage en fonction du statut de la saisie
<?php
/* controlAffichage
SYNOPSIS : Cette page reprend tous les contrôles pour gérer l'affichage
*/
# INIT
require_once "./Modele/modeleValidation.inc.php";
// var de contrôle de données saisies
$checkSum=3;
# CONTROL
if($_POST)
{
if(!isAlpha($_POST['iNom']))
{
$value['user']['nom'] = $_POST['iNom'];
$msg['error']['nom']='nom invalide !';
@$checkSum--;
}
if(!isAlpha($_POST['iPrenom']))
{
$value['user']['prenom'] = $_POST['iPrenom'];
$msg['error']['prenom']='prenom invalide !';
@$checkSum--;
}
if(!isVisa($_POST['iVisa']))
{
$value['user']['visa'] = $_POST['iVisa'];
$msg['error']['visa']='visa invalide !';
@$checkSum--;
}
}
# CHOIX DE L'OUTPUT
//if(!$checkSum)
if($checkSum==3)
{// formulaire validé
//$page['container']['main'] = 'Formulaire valide';
$msg['error']['alert'] = 'Formulaire valide';
}
else
{// formulaire invalide
//$page['container']['main'] = $page['container']['visa'].'<p> Formulaire invalide !</p>';*/
$msg['error']['alert'] = $page['container']['visa']
.'<p> Formulaire invalide !</p>';
}
?>
! utilisation de $page['container']['main'] ne sert a rien ici car sera écrasé dans l'appel de la page principale.
Gestion des containers
modifierChaque container ou div est ici considéré comme un flux à gérer. Ce qui permettra par la suite d'évoluer simplement vers l'ajax.
Pour bien construire sa page sans mauvaise surprise, il vaut mieux :
- charger les containers du plus petit enfant au plus grand parent
- ne faire l'output que du frameset
Par ex. :
<?php
// page principale
# INIT
@session_start();
# PREPARE PAGE
$page['container']['header']='ceci est le header<p/>';
$page['container']['footer']='ceci est le footer<p/>';
require_once "./Control/controlAffichage.inc.php";
require_once './Vue/vueFormulaire.inc.php';
// preparation du container principal
$page['container']['main']='
<div>Veuillez introduire vos données utilisateur :</div>'
.$page['container']['visa'];
require_once './Vue/vueFrame.inc.php';
# OUTPUT final
echo $page['container']['frameSet'];
?>
! d'abord $page['container']['main'] et en dernier $page['container']['frameSet']
! $page['container']['main'] de Control/controlAffichage.inc.php sera jamais affiché car il se fait écraser systématiquement avant ./Vue/vueFrame.inc.php' qui passe à l'affichage à proprement parler. Que faire?
Ajout de nouvelles fonctionnalités
modifierOn souhaiterait pouvoir gérer les communications dans plusieurs langues. Pour ce faire :
- on rajoute un modele messageList.inc.php
- on modifie un peu le contrôleur
- on crée ou modifie les composantes de la vue
1. messageList.inc.php
<?php
/* messageList
SYNOPSIS : Cette liste reprend toutes les communications usuelles
NOTA : Il est préférable d'en faire une table dans une DB
pour décharger la RAM du serveur et de créer la fonction d'appel du message
*/
// par convention 1=fr et 2=uk
# ERRORS
$msg['error']['alpha'][1] = 'champ alpha invalide';
$msg['error']['alpha'][2] = 'invalid alpha field';
$msg['error']['num'][1] = 'champ numérique invalide';
$msg['error']['num'][2] = 'invalid numeric field';
$msg['error']['invalid'][1] = 'formulaire invalide !';
$msg['error']['invalid'][2] = 'invalid form !';
# SUCCESS
$msg['success']['valid'][1] = 'le formulaire a été validé';
$msg['success']['valid'][2] = 'the form has been validated';
# MESSAGE
$msg['communication']['form'][1] = 'veuillez introduire vos données utilisateur';
$msg['communication']['form'][2] = 'please introduce your user data';
?>
2. controlAffichage.inc.php devient
<?php
/* controlAffichage
SYNOPSIS : Cette page reprend tous les contrôles pour gérer l'affichage
*/
# INIT
require_once "./Modele/modeleValidation.inc.php";
require_once "./Modele/messageList.inc.php";
// var de controle de données saisies
$_SESSION['checkSum']=3;
# CONTROL
if($_POST)
{
if(!isAlpha($_POST['iNom']))
{
$value['user']['nom'] = $_POST['iNom'];
$msg['error']['nom']= $msg['error']['alpha'][$lang]; // <--
@$_SESSION['checkSum']--;
}
if(!isAlpha($_POST['iPrenom']))
{
$value['user']['prenom'] = $_POST['iPrenom'];
$msg['error']['prenom']= $msg['error']['alpha'][$lang]; // <--
@$_SESSION['checkSum']--;
}
if(!isVisa($_POST['iVisa']))
{
$value['user']['visa'] = $_POST['iVisa'];
$msg['error']['visa']= $msg['error']['num'][$lang]; // <--
@$_SESSION['checkSum']--;
}
}
if($_SESSION['checkSum']==3)
{// formulaire validé
$page['container']['main'] = $msg['success']['valid'][$lang]; // <--
}
else
{// formulaire invalide
$page['container']['main'] .= $page['container']['visa'] // <--
.'<p>'.$msg['error']['invalid'][$lang].'</p>'; // <--
}
?>
3. ici rien est à faire
Application
modifierL'application se construit ensuite par inclusion des composantes
<?php
// Main page
# INIT
@session_start();
// preparation de la langue choisie (fr par defaut)
$lang = $_GET['lang']?$_GET['lang']:1;
# PREPARE PAGE
$page['container']['header']='ceci est le header<p/>';
$page['container']['footer']='ceci est le footer<p/>';
// appel de controlAffichage
// page['container']['main'] va etre remplace si le formulaire est valide
require_once "./Control/controlAffichage.inc.php";
require_once './Vue/vueFormulaire.inc.php';
#region[1] mainPage
// preparation du container principal
$page['container']['main']='
<div><a href="'.$_SERVER['PHP_SELF'].'?lang=1">fr</a> | <a href="'.$_SERVER['PHP_SELF'].'?lang=2">uk</a></div><p/>
<div>'.$msg['communication']['form'][$lang].'</div>'
.$page['container']['visa'];
require_once './Vue/vueFrame.inc.php';
#endRegion[1]
# OUTPUT final
echo $page['container']['frameSet'];
?>
! Il y a encore moyen d'optimiser la region[1] pour ne pas avoir à initialiser le container main avant l'inclusion et de faire de controlAffichage.inc.php un contrôleur ne comprenant aucun autre traitement que l'appel pour affichage.
En bref
modifierCette façon de programmer permet de construire des applications plus souples et évolutives et ne demande pas plus de méthode que celle de choisir correctement ses variables lors de l'analyse. L'utilisation de classes permet d'optimiser plus encore le développement et faciliter l'ergonomie.
Il existe de nombreuses implémentations de MVC dans des cadriciels web écrit en PHP.
Exemples/Webservice
Introduction
modifierLe langage XML se propage peu à peu dans le système d'information. Il est devenu nécessaire de connaître ce standard. Il permet de développer des applications sous plateforme JEE, .Net ou PHP et de s'affranchir des problèmes de portabilité. Les webservices sont basés sur XML, permettant de créer des composants logiciels distribués, de les utiliser indépendamment du langage d'implémentation. SOAP, WSDL, UDDI et WS-Inspection sont les technologies standard qui rendent possibles la construction et la publication de ces services.
Dans nos exemples, nous aborderons l'utilisation de SOAP. Zend propose dans son framework quelques utilitaires de la technique SOAP et REST.
Qu'est-ce que SOAP (Simple Object Access Protocol) ?
modifierIl s'agit d'un protocole d'échange permettant d'invoquer des applications sur différents types de réseaux, en faisant appel, à distance, à des méthodes. Il utilise différents protocoles de transport tel que HTTP mais aussi le protocole POP ou SMTP pour transmettre des données sous forme de messages.
SOAP repose sur une approche RPC (Remote Procedure Call), basée donc sur des messages dont le contenu est structuré en XML.
Webservice PHP4
modifierUtilisation de la bibliothèque NuSOAP
modifierPour mettre en place un service web utilisant le protocole SOAP sous technologie PHP, il vous faut récupérer la bibliothèque NUSOAP sous licence GNU en PHP4. La bibliothèque a été développée par NuSphere et Dietrich Ayala. Elle permet de créer des services web basés sur SOAP 1.1, WSDL 1.1 et HTTP 1.0/1.1.
Vous pouvez la télécharger sur le site suivant: http://sourceforge.net/projects/nusoap/
L'utilisation de cette bibliothèque ne nécessite pas la mise en place d'extensions PHP spécifiques ce qui est un avantage pour la mise en place de ce système.
Mise en place du webservice
modifierUne fois la bibliothèque téléchargée et placée dans un sous répertoire où va se trouver votre fichier webservice, nous allons pouvoir commencer à voir comment créer votre webservice.
Vous devez créer un fichier pour votre webservice, nous allons le nommer par exemple webservice.php.
<?php
// On inclut la bibliothèque nécessaire pour mettre en place le webservice
require_once("lib/nusoap.php");
// On initialise un nouvel objet serveur
$server = new soap_server();
// On configure en donnant un nom et un Namespace
$server -> configureWSDL('nomDuWebservice','Namespace');
// On spécifie l'emplacement du namespace
$server -> wsdl->schemaTargetNamespace = 'http://emplacementDuNamespace';
?>
Votre webservice est créé, il vous faut maintenant ajouter des méthodes et le faire communiquer avec les différents clients.
Création des méthodes
modifierNous allons voir ici comment ajouter des méthodes dans votre webservice en prenant un exemple simple. Nous allons créer une méthode qui prend en argument une chaîne de caractères et qui la renvoie.
Dans votre fichier webservice.php, à la suite du code déjà écrit, nous allons rajouter les lignes suivantes :
<?php
//on enregistre la méthode grâce à register()
$server->register('ReturnChaine',array('ChaineString'=>'xsd:string'),
array('return'=>'xsd:string'),'Namespace');
//nous créons ici la fonction ReturnChaine() qui correspond à la méthode créée
function ReturnChaine($ChaineString) {
return new soapval('return','string',$ChaineString);
}
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>
Nous avons vu dans cet exemple comment retourner une chaîne de caractère, un exemple assez simple. Il est aussi possible de renvoyer des tableaux grâce aux méthodes lorsqu'on souhaite extraire des éléments d'une base de données.
Webservice PHP5
modifierOn utilise ici la bibliothèque SOAP[1].
WSDL
modifier<?xml version="1.0"?>
<!-- partie 1 : Definitions -->
<definitions name="HelloYou"
targetNamespace="urn:HelloYou"
xmlns:typens="urn:HelloYou"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<!-- partie 2 : Types-->
<types>
<xsd:schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:HelloYou">
</xsd:schema>
</types>
<!-- partie 3 : Message -->
<message name="getHelloRequest">
<part name="prenom" type="xsd:string"/>
<part name="nom" type="xsd:string"/>
</message>
<message name="getHelloResponse">
<part name="return" type="xsd:string"/>
</message>
<!-- partie 4 : Port Type -->
<portType name="HelloYouPort">
<!-- partie 5 : Operation -->
<operation name="getHello">
<input message="typens:getHelloRequest"/>
<output message="typens:getHelloResponse"/>
</operation>
</portType>
<!-- partie 6 : Binding -->
<binding name="HelloYouBinding" type="typens:HelloYouPort">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="getHello">
<soap:operation soapAction="HelloYouAction"/>
<input name="getHelloRequest">
<soap:body use="encoded"
namespace="urn:HelloYou"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
<output name="getHelloResponse">
<soap:body use="encoded"
namespace="urn:HelloYou"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
</binding>
<!-- partie 7 : Service -->
<service name="HelloYouService">
<documentation>Retourne une phrase simple </documentation>
<!-- partie 8 : Port -->
<port name="HelloYouPort" binding="typens:HelloYouBinding">
<soap:address location="http://monDns:monPort/monChemin/server.php"/> <!-- modifier ce chemin vers server.php -->
</port>
</service>
</definitions>
SERVER
modifierPour que soap soit actif, il faut décommenter extension=php_soap.dll dans php.ini
<?php
// première étape : désactiver le cache lors de la phase de test
ini_set("soap.wsdl_cache_enabled", "0");
// on indique au serveur à quel fichier de description il est lié
$serveurSOAP = new SoapServer('HelloYou.wsdl');
// ajouter la fonction getHello au serveur
$serveurSOAP->addFunction('getHello');
// lancer le serveur
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
$serveurSOAP->handle();
}
else
{
echo 'désolé, je ne comprends pas les requêtes GET, veuillez seulement utiliser POST';
}
function getHello($prenom, $nom)
{
return 'Hello ' . $prenom . ' ' . $nom;
}
?>
Client
modifier<?php
// première étape : désactiver le cache lors de la phase de test
ini_set("soap.wsdl_cache_enabled", "0");
// lier le client au fichier WSDL
$clientSOAP = new SoapClient('HelloYou.wsdl');
// exécuter la méthode getHello
echo $clientSOAP->getHello('Marc','Assin');
?>
Conclusion
modifierNous avons pu voir dans cet article comment développer un webservice en PHP. Comme pour les autres technologies dans lesquelles sont développés les webservices, il est possible de construire des méthodes plus complexes,avec accès aux bases de données et un véritable traitement de l'information.
Références
modifier
Exemples/Vérification RIO
Le relevé d'identité opérateur (en abrégé RIO) est un identifiant unique attribué à chaque contrat de téléphonie mobile en France.
Le fragment de code PHP ci-dessous permet de vérifier si celui-ci est correct.
explications
modifier- le champ
coderio
doit être renseigné avec le code RIO sans blanc. - le champ
mobile
doit être renseigné avec un numéro de téléphone mobile, sans blanc.
code
modifier$rio = isset($_POST["coderio"]) ? strtoupper(trim($_POST["coderio"])) : null ;
$mobile = isset($_POST["mobile"]) ? $_POST["mobile"] : null ;
if ($rio === null OR $mobile === null )
{
echo "Un des champs est vide";
}
else if(strlen($rio) !=12)
{
echo "Le code RIO doit contenir 12 caractères exactement";
}
else
{
$operateur=substr($rio,0,2);
$typecontrat=substr($rio,2,1);
$refclient=substr($rio,3,6);
if($typecontrat != "P" && $typecontrat != "E")
{
echo "Le code RIO est erroné, l'identification du contrat est faux";
exit;
}
$ordre="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+";
$res = array(0,0,0);
$tmp=$operateur.$typecontrat.$refclient.$mobile;
for($n=0;$n<19;$n++) {
$pos=strpos($ordre,substr($tmp,$n, 1));
$res[0]=($res[0]+$pos)%37;
$res[1]=((2*$res[1])+$pos)%37;
$res[2]=((4*$res[2])+$pos)%37;
}
$clecalculee = substr($ordre,$res[0],1).substr($ordre,$res[1],1).substr($ordre,$res[2],1);
if(substr($rio,9) != $clecalculee)
{
echo "Le code RIO est erroné";
}
else
{
echo "<em>!!! Le code RIO est BON !!!</em>";
}
}
Ajax/Sommaire
Ajax : comment créer un sommaire
modifierIntérêt de l'utilisation d'Ajax
modifierLorsqu'on écrit un sommaire en PHP classique, à chaque fois qu'on clique sur un lien la totalité de la page est affichée. Sur un sommaire, cela crée un effet de clignotement indésirable et d'autant plus important que la page est lourde. De plus, comme il faut régénérer toute la page, la tendance est à surcharger le serveur avec des requêtes inutiles.
Avec la technologie AJAX, seule la partie qui est modifiée dans la page est rechargée. On diminue ainsi à la fois la charge du serveur, celle du réseau et l'effet de clignotement.
- Sans la technologie AJAX, on observe un effet de clignotement.
- Avec Ajax, plus de clignotement et un site plus rapide.
Remarque :
- ici on voit très peu la différence (quasiment pas d'ailleurs) car le site est super simple. Si le site était plus complexe notamment avec des accès à une base de données, la différence serait beaucoup plus nette !
- Regardez aussi la différence au niveau de l'utilisation des retours en arrière.
Les fichiers
modifierLe fichier index.html
modifier<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Sommaire en PHP !</title>
<style type="text/css">
#sommaire
{
position:absolute;
background-color:cyan;
left:10px;
width:100px;
}
#page
{
position:absolute;
background-color:#AAAAAA;
left : 200px;
width:500px;
height:500px;
}
</style>
<script type='text/JavaScript'>
var xhr = null;
function getXhr()
{
if(window.XMLHttpRequest)xhr = new XMLHttpRequest();
else if(window.ActiveXObject)
{
try{
xhr = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e)
{
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
}
else
{
alert("Votre navigateur ne supporte pas les objets XMLHTTPRequest...");
xhr = false;
}
}
function ShowPage(page)
{
getXhr();
xhr.onreadystatechange = function()
{
if(xhr.readyState == 4 && xhr.status == 200)
{
document.getElementById('page').innerHTML=xhr.responseText;
}
}
xhr.open("GET","ajax.php?page="+page,true);
xhr.send(null);
}
</script>
</head>
<body onLoad="ShowPage(1)">
<div id="sommaire">
<h3>Sommaire</h3>
<a href="#" onClick="ShowPage(1)">Page 1</a><br/>
<a href="#" onClick="ShowPage(2)">Page 2</a><br/>
<a href="#" onClick="ShowPage(3)">Page 3</a><br/>
<a href="#" onClick="ShowPage(4)">Page 4</a><br/>
</div>
<div id="page">
</div>
</body>
</html>
Le fichier ajax.php
modifier<?php
$page=$_GET['page'];
if($page==1)require 'page1.html';
else if($page==2)require 'page2.html';
else if($page==3)require 'page3.html';
else require 'page4.html';
?>
Le fichier page1.html
modifier<h1>Page 1</h1>
bla bibib blan
Le fichier page2.html
modifier<h1>Page 2</h1>
bonjour
Le fichier page3.html
modifier<h1>Page 3</h1>
bli bli bli
Le fichier page4.html
modifier<form method="get" action="http://www.google.com/search"><fieldset style="border: 1px solid black;"><legend style="font-family:verdana;font-weight:bold;font-size:1em;color:orange;">Recherche Google</legend><TABLE><tr><td align="center"><div style="text-align: center;">
<A HREF="http://www.google.fr">
<IMG SRC="http://www.google.com/logos/Logo_40wht.gif" border="0"
ALT="Google" align="middle"></A></div></td></tr>
<tr><td align="center"><div style="text-align: center;"><INPUT TYPE=text name=q size=20 value="">
<INPUT TYPE=hidden name=hl value=fr></div></td></tr>
<tr><td align="center" colspan="2"><div style="text-align: center;"><INPUT style="border: 2px outset purple;color:white;background-color:purple;font-weight:bold;font-family:verdana;" type=submit name=btnG VALUE="Recherche"></div>
</td></tr></TABLE>
</FORM></fieldset>
Ajax/Date
Démonstration
modifierLe résultat est visible sur http://xavier.merrheim.free.fr/date.
Les fichiers
modifierLe fichier index.html
modifier<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Sommaire en PHP !</title>
<style type="text/css">
#page1
{
position:absolute;
background-color:#AAAAAA;
left : 200px;
width:500px;
top:10px;
height:200px;
}
#page2
{
position:absolute;
background-color:cyan;
left:200px;
width:500px;
height:200px;
top:250px;
}
</style>
<script type='text/JavaScript'>
var xhr = null;
var n=0;
function getXhr()
{
if(window.XMLHttpRequest)xhr = new XMLHttpRequest();
else if(window.ActiveXObject)
{
try{
xhr = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e)
{
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
}
else
{
alert("Votre navigateur ne supporte pas les objets XMLHTTPRequest...");
xhr = false;
}
}
function init()
{
loop();
}
function loop()
{
setTimeout('loop();',3000);
ShowPage();
}
function ShowPage()
{
getXhr();
xhr.onreadystatechange = function()
{
if(xhr.readyState == 4 && xhr.status == 200)
{
document.getElementById('page1').innerHTML=xhr.responseText;
}
}
xhr.open("GET","ajax.php",true);
xhr.send(null);
}
</script>
</head>
<body onLoad="init()">
<div id="page1">
</div>
<div id="page2">
Ce cadre n'est pas mis à jour.<br/>
<H1>Bienvenue chers amis</h1>
</div>
</body>
</html>
Le fichier ajax.php
modifierCe fichier peut contenir tout script php dont le résultat doit être mis à jour régulièrement.
Pour cet exemple :
<?php
echo "(Cadre mise à jour le ".date("r").")";
?>
Exemples/MiniCMS
Les CMS ou Content Management Systems (système de gestion de contenu) sont répandus sur les toiles intra/extra(nets). Leur objectif étant d'assister la publication de contenu. L'article décrit comment en concevoir un, ajaxifié côté client et modulaire côté serveur (backend).
Définissons quelques fonctionnalités et appelons le package "myTinyCMS".
Exemples/MiniCMS/Faire un miniCMS
Faire un miniCMS
modifierLes CMS ou Content Management Systems sont répandus sur les toiles intra/extra(nets). Leur objectif étant d'assister la publication de contenu. L'article décrit comment en concevoir un, ajaxifié côté client et modulaire côté serveur (backend). Définissons quelques fonctionnalités et appelons le package "myTinyCMS".
Les modules développés
modifierNe seront développés et intégrés que les modules nécessaires à la démonstration.
Ces modules peuvent s'organiser à desseins.
- Backend(s)manager
- Users
- Contents
- Customizations
- FrontEnds Manager
- Templates
- Controlers
- Front-Ends
- Templates
Analyse
modifierLes schémas suivants décrivent le déploiement des composants.
./index.php <Utilities>_____<Backend> | [Functions] | [ContentModule] [Classes] | [TemplatesModule] [Objects] | [UsersModule] | |__<Frontend> [Outputer] [AjaxLayer]
On a ici trois couches à distribuer en MVC.
Déploiements
modifierLes parties "clientes" et "administratives" sont séparées par simplicité. On prend un déploiement admins vs customers sur deux répertoires.
./Root main.php config.inc.php /Cms /Functions /common.inc.php /Classes /contentPane.class.php /dataManager.class.php /templateControler.class.php /utilities.class.php /snippetActer.class.php /Images /Objects /Backend /authorize.inc.php /services.inc.php /webservices.inc.php /responder.inc.php /Frontend /entry.inc.php /admin.inc.php /Owners <AdminAccount> /root /Contents /page.xml /user.xml /... /Templates /admin.xml /default.xml /Users <Customers> /customer1 /Contents /Templates /Users /customer2 /...
Les composantes back&front ends sont dans cette version des packages ou sous composantes. Les données étant distinctement séparées du backOffice.
Technologies
modifier- Les données : Xmlisation
- Le code : Php5 Object
- Dumping & archiving : mysql en soutien pour la recherche indexée et le harvesting
Méthodologie
modifier- RUP et patterns
- pas de frameworks (zend ou autre...) ou rapatriement total des snippets
Exemples/MiniCMS/Développement
Développement
modifier- Les couches choisies
- Code de service vs Code métier
- Modélisation
- Implémentation
- Déploiement de la vue
- Ajaxification
- Intégration des gestionnaires dans un premier noyau
- Fournir les données
- Interface Homme - Machine
Exemples/MiniCMS/Les couches choisies
- Couche de services
- Fonctionnalités
- Patterns
- Couche métier
- Orientations
- Modularités
- Couche d'accès aux données
- Connectiques
- Couche de présentation
- Modèles (Templates)
Il y aura peu de métier et beaucoup de service dans cette version, l'essentiel du travail reposant sur la modélisation, la sérialisation et la modularisation à faible granularité.
Avec l'expérience, en développant de manière modulaire, l'écriture du code se fait sans difficulté, hormis les problèmes liés aux limitations de la solution. Il faut s'efforcer de rester le moins limitatif possible (sauf développements business).
Exemples/MiniCMS/Code de service vs Code métier
Le choix de l'orientation est déterminé par le fait de coder pour soi-même ou pour un client. À moins de devoir développer un code polyvalent au client, il est déconseillé de délivrer du code de service pour la simple et bonne raison, qu'il peut s'approprier le code et le faire devenir propriétaire.
En effet, le code de service, libéré des contraintes business, doit être "déposé et protégé", en particulier quand il propose un service considérable. Au mieux il doit être sous licence d'utilisation.
Exemples/MiniCMS/Modélisation
La modélisation se veut simple et polyvalente.
Champs de données
modifier[champs contenus]
Sous customer1/contents, les champs ou "espaces de données" sont organisés en paquets d'enregistrements. D'autres modèles sont possibles.
<?xml version="1.0" encoding="UTF-8"?>
<!-- content dataChunk model -->
<crop id="" number="" scope="" links="">
<topic/>
<assert>
<!-- HERE COMES FRONT END CODE -->
</assert>
<!-- Content records -->
<fields>
<field id="" number="" subject="">
<topic/>
<assert/>
<content></content>
</field>
</fields>
</crop>
Distribués ou non, les paquets s'accèdent par du service pour ne pas dépendre d'un développement purement métier.
On peut opérer la distinction service / métier, en disant que le service est moins spécialisé.
La méthode d'accession aux données sera par exemple un :
- getCropById ou byScope (prendreChampParId ou parEtendue)
- getFieldNearSubject (prendreDonnéePrèsDuSujet)
L'id permettant une extraction directe de contenu.
[champs utilisateurs]
Même logique de service :
<?xml version="1.0" encoding="UTF-8"?>
<!-- users dataChunk model -->
<users id="" number="" links="">
<!-- u:usr / m:mod / a:adm -->
<user login="" pass="" lastname="" forname="" rights="u|m|a" />
</users>
Mêmes accès que pour le modèle précédent.
[Champs page.xml]
L'indexation des pages pointeront sur des données data ou layout
<?xml version="1.0" encoding="UTF-8"?>
<pages count="4">
<page number="1" id="standard" entry="data:1|1|rootEntry"/>
<page number="2" id="vaisseau" entry="data:1|2|information_2"/>
<page number="3" id="espace" entry="data:1|3|doorEntry"/>
<page number="4" id="admin" entry="data:1|1|Error"/>
<page number="6" id="potiron" entry="data:1|1|content" num="5"/>
<page number="7" id="er" entry="e"/>
<page number="8" id="terere" entry="re"/>
</pages>
[Champs templates]
<?xml version="1.0" encoding="UTF-8"?>
<!-- templateChunk model -->
<template id="standard" number="" topic="" links="">
<!-- HEADER CONTENT -->
<header>
<assert>
<![CDATA[
:: HERE COMES SERVER/CLIENT SIDE SNIPPETS ::
]]>
</assert>
<!-- -->
<content>
<![CDATA[
<html><head/>
]]>
</content>
</header>
<!-- HEADER CONTENT -->
<main>
<assert>
<![CDATA[
:: HERE COMES SERVER/CLIENT SIDE SNIPPETS ::
]]>
</assert>
<!-- -->
<content>
<![CDATA[
<body>
...
]]>
</content>
</main>
<!-- FOOTER CONTENT -->
<footer>
<assert>
<![CDATA[
:: HERE COMES SNIPPET ::
]]>
</assert>
<content>
<![CDATA[
</body></html>
]]>
</content>
</footer>
</template>
La version suivante a une complétion par points d'ancrages. Cette version va orienter le projet.
<?xml version="1.0" encoding="UTF-8"?>
<!-- templateChunk model -->
<template id="nested" number="" topic="" links="">
<!-- HEADER CONTENT -->
<header>
<assert>
<![CDATA[
:: HERE COMES SERVER/CLIENT SIDE SNIPPETS ::
]]>
</assert>
<!-- -->
<content>
<styles>
<style id="" />
<style id="christmas">
body
{
background-color:red;
font-family:verdana;
padding:10px;
}
.style_01
{
/* some style in */
}
</style>
</styles>
<scripts>
<script id="scr_1" number="1"/>
<script id="scr_2" number="2"/>
...
</scripts>
</content>
</header>
<!-- HEADER CONTENT -->
<main>
<assert>
<![CDATA[
:: HERE COMES SERVER/CLIENT SIDE SNIPPETS ::
]]>
</assert>
<!-- -->
<content>
<page id="standard">
<![CDATA[
<table width="100%" border="0">
<tr><td>
{{inc:page[header]}}
</td></tr>
<tr><td>
{{inc:page[main]}}
</td></tr>
</table>
]]>
</page>
<page id="lay_01"/>
<page id="lay_02"/>
<page id="lay_03"/>
</content>
</main>
<!-- PAGE CONTENT -->
<frameset>
<assert>
<code type="text/javascript" id="scr_01">
<![CDATA[
function lastScript()
{
return "this is last script";
}
]]>
</code>
<code type="action/php" id="act_01">
<![CDATA[
ContentCompleter::setContent(array("frameset.content[text/head]","header.content[*]"));
ContentCompleter::setContent(array("frameset.content[text/body]","main.content[*]"));
]]>
</code>
<code type="action/php" id="act_02">
<![CDATA[
ContentCompleter::setContent(array("frameset.assert.code[id:scr_01]","footer.content[text/id:inc_03]"));
]]>
</code>
<code type="text/php" id="toStringClass">
<![CDATA[
class toString
{//
public $self = array();
public function __construct( /* ... */ $init )
{
}
}
$tmp = new toString(1);
]]>
</code>
</assert>
<content><!-- -->
<![CDATA[
<?xml version="1.0" encoding="UTF-8" ?>
{{inc:token[strict]}}
<html>
<head>
<style>{{inc:style[christmas]}}</style>
<script>{{inc:script[topScript]}}</script>
</head>
<body onload="feedContent()">
{{inc:page[standard]}}
</body>
<script>
{{inc:script[endScript]}}
</script>
</html>
]]>
</content>
</frameset>
<!-- FOOTER CONTENT -->
<footer>
<assert>
<![CDATA[
:: HERE COMES SNIPPET ::
]]>
</assert>
<content>
<![CDATA[
{{inc:scripts[last]}}
<script>
action.load();
</script>
<script id="inc_03">
</script>
]]>
</content>
</footer>
<tokens>
<token id="HTML4">
<![CDATA[
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
]]>
</token>
<token id="xhtml_xmlns">
<![CDATA[
http://www.w3.org/1999/xhtml
]]>
</token>
</tokens>
</template>
L'autorisation de snippets php exécutables rendra possible l'intégration côté client (customer).
L'adjonction du contenu à l'output se faisant par injection aux points d'ancrage {{inc:nomDeCle[idDeCle]}} par la méthode servant à compléter le flux avant sa sortie, comme :
- getFeedByTagName ou getPartByName ...
- incomes(header/content) à l'injection
Le contenu peut être sorti côté php ou ajaxifié comme ici par le javascript de fin de flux (frameset/content) feedContent().
Exemples/MiniCMS/Implémentation
Nous avons besoin des fonctionnalités :
TODO
- [1] Complétion des données
- (none) Create
- (done) Read
- (done) Update
- (done) Delete
- [2] Gestion des users
- [3] Complétion du frameset via templates
- (done) injection point replacer
- [4] Un webService pour les inputs&query distants
- (none) common web services
- [5] Parser de snippet
- (do ) php snippet acter
- (none) class writer & executer
- (do ) single actions executer
- (do ) php snippet acter
- [6] Vue xhtml fonctionnelle ajaxifiée
- (done) xhtml template "nested"
NICE TO HAVE
- [1.1] Versionning sur données
- (todo) new crop creating passing current to old
Le but étant de fournir un maximum de code de service pour les héritages futur le métier n'est approché qu'en vue de personnaliser le CMS client.
Concernant l'approche modulaire :
La segmentation en composantes (classes ou scriptlets) permets un découpage net et précis des différentes parties du projets développable séparément les unes des autres. L'association au sein d'un IHM ou couche de présentation via contrôleur, venant :
- avant (pour orienter le développement)
- pendant (pour stabiliser le développement)
- après (pour finaliser le développement)
L'interface utilisatrice devant tirer parti des composantes et non l'inverse. La richesse des fonctionnalités est dégagée par le service et enclavé par un code orienté solution client. Il va donc de soi de privilégier proprement, pour enrichir ses propres librairies, de chercher le plus possible à développer ses classes et composantes plutôt que de perdre son temps en métier.
Concevoir du code de haute qualité signifie souvent d'optimiser le code vers une solution à logique 'customer' càd spécialisée, ce qui n'est pas recherché lorsque l'on doit se développer de l'expérience sur le terrain. Au possible, il faut adapté sa modularité et péricliter le service en business pour le client.
Concevoir du code modulaire permets de développer concrètement sans trop d'efforts d'analyse pour les développement XP et sur un grand délais. Il est utile d'avoir conscience de ces deux approches sur le terrain ou lorsque l'on décide d'ouvrir son code au monde. La majorité des développeur ne faisant trop souvent qu'intégrer pour l'argent ou la renommée chez le client le fruit du travail des autres.
'Classes & Fermes de fonctions' contre 'scriptlets'
Les premières sont :
- plus simple à coder,
- plus simple à utiliser,
- plus simple à entretenir,
- plus simple à spécialiser,
- plus facilement réutilisable,
- plus orienté objet,
- plus susceptible d'être récupéré.
Les secondes sont :
- plus cryptiques,
- plus optimisée pour le client,
- aisément traductible en classes et fermes,
- plus fonctionnelle,
- plus procédurales,
- plus chaotiques[1].
- ↑ produire un code obscure voir chaotique présente de nombreux avantages. D'autres analystes et collègues y perdront leur temps d'une part, les piping inhérents au code permettant l'implémentation, le hacking, le reroutage et la divergence fonctionnelle de script. Ce qui n'est pas sans avantages.
[1] Complétion des données
modifierPour disposer des fonctionnalités de gestion de données.
- On a besoin de Getter/Setter de données :
- - depuis la vue
- - depuis le webservice
- - depuis l'URL
- Pour les fichiers xml
Les fonctionnalités d'appels sont regroupées ci-dessous
<?php
class dataManager
{//
private $pattern;
protected $stack, $domDoc;
//
public static
$crop
= array(
"std"=> '<?xml version="1.0" encoding="UTF-8"?>
<crop><topic /><fields /></crop>'
,""=>''
),
$data
= array(
"root"=>"/miniCMS/Owners/"
);
# TODO GETTER/SETTER
public static function getContentByData($pData)
/**
* get the content from data
*/
{}
public static function setContentByData($pData)
/**
* set the content to data
*/
{}
public function setCropForCustomer($pPath, $pCrop)
/**
* set crop to customer directory
*/
{
file_put_contents($pPath, $pCrop);
}
# STATE
public function dataManager()
{
$this->initialize();
}
public function initialize()
/**
* init
*/
{
$this->domDoc = new DOMDocument();
if(@$this->data['path'])
{
$this->domDoc->load( $this->data['path'] );
}
return $this->domDoc;
}
public function saveDocument()
/**
* save document
*/
{
$this->domDoc->save($this->data['path']) ;
}
}
?>
On implémente ces fonctions utilitaires à utilities.class.php
public static function searchNodesByContent($pDocument, $pQueries)
/**
* return node matching request
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
/* ceci est un exemple de mauvais code de service,
il ne fonctionne que pour un nombre limité de structure de crop
c'est donc un code métier à surcharger. */
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
foreach ( $u->childNodes as $v )
{
if( $v->nodeName == $pQueries['node']
&& ! utilities::isContent ( $pQueries['value']
, $v->nodeValue ) )
$_fields->item(0)->removeChild($u) ;
}
}
/* OBSOLÈTE - Malheureusement, ce code ne marche pas une fois appelée par getNodes
for($i=0; $i<count($pNodeSet); $i++)
{
$_content = $pNodeSet[$i]->childNodes ;
// cette fonction ne descend qu'à un niveau
foreach($_content as $v)
{
if( $v->nodeName == $pQueries['node']
&& ! utilities::isContent ( $pQueries['value']
, $v->nodeValue ) )
{
$pNodeSet[$i]->parentNode->removeChild($pNodeSet[$i]) ;
}
}
}
*/
return $pDocument ;
}
public static function searchNodesByAttribute($pDocument, $pQueries)
/**
* return node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
if( !($u->getAttribute($pQueries['attribute'])
== $pQueries['value']) ) // 1:1 match
$_fields->item(0)->removeChild($u) ;
}
return $pDocument ;
}
On implémente la méthode getContentByData du dataManager
public static function getContentByData($pDocument, $pData)
/**
* get the content from data
*/
{
/* DEPRECIEE - code pour l'appel à getNodes déprécié pour searchNodes
if($pData['node'])
{
if($pData['node'] != "*")
{
$expr = '//'.$pData['node'].'/..' ;
} else {
return $pDocument ;
}
} else {
return false ;
}
*/
return utilities::searchNodes($pDocument, $pData) ;
}
# Qui devient ici capable de distinguer
# recherche de contenu d'avec les attributs de nœuds
public static function getContentByData($pDocument, $pData)
/**
* get the content from data
*/
{
if(@$pData['attribute'])
{
return utilities::searchNodesByAttribute($pDocument, $pData) ;
}
else
{
return utilities::searchNodesByContent($pDocument, $pData) ;
}
}
Un test du getContentByData nous rend nominal pour cette phase dans cette modélisation métier.
Le refactoring du business en services serait apprécié...
<?php
require_once "./Cms/Classes/dataManager.class.php";
require_once "./Cms/Classes/utilities.class.php";
//echo '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
$_manager = new dataManager();
//
$_manager->data['path'] = $_manager->data['root'] . "Customer1/Contents/crop1.xml"; //
// init manager
$_document = $_manager->initialize();
# QUERY TESTS
// setting de valeur à récupérer
$_manager->data['query_1'] = array("node"=>"subject","value"=>"potager");
$_manager->data['query_2'] = array("node"=>"content","value"=>"molle2");
// queries test
$res = dataManager::getContentByData(
dataManager::getContentByData($_document, $_manager->data['query_1'])
,$_manager->data['query_22']);
# OUTPUT
echo $res->saveXML();
?>
- -> Sur l'input crop1.xml
<?xml version="1.0" encoding="UTF-8"?>
<crop>
<topic>Cultiver son potager</topic>
<scope>légumes potager soupe</scope>
<fields>
<field id="k_01">
<subject>les légumes du potager</subject>
<content>Le potager contient une terre molle.
Parsemé ça et là, on trouve carottes,
petits poids et potirons
</content>
</field>
<field id="k_02">
<subject>les légumes du test</subject>
<content>Le potager contient une terre molle.
Parsemé ça et là, on Test carottes,
petits poids et potirons</content>
</field>
<field id="k_03">
<subject>les légumes du potager</subject>
<content>Le potager contient une terre molle2.
Parsemé ça et là, on trouve carottes,
petits poids et potirons</content>
</field>
</fields>
</crop>
- -> On a pour résultat :
<?xml version="1.0" encoding="UTF-8" ?>
<crop>
<topic>Cultiver son potager</topic>
<scope>légumes potager soupe</scope>
<fields>
<field id="k_03">
<subject>les légumes du potager</subject>
<content>Le potager contient une terre molle2.
Parsemé ça et là, on trouve carottes, petits poids et potirons
</content>
</field>
</fields>
</crop>
Le deuxième test de get par l'id est également concluant
# QUERY TESTS
// setting de valeur à récupérer
$_manager->data['query_1'] = array("node"=>"subject", "value"=>"potager");
$_manager->data['query_2'] = array("attribute"=>"id", "value"=>"k_03");
// query test
$res = dataManager::getContentByData(
dataManager::getContentByData($_document, $_manager->data['query_1'])
,$_manager->data['query_2']);
# OUTPUT
echo $res->saveXML();
Son résultat étant :
<?xml version="1.0" encoding="UTF-8" ?>
<crop>
<topic>Cultiver son potager</topic>
<scope>légumes potager soupe</scope>
<fields>
<field id="k_03">
<subject>les légumes du potager</subject>
<content>Le potager contient une terre molle2.
Parsemé ça et là, on trouve carottes,
petits poids et potirons
</content>
</field>
</fields>
</crop>
On implémente à utilities.class.php les méthodes :
public static function writeContentByAttribute($pDocument, $pQueries)
/**
* write node content matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
if( $u->getAttribute($pQueries['attribute'])
== $pQueries['value'] ) // 1:1 match
{
$v = $u->getElementsByTagName($pQueries['node']) ;
$v->item(0)->nodeValue = $pQueries['replacement'] ;
$_flg = true ;
}
}
}
return $_flg ;
}
public static function deleteContentByContent($pDocument, $pQueries)
/**
* delete node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
foreach ( $u->childNodes as $v )
{
if( $v->nodeName == $pQueries['node']
&& utilities::isContent ( $pQueries['value'] ,
$v->nodeValue ) )
{
$_fields->item(0)->removeChild($u) ;
$_flg = true ;
}
}
}
}
return $_flg ;
}
public static function deleteContentByAttribute($pDocument, $pQueries)
/**
* delete node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
if( $u->getAttribute($pQueries['attribute'])
== $pQueries['value'] ) // 1:1 match
{
$_fields->item(0)->removeChild($u) ;
$_flg = true ;
}
}
return $_flg ;
}
et à la classe dataManager.class.php
public function setContentByData($pDocument,$pData)
/**
* set the content to data
*/
{
if(@$pData['attribute'])
{
utilities::writeContentByAttribute($pDocument, $pData) ;
}
else
{
utilities::writeContentByContent($pDocument, $pData) ;
}
$this->saveDocument();
}
Le test suivant permet de valider la phase d'écriture :
# QUERY TESTS
// setting de valeur à récupérer
$_manager->data['query_1'] = array("attribute"=>"id", "value"=>"k_03","node"=>"subject",
"replacement"=>"this is the new subject");
// query test
$_manager->setContentByData($_document, $_manager->data['query_1']);
avec le resultat
<field id="k_03">
<subject>this is the new subject</subject>
<content>Le potager contient une terre molle2.
Parsemé ça et là, on trouve carottes,
petits poids et potirons
</content>
</field>
Liste des défauts
modifier- searchContents / getContentByData est du code métier
- -> le transcrire en code de service
- pour la suppression, delete reste à implémenter correctement
- -> à dataManager
[2] Gestion des utilisateurs
modifierLa gestion des utilisateurs suivant le modèle ne demande que l'extraction de la ligne utilisateur pour recueillir ses infos.
Dans la classe utilities.class.php on rajoute ces deux méthodes :
public static function getAttributesContents($pNode)
/**
* retourne les attributs dans un tableau
*/
{
foreach ($pNode->attributes as $attrName => $attrNode)
{
$_tab[$attrName] = $attrNode->value ;
}
return $_tab ;
}
public static function getLineByAttribute($pDocument, $pQueries)
/**
* return node matching request by attribute
*/
{
$_users = $pDocument->getElementsByTagName('user');
foreach ($_users as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
$_flg = true ;
foreach($pQueries as $_qa )
{
if( !($u->getAttribute($_qa['attribute'])
== $_qa['value']) || !$_flg )
{ // 1:1 match
$_flg = false ;
}
}
if($_flg)
return utilities::getAttributesContents($u) ;
}
}
return false ;
}
Un test concluant de ces méthodes nous rend opérationnel pour cette phase
<?php
require_once "./Cms/Classes/dataManager.class.php";
require_once "./Cms/Classes/utilities.class.php";
$_manager = new dataManager();
//
$_manager->data['path'] = $_manager->data['root'] . "Customer1/Contents/user1.xml"; //
# QUERY TESTS
$_manager->data['query_1'] = array(
array("attribute"=>"login", "value"=>"user2")
, array("attribute"=>"pass", "value"=>"pass2"));
// query test
$_res = utilities::getLineByAttribute($_manager->initialize(), $_manager->data['query_1']) ;
# OUTPUT
print_r_html($_res);
?>
Résultat :
Array ( [login] => user2 [pass] => pass2 [lastname] => name2 [forname] => forName2 [rights] => u [customer] => customer2 )
Liste des défauts
modifierCette gestion des utilisateurs ne prend actuellement que les attributs de nœuds...
- -> il serait utile de pourvoir des données supplémentaires dans le nœud
user
.
[3] Complétion du frameset
modifier- Pour commencer nous déclarons une classe utilities.class.php qui reprendra toutes les fonctionnalités utiles pour notre solution
<?php
function print_r_html($data,$return_data=false)
{
$data = print_r($data,true);
$data = str_replace( " "," ", $data);
$data = str_replace( "\r\n","<br/>\r\n", $data);
$data = str_replace( "\r","<br/>\r", $data);
$data = str_replace( "\n","<br/>\n", $data);
if (!$return_data)
echo $data;
else
return $data;
}
class utilities
{
public static function getNodes($pDomDoc, $pXpathString)
/**
* get the node from DomDoc with XpathString
*/
{
$xp = new DOMXPath($pDomDoc);
$xp->registerNamespace('x', 'http://www.w3.org/1999/xhtml');
$xp->registerNamespace('xhtml', 'http://www.w3.org/1999/xhtml');
$xp->registerNamespace('i18n', 'http://apache.org/cocoon/i18n/2.1');
$ret = array();
$nodes = $xp->query($pXpathString);
foreach ($nodes as $node)
{
array_push($ret, $node);
}
}
public static function isContent($pPattern, $pFeed)
/**
* check is pattern in feed
*/
{
return preg_match('/('.$pPattern.')/',$pFeed)?true:false;
}
}
?>
- La classe templateControler regroupe les fonctions sur les templates. Le contenu à publier est segmenté en flux.
- Son objectif est de :
- - reformater,
- - assembler,
- - publier.
<?php
class templateControler
{
/**
* Control the templates
*
* @var (mixed) ($domDoc, $data, $stack)
*/
//
private $pattern
= array(
'inc'=>'/{{inc:[a-zA-Z]*[\[\]\*]*}}/' // inclusion patterns
,''=>''
);
protected $domDoc, $stack;
//
public $data;
/*
public function __construct()
{//
$this->domDoc = new DOMDocument();
$this->initialize();
}
*/
/**
* Enter description here...
*
* @param unknown_type $pVar
* @return templateControler
*/
public function templateControler($pVar)
{
$this->domDoc = new DOMDocument();
$this->setter($pVar);
$this->initialize();
}
/**
* Enter description here...
*
* @param unknown_type $pVar
*/
public function setter($pVar)
{//
$this->data = $pVar;
}
// TODO GETTER
public function initialize()
{//
$this->domDoc->load( $this->data['path'] );
}
/**
* Enter description here...
*
*/
public function getNode($pXPath) //"//html/body/*[@class='text']";
{//
return $this->data['feed'] = utilities::getNodes($this->domDoc, $pXPath);
}
public function getAnchors($pFeed)
{//
preg_match_all($this->pattern['inc'], $pFeed, $this->data['result']);
return $this->data['result']; // on prefere les données centralisées dans la classe
}
public function publicTemplate()
{//
return $this->domDoc->saveXML();
}
}
Le test suivant permet de voir si on prend bien les ancres d'injection, par exemple, pour le nœud frameset/content
//
$tmp = new templateControler(array("path"=>"object.xml"));
$tst = $tmp->getNode("//template/frameset/content");
print_r_html($tmp->getAnchors($tst[0]->nodeValue));
Array ( [0] => Array ( [0] => {{inc:styles[*]}} [1] => {{inc:scripts[*]}} [2] => {{inc:bodies[*]}} ) )
On implémente ces deux nouvelles fonctions à templateControler
/**
* getContent retourne le contenu du/des nœuds demandés
*
* > (nodeName, nodeId) - (string) >
*/
public function getContent($nodeName,$nodeId)
{//
// set expression
$_expr = "//" . $nodeName . ($nodeId&&$nodeId!='*'?"[id='$nodeId']":"");
$set = $this->getNode($_expr);
$content=null;
foreach($set as $k=>$v)
{
$content .= "<" . $nodeName . ">" . trim($v->nodeValue) . "</" . $nodeName . ">";
}
return $content?$content:"";
}
/**
* replace anchor by content
*/
public function anchorContentReplacer()
{//
$i=0;
foreach($this->data['result'][0] as $k=>$v)
{
// formatting anchor's syllabes
$v = str_replace(array('{','[','}',']'),array('',' ','',''),$v);
$tmp = explode(":",$v);$_ = explode(' ',$tmp[1]);
// replacing
$this->data['result'][0][$i++]
= array("node"=>$_[0],"value"=>$this->getContent($_[0], $_[1]));
}
}
// TODO : getNodeById pour disposer d'un getElementById moins spécialisé et fonctionnel ici.
Le test suivant permet de voir si on remplace bien l'ancre par son contenu :
require_once "templateControler.class.php";
//
$tmp = new templateControler(array("path"=>"template.xml"));
$tmp->getNode("//template/frameset/content");
$tmp->getAnchors(
$tmp
->data['feed'][0]
->nodeValue);
$tmp->anchorContentReplacer();
print_r_html(
$tmp->data['result']
);
Array ( [0] => Array ( [0] => Array ( [node] => styles [value] => body { background-color:red; font-family:verdana; padding:10px; } .style_01 { /* some style in */ } ) [1] => Array ( [node] => scripts [value] => ) [2] => Array ( [node] => bodies [value] => ) ) )
Le contenu est bien localisé dans le template, par conséquent l'objectif de cette phase est atteint. On est opérationnel pour l'injection.
- La classe snippetActer regroupe les fonctions de computation des snippets.
class SnippetActer
{//
protected $_type,$_content,$_stack;
# GET/SET
public function setType()
{//
list($_1,$_2)=explode('/',$this->_type);
$this->actCode(array($_1,$_2));
}
public function setContent($pContent)
{//
$this->_content = $pContent;
}
# STATE
/*
* act statements
*/
public function actCode($pType)
{//
switch(true)
{
case $pType[0]=="text"&&$pType[0]=="php":
//
$this->write();
break;
case $pType[0]=="text"&&$pType[0]=="javascript":
$this->inject();
//
break;
case $pType[0]=="action"&&$pType[0]=="php":
$this->unstack();$this->act();
break;
}
}
/*
* stack statements
*/
public function unstack()
{//
$_this->_stack = explode(";",$this->_content);
}
/*
* execute statements
*/
public function act()
{//
foreach($this->_stack as $v)
eval($v);
}
}
Liste des défauts
modifier- Les snippets actifs à la volée peuvent ralentir l'output
- -> l'ajaxification peut fournir le contenu en deux temps (données avant/après traitements)
- Cette classe reste à faire...
[4] Un webservice
modifierLe [[../../webService|webService]] propose par défaut une synergie avec d'autres solutions, sites, applications. Des fonctionnalités distantes permettent l'interfaçage logicielle avec le serveur de données, option qui deviendra basique pour les systèmes orientés données.
<?php
/*
webservice servker
*/
# no chache in testing
ini_set("soap.wsdl_cache_enabled", "0");
# wsdl
$serveurSOAP = new SoapServer('service.wsdl');
# provided services
/* inqueries services
in :
out :
*/
$serveurSOAP->addFunction('request');
// ...
/* high level services
in :
out :
*/
$serveurSOAP->addFunction('harvest');
// ...
/* low level services
in :
out :
*/
$serveurSOAP->addFunction('deploy');
$serveurSOAP->addFunction('f_01');
$serveurSOAP->addFunction('f_02');
// ...
# server start
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
$serveurSOAP->handle();
}
# services integration
require_once "./Services/request.srv.php";
require_once "./Services/service_01.srv.php";
require_once "./Services/service_02.srv.php";
# online status checker
function listen($pIn)
{
return 'Copy : ' . $pIn;
}
?>
Liste des défauts
modifierExemples/MiniCMS/Déploiement de la vue
Le frontOffice
modifierLe front office est orienté ajax.
<?xml version="1.0" encoding="UTF-8"?>
<!-- templateChunk model -->
<template id="nested" number="" topic="" links="">
<!-- HEADER CONTENT -->
<header>
<assert>
<![CDATA[
:: HERE COMES SERVER/CLIENT SIDE SNIPPETS ::
]]>
</assert>
<!-- -->
<content>
<style id="" />
<style id="standard">
body
{
/* background-color:red; */
font-family:verdana;
font-size:10px;
margin:0px 0px 0px 0px;
padding:0px;
}
.click
{
cursor:pointer;
}
.style_01
{
/* some style in */
}
.encoder
{
font-size:11px;
font-family:verdana;
width:95%;
}
.title td
{
font-weight:bold;
}
.specButton
{
font-weight:bold;
cursor:pointer;
font-size:10px;
font-family:verdana;
}
div
{
/* border: grey 1px solid ; */
}
.dirPath
{
background-color:yellow;
}
.red
{
color:red;
}
.green
{
color:green;
}
.dark
{
padding:2px;
background:#bbb;
/* font-weight:bold; */
color:white;
}
.panel
{
position: fixed;
background:#eee;
top: 1em;
right: 2%;
border: 1px solid #000000;
padding: 1em;
z-index: 10;
width: 200;
}
.adminMenu
{
position: fixed;
background:#eee;
top: 1em;
left: 2%;
border: 1px solid #000000;
padding: 1em;
z-index: 10;
width: 200;
}
.bottomTool
{
position: fixed;
background:#eee;
bottom: 1em;
left: 2%;
border: 1px solid #000000;
padding: 1em;
z-index: 10;
/* width: 75%; */
overflow:auto;
}
/* */
input
{
width:80%;
}
</style>
<script id="prototype" number="" >
<![CDATA[
<script src="./Cms/Objects/prototype.js"></script>
]]>
</script>
<script id="editor" number="">
<![CDATA[
// inject special tags in the textPad as bold, strike...
// input target place, start position, end position
// output nonde
function txtInject(trg,repdeb, repfin)
{//
document.all('txt#content').focus();
/* pour l'Explorer Internet */
if(typeof document.selection != 'undefined')
{
/* Insertion du code de formatage */
var range = document.selection.createRange();
var insText = range.text;
range.text = repdeb + insText + repfin;
/* Ajustement de la position du curseur */
range = document.selection.createRange();
if (insText.length == 0)
{
range.move('character', -repfin.length);
} else {
range.moveStart('character', repdeb.length + insText.length + repfin.length);
}
range.select();
}
/* pour navigateurs plus récents basés sur Gecko*/
else if(typeof input.selectionStart != 'undefined')
{
/* Insertion du code de formatage */
var start = input.selectionStart;
var end = input.selectionEnd;
var insText = input.value.substring(start, end);
input.value = input.value.substr(0, start) + repdeb + insText + repfin + input.value.substr(end);
/* Ajustement de la position du curseur */
var pos;
if (insText.length == 0)
{
pos = start + repdeb.length;
} else {
pos = start + repdeb.length + insText.length + repfin.length;
}
input.selectionStart = pos;
input.selectionEnd = pos;
} else {
/* requête de la position d'insertion */
var pos;
var re = new RegExp('^[0-9]{0,3}$');
while(!re.test(pos))
{
pos = prompt("Insertion à la position (0.." + input.value.length + "):", "0");
}
if(pos > input.value.length)
{
pos = input.value.length;
}
/* Insertion du code de formatage */
var insText = prompt("Veuillez entrer le texte à formater:");
input.value = input.value.substr(0, pos) + repdeb + insText + repfin + input.value.substr(pos);
}
}//
// sort a text pad in the main gui if double clicked on the content zone
// input content string, text container id
// output reformated string
function tinyTextPad(str,txt_id)
{// text editor
// set the utilities images on the content zone of the container
str= "<div id='txtPad' align='left'><img src='./Cms/Images/bold.gif' style='cursor:pointer;' onclick='trg=parentNode.nextSibling;txtInject(trg,\"<b>\",\"</b>\");' /><img src='./Cms/Images/italic.gif' onclick='trg=parentNode.nextSibling;txtInject(trg,\"<i>\",\"</i>\");' /><img src='./Cms/Images/underline.gif' style='cursor:pointer;' onclick='trg=parentNode.nextSibling;txtInject(trg,\"<u>\",\"</u>\");' /><img src='./Cms/Images/stroke.gif' style='cursor:pointer;' onclick='trg=parentNode.nextSibling;txtInject(trg,\"<s>\",\"</s>\");' /><img src='./Cms/Images/p.gif' style='cursor:pointer;' onclick='trg=parentNode.nextSibling;txtInject(trg,\"<p>\",\"</p>\");' /> | <img src='./Cms/Images/ul.gif' style='cursor:pointer;' onclick='trg=parentNode.nextSibling;txtInject(trg,\"<ul>\",\"</ul>\");' /><img src='./Cms/Images/li.gif' style='cursor:pointer;' onclick='trg=parentNode.nextSibling;txtInject(trg,\"<li>\",\"</li>\");' /></div><textarea id='"+txt_id+"' rows='15'>"+str+"</textarea>";
return str;
}//
]]>
</script>
<script id="topScript" number="">
<![CDATA[
function reformat( pContent )
{
return pContent.replace(/=/gi,'``i').replace(/\"/gi,'``o').replace(/\?/g,'``q').replace(/&/g,'``e').replace(/%/g,'``u').replace(/#/g,'``a');
}
function trim( content )
{
return content.replace(/^\s+|\s+$/g,"");
}
function collapse(pDom)
{
switch(pDom.innerHTML)
{
case "[_]":
pDom.innerHTML = "[+]";
break;
case "[+]":
pDom.innerHTML = "[_]";
break;
}
}
function showHide(pDom,pFlg)
{
if(pDom.style.display=='none')
pDom.style.display='' ;
else
pDom.style.display='none' ;
}
function doHtml( content )
{
content = content.replace(/</g, "<").replace(/>/g,">");
return content ;
}
function feedContainer( pDom )
/*
*/
{
var ctc = pDom.innerHTML ;
cpt = document.getElementById('iCounter') ;
var nbr = Number((cpt.value)) ;
if(trim(ctc).substring(0,4) != "<tex" && trim(ctc).substring(0,4) != "<TEX" )
{
cpt.value = nbr +1 ;
pDom.innerHTML = "<textarea id='t_"+nbr+"' style='width:99%' rows='10' cols='20'>" + trim(ctc) + "</textarea>" ;
}
else
{
cpt.value = nbr -1 ;
placeContent( pDom.firstChild.value, pDom.id ) ;
}
}
var _ = function() {} ;
// object access
_.prototype.oa = {
tab:new Array(),
getNodesById:function(pDom, pId)
/**
getNodes by id function
*/
{
if(pDom.getElementsByTagName)
{
all = pDom.getElementsByTagName("*") ;
for(g=0; g<all.length; g++)
{
if( all[g].getAttribute && all[g].getAttribute("id") == pId )
{
this.tab.push(all[g]) ;
}
}
return this.tab ;
}
},
getNodesWithId:function(pDom, pId)
/**
getNodes by id function
*/
{
if(pDom.getElementsByTagName)
{
var reg=new RegExp("("+pId+")","g") ;
all = pDom.getElementsByTagName("*") ;
for(g=0; g<all.length; g++)
{
if( all[g].getAttribute && reg.test(all[g].getAttribute("id")) )
{
this.tab.push(all[g]) ;
}
}
return this.tab ? this.tab : false ;
}
},
each:function() // pAction
/**
*/
{
for(var i=0;i<this.tab.length;i++)
{
//
}
}
}
_.prototype.Remote = {
getConnector: function()
{
var connectionObject = null ;
if (window.XMLHttpRequest)
{
connectionObject = new XMLHttpRequest() ;
}
else if (window.ActiveXObject)
{
connectionObject = new ActiveXObject('Microsoft.XMLHTTP') ;
}
return connectionObject ;
},
configureConnector: function(connector, callback)
{
connector.onreadystatechange = function()
{
if (connector.readyState == 4)
{
if (connector.status == 200)
{
callback.call(connector, {
text: connector.responseText,
xml: connector.responseXML
});
}
}
}
},
load: function(request)
{
var url = request.url || "" ;
var callback = request.callback || function() {} ;
var connector = this.getConnector() ;
if (connector)
{
this.configureConnector(connector, callback) ;
connector.open("GET", url, true) ;
connector.send("") ;
}
},
save: function(request)
{
var url = request.url || "" ;
var callback = request.callback || function() {} ;
var data = request.data || "" ;
var connector = this.getConnector() ;
if (connector)
{
this.configureConnector(connector, callback);
connector.
open("POST", url, true);
connector.
setRequestHeader("Content-type", "application/x-www-form-urlencoded") ;
connector.
setRequestHeader("Content-length", data.length) ;
connector.
setRequestHeader("Connection", "close") ;
connector.
send(data);
}
}
}
function placeContent ( pContent, pIdTarget )
/**
set the content to targetted container
*/
{
_.Remote.save(
{
url: "./main.php",
data: "qry=2&id=" + pIdTarget + "&content=" + reformat(pContent),
callback: function(response)
{
var nfo = segment( response.text ) ;
var node = document.getElementById( nfo[0] ) ;
node.innerHTML = nfo[2] ;
if($('contentKey').value==1)
{
setNodes(node);
}
_.oa.tab = new Array() ;
feedContent() ;
}
}
);
}
]]>
</script>
<script id="endScript" number="">
<![CDATA[
_ = new _() ;
function segment(pStr)
/**
*/
{
var reg=new RegExp("~~", "g") ;
return pStr.split(reg) ;
}
function isToFeed()
/**
check is still to feed
*/
{
return (set = _.oa.getNodesWithId(document.body,"lay") )?set:false ;
}
function feedLayout()
/**
feed the layout
*/
{
var ctc = _.oa.getNodesWithId(document.body,"lay") ;
if(ctc[0])
_.Remote.save({
url: "./main.php",
data: "qry=1&id="+ctc[0].id+"&owner=" + document.getElementById("customerName").value,
callback: function(response)
{
var nfo = segment( response.text ) ;
var node = document.getElementById( nfo[0] ) ;
node.innerHTML = nfo[2] ;
node.id = nfo[1] ;
_.oa.tab = new Array() ;
feedContent() ;
}
});
}
function feedData()
/**
feed the data
*/
{
var ctc = _.oa.getNodesWithId(document.body,"data") ;
if(ctc[0])
_.Remote.save({
url: "./main.php",
data: "qry=1&id="+ctc[0].id+"&request=1&owner=" + document.getElementById("customerName").value,
callback: function(response)
{
var nfo = segment( response.text ) ;
var node = document.getElementById( nfo[0] ) ;
node.innerHTML = nfo[2] ;
node.id = nfo[1] ;
_.oa.tab = new Array() ;
feedContent() ;
}
});
}
function feedContent()
/**
feed the content of the page by tag expr and Id
*/
{
feedLayout() ;
feedData() ;
if($('contentKey').value!=0)
{
if($('mainContent'))
{
$('mainContent').ondblclick = function (e) {feedContainer(this);};
}
}
}
]]>
</script>
<script id="pagerScript" number="">
<![CDATA[
function cropPage()
/*
get the crop page
*/
{
_.Remote.save(
{
url: "./main.php",
data: "qry=7",
callback: function(response)
{
var nfo = segment( response.text ) ;
document.getElementById("mainContent").innerHTML = nfo[2];
}
});
}
function itemPage()
/*
get the item page
*/
{
_.Remote.save(
{
url: "./main.php",
data: "qry=8",
callback: function(response)
{
var nfo = segment( response.text ) ;
document.getElementById("mainContent").innerHTML = nfo[2];
}
});
}
function pagePage()
/*
get the page page
*/
{
_.Remote.save(
{
url: "./main.php",
data: "qry=9",
callback: function(response)
{
var nfo = segment( response.text ) ;
document.getElementById("mainContent").innerHTML = nfo[2];
}
});
}
]]>
</script>
<script id="adminScript" number="">
<![CDATA[
// $('mainContent').onDblClick = "feedContainer(this);";
]]>
</script>
</content>
</header>
<!-- HEADER CONTENT -->
<main>
<assert>
<![CDATA[
:: HERE COMES SERVER/CLIENT SIDE SNIPPETS ::
]]>
</assert>
<!-- -->
<content>
<page id="standard">
<![CDATA[
<input type="hidden" id="iCounter" name="nCounter" value="0" />
<div id="lay:frameContent" class="toFeed" >> mainEntry container</div>
]]>
</page>
<page id="frameContent">
<![CDATA[
<div id="lay:topContent"></div>
<div id="lay:mainContent"></div>
<div id="lay:bottomContent"></div>
]]>
</page>
<page id="topContent">
<![CDATA[
<div> > My Tiny CMS | TOP CONTENT</div>
]]>
</page>
<page id="loginPane">
<![CDATA[
<div>
<input id="iUsr" type="text" value=" " style="width:96%;" /><br/>
<input id="iButton" type="button" value="log on" style="width:100%;" />
</div>
]]>
</page>
<page id="menuPane">
<![CDATA[
<div class="panel">
<span onclick="showHide($('cMenu'),1);collapse(this);">[+]</span>
<span id="cMenu"> !.crop | !.item | !.page
<p />
<span onclick="showHide($('loginPane'),2);">:: Login Pane ::</span>
<span><div id="lay:loginPane"></div></span>
</span>
</div>
]]>
</page>
<page id="mainContent">
<![CDATA[
<div id="mainContent">
<div id="data:1|1|rootEntry"></div>
</div>
]]>
</page>
<!--
The bottom tool may contain all the moderator and admin utilities
-->
<page id="bottomTool">
<![CDATA[
<!--
<div class="bottomTool">
<span onclick="showHide($('cBottom'),1);collapse(this);">[+]</span>
<span id="cBottom">:: Bottom Tool ::
<div id="cBottomPane"></div>
</span>
</div>
-->
]]>
</page>
<page id="bottomContent">
<![CDATA[
<div> myTinyCS @ | version 1.0 </div>
]]>
</page>
<page id="lay_01" />
<page id="lay_02" />
<page id="lay_03" />
<!--
!.crop | !.item | !.page
-->
</content>
</main>
<!-- PAGE CONTENT -->
<frameset>
<assert>
<code type="text/javascript" id="scr_01">
<![CDATA[
function lastScript()
{
return "this is last script";
}
]]>
</code>
</assert>
<content><!-- -->
<![CDATA[
<?xml version="1.0" encoding="UTF-8" ?>
{{inc:token[xhtmlTransitional]}}
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<head>
<style>{{inc:style[standard]}}</style>
<script>{{inc:script[topScript]}}</script>
<script>{{inc:script[pagerScript]}}</script>
{{inc:script[prototype]}}
</head>
<body onload="feedContent()">
{{inc:customer}}
{{inc:menuPane}}
{{inc:bottomPane}}
<!-- Page entry -->
<div id='mainContent'>
{{inc:page[standard]}}
</div>
</body>
<script>
{{inc:script[endScript]}}
</script>
</html>
]]>
</content>
</frameset>
<tokens>
<token id="xhtmlTransitional">
<![CDATA[
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
]]>
</token>
<token id="HTML4">
<![CDATA[
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
]]>
</token>
<token id="xhtmlstrict">
<![CDATA[
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
]]>
</token>
<token id="xhtml_xmlns">
<![CDATA[
http://www.w3.org/1999/xhtml
]]>
</token>
</tokens>
</template>
Le backOffice
modifier- Config.inc.php
<?php
/**
* configure my tiny CMS
*/
define("ROOT","") ;
define("CMS" ,ROOT."/Cms") ;
define("OWN" ,ROOT."/Owners") ;
define("CLA" ,CMS . "/Classes") ;
define("FUN" ,CMS . "/Functions") ;
//
define("OBJ" ,CMS . "/Objects") ;
define("BCK" ,OBJ . "/Backend") ;
define("FNT" ,OBJ . "/Frontend") ;
?>
- main.php
- point d'entrée back&front end
- un switch distingue les appels entrants avec et sans paramètres
<?php
require_once "./config.inc.php" ;
if( !@$_REQUEST['qry'] )
/*
* output type switch for ajax layer
*/
{
include_once FNT."/entry.inc.php" ;
}
else
{
include_once BCK."/responder.inc.php" ;
}
?>
- responder.inc.php
- appels entrant avec interrogation
<?php
# TESTING 1.0
echo $_REQUEST["id"] . "~@~" . $_REQUEST['id'] . " / " . $_REQUEST["name"] . " " . $_REQUEST["surname"] ;
?>
- entry.inc.php
- appels entrant sans interrogation
<?php
//
//require_once CLA . "/dataManager.class.php" ;
require_once CLA . "/utilities.class.php" ;
require_once CLA . "/templateControler.class.php" ;
//
$tmp = new templateControler(array("path"=>"./Owners/Root/Templates/default.xml"));
# STATE
$tmp->initFrame(
$tmp
->getNode("//template/frameset/content")
) ;
$tmp->getAnchors( $tmp->data['feed'][0]->nodeValue ) ;
$tmp->anchorContentReplacer() ;
$tmp->setFrame() ;
# OUTPUT
echo $tmp->data['frameset'] ;
?>
Exemples/MiniCMS/Ajaxification
L'ajaxification à implémenter côté serveur et client permet une plus grande souplesse d'interaction entre la vue et le backend. Le séquencement du contenu en flux offre une plus grande réactivité.
Pour ce faire :
Dans le template des owners, on implémente la couche AJAX suivante, "topScript" nourrit le début de document et "bottomScript" l'exécution de fin de document.
<script id="topScript" number="">
<![CDATA[
var $ = function() {};
// object access
$.prototype.oa = {
tab:new Array(),
getNodesById:function(pDom, pId)
/**
getNodes by id function
*/
{
if(pDom.getElementsByTagName)
{
all = pDom.getElementsByTagName("*");
for(g=0; g<all.length; g++)
{
if( all[g].getAttribute
&& all[g].getAttribute("id")
== pId )
{
this.tab.push(all[g]);
}
}
return this.tab;
}
},
getNodesWithId:function(pDom, pId)
/**
getNodes by id function
*/
{
if(pDom.getElementsByTagName)
{
var reg=new RegExp("("+pId+")","g");
all = pDom.getElementsByTagName("*");
for(g=0; g<all.length; g++)
{
if( all[g].getAttribute &&
reg.test(all[g].getAttribute("id")) )
{
this.tab.push(all[g]);
}
}
return this.tab;
}
},
each:function() // pAction
/**
TODO
*/
{
for(var i=0; i<this.tab.length; i++)
{
//
}
}
}
$.prototype.Remote = {
getConnector: function()
{
var connectionObject = null;
if (window.XMLHttpRequest)
{
connectionObject = new XMLHttpRequest();
} else if (window.ActiveXObject) {
connectionObject = new ActiveXObject('Microsoft.XMLHTTP');
}
return connectionObject;
},
configureConnector: function(connector, callback)
{
connector.onreadystatechange = function()
{
if (connector.readyState == 4)
{
if (connector.status == 200)
{
callback.call(connector, {
text: connector.responseText,
xml: connector.responseXML
});
}
}
}
},
load: function(request)
{
var url = request.url || "";
var callback = request.callback || function() {};
var connector = this.getConnector();
if (connector) {
this.configureConnector(connector, callback);
connector.open("GET", url, true);
connector.send("");
}
},
save: function(request) {
var url = request.url || "";
var callback = request.callback || function() {};
var data = request.data || "";
var connector = this.getConnector();
if (connector)
{
this.configureConnector(connector, callback);
connector.open("POST", url, true);
connector.
setRequestHeader("Content-type",
"application/x-www-form-urlencoded");
connector.
setRequestHeader("Content-length", data.length);
connector.
setRequestHeader("Connection", "close");
connector.
send(data);
}
}
}
]]>
</script>
<script id="endScript" number="">
<![CDATA[
$ = new $();
function segment(pStr)
/**
segment the callback
*/
{
var reg=new RegExp("~@~+", "g");
return pStr.split(reg);
}
function feedContent()
/**
feed the content of the page by tag expr and Id
*/
{
var ctc = $.oa.getNodesWithId(document.body,"id");
for(var i=0; i < ctc.length ; i++ )
{
//
$.Remote.save({
url: "./main.php",
data: "qry=1&id="+ctc[i].id+"&name=Ben&surname=Abdell",
callback: function(response)
{
var nfo = segment( response.text );
document.getElementById( nfo[0] ).innerHTML = nfo[1];
}
});
}
}
]]>
</script>
On teste sur un segment du body. Par convention, le container à nourrir porte l'id composé "id:idDuContainer".
<page id="standard">
<![CDATA[
<table width="100%" border="0" id="table1">
<tr><td>
<div id="id:topContent"/>
</td></tr>
<tr><td>
<div id="id:mainContent"/>
</td></tr>
<tr><td>
<div id="id:bottomContent"/>
</td></tr>
</table>
]]>
</page>
Un test sur la page "standard" donne en output pour l'appel ajax :
$.Remote.save({
url: "./main.php",
data: "qry=1&id="+ctc[i].id+"&name=Ben&surname=Aldell",
callback: function(response)
{
var nfo = segment( response.text );
document.getElementById( nfo[0] ).innerHTML = nfo[1];
}
});
=> le résultat suivant
<table id="table1" width="100%" border="0">
<tbody>
<tr>
<td>
<div id="id:topContent">id:topContent / Ben Aldell</div>
</td>
</tr>
<tr>
<td>
<div id="id:mainContent">id:mainContent / Ben Aldell</div>
</td>
</tr>
<tr>
<td>
<div id="id:bottomContent">id:bottomContent / Ben Aldell</div>
</td>
</tr>
</tbody>
</table>
On est nominal pour l'ajaxification de la complétion du contenu côté client.
Implémentation des contrôleurs côté serveur
modifier- Common.inc.php :
<?php
/*
common application functiunalities
would be fine to merge all request to requestForData
*/
require_once CLA . "/utilities.class.php";
require_once CLA . "/dataManager.class.php";
require_once CLA . "/templateControler.class.php";
function requestForLayout($pTerm,$pOwner="Root",$pTemplate="default")
{
$tmp = new templateControler(array("path"=>"./Owners/" . $pOwner . "/Templates/" . $pTemplate . ".xml"));
$node = $tmp->getNode('//page[@id="' . $pTerm . '"]');
return $node[0]->nodeValue;
}
function requestForData($pTerm)
{
$_lst = explode("|",$pTerm) ;
$_manager = new dataManager();
$_manager->data['path'] = $_manager->data['root'] . "Root/Contents/" . $_lst[0] . ".xml";
$_document = $_manager->initialize();
if ($_lst[1])
$_manager->data['query_1'] = array("attribute"=>"number", "value"=>$_lst[1]);
if($_lst[2])
$_manager->data['query_2'] = array("attribute"=>"id", "value"=>$_lst[2]);
$res = dataManager::getContentByData(
dataManager::getContentByData($_document, $_manager->data['query_1']),
$_manager->data['query_2']
);
$node = isset($_lst[3]) ? $_lst[3] : "content";
$str = $res->getElementsByTagName($node);
return $str->item(0)->nodeValue;
}
- Responder.inc.php :
<?php
//
$_ = explode(":",$_REQUEST['id']);
$_content = "";
switch( true )
/**
* content type switcher
*/
{
case ($_[0] == "lay") :
// template request
$_content = requestForLayout($_[1]);
break;
case ($_[0] == "data") :
// data request
$_content = requestForData($_[1]);
break;
}
# OUTPUT
echo $_REQUEST['id'] . "~@~" . $_[1] . "~@~" . $_content;
Auto complétion du layout
modifierOn implémente feedLayout pour auto compléter et chercher les fragments sur le template pour l'injecter sur les encres
<script id="endScript" number="">
<![CDATA[
$ = new $();
function segment(pStr)
/**
*/
{
var reg=new RegExp("~@~+", "g");
return pStr.split(reg);
}
function isToFeed()
/**
check is still to feed
*/
{
return (set = $.oa.getNodesWithId(document.body,"lay") )?set:false;
}
function feedLayout()
/**
feed the layout
*/
{
var ctc = $.oa.getNodesWithId(document.body,"lay");
if(ctc[0])
$.Remote.save({
url: "./main.php",
data: "qry=1&id="+ctc[0].id,
callback: function(response)
{
var nfo = segment( response.text );
var node = document.getElementById( nfo[0] );
node.innerHTML = nfo[2];
node.id = nfo[1];
$.oa.tab = new Array();
feedContent();
}
});
}
function feedContent()
/**
feed the content of the page by tag expr and Id
*/
{
feedLayout();
//feedData();
}
]]>
</script>
Auto complétion des données
modifier <script id="endScript" ...>
// ...
function feedData()
/**
feed the data
*/
{
var ctc = $.oa.getNodesWithId(document.body,"data");
if(ctc[0])
$.Remote.save({
url: "./main.php",
data: "qry=1&id="+ctc[0].id+"&request=1",
callback: function(response)
{
var nfo = segment(response.text);
var node = document.getElementById(nfo[0]);
node.innerHTML = nfo[2];
node.id = nfo[1];
$.oa.tab = new Array();
feedContent();
}
});
}
function feedContent()
/**
feed the content of the page by tag expr and Id
*/
{
feedLayout();
feedData();
}
</script>
Test de réactivité de la couche ajax
modifierLa fonction feedContent se charge de charger les flux sur la page en provenance du template et des champs de données.
Le template étant :
<page id="standard">
<![CDATA[
<div id="lay:frameContent" class="toFeed">
</div>
]]>
</page>
<page id="_frameContent">
<![CDATA[
<table width="100%" border="0" id="table1">
<tr><td>
<div id="lay:topContent" />
</td></tr>
<tr><td>
<div id="lay:mainContent" />
</td></tr>
<tr><td>
<div id="lay:bottomContent" />
</td></tr>
</table>
]]>
</page>
<page id="topContent">
<![CDATA[
<table width="100%" border="1" cellpadding="0" cellspacing="0">
<tr>
<td width="40%">
<div id="lay:loginPane" >:: loginPane ::</div>
</td>
<td width="60%">
<div>My Tiny CMS</div>
<div id="lay:menuPane" >:: menuPane ::</div>
</td>
</tr>
</table>
]]>
</page>
<page id="loginPane">
<![CDATA[
<div>
<div>
<input id="iUsr" type="text" value=" " /><br/>
<input id="iPwd" type="text" value=" " />
</div>
</div>
]]>
</page>
<page id="menuPane">
<![CDATA[
<div> :: menuPane - new :: </div>
]]>
</page>
<page id="mainContent">
<![CDATA[
<div id="data:1|1|title">content</div>
<div id="data:1|2|table">content</div>
<div id="data:1|3|bottom">content</div>
]]>
</page>
<page id="bottomContent">
<![CDATA[
<table width="100%" border="1" cellpadding="0" cellspacing="0">
<tr height="100"><td>
<div align="center"> THIS IS THE BOTTOM CONTENT</div>
</td></tr>
</table>
]]>
</page>
et le crop 1.xml :
<?xml version="1.0" encoding="UTF-8"?>
<crop id="test" number="1">
<topic>Cultiver son potager</topic>
<scope>légumes potager soupe</scope>
<fields>
<field number="1" id="title">
<subject>les légumes du potager</subject>
<content>
<![CDATA[
les légumes du potager
]]>
</content>
</field>
<field number="2" id="table">
<subject>table content</subject>
<content>
<![CDATA[
<table>
<tr>
<td>
le potagé contient :
</td>
<td>
<ul>
<li>une terre molle.</li>
<li>Parsemé ça et là, on Test carottes, </li>
<li>petits poids et potirons</li>
</ul>
</td>
</tr>
</table>
]]>
</content>
</field>
<field number="3" id="bottom">
<subject>this is the new subject</subject>
<content>
<![CDATA[
le potagé contient une terre molle2.
Parsemé ça et là, on trouve carottes, petits poids et potirons
]]>
</content>
</field>
</fields>
</crop>
On a pour rendu en sortie :
Exemples/MiniCMS/Premier noyau
Nous préparons la première version de notre solution myTinyCMS. Comme nous sommes nominal pour les quatre phases, myTinyCMS permets :
- [1] De gérer les données
- [2] De gérer les utilisateurs
- [3] De gérer un frameset complexe
- [4] D'effectuer des mouvements par son webservice
La première intégration ou version 1 de notre noyau consistera en des contrôleurs de test préfigurant la première beta de myTinyCMS.
L'objectif de ce contrôleur est d'intégrer le frameset sur trois thèmes :
- [1] authentification
- [2] lecture / écriture contenu
- [3] navigation
- [4] gestion de contenu
Le contenu sera géré directement sur le frameset par défaut. Backoffice et Frontoffice étant distingué par le switch Mod||Adm vs Usr, déverrouillant les contrôles de gestion et d'encodage.
Fonctionnellement le noyau présente deux phase d'affichage :
- La première serveur vers client (output total du frameset)
- La seconde cliente appelant l'injection dans les containers
Le Second noyau
modifierMyTinyCMS est maintenant un mini content manager system orienté wiki. L'orientation du CMS génère le code métier suivant
Classes
modifierClasses/contentPane.class.php regroupe les flux d'entrée et sortie comme les menus. Il est spécialisé pour notre solution. Le code peut être amélioré.
<?php
class contentPane
/*
* this class contains the mod menu treats
*/
{
public $data ;
public function setFeed()
/*
* only letters and numbers can be placed in data feed name data["abc.."]
*/
{
$this->data["menuPane"] = '
<div class="panel">
<span onclick="showHide($(\'cMenu\'),1);collapse(this);" class="click">[+]</span>
<span id="cMenu" class="click"> <a href="'.$_SERVER['PHP_SELF'].'?entry='.(@$_REQUEST["entry"]?$_REQUEST["entry"]:'Dummy').'&crop=1">!.crop</a> | <a href="'.$_SERVER['PHP_SELF'].'?entry='.(@$_REQUEST["entry"]?$_REQUEST["entry"]:'Dummy').'&page=1">!.page</a>
<p />
<form action="" method="post" name="form1">
{{loginPane}}
</form>
{{cropPane}}
{{userPane}}
{{pagePane}}
</span>
</div>
' ;
$this->data["loginPane"] = '
<span onclick="showHide($(\'loginPane\'),2);">:: Login Pane ::</span>
<span>
<div>
<input id="iLogin" name="iLogin" type="text" value=" " style="width:96%;" /><br/>
<input id="iButton" type="submit" value="log on" style="width:100%;" />
</div>
</span>
' ;
}
public function setTagFeed($pTag,$pFeed)
{
$this->data[$pTag] = $pFeed ;
}
public function contentPane()
{
$this->init();
}
public function placePane()
{
preg_match_all('/{{[a-z0-9A-Z]*}}/',$this->data["menuPane"],$matches) ;
foreach($matches[0] as $v)
{
$v = str_replace(array('{','[','}',']'),array('',' ','',''),$v);
if(!$this->special($v))
{
$this->data["menuPane"] = preg_replace('/{{'.$v.'}}/',@$this->data[$v],$this->data["menuPane"]) ;
}
}
}
public function special($pTag)
{
return false ;
}
public function tagReserved()
{
switch (true)
{
case (isset( $_SESSION["autho"]['credit'])) :
$this->data["loginPane"] = "<div>
<br/>
<input type='submit' value='unlog' name='iUnlog' style='width:96%;' />
</div>" ;
# CROP PANE
// set the crop list
$cropList = getFileList($_SESSION["path"] . "/Contents") ;
$this->data["cropPane"] = "<p/><div class='dark' onclick='showHide($(\"cCropPane\"),1)' style='cursor:pointer;'>:: Crop pane ::</div><p/><div id='cCropPane'>" ;
foreach($cropList as $v)
{
$this->data["cropPane"] .= '<a href="
' . ($_SERVER["PHP_SELF"]) . '?entry=' . @$_REQUEST["entry"] . '&crop=' . $v . '">' . $v . '</a><br />' ;
}
$this->data["cropPane"] .= "</div>" ;
# USER PANE
$this->data["userPane"] = "<p/><a href='".$_SERVER['PHP_SELF']."?entry=".$_REQUEST['entry']."&user=1'>[ users ]</a> ";
# PAGE PANE
$this->data["pagePane"] = "<a href='".$_SERVER['PHP_SELF']."?entry=".$_REQUEST['entry']."&page=1'>[ pages ]</a>";
break ;
}
}
public function getNewCrop()
{
return "
<script>
function saveNewCrop()
{
var tab = $('cCropId').value
+ '~~'
+$('cTopic').value
+'~~'
+$('cScope').value;
new Ajax.Request('./main.php',
{
method:'get',
parameters:'qry=22&value=' + tab + '&owner=" . $_REQUEST["entry"] . "',
onSuccess: function(transport)
{
$('cComCrop').innerHTML = transport.responseText ;
},
onFailure: function(){ }
});
}
</script>
<div class='bottomTool' style=''>
<form action='' method='post' name='frm2'>
<div onclick='showHide($(\"cCropPanel\"));collapse(this);' class='click'>[_]</div>
<div id='cCropPanel'>
<table width='600'>
<tr>
<td width='50'>Id</td>
<td width='500'>
<input type='text' value='' class='encoder' id='cCropId'/>
</td>
</tr>
<tr>
<td>Topic</td>
<td><input class='encoder' id='cTopic' type='text' value='' /></td>
</tr>
<tr>
<td>Scope</td>
<td><input class='encoder' id='cScope' type='text' value=''/></td>
</tr>
<tr>
<td colspan='2'>
<p><span class='specButton' onclick='saveNewCrop();/* location.href=\"".($_SERVER['PHP_SELF'].'?'."entry=".$_REQUEST['entry'])."\";*/'><b>[ save ]</b></span> <span id='cComCrop' class='green'></span></p>
<td>
</tr>
</table>
</div>
</div>
</form>
";
}
public function getCropPane($pCrop)
{
$this->getFieldList("field",$pCrop) ;
// set crop dropdownlist
$lst = "<select>" ;$i=1;
foreach($this->data["list"] as $k=>$v)
{
$lst .= "<option value='".$i++."'></option>" ;
}
$lst .= "</select>" ;
return "
<script>
function setNodes(pNode)
{
var dataNodes = _.oa.getNodesWithId(pNode,'|');
for(i=0;i<dataNodes.length;i++)
{
var dataContent = reformat(dataNodes[i].innerHTML);
new Ajax.Request('./main.php',
{
method:'post',
parameters:'qry=30&id='+dataNodes[i].id+'&owner='+$('customerName').value+'&data='+dataContent+'&key='+$('userKey').value,
onSuccess: function(trs)
{
$('cComCrop').innerHTML = trs.responseText;
},
onFailure:function() {}
});
}
}
function getCropContent(itemValue,cropName)
{
new Ajax.Request('./main.php',
{
method:'get',
parameters:'qry=15&item=' + itemValue + '&crop=' + cropName,
onSuccess: function(transport)
{
var nfo = segment(transport.responseText) ;
$('cTextSubject').value = nfo[0] ;
$('cTextContent').value = nfo[1] ;
},
onFailure: function(){ }
});
}
function saveNewItemCrop()
{
var tab = $('cCropSelect').value+'~~'+$('cCropId').innerHTML
+ '~~'
+$('cTopic').value
+'~~'
+$('cScope').value
+'~~'
+$('cTextSubject').value
+'~~'
+$('cTextContent').value ;
new Ajax.Request('./main.php',
{
method:'get',
parameters:'qry=21&owner='+$('customerName').value+'&value=' + tab,
onSuccess: function(transport)
{
$('cComCrop').innerHTML = transport.responseText ;
},
onFailure: function(){ }
});
}
function saveCropItem()
{
var tab = $('cCropSelect').value+'~~'+$('cCropId').innerHTML
+ '~~'
+$('cTopic').value
+'~~'
+$('cScope').value
+'~~'
+$('cTextSubject').value
+'~~'
+reformat($('cTextContent').value);
new Ajax.Request('./main.php',
{
method:'get',
parameters:'qry=20&owner='+$('customerName').value+'&value=' + tab,
onSuccess: function(transport)
{
$('cComCrop').innerHTML = transport.responseText ;
},
onFailure: function(){ }
});
}
</script>
<div class='bottomTool' style=''>
<form action='' method='post' name='frm2'>
<div onclick='showHide($(\"cCropPanel\"));collapse(this);' class='click'>[_]</div>
<div id='cCropPanel'>
<table width='600'>
<tr>
<td width='50'>Id</td>
<td width='500' id='cCropId'>".$_REQUEST["crop"]."</td>
</tr>
<tr>
<td>Topic</td>
<td><input class='encoder' id='cTopic' type='text' value='".@$this->data["list"]["topic"]."' /></td>
</tr>
<tr>
<td>Scope</td>
<td><input class='encoder' id='cScope' type='text' value='".@$this->data["list"]["scope"]."'/></td>
</tr>
<tr>
<td>Item</td>
<td>
<select class='encoder' id='cCropSelect' onchange='getCropContent(this.value,\"".$_REQUEST['crop']."\");'>
".getCropOptions($_REQUEST['crop'])."
</select>
</td>
</tr>
<tr>
<td colspan='2'>
<p>:: Crop Item :: <span class='specButton' onclick='saveCropItem()'>[ save</span> | <span class='specButton' onclick='saveNewItemCrop();'>new ]</span>
<span id='cComCrop' class='green'></span>
</p>
<input type='text' value='' id='cTextSubject' class='encoder'/>
<textarea id='cTextContent' rows='10' class='encoder'></textarea>
</td>
</tr>
</table>
</div>
</div>
</form>
";
}
public function getFieldList($pNodeName,$pCrop)
{
$this->data["list"] = getNodeList($pNodeName,$pCrop) ;
}
public function getPagePane( )
{
return "<div class='bottomTool'><span onclick='showHide($(\"cPagePanel\"));collapse(this);' class='click'>[_]</span>
<div id='cPagePanel'><p>:: Page Pane :: </p>
"
. getPagesPanel($_REQUEST["entry"]) .
"
</div>
</div>";
}
public function getUserPane( )
{
$dom = new DOMDocument() ;
$dom->load("./Owners/" . (@$_REQUEST["entry"]?$_REQUEST["entry"] : 'Dummy'). '/Contents/user.xml') ;
$users = $dom->getElementsByTagName("users") ;
$val = "
<script>
function userEdit(pVal)
/*
edit the user line
*/
{
var tab =[] ;
if($('login_'+pVal).innerHTML.substring(0,1) == '<')
{
$('login_'+pVal).innerHTML = tab[0] = $('iLog_'+pVal).value ;
$('pass_'+pVal).innerHTML = tab[1] = $('iPas_'+pVal).value ;
$('forname_'+pVal).innerHTML = tab[2] = $('iFor_'+pVal).value ;
$('lastname_'+pVal).innerHTML = tab[3] = $('iLas_'+pVal).value ;
$('customer_'+pVal).innerHTML = tab[4] = $('iCus_'+pVal).value ;
$('b'+pVal).innerHTML = '[edit]';
new Ajax.Request('./main.php',
{
method:'get',
parameters:'qry=25&item=' + pVal + '&value=' + tab + '&owner=' + $('customerName').value + '&key='+$('userKey').value,
onSuccess: function(transport)
{
$('cComUser').innerHTML = transport.responseText ;
location.href='".($_SERVER['PHP_SELF'].'?'."entry=".$_REQUEST['entry'])."&user=1';
},
onFailure: function(){ }
});
}
else
{
$('login_'+pVal).innerHTML = \"<input id='iLog_\"+pVal+\"' type='text' value='\"+ ($('login_'+pVal).innerHTML) +\"' />\" ;
$('pass_'+pVal).innerHTML = \"<input id='iPas_\"+pVal+\"' type='text' value='\"+ ($('pass_'+pVal).innerHTML) +\"' />\" ;
$('forname_'+pVal).innerHTML = \"<input id='iFor_\"+pVal+\"' type='text' value='\"+ ($('forname_'+pVal).innerHTML) +\"' />\" ;
$('lastname_'+pVal).innerHTML = \"<input id='iLas_\"+pVal+\"' type='text' value='\"+ ($('lastname_'+pVal).innerHTML) +\"' />\" ;
$('customer_'+pVal).innerHTML = \"<input id='iCus_\"+pVal+\"' type='text' value='\"+ ($('customer_'+pVal).innerHTML )+\"' />\" ;
$('b'+pVal).innerHTML = '[save]';
}
}
function saveNew()
{
var tab = [] ;
tab[0] = $('iLogNew').value;
tab[1] = $('iPasNew').value;
tab[2] = $('iForNew').value;
tab[3] = $('iLasNew').value;
tab[4] = $('iCusNew').value;
new Ajax.Request('./main.php',
{
method:'get',
parameters:'qry=26&value=' + tab+ '&owner=' + $('customerName').value +'&key=' + $('userKey').value,
onSuccess: function(transport)
{
$('cComUser').innerHTML = transport.responseText ;
location.href='".($_SERVER['PHP_SELF'].'?'."entry=".$_REQUEST['entry'])."&user=1';
$('new').innerHTML = '';
},
onFailure: function(){ }
});
;
}
function addUser()
{
$('new').innerHTML = '<p/><table width=\"600\"><tr class=\"title\"><td onclick=\"saveNew();\" class=\"specButton\"><b>[save]</b></td><td><input type=\"text\" id=\"iLogNew\"/></td><td><input type=\"text\" id=\"iPasNew\"/></td><td><input type=\"text\" id=\"iForNew\"/></td><td><input type=\"text\" id=\"iLasNew\"/></td><td><input type=\"text\" id=\"iCusNew\"/></td></tr></table><p/>' ;
}
</script>
<table width='600'>
<tr class='title'><td/>
<td>login</td>
<td>pass</td>
<td>lastname</td>
<td>forename</td>
<td>customer</td>
</tr>" ;
$i = 0;
foreach($users->item(0)->childNodes as $v)
{
if($v->nodeType != XML_TEXT_NODE)
{
if(isset($_SESSION['autho']['admin']))
{
$val .= "<tr>
<td onclick='userEdit(".$i.");'><b id='b".$i."' class='specButton'>[edit]</b></td><td id='login_".$i."'>" . trim($v->getAttribute("login")) . "</td><td id='pass_".$i."'>" . trim($v->getAttribute("pass")) . "</td><td id='lastname_".$i."'>" . trim($v->getAttribute("lastname")) . "</td><td id='forname_".$i."'>" . trim($v->getAttribute("forname")) . "</td><td id='customer_".$i."'>" . trim($v->getAttribute("customer")) ."</td>
</tr>" ;
}
}
$i++;
}
if(!isset($_SESSION['autho']['admin']))
$val .= "<tr><td colspan='5'>You're not authorized to modify the privileges</td></tr>";
$val .= "</table>";
return "<div class='bottomTool'>" . $val . "<hr /><div id='new'></div><span class='specButton' onclick='addUser();'>[add user]</span> <span id='cComUser' class='green'></span></div>";
}
public function init()
{
$this->setFeed() ;
$this->tagReserved() ;
$this->placePane() ;
}
}
# Ouput
$_container["content"] = new contentPane() ;
?>
Voici Classes/dataManager.class.php dans sa version finale. Il est orienté données.
<?php
class dataManager
{//
private $pattern;
protected $stack;
//
public
$domDoc,
$crop
= array(
"std"=> '<?xml version="1.0" encoding="UTF-8"?>
<crop><topic /><fields /></crop>'
,""=>''
),
$data
= array(
"root"=>"./Owners/"
);
# TODO GETTER/SETTER
public function setDocument($pPath)
{
$this->domDoc->load( $pPath );
}
public static function getNodeByData($pData)
/**
* get the content from data
*/
{
$res = xpathExtension::getNodes($this->domDoc, $pXPath);
return $res['content'];
}
public function dispatch($pData)
/**
* switch direction by value
*/
{
}
public static function getContentByData($pDocument, $pData)
/**
* get the content from data
*/
{
if(@$pData['attribute'])
{
return utilities::searchNodesByAttribute($pDocument, $pData) ;
}
else
{
return utilities::searchNodesByContent($pDocument, $pData) ;
}
}
public function setContentByData($pDocument,$pData)
/**
* set the content to data
*/
{
if(@$pData['attribute'])
{
utilities::writeContentByAttribute($pDocument, $pData) ;
}
else
{
utilities::writeContentByContent($pDocument, $pData) ;
}
$this->saveDocument();
}
public static function setCropForCustomer($pPath, $pCrop)
/**
* set crop to customer directory
*/
{
file_put_contents($pPath, trim($pCrop));
}
# STATE
public function dataManager()
{
$this->initialize();
}
public function initialize()
/**
* init
*/
{
$this->domDoc = new DOMDocument();
if(@$this->data['path'])
{
$this->domDoc->load( $this->data['path'] );
}
return $this->domDoc;
}
public function saveDocument()
/**
* save document
*/
{
$this->domDoc->save($this->data['path']) ;
}
}
?>
Voici le Classes/templateControler.class.php orienté layout.
<?php
class templateControler
{
/**
* Control the templates
*
* @var (mixed) ($domDoc, $data, $stack)
*/
//
private $pattern
= array(
'inc'=>'/{{inc:[a-zA-Z]*[\[\]\*a-zA-Z]*}}/' // inclusion patterns
,''=>''
);
protected $domDoc, $stack;
//
public $data;
/*
public function __construct()
{//
$this->domDoc = new DOMDocument();
$this->initialize();
}
*/
/**
* Enter description here...
*
* @param unknown_type $pVar
* @return templateControler
*/
public function templateControler($pVar)
{
$this->domDoc = new DOMDocument();
$this->setter($pVar);
$this->initialize();
}
/**
* Enter description here...
*
* @param unknown_type $pVar
*/
public function setter($pVar)
{//
$this->data = $pVar;
}
// TODO GETTER
public function initialize()
{//
$this->domDoc->load( $this->data['path'] );
}
public function initFrame($pDomValue)
/**
*
*/
{//
$this->data['frameset'] = trim($pDomValue[0]->nodeValue) ;
}
/**
* Enter description here...
*
*/
public function getNode($pXPath)
{//
$this->data['feed'] = utilities::getNodes($this->domDoc, $pXPath);
return $this->data['feed'];
}
/**
* getContent retourne le contenu du/des nœuds demandés
*
* > (nodeName, nodeId) - (string) >
*/
public function getContent($nodeName,$nodeId)
{//
// set expression
$_expr = "//" . $nodeName . ($nodeId&&$nodeId!='*'?"[@id='$nodeId']":"");
$set = $this->getNode($_expr);
$content=null;
if($set)
foreach($set as $k=>$v)
{
$content .= "\r\n" . ($v->nodeValue)."\r\n" ;
}
return $content?$content:"";
}
/**
* replace anchor by content
*/
public function anchorContentReplacer()
{//
$i=0;
foreach($this->data['result'][0] as $k=>$v)
{
// formatting anchor's syllabes
$v = str_replace(array('{','[','}',']'),array('',' ','',''),$v);
$tmp = explode(":",$v);$_ = explode(' ',$tmp[1]);
// replacing
$this->data['result'][0][$i++] = array("node"=>$_[0],"value"=>$this->getContent($_[0], @$_[1]),"id"=>@$_[1]);
}
}
public function setTag($pAnchor,$pValue)
/**
* put the frameset out
*/
{
$this->data['frameset'] = preg_replace('/{{inc:' . $pAnchor . '}}/',$pValue, $this->data['frameset'] ) ;
}
public function setFrame()
/**
* put the frameset out
*/
{
foreach($this->data['result'][0] as $v)
{
if( $v['node'] != 'page' || isset($_REQUEST['admin']))
$this->data['frameset'] = preg_replace('/{{inc:' . $v['node'] . '[\[\]\*'.$v['id'].']*}}/', $v['value'], $this->data['frameset'] ) ;
else
{
$this->data['frameset'] = preg_replace('/{{inc:' . $v['node'] . '[\[\]\*'.$v['id'].']*}}/', getPage(@$_REQUEST['page']?$_REQUEST['page']:$v['id']), $this->data['frameset'] ) ;
}
}
}
public function getAnchors($pFeed)
{//
preg_match_all($this->pattern['inc'], $pFeed, $this->data['result']);
return $this->data['result']; // on prefere les données centralisées dans la classe
}
public function publicTemplate()
{//
return $this->domDoc->saveXML();
}
}
?>
Classes/utilities.class.php dans sa version finale
<?php
class utilities
{
public static function getNodes($pDomDoc, $pXpathString)
/**
* get the node from DomDoc with XpathString
*/
{
$xp = new DOMXPath($pDomDoc);
$xp->registerNamespace('x', 'http://www.w3.org/1999/xhtml');
$xp->registerNamespace('xhtml', 'http://www.w3.org/1999/xhtml');
$xp->registerNamespace('i18n', 'http://apache.org/cocoon/i18n/2.1');
$ret = array();
$nodes = $xp->query($pXpathString);
foreach ($nodes as $node)
{
array_push($ret, $node);
}
return count($ret)?$ret:false;
}
public static function isContent($pPattern, $pFeed)
/**
* check is pattern in feed
*/
{
return preg_match('/('.$pPattern.')/',$pFeed)?true:false;
}
public static function searchNodesByContent($pDocument, $pQueries)
/**
* return node matching request by content
*/
{
$_fields = $pDocument->getElementsByTagName('fields'); $i = 0;
// ceci est un exemple de mauvais code de service, il ne fonctionne que pour un nombre limité de structure de crop
// c'est donc un code métier à surcharger.
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
foreach ( $u->childNodes as $v )
{
if( $v->nodeName == $pQueries['node'] && ! utilities::isContent ( $pQueries['value'] , $v->nodeValue ) )
{
@$tab[++$i] = $u ;
}
}
}
}
// removing
if(isset($tab))
foreach($tab as $v)
$_fields->item(0)->removeChild($v) ;
/* DEPRECIEE - Malheureusement, ce code ne marche une fois appelée par getNodes
for($i=0; $i<count($pNodeSet); $i++)
{
$_content = $pNodeSet[$i]->childNodes ;
// cette fonction ne descend qu'à un niveau
foreach($_content as $v)
{
if( $v->nodeName == $pQueries['node'] && ! utilities::isContent ( $pQueries['value'] , $v->nodeValue ) )
{
$pNodeSet[$i]->parentNode->removeChild($pNodeSet[$i]) ;
}
}
}
*/
return $pDocument ;
}
public static function searchNodesByAttribute($pDocument, $pQueries)
/**
* return node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');$i=0;
// ceci est un exemple de mauvais code de service, il ne fonctionne que pour un nombre limité de structure de crop
// c'est donc un code métier à surcharger.
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
if( !($u->getAttribute($pQueries['attribute']) == $pQueries['value']) ) // 1:1 match
@$tab[++$i] = $u ;
}
}
// removing
if(isset($tab))
foreach($tab as $v)
$_fields->item(0)->removeChild($v) ;
return $pDocument ;
}
public static function getAttributesContents($pNode)
/**
*
*/
{
foreach ($pNode->attributes as $attrName => $attrNode)
{
$_tab[$attrName] = $attrNode->value ;
}
return $_tab ;
}
public static function getLineByAttribute($pDocument, $pQueries)
/**
* return node matching request by attribute
*/
{
$_users = $pDocument->getElementsByTagName('user');
// ceci est un exemple de mauvais code de service, il ne fonctionne que pour un nombre limité de structure de crop
// c'est donc un code métier à surcharger.
foreach ($_users as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
$_flg = false ;$i=$j=0;
foreach($pQueries as $_qa )
{
if( ($u->getAttribute($_qa['attribute']) == trim($_qa['value'])) )
{ // 1:1 match
++$j;
} else {
--$j;
}
$i++;
}
if($i==$j)
return utilities::getAttributesContents($u) ;
}
}
return false ;
}
public static function writeContentByContent($pDocument, $pQueries)
/**
* write node content matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
foreach ( $u->childNodes as $v )
{
if( $v->nodeName == $pQueries['node'] && utilities::isContent ( $pQueries['value'] , $v->nodeValue ) )
{
$v->nodeValue = $pQueries['content'] ;
$_flg = true ;
}
}
}
}
return $_flg ;
}
public static function writeContentByAttribute($pDocument, $pQueries)
/**
* write node content matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
if( $u->getAttribute($pQueries['attribute']) == $pQueries['value'] ) // 1:1 match
{
$v = $u->getElementsByTagName($pQueries['node']) ;
$ct = $v->item(0)->ownerDocument->createCDATASection("\n" . $pQueries['replacement'] . "\n");
$v->item(0)->appendChild($ct) ;
$_flg = true ;
}
}
}
if (!$_flg)
// attribute is note set yet
{
$node = $_fields->item(0)->firstChild->nextSibling->cloneNode(true) ;
$_fields->item(0)->appendChild($node) ;
// filling id
$_fields->item(0)->lastChild->setAttribute("id", $pQueries['value']) ;
// filling number
$_fields->item(0)->setAttribute("count", $cpt = ((int)$_fields->item(0)->getAttribute("count"))+1) ;
$_fields->item(0)->lastChild->setAttribute("number", $cpt) ;
utilities::writeContentByAttribute($pDocument, $pQueries) ;
}
return $_flg ;
}
public static function deleteContentByContent($pDocument, $pQueries)
/**
* delete node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
foreach ( $u->childNodes as $v )
{
if( $v->nodeName == $pQueries['node'] && utilities::isContent ( $pQueries['value'] , $v->nodeValue ) )
{
$_fields->item(0)->removeChild($u) ;
$_flg = true ;
}
}
}
}
return $_flg ;
}
public static function deleteContentByAttribute($pDocument, $pQueries)
/**
* delete node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
if( $u->getAttribute($pQueries['attribute']) == $pQueries['value'] ) // 1:1 match
{
$_fields->item(0)->removeChild($u) ;
$_flg = true ;
}
}
return $_flg ;
}
}
?>
Functions
modifierFunctions/common.inc.php dans sa version finale
<?php
/*
common application functiunalities
would be fine to merge all request to requestForData
*/
require_once CLA . "/utilities.class.php" ;
require_once CLA . "/dataManager.class.php" ;
require_once CLA . "/templateControler.class.php" ;
function requestForLayout($pTerm,$pOwner="Root",$pTemplate="default")
/**
* layout request
*/
{
$tmp = new templateControler(array("path"=>"./Owners/" . $pOwner . "/Templates/" . $pTemplate . ".xml")) ;
$node = $tmp->getNode('//page[@id="' . $pTerm . '"]') ;
return $node[0]->nodeValue ;
}
function requestForData($pTerm,$pOwner)
/**
* data request
*/
{
$_lst = explode("|",$pTerm) ;
$_manager = new dataManager() ;
$_manager->data['path'] = $_manager->data['root'] . $pOwner . "/Contents/" . $_lst[0] . ".xml" ; //
$_document = $_manager->initialize() ;
if($_lst[1])
$_manager->data['query_1'] = array("attribute"=>"number", "value"=>$_lst[1]) ;
if($_lst[2])
$_manager->data['query_2'] = array("attribute"=>"id", "value"=>$_lst[2]) ;
$res = dataManager::getContentByData(
dataManager::getContentByData($_document, $_manager->data['query_1'])
,$_manager->data['query_2']) ;
$node = isset($_lst[3]) ? $_lst[3] : "content" ;
$str = $res->getElementsByTagName($node) ;
return $str->item(0)->nodeValue ;
}
function getPagesPanel($pEntry)
{
if( $_SESSION["autho"]["admin"] || $_SESSION["autho"]["moderator"] )
{
$dom = new DOMDocument() ;
$dom->load("./Owners/" . $pEntry . "/Contents/page.xml") ;
$root = $dom->getElementsByTagName("pages");
$str = "
<script>
function pageEdit(pVal)
{
var tab =[] ;
if($('number_'+pVal).innerHTML.substring(0,1) == '<')
{
$('number_'+pVal).innerHTML = tab[0] = $('iNum_'+pVal).value ;
$('id_'+pVal).innerHTML = tab[1] = $('iId_'+pVal).value ;
$('entry_'+pVal).innerHTML = tab[2] = $('iEnt_'+pVal).value ;
$('b'+pVal).innerHTML = '[edit]';
new Ajax.Request('./main.php',
{
method:'get',
parameters:'qry=35&item=' + pVal + '&value=' + tab + '&owner=' + $('customerName').value + '&key='+$('userKey').value,
onSuccess: function(transport)
{
$('cComPage').innerHTML = transport.responseText ;
location.href='".($_SERVER['PHP_SELF'].'?'."entry=".$_REQUEST['entry'])."&page=1';
},
onFailure: function(){ }
});
}
else
{
$('number_'+pVal).innerHTML = \"<input id='iNum_\"+pVal+\"' type='text' value='\"+ ($('number_'+pVal).innerHTML) +\"' />\" ;
$('id_'+pVal).innerHTML = \"<input id='iId_\"+pVal+\"' type='text' value='\"+ ($('id_'+pVal).innerHTML) +\"' />\" ;
$('entry_'+pVal).innerHTML = \"<input id='iEnt_\"+pVal+\"' type='text' value='\"+ ($('entry_'+pVal).innerHTML) +\"' />\" ;
$('b'+pVal).innerHTML = '[save]';
}
}
function saveNewPage()
{
var tab = [] ;
tab[0] = $('iNumNew').value;
tab[1] = $('iIdNew').value;
tab[2] = $('iEntNew').value;
new Ajax.Request('./main.php',
{
method:'get',
parameters:'qry=36&value=' + tab+ '&owner=' + $('customerName').value +'&key=' + $('userKey').value,
onSuccess: function(transport)
{
$('cComPage').innerHTML = transport.responseText ;
location.href='".($_SERVER['PHP_SELF'].'?'."entry=".$_REQUEST['entry'])."&page=1';
$('new').innerHTML = '';
},
onFailure: function(){ }
});
;
}
function addPage()
{
$('new').innerHTML = '<p/><table width=\"600\"><tr class=\"title\"><td onclick=\"saveNewPage();\" class=\"specButton\"><b>[save]</b></td><td><input type=\"text\" id=\"iNumNew\"/></td><td><input type=\"text\" id=\"iIdNew\"/></td><td><input type=\"text\" id=\"iEntNew\"/></td></tr></table><p/>' ;
}
</script>
<table width='600'>
<tr class='title'>
<td/>
<td>number</td>
<td>id</td>
<td>entry</td>
</tr>
";
$i = 0 ;
foreach($root->item(0)->childNodes as $v)
{
if($v->nodeType != XML_TEXT_NODE)
{
@$str .= "<tr><td><span onclick='pageEdit(".$i.");'><b id='b".$i."' class='specButton'>[edit]</b></td><td id='number_".$i."'>". $v->getAttribute("number") . "</td><td id='id_".$i."'>" . $v->getAttribute("id") . "</td><td id='entry_".$i."'>" . $v->getAttribute("entry") . "</td></tr>" ;
}
$i++ ;
}
return $str .= "</table> <hr /><div id='new'></div><span class='specButton' onclick='addPage();'>[add page]</span> <span id='cComPage' class='green'></span>" ;
}
}
function reformat($pContent)
{
$in = array("``i", "``o","``q","``e","``u","``a");
$out = array("=","\"", "?","&","%","#");
return stripslashes(str_replace($in,$out,$pContent)) ;
}
function formatData($pContent)
/**
* data formatter
*/
{
$pContent = reformat($pContent);
$_pattern = '/(\[)([a-zA-Z0-9]*)(:)?([a-zA-Z0-9]*)?(\[)([<>\-(\"\'a-zA-Z0-9:\[\] ]*)(\]\])/' ;
$_manager = new dataManager() ;
// TODO
$_manager->data['path'] = $_manager->data['root'] . "Root/Contents/" . "1.xml"; //$_lst[0] .
$_document = $_manager->initialize() ;
$flg = preg_match($_pattern, $pContent, $_matches )? true: false ;
if ( $flg )
{
// save feed
$_manager->data['query_1'] = array("attribute"=>"id", "value"=>$_matches[4], "node"=>"content",
"replacement"=>$_matches[6]) ;
// query test
$_manager->setContentByData($_document, $_manager->data['query_1']) ;
$_str = preg_replace($_pattern, ("<" . $_matches[2] . " id='".$_matches[4]."'>" . $_matches[6] . "</" . $_matches[2] . ">"), $pContent) ;
} else {
$_str = $pContent ;
}
return $_str ;
}
function getPage($pId)
{
$dom = new DOMDocument() ;
$fileName = "./Owners/" . $_REQUEST['entry'] . '/Contents/page.xml' ;
$dom->load($fileName) ;
$node = $dom->getElementsByTagName("pages") ;
foreach($node->item(0)->childNodes as $v )
if($v->nodeType != XML_TEXT_NODE)
if($v->getAttribute("id") == $pId)
return "<div id='".$v->getAttribute("entry") . "'></div>" ;
}
function getFileList($pPath)
/*
* Lists all the files in a directory
*/
{
$i = 0 ; $lst = "" ;
if (@$handle = opendir($pPath))
while (false !== ($file = readdir($handle)))
{
if ( $file == "." || $file == ".." || preg_match("/page/",$file) || preg_match("/user/",$file) )
{}
else
{
@$lst[@++$i]=$pPath . "/" . $file ;
}
}
return $lst ;
}
function getCropOptions($pDoc)
{
$dom = new DOMDocument();
$dom->load( $pDoc );
$nodes = $dom->getElementsByTagName("fields");
foreach( $nodes->item(0)->childNodes as $v )
{
if($v->nodeType != XML_TEXT_NODE)
@$opt .= "<option value='" . $v->getAttribute("number") . "'>" . $v->getAttribute("number") . " | " . $v->getAttribute("id") . "</option>" ;
}
return $opt ;
}
function getCropItem($pItemValue,$pCrop)
{
$dom =new DOMDocument();
$dom->load($pCrop);
$node = $dom->getElementsByTagName("fields");
foreach( $node->item(0)->childNodes as $v )
{
if($v->nodeType != XML_TEXT_NODE && $v->getAttribute("number") == trim($pItemValue))
{
$subNodes = $v->childNodes ;
foreach($subNodes as $u)
{
if($u->nodeName == "subject")
$tab = $u->nodeValue ;
if($u->nodeName == "content")
$tab .= "~~" . $u->nodeValue;
}
return @$tab ;
}
}
}
function saveCropItem($pValue,$pOwner)
{
$pValue = reformat($pValue) ;
$tab = explode("~~",$pValue) ;
$dom =new DOMDocument();
$dom->load($tab[1]);
$node = $dom->getElementsByTagName("fields");
$topic = $dom->getElementsByTagName("topic");
$topic->item(0)->nodeValue = $tab[2];
$scope = $dom->getElementsByTagName("scope");
$scope->item(0)->nodeValue = $tab[3];
foreach( $node->item(0)->childNodes as $v )
{
if($v->nodeType != XML_TEXT_NODE && $v->getAttribute("number") == $tab[0])
{
try
{
$subNodes = $v->childNodes ;
foreach($subNodes as $u)
{
if($u->nodeName == "subject")
$u->nodeValue = $tab[4] ;
if($u->nodeName == "content")
$u->nodeValue = $tab[5] ;
}
$dom->save($tab[1]);
return "Crop item saved !" ;
}
catch(Exception $ex)
{
return "Error when saving" ;
}
}
}
}
function saveCropNewItem($pValue,$pOwner)
{
$tab = explode("~~",$pValue) ;
$dom =new DOMDocument();
$dom->load($tab[1]);
$_fields = $dom->getElementsByTagName("fields");
$_fields->item(0)->setAttribute("count", $cpt = ((int)$_fields->item(0)->getAttribute("count"))+1) ;
try
{
$node = $dom->createElement("field");
$node->setAttribute("id",$tab[4]) ;
$node->setAttribute("number",$cpt) ;
$subject = $dom->createElement("subject");
$subject->nodeValue=$tab[4];
$content = $dom->createElement("content");
$ct = $content->ownerDocument->createCDATASection("\n" . $tab[5] . "\n");
$content->appendChild($ct);
$node->appendChild($subject) ;
$node->appendChild($content) ;
$_fields->item(0)->appendChild($node) ;
$dom->save($tab[1]);
return "New item saved !" ;
}
catch(Exception $ex)
{
return "Error when saving" ;
}
}
function createCrop($pOwner,$pPath)
{
copy( OWN . "/Root/Dummies/1.xml", $pPath ) ;
return 'One new crop created but without data !';
}
function saveCrop($pOwner, $pId, $pData,$pKey)
{
$pData = reformat($pData) ;
try
{
$tab = explode("|",$pId) ;
$dom =new DOMDocument();
$cropName = './Owners/'.$pOwner.'/Contents/'.$tab[0].".xml" ;
if(is_file($cropName))
{
$dom->load($cropName);
$nodes = $dom->getElementsByTagName("fields") ;
$flg = false;
foreach($nodes->item(0)->childNodes as $v)
{
if($v->nodeType != XML_TEXT_NODE)
{
if($v->getAttribute("number")==@$tab[1])
{
foreach($v->childNodes as $u)
if($u->nodeType != XML_TEXT_NODE && $u->nodeName=="content")
{
$u->nodeValue = $pData ;$flg=true;
}
}
}
}
$dom->save($cropName) ;
return $flg ? 'Crop updated !' : 'Crop missing reference(s) !' ;
} else {
createCrop($pOwner,$cropName) ;
}
}
catch(Exception $ex)
{
return $ex;
}
}
function saveNewCrop($pOwner,$pValue)
{
try
{
$tab = explode("~~",$pValue) ;
if(!is_file('Owners/'.(@$_REQUEST["entry"]?$_REQUEST["entry"]:'Dummy').'/Contents/'.$tab[0]))
{
$dom =new DOMDocument("1.0");
$root = $dom->createElement("fields");
$root->setAttribute("count", 0) ;
$topic = $dom->createElement("topic");
$topic->nodeValue = $tab[1];
$scope = $dom->createElement("scope");
$scope->nodeValue = $tab[2];
$root->appendChild($topic) ;
$root->appendChild($scope) ;
$dom->appendChild($root) ;
$dom->save('./Owners/'.$pOwner.'/Contents/'.$tab[0].".xml");
return "New crop created !" ;
} else {
return "Crop already exists !" ;
}
}
catch(Exception $ex)
{
return "Error when saving" ;
}
}
function saveUser($pOwner,$pValue,$pKey,$pStatus)
{
require_once CLA . "/dataManager.class.php" ;
require_once CLA . "/utilities.class.php" ;
$_manager = new dataManager() ;
//
$_manager->data['path'] = $_manager->data['root'] . $pOwner . "/Contents/user.xml" ; //
$_manager->data['query_1'] = array(
array("attribute"=>"key", "value"=>$pKey)
) ;
$_res = utilities::getLineByAttribute($_manager->initialize(), $_manager->data['query_1']) ;
$flg=false;
if(isset($_res["login"]))
{
$flg = true;
}
$tab = explode(",", $pValue) ;
//TODO
switch ($pStatus)
{
case 1 :
if($flg)
{
$dom = new DOMDocument() ;
$userFile = "./Owners/" . $pOwner . "/Contents/user.xml" ;
$dom->load($userFile) ;
$root = $dom->getElementsByTagName("users");
$i=0;
$nodes = $root->item(0)->childNodes ;
$nodes->item($_REQUEST["item"])->setAttribute("login",$tab[0]) ;
$nodes->item($_REQUEST["item"])->setAttribute("pass",$tab[1]) ;
$nodes->item($_REQUEST["item"])->setAttribute("forname",$tab[2]) ;
$nodes->item($_REQUEST["item"])->setAttribute("lastname",$tab[3]) ;
$nodes->item($_REQUEST["item"])->setAttribute("customer",$tab[4]) ;
$nodes->item($_REQUEST["item"])->setAttribute("key",md5($tab[2])) ;
$dom->save($userFile) ;
return 'User updated';
}else{
return 'Nothing has been done';
}
break;
case 2 :
if($flg)
{
try
{
$dom = new DOMDocument() ;
$userFile = "./Owners/" . $pOwner . "/Contents/user.xml" ;
$dom->load($userFile) ;
$root = $dom->getElementsByTagName("users");
$new = $dom->createElement("user");
$new->setAttribute("login",$tab[0]);
$new->setAttribute("pass",$tab[1]);
$new->setAttribute("forname",$tab[2]);
$new->setAttribute("lastname",$tab[3]);
$new->setAttribute("customer",$tab[4]);
$new->setAttribute("key",md5($tab[2]));
$root->item(0)->appendChild($new);
$dom->save($userFile);
return 'New user add !' ;
}
catch (Exception $ex) {
return $ex ;
}
}else{
return 'Nothing has been done' ;
}
break;
}
}
function savePage($pOwner,$pValue,$pKey,$pStatus)
{
require_once CLA . "/dataManager.class.php" ;
require_once CLA . "/utilities.class.php" ;
$_manager = new dataManager() ;
//
$_manager->data['path'] = $_manager->data['root'] . $pOwner . "/Contents/user.xml" ; //
$_manager->data['query_1'] = array(
array("attribute"=>"key", "value"=>$pKey)
) ;
$_res = utilities::getLineByAttribute($_manager->initialize(), $_manager->data['query_1']) ;
$flg=false;
if(isset($_res["login"]))
{
$flg = true;
}
$tab = explode(",", $pValue) ;
//TODO
switch ($pStatus)
{
case 1 :
if($flg)
{
$dom = new DOMDocument() ;
$pageFile = "./Owners/" . $pOwner . "/Contents/page.xml" ;
$dom->load($pageFile) ;
$root = $dom->getElementsByTagName("pages");
$i=0;
$nodes = $root->item(0)->childNodes ;
$nodes->item($_REQUEST["item"])->setAttribute("number",$tab[0]) ;
$nodes->item($_REQUEST["item"])->setAttribute("id",$tab[1]) ;
$nodes->item($_REQUEST["item"])->setAttribute("entry",$tab[2]) ;
$dom->save($pageFile) ;
return 'Page updated';
}else{
return 'Nothing has been done';
}
break;
case 2 :
if($flg)
{
try
{
$dom = new DOMDocument() ;
$pageFile = "./Owners/" . $pOwner . "/Contents/page.xml" ;
$dom->load($pageFile) ;
$root = $dom->getElementsByTagName("pages");
$new = $dom->createElement("page");
$new->setAttribute("number",$tab[0]);
$new->setAttribute("id",$tab[1]);
$new->setAttribute("entry",$tab[2]);
$root->item(0)->appendChild($new);
$dom->save($pageFile);
return 'New page add !' ;
}
catch (Exception $ex) {
return $ex ;
}
}else{
return 'Nothing has been done' ;
}
break;
}
}
function getNodeList($pNodeName,$pPath)
/*
* Lists all the files in a directory
*/
{
return xml2phpArray(simplexml_load_string(file_get_contents($pPath)),array());
}
function getAttribute($node)
{// >((dom)node) ((array)tab)>
$tab=array() ;
foreach($node->attributes() as $k1->$v1)
{
$tab[$k1->{''}->name]=$k1->{''}->value ;
}
return $tab;
}//
function xml2phpArray($xml,$arr){
$iter = 0;
foreach($xml->children() as $b){
$a = $b->getName();
if(!$b->children()){
$arr[$a] = trim($b[0]);
}
else{
$arr[$a][$iter] = array();
$arr[$a][$iter] = xml2phpArray($b,$arr[$a][$iter]);
}
$iter++;
}
return $arr;
}
function print_r_html($data,$return_data=false)
/*
* Print_r_htl modified to return array
*/
{
$data = print_r($data,true) ;
$data = str_replace( " "," ", $data) ;
$data = str_replace( "\r\n","<br/>\r\n", $data) ;
$data = str_replace( "\r","<br/>\r", $data) ;
$data = str_replace( "\n","<br/>\n", $data) ;
if (!$return_data)
return $data ;
else
return $data ;
}
?>
Backend
modifierLe backend est implémenter suivant quelques règles métier imposées par l'orientation du CMS également.
Objects/Backend/authorize.inc.php
<?php
/*
* Authorize component
*/
require_once CLA . "/dataManager.class.php" ;
require_once CLA . "/utilities.class.php" ;
$_manager = new dataManager() ;
//
$_manager->data['path'] = $_manager->data['root'] . (@$_REQUEST["entry"]?$_REQUEST["entry"]:"Root") . "/Contents/user.xml" ; //
$_credit = isset($_SESSION['autho']['credit'])?trim($_SESSION['autho']['credit']):null ;
//
if ( isset($_REQUEST['iLogin']) )
$_credit = trim($_REQUEST['iLogin']) ;
if( isset($_REQUEST['iUnlog']))
{
unset($_SESSION["autho"]["credit"]) ;
unset($_credit) ;
}
try
{
# QUERY TESTS
$_manager->data['query_1'] = array(
array("attribute"=>"pass", "value"=>@$_credit)) ;
// query test
$_res = utilities::getLineByAttribute($_manager->initialize(), $_manager->data['query_1']) ;
if(isset($_res["login"]))
{
$_SESSION["autho"]["credit"] = $_res["pass"] ;
}
else
{
unset($_SESSION["autho"]["credit"]) ;
}
# REGION [0] - Keep order of privileges
if(preg_match('/u/',@$_res['rights']))
{
$_SESSION["autho"]["user"] = 1 ;
unset($_SESSION["autho"]["moderator"]) ;
unset($_SESSION["autho"]["admin"]) ;
}
if(preg_match('/m/',@$_res['rights']))
{
$_SESSION["autho"]["moderator"] = 1 ;
unset($_SESSION["autho"]["admin"]) ;
}
if(preg_match('/a/',@$_res['rights']))
{
$_SESSION["autho"]["admin"] = 1 ;
}
# END REGION [0]
} catch(Exception $ex) { }
//var_dump($_res);
?>
Objects/Backend/responder.inc.php dans sa version finale
<?php
//
$_ = explode(":",@$_REQUEST['id']) ;
$_qry = $_REQUEST['qry'] ;
$_content = "" ;
switch( true )
/**
* content type switcher
*/
{
case ($_[0] == "lay") :
// single query order : template request
$_content = requestForLayout( $_[1], $_REQUEST['owner'], @$_REQUEST['template']?$_REQUEST['template']:'default' ) ;
break ;
case ($_[0] == "data") :
// single query order : data request
$_content = requestForData( $_[1], $_REQUEST['owner'] ) ;
break ;
case(@$_qry == 2) :
// single query order : formatData
$_content = formatData(@$_REQUEST['content']) ;
break ;
case(@$_qry == 3) :
// composed query instruction :
//$_content = ;
break ;
case(@$_qry == 4) :
// single query order : getCustomerEntryList
require_once BCK . "/services.inc.php" ;
if( @$_SESSION["autho"]["credit"] )
{
$_adminPage = new adminPage() ;
$_adminPage->prepareAdminPage( getCustomerEntryList( OWN ) ) ;
$_content = $_adminPage->str ;
} else {
$_content = "no access granted" ;
}
break ;
case (@$_qry == 5) :
// create entries for customer
$_content = $_REQUEST['nu']." !.added [ refresh the page ]" ;
mkdir( OWN . "/" . $_REQUEST['nu'] , 0700);
mkdir( OWN . "/" . $_REQUEST['nu']."/Contents" , 0700);
copy( OWN . "/Root/Dummies/1.xml", OWN . "/" . $_REQUEST['nu'] . "/Contents/1.xml" ) ;
copy( OWN . "/Root/Dummies/user.xml", OWN . "/" . $_REQUEST['nu'] . "/Contents/user.xml" ) ;
copy( OWN . "/Root/Dummies/page.xml", OWN . "/" . $_REQUEST['nu'] . "/Contents/page.xml" ) ;
mkdir( OWN . "/" . $_REQUEST['nu']."/Templates" , 0700);
copy( OWN . "/Root/Dummies/default.xml", OWN . "/" . $_REQUEST['nu'] . "/Templates/default.xml" ) ;
copy( OWN . "/Root/Dummies/admin.xml", OWN . "/" . $_REQUEST['nu'] . "/Templates/admin.xml" ) ;
mkdir( OWN . "/" . $_REQUEST['nu']."/Users" , 0700);
mkdir( OWN . "/" . $_REQUEST['nu']."/Tmp" , 0700);
break ;
case (@$_qry == 6) :
// save file modified
require_once "services.inc.php" ;
$_content = $_REQUEST['path']." !.saved" ;
file_put_contents($_REQUEST['path'], noHtml($_REQUEST['content']));
doFlush();
break ;
case (@$_qry == 7) :
//
$_content = "> crop page" ;
break;
case (@$_qry == 8) :
//
$_content = "> item page" ;
break;
case (@$_qry == 9) :
//
$_content = "> page page" ;
break;
case (@$_qry == 15) :
$_content = getCropItem($_REQUEST['item'],$_REQUEST['crop']) ;
break;
case (@$_qry == 20) :
$_content = saveCropItem($_REQUEST['value'],$_REQUEST['owner']) ;
break;
case (@$_qry == 21) :
$_content = saveCropNewItem($_REQUEST['value'],$_REQUEST['owner']) ;
break;
case (@$_qry == 22) :
$_content = saveNewCrop($_REQUEST["owner"],$_REQUEST['value']) ;
break;
case (@$_qry == 25) :
$_content = saveUser($_REQUEST['owner'], $_REQUEST['value'], $_REQUEST['key'], 1) ;
break;
case (@$_qry == 26) :
$_content = saveUser($_REQUEST['owner'], $_REQUEST['value'], $_REQUEST['key'], 2) ;
break;
case (@$_qry == 30) :
$_content = saveCrop($_REQUEST['owner'], $_REQUEST['id'], $_REQUEST['data'], $_REQUEST['key']) ;
break;
case (@$_qry == 35) :
$_content = savePage($_REQUEST['owner'], $_REQUEST['value'], $_REQUEST['key'],1) ;
break;
case (@$_qry == 36) :
$_content = savePage($_REQUEST['owner'], $_REQUEST['value'], $_REQUEST['key'],2) ;
break;
}
# OUTPUT
switch(true)
{
case ($_qry==6):
echo $_content ;
break;
case ($_qry==15) :
echo $_content;
break;
case ($_qry==20) :
echo $_content;
break;
case ($_qry==21) :
echo $_content;
break;
case ($_qry==22) :
echo $_content;
break;
case ($_qry==25) :
echo $_content;
break;
case ($_qry==26) :
echo $_content;
break;
case ($_qry==30) :
echo $_content;
break;
case ($_qry==35) :
echo $_content;
break;
case ($_qry==36) :
echo $_content;
break;
default:
echo @$_REQUEST['id'] . "~~" . ( @$_[1] ? $_[1] : $_[0] ) . "~~" . $_content ;
break;
}
?>
Objects/Backend/services.inc.php
<?php
/*
* Backoffices Services
*
* here comes all the backoffice treatments as
*
*/
function noHtml ($pString)
{
$_in = array('/</','/>/') ;
$_out = array('<','>') ;
return preg_replace($_in,$_out,$pString) ;
}
function doFlush ()
{
// check that buffer is actually set before flushing
if (ob_get_length())
{
@ob_flush();
@flush();
@ob_end_flush();
}
@ob_start();
}
function getUserList ()
{
return $lst ;
}
function getCustomerEntryList( $pPath, &$_lst=array() )
/*
* get recursively the files in a given initial path
*/
{
$tmp = getFileList( $pPath ) ;
try
{
if ( $tmp )
foreach ( @$tmp as $k=>$v )
{
if( is_dir($v) )
{
$_lst[$pPath][$v] = array() ;
getCustomerEntryList ( $v, $_lst[$pPath] ) ;
}
else
{
$_lst[$pPath][@++$i] = $v ;
}
}
}
catch (Exception $ex) {}
return $_lst ;
}
class adminPage
/*
* prepare admin page
*/
{
public $str = "
<div id='editPanel' class='adminMenu'>
<input type='text' id='iCustomer' value=':: add customer ::' onclick='this.value=\"\";' />
<input type='button' value='do' onclick='setContent(\"addUserCom\",\"qry=5&nu=\"+$(\"iCustomer\").value) ;' />
<div id='addUserCom' class='green'></div>
<input type='hidden' name='iPath' id='iPath' value='' />
<div id='cMod' style='display:none' ><!-- -->
<textarea id='iMod' rows='10' cols='100' style='width:100%;font-size:10px;font-family:verdana;'>content</textarea>
<input type='button' value='save' onclick='saveMod();' />
</div>
</div>
<br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br />
";
function deployArray ( $pArray, &$str )
/*
*
*/
{
//echo "tmp:".count($pArray) ;
try
{
if (is_array($pArray))
foreach( @$pArray as $k=>$v )
{
$this->str .= ( preg_match( '/\//', $k ) ? "<p><b>" . ((substr_count($k,'/')>2)?" <span class='dirPath'>" . $k:$k) . "</span></b></p>": "" ) . ( ! is_array( $v ) ? " <a style='cursor:pointer;' onclick='editFileContent(\"$v\");'>[edit]</a> <a href='$v'>" . $v . "</a>" : "" ) . " <br />" ;
if( count( $v ) )
$this->deployArray( $v, $this->str ) ;
}
} catch( Exception $ex ) {}
}
function prepareAdminPage( $pArray )
/*
*
*/
{
$tmp = array () ;
$this->deployArray( $pArray, $tmp ) ;
}
}
class cropPage
{
public function cropPage()
{
}
public function getCropList()
{
}
}
?>
Objects/Backend/webservices.inc.php
<?php
?>
FrontEnd
modifierDeux points d'entrées : Objects/Frontend/admin.inc.php
<?php
//
require_once CLA . "/utilities.class.php" ;
require_once CLA . "/templateControler.class.php" ;
require_once FUN. "/common.inc.php" ;
//
$tmp = new templateControler(array("path"=>"./Owners/".(@$_REQUEST["entry"]?@$_REQUEST["entry"]:"Dummy")."/Templates/admin.xml"));
# STATE
// get the content main page
$tmp->initFrame(
$tmp
->getNode("//template/frameset/content")
) ;
// authentification
$tmp->setTag("customer","<input type='hidden' value='".(@$_REQUEST['entry']?$_REQUEST['entry']:"Root")."' id='customerName' />" );
if(@$_SESSION["autho"]["credit"])
$tmp->setTag("logged","<input type='hidden' value='1' id='iLogged' />" );
$tmp->getAnchors( $tmp->data['feed'][0]->nodeValue ) ;
$tmp->anchorContentReplacer() ;
$tmp->setFrame() ;
# OUTPUT
echo $tmp->data['frameset'] ;
?>
Objects/Frontend/entry.inc.php dans sa version finale
<?php
require_once CLA . "/utilities.class.php" ;
require_once CLA . "/templateControler.class.php" ;
require_once FUN. "/common.inc.php" ;
require_once CLA . "/contentPane.class.php" ;
$_SESSION["path"] = $_path = "./Owners/" . (@$_REQUEST['entry']?$_REQUEST['entry']:"Root") ;
//
$tmp = new templateControler
(
array("path"=>$_path
. "/Templates/default.xml")
);
//echo print_r_html($_SESSION) ;
# STATE
$tmp->initFrame(
$tmp
->getNode("//template/frameset/content")
) ;
// authentification
$tmp->setTag("customer","<input type='hidden' value='".(@$_REQUEST['entry']?$_REQUEST['entry']:"Dummy")."' id='customerName' />
<input type='hidden' value='".md5(@$_SESSION["autho"]["credit"])."' id='userKey' />
<input type='hidden' value='" . (@$_cms['status']?1:0) . "' id='contentKey' />
<input type='hidden' value='0' id='iCounter' />
" );
if(@$_SESSION["autho"]["credit"])
{
$tmp->setTag("logged","<input type='hidden' value='1' id='iLogged' />" );
}
// if cms entry - display menu
if(@$_cms["status"] == 1)
{
$tmp->setTag("menuPane", $_container["content"]->data["menuPane"] );
if(@$_SESSION['autho']['credit'])
switch(true)
{
case ( isset($_REQUEST["crop"]) && $_REQUEST["crop"] != 1 && !@$_REQUEST["page"]==1 ) :
$tmp->setTag("bottomPane", $_container["content"]->getCropPane(@$_REQUEST["crop"]) );
break;
case isset( $_REQUEST["page"] ) :
$tmp->setTag("bottomPane", $_container["content"]->getPagePane( ));
break;
case isset( $_REQUEST["user"] ) :
$tmp->setTag("bottomPane", $_container["content"]->getUserPane( ));
break;
case ( @$_REQUEST["crop"] == 1 ) :
$tmp->setTag("bottomPane", $_container["content"]->getNewCrop() );
break;
}
}
$tmp->getAnchors( $tmp->data['feed'][0]->nodeValue ) ;
$tmp->anchorContentReplacer() ;
// customer name
$tmp->setFrame() ;
# OUTPUT
echo $tmp->data['frameset'] ;
?>
Le Troisieme noyau
modifierMyTinyCMS est ici un CMS orienté données et frontEnd. Avec le frontOffice ajaxifié présenté dans la rubrique relatif à la vue, il permet la création de sites customer directement dans moyennant quelques connaissances en html.
L'applicabilité des modules ayant été démontrée. Cette solution est achevée ici mais peut être augmentée ou améliorée.
myTinyCMS est sous licence GNU copyLeft et utilisable. Si vous implémentez la solution, laissez votre copyright ou alias derrière le mien.
Le copy étant
Copyright (C) zulul Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".
Télécharger
modifierMyTinyCMS est nominal... Il peut être telechargé ici
Exemples/MiniCMS/Fournir les données
Pour la vue, les données sont extraites des champs et ensuite injectées aux points d'ancrages désignés sur les templates. Un des objectifs étant de bien synchroniser l'injection serveur avec les sorties clientes en mini flux sur les containers de la vue xhtml. La complétion des données étant sélective.
Exemples/MiniCMS/IHM
L'Interface Homme - Machine consistera à fournir à l'utilisateur des options d'encodage et d'identification dans la vue.
Le découpage modulaire dégage la solution des itérations avenir dans l'ajout de nouvelles fonctions.
Deux points d'entrées permettent de découpler
- Site web : http://localhost:888/miniCMS/main.php
- CMS : http://localhost:888/miniCMS/main.php?usr=log
Ce dernier proposant l'authentification.
L'intégrateur pourra créer une interface d'administration à loisir sur cette adresse ou modifier le point d'entrée, en supprimant ou modifiant les paramètres ou en passant les données par POST plutôt que dans l'URL...
Cette partie ne sera pas détaillée dans cet exemple, seule l'interaction avec l'encodeur sera vu.
Le point d'entrée du site devra faire la distinction entre les sous sites ou sous domaines.
Exemples/MiniCMS/Rétrospective
Si l'envie vous prend d'intégrer ce CMS, c'est à dire de le coder localement chez vous ou de l'adapter à votre environnement ou solution. Je vous conseille de toujours travailler avec l'API approprié, c'est à dire ici : php.net. Cette solution est suffisamment légère pour pouvoir être traduite à loisir en Ruby, Java, Python ou autre.
L'inconvénient majeur actuel étant que l'utilisateur ou le webmaster qui l'utilisera devra avoir des notions d'HTML, la vue ajaxifiée étant ouverte pour une saisie directe au plus au niveau, càd la vue elle même offrant le rendu de ce qui va être encodé.
Les différentes couches ou composantes sous forme de scripts, snippets, classes, functions peuvent être revues, surchargés, améliorées ou étendues. Celles-ci devenant des incréments que vous jugerez utiles de placer à la suite dans ces pages.
Exemples/MiniCMS/Conclusion
Développer un collectionneur et publicateur de données comme typo3 ou joomla est plus une affaire d'organisation modulaire que d'approche en terme de service à fournir. Ce dernier n'étant que la collecte et la diffusion, avec quelques options quant au versionning et la mise à jour du contenu. Les règles sont simples, mais à développer...
GFDL | Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture. |