rap_karobas_2005
-
Upload
thibault-normand -
Category
Documents
-
view
265 -
download
0
description
Transcript of rap_karobas_2005
Année 2004 - 2005
Julien BaudufThibault Normand
Institut Universitaire des Technologies de NiceLicence Professionnelle
Administration des Sytêmes en Réseaux
KAROBASMise en place d'une infrastucture commerciale
etmigration technologique.
Durée : 4 Mois
Tuteur de stage : M. Michel SYSKATuteur d'entreprise : M. Patrice CHEREAU
Karobas.fr 1 / 93
Année 2004 - 2005
REMERCIEMENTS
Nous tenons tout d’abord à remercier Monsieur Patrice CHEREAU, Président
Directeur Général de la société KAROBAS qui a permis la réalisation notre stage
dans sa société.
Puis, les membres du personnel qui nous ont réservés un accueil chaleureux, ce qui
a permis de nous intégrer dans l’entreprise et effectuer notre stage de Licence
Professionnelle Informatique dans d’excellentes conditions.
Nous remercions tout particulièrement nos maîtres de stage, Monsieur Olivier
CHEREAU, Directeur de Production et Monsieur Nicolas VALENTINO, pour l’aide
apportée tant dans l’élaboration que dans la réalisation des divers projets.
Nous remercions enfin, Monsieur Michel SYSKA, responsable de la section
Administration Systèmes et Réseaux et professeur à l’I.U.T. de Nice – Sophia-
Antipolis, de s’être assuré du bon déroulement de notre stage.
Karobas.fr 2 / 93
Année 2004 - 2005
INTRODUCTION
Pour le déroulement de notre stage, nous avons été accueillis par l’entreprise
Karobas. Cette entreprise ambitieuse désire mettre toutes les chances de son coté
afin de réussir.
Dans cette optique, l’entreprise nous a sollicités pour effectuer plusieurs
tâches en vue de leur apporter des atouts pour pouvoir évoluer sur des bases
saines.
Tout d’abord soucieuse de sa sécurité, l’entreprise nous a demandé d’entreprendre
une restructuration de leur réseau afin de protéger et d’améliorer leurs systèmes
informatiques, tant au niveau du réseau interne que de leur serveur externe et les
possibilités de communications avec les deux.
Puis dans un esprit d’efficacité, la société exprime le besoin de facilité les échanges
du personnel au quotidien, de ce fait les nouveaux systèmes de communications et
leurs avantages sont à étudier.
Et afin de mieux répondre aux futures évolutions, l’entreprise désire remettre à plat
la base de données liée à une partie majeure de leur activité, ainsi que le site
Internet qui s’y rattache.
Au cours de ce mémoire, nous présenterons de manière générale, l’évolution
des différents travaux entrepris en nous arrêtant sur les difficultés techniques que
nous avons rencontrées.
Pour conclure, nous réalisons un bilan de ce stage en tenant compte du travail
réalisé.
Karobas.fr 3 / 93
Année 2004 - 2005
PRESENTATION DE L’ENTREPRISE
Karobas est une petite société qui modélise des environnements jouables en
3-Dimentions. Les lieux matérialisés sont issue du monde réel ou de projets. Ces
simulations apparaissent comme fortement intéressantes pour certaines
constructions dont le résultat peut être présenté avant son aboutissement. Il est
possible de citer des exemples, comme le nouvel aéroport de la ville de Marseille ou
le futur hôpital de la ville de Nice.
Mais l’activité principale est la conception d’un jeu, gratuit et jouable en ligne
destiné à devenir à terme une vitrine pour l’entreprise. Il permet à l’entreprise d’y
faire connaître son potentiel et toutes ses innovations dans un environnement
ludoéducatif qu’y invite les enfants à s’amuser tout en apprenant. On y conduit un
’petit bonhomme’ qui visite et se retrouve face à des scénarios qui lui permettent de
découvrir des lieux issus de la région P.A.C.A ainsi que leur histoire, ou de réfléchir
sur des situations précises de la vie courante. Pour l’instant disponible en mono
utilisateur sur la région P.A.C.A, il existe une cellule de test Multi-Utilisateurs sur la
région Rhône-Alpes où s’est créé des partenariats avec des groupes scolaires
intéressés par les avantages et les possibilités au niveau de l’éducation.
Karobas.fr 4 / 93
Année 2004 - 2005
SommaireLa sécurité............................................................................................................................................ 6
Protection du réseau de la société du réseau Internet et inversement..............................................8Sécurité des accès distants............................................................................................................. 10Sécurité des serveurs internes à la société..................................................................................... 12Centralisation des informations..................................................................................................... 14
L’Informatique au service de la Communication...............................................................................16Etude des solutions........................................................................................................................ 17
OpenGroupware........................................................................................................................ 18MDaemon..................................................................................................................................19Small Business Server...............................................................................................................20
Choix de la solution....................................................................................................................... 21Installation et Configuration.......................................................................................................... 22Le serveur Small Business Server................................................................................................. 28
Le développement : La base de données et le site..............................................................................31Analyse de la base données éxistante............................................................................................ 32Analyse des possiblités de migration.............................................................................................32Méthode d'abstraction à la base de données.................................................................................. 34Utilisation de la méthode d'abstraction et concepts du site .......................................................... 41Fonctionnalités du script................................................................................................................44Systême de traitements automatisés.............................................................................................. 50
Karobas.fr 5 / 93
Année 2004 - 2005
I – La sécurité
Karobas.fr 6 / 93
Année 2004 - 2005
Le réseau de l'entreprise comprends 6 ordinateurs appartenant à la société, et
7 ordinateurs provenants des stagiaires présents dans l'entreprise. L'accès au
réseau internet est controllé par l'utilisateur d'un routeur/firewall connecté à un
modem ADSL par liason PPPoE (PPP over Ethernet) procurant une connection
internet de type débit max 8Mo Wanadoo. Voici la configuration du réseaux de
l'entreprise :
fig 1. Connectivité Internet initiale
Cette architecture convient très bien pour une utilisation normale du réseaux
internet, mais elle devient limitée lorsque l'on décide de fournir des services à des
clients, tels que un accès VPN sécurisé, un serveur de mail, etc ... . Passons
maintenant à la configuration du serveur d'application Virtools, elle quasiment
identique car elle ne comporte qu'une machine et un routeur firewall de même
marque que celui utilisé par le réseaux interne de l'entreprise. Après analyse des
necessité du serveur d'application Virtools, il a été retenu qu'il necessite l'ouverture
de port bien spécifique tels que : 18005 (TCP) pour la connection client, et 50000 –
50100 (UDP) pour les requêtes de mise à jour du mediacache du client; (le
mediacache correspond aux fichiers mis en cache sur l'ordinateur du client, ainsi il
execute locallement l'application virtools ce qui evite par ailleurs de devoir
retélécharger les fichiers à chaque utilisation). Le serveur d'application héberge deux
sites web karobas.net et karobas.fr, tous les deux sur la même machine; karobas.net
correspond au site commercial de l'entreprise (c'est sa vitrine commercialle), et
karobas.fr : portail d'accès et d'inscription au jeu mis en ligne. Ces deux sites web
utilise IIS intégré à Microsoft Windows Server 2003 et son codés en ASP. Pour gérer
la quantité d'informations necessaire au bon fonctionnement du jeu et de la gestion
Karobas.fr 7 / 93
Année 2004 - 2005
des utilisateurs, le serveur héberge une base de données de type MsSQL regroupant
base de données du jeu et des sites.
Protection du réseau de la société du réseau Internet et inversement :
Il est évident que le serveur d'application représente un plus gros risque face
aux attaques événtuelles de personnes mal intentionnées; pour cela, il a fallut mettre
en place un firewall dédié en amont du serveur d'application afin de rendre plus sûr
les connections et d'avoir des traces d'activités illicites. Nous avons décidé de mettre
en place deux firewalls (un pour la société dans le but de repondre aux attentes, et
un pour le serveur d'application); nous avons dans un premier temps chercher des
solutions embarquées autonomes, mais cela représentait un surcout trop elevé. La
société possédait de vieux ordinateurs tels que des pentium-1 100 Mhz, pentium-2
266, etc ; nous avons ainsi opté pour un firewall léger embarqué sur une machine
dédiée : IPCop (http://www.ipcop.org) basée sur LFS [ Linux from Scratch ],
Smoothwall basée sur Red Hat(http://www.smoothwall.com), M0n0Wall basée sur
une BSD. Après analyse de chacune de ces distributions, nous avons retenu IPCop
car c'est une distribution qui offre de nombreux avantages tels que l'extensibilité par
addons et plugins divers, une interface de gestion clair par site web, surtout par la
faible consommation de ressources pour les fonctionnalités offertes.
La mise en place du firewall IPCop de la société a été très simple, tout d'abord
installation de la distribution à partir d'un CD (40 Mo), execution et installation par
assistant sous shell unix. La distribution fonctionne sur le principe de modèle de
réseau, ainsi sont nommés les réseaux par un code de couleur RED (rouge) pour
l'interface extèrieure, ORANGE pour l'interface DMZ (Zone d'accès internet
controllée c'est en général dans cette zone que l'on place les serveurs réseaux),
BLUE (bleu) pour l'interface WiFi, et GREEN (vert) pour le réseaux interne. Dans
notre cas, la configuration RED + GREEN était la mieux adaptée : RED, interface
connectée au modem Alcatel et GREEN connectée au réseau interne.
Afin de pouvoir passer le firewall en tête de ligne dans l'optique d'exercer un contrôle
d'accès, il a fallut mettre à jour le modem Alcatel SpeedTouch Home en version Pro
pour qu'il puisse gérer de lui même la connection, ainsi le modem c'est vu devenir lui
Karobas.fr 8 / 93
Année 2004 - 2005
un modem routeur NAT, ce qui a permis de réduire les problêmes de déconnection
ponctuelle rencontrés avec la configuration précédente. Voici la configuration actuelle
du réseau de la société :
fig 2. Connectivité internet actuelle
La machine IPCop possède deux interfaces réseaux chacune connectée de part et
d'autre du firewall. Ainsi le firewall devient une passerelle Internet, il ne gère pas la
connection internet car elle est autogérée par le modem ST-Pro, c'est comme si le
firewall était un pont entre deux réseaux distincts.
Afin de rendre plus agréable la navigation et d'economiser de la bande
passante sortante et entrante, le firewall a été pourvu d'un système de Proxy Cache
permettant de mettre en cache les pages les plus visitées afin d'accélérer la
navigation. En effet, lorsque l'utilisateur fait une requête vers un site internet, il passe
par sa passerelle internet (qui est le firewall je rappel), passe de manière
transparente par Squid qui va aller à la place de l'utilisateur chercher la page puis la
mettre en cache, et enafin re-expédier le contenu eventuellement traité / filtré à
l'utilisateur. Lorsque l'utilisateur redemande la même page, il effectue la même
procedure à un détail près : c'est le proxy cache qui réponds directement et non le
site web extérieur; par conséquent cela permet d'économiser des requêtes, du temps
et de la bande passante. Pour rendre la configuration transparente aux yeux de
l'utilisateur, le proxy cahce est configuré en mode transparent, c'est à dire que tous
les paquets à destination du port TCP 80 (HTTP) sont redirigés vers Squid (par
défaut 3128 TCP); pour mettre en place cela il suffit d'ajouter une règles dans le
firewall à l'aide de l'outil de configuration de firewall iptables. Toujours dans le but
d'accélerrer la navigation et donc la satisfaction de l'utilisateur, le firewall possède un
Karobas.fr 9 / 93
Année 2004 - 2005
cache DNS, permettant d'accélérer les réponses au requêtes de résolution de nom, il
fonctionne sur le même principe que Squid avec le cache HTTP.
Concernant les règles de base du firewall, nous avons adopté la régle la plus
sécurisée qui dit « Tout ce qui n'est pas explicitement autorisé est interdit », c'est à
dire le firewall bloque tout le trafic sauf ce qui est autorisé à passer dans le sens RED
vers GREEN (internet vers réseau local), dans l'autre sens tout est autorisé ormis
l'IP-spoofing; les paquets ayant une adresse source externe provenant de l'interieur
sont automatiquement filtrés; comme les paquets provenant de réseaux privés
provenant de l'extérieur. Pour concerver, des traces d'attaques eventuelles, le
firewall est equipé d'un système de detection d'intrusion appelé Snort, il fonctionne
sur le principe de règle de detections prédéfini permettant d'identifier une attaque
eventuelle; il ne réagit pas aux attaques, il enregistre simplement les activités jugées
étranges du réseaux; il permet d'avoir des informations sur ce qu'il transite sur le
réseau.
Sécurité des accès distants :
Par la suite, nous verrons qu'il a été necessaire de mettre en place un système
de contrôle permettant l'accès par VPN (Virtual Private Network), qui est un système
utilisé pour rallier deux réseaux de plages d'adresses différentes à un même réseau
appellé dans ce cas VPN N2N (pour Network to Network), c'est utilisé pour la
communication entre deux routeurs qui doivent ainsi gérer 3 plages d'adresses
différentes : 2 pour les adresses des réseaux, plus une pour le réseau VPN. Un VPN
peut être une solution à l'accès d'un ordinateur à des ressources comprises dans un
réseaux interne, il est appelé dans ce cas S2N (pour system to network ou
RoadWarrior); ainsi la personne authentifiée et connectée fait partie intégrante du
réseau interne de la société alors qu'elle peut se trouver chez elle. Le problème
majeur du VPN est que les classes d'adresses doivent être différentes de chaque
coté du VPN par conséquent, il a été nécessaire de changer la classe d'adresse du
réseau interne; nous avons choisi une adresse privée de classe B pour un usage en
classe C. Voici le schéma d'implentation du VPN dans le réseau de l'entreprise:
Karobas.fr 10 / 93
Année 2004 - 2005
fig 3. Implentation du VPN
Comme il est indiqué sur le schéma, nous avons procédé à l'installation de la
solution OpenVPN, qui est un programme basé sur les liasons TLS / SSLv3
contrairement au VPN habituels qui utilise un cryptage IPSec sur un mode de
transfert TCP (appelé en capsulation TCP) alors que OpenVPN utilise le mode de
connection UDP (appelé mode non connecté), il est baucoup plus rapide de ce fait,
d'après des mesures avec un P2 266, on atteind un taux de transfert de 1,45 Mo/s
avec une clé de 1024bits contre 400 Ko/s avec une solution IPSec ce qui est très
raisonnable du fait que l'on utilisera jamais le taux de transfert maximal sachant les
limitations de la ligne Internet.
OpenVPN est basé sur un mode client / serveur, c'est à dire qu'il est necessaire
d'installer un serveur accessible en écoute (peut être multi – client ou pas, peut
fonctionner en mode P2P) pour que les clients puissent utiliser le VPN. La phase
d'authentification s'execute sur un canal crypté basé sur TLS avec un clé variable,
pour s'identifier un client doit posséder un certificat personnel et valide auprès du
serveur VPN, à tout cela peut s'ajouter un mcanisme de chiffrement simple ou
complexe : va du Blowfish, à DSA en passant par CAST5-CBC. Tout en sachant que
la clé de connection est renouvellée tous les intervalles définis (par défaut 60
minutes). OpenVPN représente une solution de sécurité pratique sûre et simple à
mettre en oeuvre. Seule ombre au tableau la différence de classe d'adresse mais ce
défaut est commun avec toutes les technologies VPN existantes à l'heure actuelle.
Karobas.fr 11 / 93
Année 2004 - 2005
Sécurité des serveurs internes à la société :
Suite à l'installation de la suite groupware, nous nous sommes aperçu que le
firewall était mal dimensionné pour les besoins de la société, par conséquent, il a été
necessaire de renouveler la machine sur lequel tournait la distribution dédiée IPCop.
Suite à la rencontre de quelques problèmes concernant la configuration d'une
application, en l'occurence Squid en mode Reverse proxy afin de masquer et
contrôler la connection au webmail du logiciel de groupware retenu, nous avons
choisi d'installer une distribution linux traditionnelle. Notre choix c'est porté sur le
système Gentoo; pourquoi Gentoo ? Pour plusieurs raisons, l'habitude de ce
système, mais aussi l'adaptabilité extrème et la sureté du système, en effet nous
avons mis en place le mode de sécurité Hardened de Gentoo, qui est un noyau
incluant de nombreux dispositifs anti-piratage, et contrôle du système. Nous avons
choisi d'activer les fonctionnalités SSP (Smash Stack Protector) et PIC permmettant
la protection des executables et piles d'execution pour eviter ainsi la première raison
de succès de piratage, les attaques par Buffer Overflow (Dépassement de capacité).
Nous avons mis en place les mêmes logiciels installés sur IPCop tels que Squid,
DHCPD, DnsMasq, Snort. Concernant la facilité d'administration, il est clair que
Gentoo est baucoup plus difficile à configurer en passerelle internet qu'une
distribution spécialisée / pré-configurée comme IPCop mais l'accent est mis sur la
modularité et l'evolutivité du système Gentoo. De plus, Gentoo est réputé très stable
quand elle est bien configurée, c'est à dire qu'une fois mis en place, elle ne sera plus
un problème, seule ombre les crash matériels.
Nous avons procédé à l'installation de Squid en Reverse-Proxy, afin de pouvoir
traiter les demandes des clients autorisé à accéder au webmail du groupware. Un
transfert de port ne peut suffir car le serveur web fournit d'autres services au réseaux
local qui deviennent accessible depuis l'extérieur, par conséquent la mise en place
d'un reverse-proxy a été necessaire pour eviter que le client navigue et accède au
dossier, pour lui il sera comme 'chrooté', c'est à dire que la racine pour le client sera
un dossier du serveur interne.
Karobas.fr 12 / 93
Année 2004 - 2005
Fig 4. Implentation du reverse proxy
Suite à une mise à jour de Exchange 2003, nous avons du changer de
programme de rédirection pour finalement installer et configurer Apache en reverse-
proxy SSL. En effet il y a un problème avec l'authentification intégrée MS. En passant
par Apache nous perdons tous les avantages de la mise en cache des pages
permettant d'accélérer les requêtes clients.
Voici la structure final, du firewall servant à protéger la société des attaques
extérieures:
fig 5. Structure du firewall Gentoo (cf annexes)
Karobas.fr 13 / 93
Année 2004 - 2005
Ce schéma disponible en version complète dans les annexes, montre le filtrage
entrant et sortant des paquets. En effet, le par-feu utilise la règle : "Tout ce qui n'est
pas autorisé et refusé" qui est la règle la plus sécurisée; cette règle est utilisée pour
le traffic entrant. Pour le controle et le suivant du traffic sortant, de différent dispositif
ont été mis en place tels que : Proxy HTTP, filtrage mail (Antispam / Antivirus), une
passerelle de connection SSL. Ce systême nous a permis de tracer l'activité d'un
virus présent dans la société à notre arrivée et non detecté par les système anti-virus
client.
Centralisation des informations :
Afin de palier aux problèmes d'accès des utilisateurs en groupe de travail, car
les droits sont multiples et stockés sur chaque ordinateurs clients, nous avons mis en
place un domaine Microsoft Active Directory 2003 dirigé par un contrôleur de
domaine primaire Windows 2003 Server. Cette architecture permet de centraliser les
accès aux ressources, périphériques, et rends le réseaux extensible à souhait; il
suffit d'inscrire la machine au domaine, elle sera configurée automatiquement, les
logiciels manquants seront installés à la connection de l'utilisateur. Par exemple, un
utilisateur non connecté au domaine doit à chaque accès à une ressource du
domaine s'identifier comme étant un utilisateur du domaine, alors que dans le cas
d'un passage complet de l'ordinateur l'authentification s'effectue dès l'ouverture de
session, par identification sur le contôleur de domaine. Par la suite, nous verrons
l'utilité du domaine lié à la solution groupware retenue.
Le passage en domaine sera expliqué de manière plus précise dans la
prochaine partie traitant des solutions GroupWare étudiées. Le domaine apporte
donc la centralisation des informations utilisateurs, de l'administration; en effet un
administrateur du domaine est administrateur sur chaque machines clientes. Par
ailleurs, la société a émis un voeux à propos des droits d'accès sur chaque
machines, nous avons du mettre en place une politique qui énonce qu'un utilisateur
est un simple utilisateur du domaine mais il doit être administrateur local de sa
machine pour garder la possibilité de pouvoir contrôler les applications installées et à
Karobas.fr 14 / 93
Année 2004 - 2005
installer sur sa propre machine. Pour cela chaque compte utilisateur du domaine
possède un alias local sur la machine de l'utilisateur comme étant Administrateur de
sa machine.
Nous venons de mettre en place un réseau et des politiques d'accès plus
sécurisés que dans la structure antérieure. Certes nous devons nous attendre à
quelques plaintes concernant ces restrictions mais "on a pas rien sans rien !". La
mise en place des firewalls a constitué une étape importante de l'évolution de la
sécurité : maintenant nous filtrons les attaques sur les services réseaux ouvert aux
public (IIS, FTP, ...), nous concervons des traces d'activités par toute une série de
logs et audit système.
Nous allons traiter maintenant de l'objectif principal du stage qui était de mettre
en place une solution de groupware dans la société afin de coordonner les activités
exterieures des commerciaux (prises de rdv, demande reunion, email de suivi, etc
...). Cette étape a constitué et constitu encore une somme de travail hebdomadère,
du à l'autoformation à la suite, à la formation des utilisateurs, l'administration, la
maintenance du serveur.
Karobas.fr 15 / 93
Année 2004 - 2005
II – L’Informatique au service de la Communication
Karobas.fr 16 / 93
Année 2004 - 2005
L’un des objectifs, est la mise en place au sein de l’entreprise d’un système collaboratif. L’une des ces principales fonctionnalités est la gestion des calendriers et contacts partagés. Ce système est nécessaire car l’ensemble des collaborateurs étant que très
rarement réunit, la communication s’avère difficile.
Une seule contrainte est donnée : l’interopérabilité avec l’outil Ms Outlook qui est
massivement utilisé et obligatoire pour une synchronisation de leurs différents
appareils Windows Mobile.
Afin de déterminer le logiciel le plus adapté vis-à-vis de l’entreprise, nous avons
testé quelques grands noms dans le domaine Groupware. Ce qui nous a permis
d’obtenir les avantages et les inconvénients de chacun, face au désir de l’entreprise
et à ses contraintes. Le Groupware renvoie à des applications permettant à des
utilisateurs géographiquement éloignés de travailler en équipe. Le travail en équipe
peut se concrétiser par le partage d’informations, ou la création et l’échange de
données informatisées.
1. Etude des solutions
L’étude porte sur trois systèmes, de styles différents, mais avec des fonctionnalités
assez rapprochées : OpenGroupare, Mdeamon Groupware et Exchange.
Ces trois systèmes, sélectionnés par nos soins, possèdent un système permettant
une communication via Outlook.
Karobas.fr 17 / 93
Année 2004 - 2005
OpenGroupware
OpenGroupware est l’un des meilleurs Groupware issue du monde libre. La société
SKYRIX supporte et commercialise cette application en Allemagne, mais a
transformé son code en projet sous licence G.P.L. en 2000. Cette solution est
considérée comme la meilleure alternative libre au couple propriétaire Exchange /
Outlook.
OpenGroupware est une véritable solution de messagerie qui facilite la collaboration
au sein de l’entreprise, grâce à des fonctionnalités telles que le partage de
documents, d’agendas, de carnets d’adresses et de messagerie.
Cet outil, fonctionne sous linux et s’installe assez facilement pour une personne
ayant quelques connaissances dans ce système d’exploitation. L’ensemble est
développé en Objective C et est basé sur une base de données travaillant sous
PostGrey et sur le serveur web Apache.
L’accès à ses différentes fonctionnalités s’effectue par un client léger comme
Internet Explorer ou Mozilla, ou par des clients lourds tel que Thunderbird ou
Outlook, via un connecteur. Le point négatif est que le connecteur Outlook,
ZideLook, est payant et reste assez onéreux, dans les 60 € par poste. Zidelook ne
gère pas complètement l’ensemble des fonctionnalités d’Outlook comme par
exemple la notion de rendez-vous ou de contacts privés.
Sinon son administration par interface web reste simple à prendre en mains.
Son environnement n’est pas totalement finalisé et le projet reste encore en version
bêta, mais dans le futur cette application peut séduire beaucoup de société.
Karobas.fr 18 / 93
Année 2004 - 2005
MDaemon
MDaemon, fonctionnant sous Windows, est un serveur de messagerie professionnel
doté des technologies les plus récentes pour le traitement, la sécurisation des
communications électroniques, la gestion des comptes utilisateurs et le partage
d’informations.
Ainsi, elle intègre des services anti-Spam, Anti-Phishing auquel on peut y ajouter un
module antivirus disponible dans la gamme.
En configuration Internet ou Intranet, MDaemon est une solution complète et
entièrement personnalisable : services SMTP, POP, IMAP (version Pro), listes de
diffusion, annuaire centralisé, partage de calendriers, de contacts et de tâches,
dossiers publics, interface WebMail (WorldClient), messagerie instantanée sécurisée
(ComAgent), etc.
Ce système s’intègre au système Active Directory si l’on dispose d’un domaine
Microsoft, où à différents systèmes L.D.A.P. Sinon, il possède sa propre console
d’administration pour la gestion des utilisateurs et de leurs autorisations. Cette
console existe sous forme web, appelé WebAdmin. Sa version professionnelle
intègre la notion de travail collaboratif qui permet de gérer les dossiers Courrier,
Calendrier, Contacts et Tâches à partir d'un simple navigateur Web, ou associé au
module Outlook Connector.
Après quelques tests, cet outil semble être très pratique pour son intégration et son
utilisation, mais son connecteur pour Outlook reconnaît des problèmes au niveau de
la synchronisation. Il ne gère pas les accès concurrents et ne permet pas d’intégrer
par exemple deux rendez-vous simultanés.
Karobas.fr 19 / 93
Année 2004 - 2005
Small Business Server
Beaucoup de petites et moyennes entreprises connaissent une politique de
réduction des coûts. Dans cette optique, Microsoft a développé Small Business
Server. Ce système basé sur la plateforme 2003 serveur, intègre un ensemble de
fonctionnalités adapté à ce type d’entreprise.
Small Business Server est composé de :
- Microsoft 2003 Server, système très utilisé dans les entreprises qui
permet d’engendrer la sécurité et les avantages de la gestion sous
forme de domaine.
- Echange 2003 Server, une solution de messagerie électronique
professionnelle reconnue pour sa simplicité d’administration, et son
potentiel.
- Outlook 2003, produit indispensable du serveur Exchange permettant
d’implanter ses fonctionnalités. Il gère et organise des messages
électroniques, des planifications, des tâches, des notes, des contacts
et d'autres informations de manière centralisée.
- Windows SharePoint Services, rassemble un ensemble d’outils
destiné au travail collaboratif. C’est un moteur de création de site web
destiné à la communication de groupe.
Karobas.fr 20 / 93
Année 2004 - 2005
2. Choix de la solution
L’ensemble des solutions ont été présenté à l’entreprise, en y évoquant les
avantages et les inconvénients de chacune.
OpenGroupware n’a pas reçu un très bon accueil, soit disant gratuit, mais
enfin de compte payant et cher pour une simple comptabilité avec Outlook. De plus
l’installation nécessite un serveur sous linux, qui à part le parfeux, n’est pas
considéré comme une priorité. Aucune personne n’ayant beaucoup travaillé avec ce
système, son administration en serait difficile. L’application est bien adaptée à
Thunderbird mais l’entreprise n’est pas prête pour migrer vers cette solution.
MDaemon Professionnel est à un prix avoisinant Small Business
Server pour un petit nombre de licences. En effet, ce système est plus intéressant
pour une grosse société désirant acquérir un plus grand nombre de licence car le
prix des licences supplémentaires est moins élevé que pour le serveur de chez
Microsoft. De plus, le connecteur n’étant pas totalement opérationnel, l’entreprise
n’est pas près à investir dans un système qui manque de stabilité.
Small Business Server est la solution choisie. En effet cette solution, destinée
au P.M.E., intègre un serveur de domaine et un service de messagerie évolué pour
un coût avoisinant celui d’une simple licence 2003 Server. Ce système nécessite
une machine dédiée avec beaucoup de capacité, mais regroupe des fonctionnalités
attrayantes comme Outlook Web Access qui rassemble la majorité des possibilités
d’Outlook dans un Webmail, ainsi que CompanyWeb qui est un site intranet
entièrement modulaire pouvant très facilement s’adapter à l’entreprise. Le point
négatif retenu est le coût de la licence supplémentaire qui reste élevé.
En conclusion, même si le prix de certains produits est attrayant, la société désire
acquérir un produit fiable et réputé afin d’éviter certaines mauvaises surprises après
sa mise en place.
Karobas.fr 21 / 93
Année 2004 - 2005
3. Installation et Configuration
L’une des principales possibilités réunissant les trois produits testés reste le serveur
mail. Mais l’entreprise utilisant un service externe pour effectuer cette fonctionnalité,
la mise en place de la solution s’avère être source de problèmes et de discussions
pour une utilisation en fonction des souhaits et des possibilités demandés par
l’entreprise.
Suite à ces contraintes, notre système a subi quelques étapes pendant sa mise en
place avant d’être totalement opérationnel.
A notre arrivée, la messagerie de l’entreprise fonctionne tout simplement grâce à un
système de relais de mail. Cette technologie est un service offert par le fournisseur
du nom de domaine « karobas.fr » (ici le fournisseur est « lerelaisinternet.com »). Il
permet de créer des adresses mails « @karobas.fr » directement relayés vers une
adresse destinataire de leur choix. En pratique, l’émetteur du message envoie un
mail à une adresse du domaine, ce message arrive sur un serveur de l’I.S.P. qui le
relaie à l’adresse choisie par le destinataire, ici des comptes créés sur un service
tiers « laposte.net ».
Le destinataire n’a plus qu’à consulter son adresse « laposte.net » pour découvrir
tous les mails qui lui sont destinés. En théorie, ce système fonctionne très bien et
décharge l’entreprise de toute gestion de messagerie, seul un problème persiste, un
employé de l’entreprise ne peut envoyer de mail avec son adresse « @karobas.fr »
car le service laposte.net ne fournit pas de telle possibilité.
Pour conserver ce système qui n’impose aucune maintenance au niveau
messagerie, le serveur Exchange doit s’adapter.
Karobas.fr 22 / 93
Année 2004 - 2005
Dans un premier temps, le serveur Exchange doit offrir la possibilité de
partager l’agenda et les contacts de chacun des employés. Cette fonctionnalité
n’oblige pas l’utilisation du service de messagerie dans le cadre de l’externe.
L’entreprise souhaite alors conserver son ancien système de gestion des mails
« @karobas.fr » par crainte de connaître des problèmes de maintenance.
La première installation et configuration du produit est dans l’optique de
séparer la messagerie externe de l’interne. De ce fait, l’ensemble de la configuration
utilise un nom de domaine de type « karobas.local ».
Ce nom de domaine, un peu particulier, est conseillé lors de l’installation du serveur
par Microsoft. L’extension .local est suggérée lorsque qu’on ne désire pas contrôler
le domaine sur Internet.
La solution, comme décrite sur le schéma ci-dessous, ne bouleverse pas
l’utilisateur quant à l’utilisation de son service de mail externalisé. Mais l’apparition
d’un nouveau compte sous Outlook communicant avec le serveur Exchange offre
beaucoup de fonctionnalités non connues pour un utilisateur ne maîtrisant pas le
produit.
La connexion est immédiate pour un ordinateur fixe présent dans les
murs de la société, mais un problème se pose pour les utilisateurs nomades. D’où la
mise en place du service OpenVPN sur le Firewall de la société, qui permet à
l’utilisateur d’être relié au réseau interne via un tunnel sécurisé. Grâce à son client
graphique, les utilisateurs du VPN s’adaptent très bien à cet outil.
Karobas.fr 23 / 93
Année 2004 - 2005
Dès maintenant, les utilisateurs disposent d’un moyen de communication
interne. Ce système complètement intégré à l’outil d’organisation Outlook impose
une formation aux utilisateurs.
Tout d’abord, l’outil s’avère comme la plupart des produits Microsoft
assez simple de fonctionnement et très bien adapté au milieu de l’entreprise, mais
seulement pour une utilisation courante. Ainsi l’utilisation de prise de rendez-vous en
groupe, sous forme de réunion, a été rapidement assimilée après une démonstration
individuelle lors de la nouvelle configuration du produit sur chacun des ordinateurs.
La configuration passe par la notion de partage, où chaque utilisateur donne les
droits qu’il désire à ces collègues. Car dans ce système chaque utilisateur donne un
droit entièrement personnalisable à un autre utilisateur en fonction des besoins sur
ses contacts et son calendrier, tout en gardant la possibilité de conserver une partie
de vie privée.
Puis toujours dans le cadre de la formation, chaque cas particulier
envisagé par l’utilisateur devient pour nous même un cas de recherche et
d’autoformation.
Mais suivant une utilisation courante testée par un certain nombre
d’utilisateurs, les conclusions s’avèrent être plus ou moins convaincantes. N’est pas
remise en question l’utilisation des fonctionnalités mais la difficulté à jongler entre les
comptes, soit pour l’externe, soit pour l’interne. Sachant que lors d’un envoi au
personnel de l’entreprise le compte « karobas.local » doit être forcément utilisé pour
conserver le passage et le filtrage par le serveur Exchange, celui-ci conservant les
Karobas.fr 24 / 93
Année 2004 - 2005
notions de rendez-vous et autres possibilités d’Outlook. Ainsi, après des pertes lors
d’envoie de mail via le compte « karobas.local » sur les comptes de correspondants
externes, ou l’envoi de prise de discutions interne via les comptes « laposte.net », il
est obligatoire d’évoluer vers un environnement plus simplifié.
Ainsi pour améliorer le confort des utilisateurs, nous réfléchissons de nouveau sur la
configuration du serveur Exchange.
Tout en conservant leur système externe de messageries, nous proposons à la
société de modifier le domaine du serveur Small Business Server, pour qu’il
devienne « karobas.fr ». Ainsi, dans cette configuration le système permet l’envoi de
mail via le serveur Exchange à un correspondant extérieur. Le mail est acheminé en
possédant une adresse « @karobas.fr » et non comme anciennement
« @karobas.local ».
Afin de permettre la migration du nom de domaine, nous devons veiller à ce que
l’ensemble du domaine soit toujours accessible. Car une fois migré, le serveur Small
Business Server se comporte comme s’il est le maître du domaine et qu’il possède
l’ensemble de sa gestion. Cela provoque le fait, que l’ensemble des éléments
portant un nom « .karobas.fr » sur Internet ne sont plus joignable par le réseau
interne, car le serveur étant le serveur DNS du domaine, il pense que tout ceux qui
portent son nom se trouvent dans son réseau. De ce fait, l’ensemble des noms
comme www.karobas.fr, ftp.karobas.fr, qui pointent vers un serveur externe, doivent
être inscrit manuellement dans le serveur DNS. De plus, l’ensemble des adresses
mails doivent être identique aux différentes adresses mails relayées par le
fournisseur du nom de domaine « lerelaisinternet.com » sous peine que le
destinataire d’un mail réponde à une adresse inexistante du monde Internet.
Karobas.fr 25 / 93
Année 2004 - 2005
Dans cette configuration, tous les mails conservent le même sens de
circulation, les utilisateurs disposent toujours de deux comptes mais un pour la
réception depuis l’extérieur, et un pour l’envoi, filtré par le serveur Exchange qui
conserve l’ensemble des mails destinés à être redistribués sur le réseau interne.
En définitive, le système étant opérationnel, il est alors possible de passer à
une autre étape, la mise en place d’un seul compte sous Outlook, celui du serveur
Exchange. En fait, le serveur offre la possibilité de ramasser les mails de boîtes
externes, et donc récupérer l’ensemble des mails reçus par les utilisateurs provenant
des différents comptes mails relais d’adresse « @karobas.fr ». Ce service s’appelle
POP3Connector.
Le seul désavantage à ce système est qu’une personne nomade, qui n’a pas accès
à Internet avec son ordinateur, ne puisse plus consulter ses mails, comme par
Webmail, service offert par laposte.net, tant que les mails ne sont pas ramassés par
Outlook. Le serveur Exchange possède un outil de ce type Outlook Web Access,
qui fonctionne à partir du serveur Web I.I.S. Donc ce système doit être mis en ligne
sur Internet. Pour éviter d’exposer le serveur de messagerie et de domaine
directement sur Internet, nous décidons de le protéger en installant un Proxy
inversé. Ce Proxy permet de servir de passerelle pour aller communiquer avec le
site interne de Small Business Server. La solution choisie pour assurer ce travail est
le serveur Apache, qui est en version définitive un des seuls programmes pouvant
servir comme Proxy Inversé assurant le transport de type S.S.L. du serveur Outlook
Karobas.fr 26 / 93
Année 2004 - 2005
Web Access jusqu’au client.
Ensuite, le service POP3Connector relève les mails toutes les quinze minutes au
minimum, ce qui ai jugé beaucoup trop long. Après quelques recherches, on a
découvert PullMail un outil alternatif à POP3Connector. Cet outil gratuit ne relève les
mails que par POP3 et non POP-S, et s’utilise en environnement de commande.
Donc, on installe et configure Stunnel pour permettre de créer un service
fonctionnant sur le port 110 qui implémente la connexion S.S. L. jusqu’à un port
POPS distant, dans notre cas le serveur de messagerie GMAIL. Donc, après la
conception d’un fichier de commande .bat qui lance PullMail paramétré en fonction
des différents comptes mails, et la création d’une tache planifiée exécutant ce script
tous les cinq minutes, notre serveur est opérationnel au niveau de la messagerie.
Le fonctionnement est un peu complexe, mais l’entreprise peut continuer
d’utiliser son service de messagerie externe même si le serveur Exchange tombe en
panne.
Karobas.fr 27 / 93
Année 2004 - 2005
4 Le serveur Small Business Server
Le système de messagerie étant opérationnel, l’ensemble des ordinateurs
peuvent se joindre au domaine créé par l’environnement 2003 Server.
Afin de conserver les habitudes de l’entreprise, chaque utilisateur doit rester
administrateur de sa machine pour y installer et configurer l’ensemble des logiciels
nécessaires au travail. Donc, lors du passage d’un ordinateur sur le domaine, son
utilisateur, inscrit dans l’Active Directory, est inséré dans le groupe local
administrateur de la machine dont il a la gestion. Il devient administrateur local de sa
machine tout en étant utilisateur du domaine.
Ayant rejoint le domaine, l’ensemble des utilisateurs ont découvert le système
Windows SharePoint Services. Cet intranet appelé « CompanyWeb », très simple à
utiliser et à modifier, s’adapte à beaucoup de situations, comme par exemple, un
reporting des tâches à effectuer, des forums, calendriers, listing, l’ensemble très
modelable pour permettre de communiquer et de s’organiser. Cet outil devient
communément utilisé par l’ensemble de la société.
Karobas.fr 28 / 93
Année 2004 - 2005
Au niveau de son administration, Small Business Server possède un système basé
lui aussi sur SharePoint. Ce dernier affiche une nouvelle forme de console
d’administration différente de celle d’un Server 2003. Au lieu de retrouver au
démarrage d’une session, une interface permettant de gérer les rôles du serveur,
c'est-à-dire d’installer et de configurer l’ensemble des fonctions du serveur tel que le
service Terminal Server ou le service de D.N.S. par exemple, ce système plus abouti
permet la gestion entière du serveur tel que l’ensemble des utilisateurs, les
différentes stratégies et politiques de groupe. Cette application remplace les
principaux éléments lié à la console MMC.
Karobas.fr 29 / 93
Année 2004 - 2005
Pour la protection du système, au lieu d’une consultation des différents journaux
d’alertes, le système envoie un mail quotidien qui prévient de l’état général du
serveur. Dans ce rapport détaillé, on y consulte par exemple l’espace disque dur
restant, l’ensemble des mémoires utilisées, la totalité des journaux qui suivent
l’activé quotidienne du serveur.
Karobas.fr 30 / 93
Année 2004 - 2005
En définitive, l’ensemble de ses aides simplifie la configuration et l’administration de
ce serveur destiné aux petites sociétés.
Karobas.fr 31 / 93
Année 2004 - 2005
III – Le développement : La base de données et le site
Karobas.fr 32 / 93
Année 2004 - 2005
Le jeu hebergé sur le serveur d'applications necessite une base de données
éxécutée par MsSQL. Pour pouvoir s'incrire l'utilisateur doit utiliser un portail
d'inscription en ligne disponible à l'adresse http://www.karobas.net; ce site a été codé
en ASP par un ancien employé de la société. Pour des raisons financières, la société
décide d'abandonner l'utilisation du serveur MsSQL pour passer sur un serveur
gratuit MySQL, et de profiter de la migration de la base de données pour changer la
technologie du site vers PHP, et d'adapter la base à leur nouveau besoins. Cette
migration va entrainer la refonte complète du site Internet et l'adaptation du jeu à la
nouvelle base.
Analyse de la base données éxistante :
La base de données actuelle heberge deux jeux : TaRégionPACA et
RhonesSud; en une seule base unique. RhonesSud est un laboratoire de test pour la
future version multijoueur du jeu, en effet pour l'instant le jeu (TaRegionPACA) est
mono-utilisateur : les utilisateurs d'un même monde ne peuvent communiquer, ni se
voirent. Un des problèmes majeurs de cette base de données c'est qu'elle n'a pas
été prévu de manière ouverte et extensible, par concéquent au fur et à mesure des
modifications, ajout de tables dans la base, nous sommes arrivés à un modèle
desorganisé et ne répondant plus aux besoins de la société. (cf schéma de
l'ancienne base.) Du fait que la base stocke des informations différentes dans des
modèles structurels identiques, les informations deviennent de plus en plus
ingérables, par exemple : les utilisateurs de RhonesSud possèdent un champs
Activation qui est mis à null pour indiquer qu'il ne sont pas activés, alors que dans
TaRégionPACA le champs est à 0 pour indiquer le même état.
Il résulte une reconception complète de la base de donnée dans le but de
rendre plus claire son utilisation dans le jeu.
Analyse des possiblités de migration :
Avant de décider du serveur de base données de remplacement, il fallut
Karobas.fr 33 / 93
Année 2004 - 2005
analyser les besoins actuels du jeux, et la possibilté de connectivité du serveur
Virtools avec les bases de données disponibles. Dans un premier temps, nous avons
étudié si la base utilisait des fonctionnalités spécifiques telles que les déclencheurs
(triggers), procédures stockées, liaisons de tables, pour pouvoir adapter la nouvelle
base en fonction des possibilités de la base de destination. La base de données
n'utilise aucunes contraintes d'intégrité, ni procédures stockées, c'est ainsi que c'est
porté notre choix sur la base de données libre MySQL dans sa version stable 4.1.13.
La migration des données risque de poser quelques problèmes du fait de la
restructuration complète de la base de données. En effet certains champs ont été
renommés, déplacés, voir même supprimés du contexte de leur utilisation. Nous
avons choisi de mettre en place des contraintes d'intégrité (liaisons Clés étrangères),
de réfléchir sur les types de champs à insérer plus adéquat (type Date, Integer, ...)
pour assure une intégrité des données; ces mécanismes ont été mis en place par
l'utilisation du moteur InnoDB : moteur de base de données permettant les
transactions et les liaisons de tables. La mise en place de clès étrangères permet
dans un premier temps d'assurer la présence des informations, et d'eviter les
insertions orphelines (sans relations) lors de manipulation de suppression, ou de
mise à jour d'informations. Dans l'objectif de l'extension futur du jeu vers un jeu
mondial, il fallait gérer les différentes collections de caractères par conséquent les
informations sont stockées sont forme encodées en UTF-8 qui est transparent pour
l'utilisateur de la base de données, et tous ses clients instantanés. Ce codage de
caractères permet des stocker des caractères complexes comme ceux de la langue
japonaise, choinoises, ou russe.
Nous avons noté par ailleurs, un problème de compatibilité avec le serveur
d'application virtools dans le module de connection de base. En effet, après une série
de tests, le serveur virtools n'accepte pour l'instant qu'unseul type de codage UTF-8 :
le cadoge marqué, c'est à dire qu'un bit est resevé pour marqué le codage. Des
symptomes comme la suppression de caractères dont la position dans la chaine est
un multiple de 4 apparait lorsque l'on utilise des carctère hors table ASCII (par
exemple : P_Garçon.nmo -> P_Garçon.nm, la 8eme lettre est supprimée; si on ajoute
une lettre 'P_Garçon.nmoa' l'affichage est correcte). Dans un premier temps nous
avons pensé que cela venait du pilote MyODBC de connection à la base MySQL;
Karobas.fr 34 / 93
Année 2004 - 2005
mais après analyse approfondie nous en avons déduit que cela venait du controleur
ODBC de virtools server. Ce problème est reconnu dans la communauté de Virtools,
et sera fixé dans les prochaines versions.
Une contrainte technique vient s'ajouter à la migration technologique du serveur
dûe au passage vers PHP/MySQL. Le personnel de la société n'étant pas habitué à
manipuler ces logiciels, il fallut developper une méthode d'abstraction objet à la base
de données dans le but de rapprocher la programmation PHP à celle de Java.
Méthode d'abstraction à la base de données :
Lors de la conception de la base de données nous avons déduis qu'il avait un
ensemble d'objets à gérer, ils sont au nombre de 5 : des utilisateurs, des productions,
des quartiers, des missions, et des panneaux.
La conception du site utilise le modèle objet de PHP5 pour la programmation.
Nous avons conçu une méthode de programmation permettant de s'abstraire de la
base de données et de l'utiliser de manière transparente pour le programmeur. En
effet, l'accès à la base de données est générée par l'éxécution du script Python
permettant de transcrire une base de données MySQL relationnelle en un ensemble
de classe PHP écrite par l'exécution du script Python.
Fig 1. Fonctionnement général du script
Le script python est composé de deux parties : la première, un analyseur syntaxique permettant de créer une structure exploitable; et la seconde partie
Karobas.fr 35 / 93
Année 2004 - 2005
permmettant la génération des fichiers.
Pour le bon fonctionnement du script, il est nécessaire des respecter quelques règles
:
• Normalisation des nom des champs : tous les identifiants de la table et des
tables étrangères doivent commencer par 'ID_'; par exemple, la table
Utilisateur possède un identifiant ID_UTILISATEUR; Tous les noms des
champs sont stockés en majuscule pour eviter les confusions sur les logiciels
sensible à la casse de caractères.
• Les clès étrangères doivent posséder leur cardinalité sous forme '[x:y]' dans
l'espace de commentaire; cette étape permet de définir si l'objet référant doit
gérer une collection de références ou un alias unique.
• La detection de certains types : pour le type boolean, il est necessaire
d'ajouter une marque '[b]' dans le commentaire.
• Les clés étrangères doivent porter le même nom que l'identifiant de la table
qu'il représente, et doivent être positionné en fin de table. (Contrainte
optionnelle mais recommandée)
Chaque fichiers générés correspond à une table existante de la base de
données, étant donné que les relations entre classes découlent directement de la
base de données, les erreurs sont localisées directement dans la conception de
celle-ci (sauf erreurs eventuelles de script, dans ce cas toutes les classes sont
affectées). Nous allons étudier plus en détail le principe de fonctionnement du script
nommée sql2phpclass.
Dans un premier temps, nous avons construit et mis en place la base de
données sur un serveur MySQL, puis à l'aide d'un logiciel tiers ou la commande
mysqldump, nous avons créé un fichier script SQL correspondant à la base de
donnée récement developpée.
Karobas.fr 36 / 93
Année 2004 - 2005
Exemple de script SQL :
CREATE TABLE `continent` ( `ID_CONTINENT` int(10) NOT NULL auto_increment COMMENT 'Identifiant d''un continent', `NOM` varchar(50) default NULL COMMENT 'Nom du continent', `ID_SURVOL3D` int(10) default NULL COMMENT '[0:1] Un continent a un survol', `ID_PLANETE` int(10) NOT NULL default '0' COMMENT '[0:1] Un continent a un survol', PRIMARY KEY (`ID_CONTINENT`), KEY `FK_PLANETE` (`ID_PLANETE`), KEY `ID_SURVOL3D` (`ID_SURVOL3D`), CONSTRAINT `continent_ibfk_1` FOREIGN KEY (`ID_SURVOL3D`) REFERENCES `survol3d` (`ID_SURVOL3D`) ON DELETE NO ACTION, CONSTRAINT `FK_PLANETE` FOREIGN KEY (`ID_PLANETE`) REFERENCES `planete` (`ID_PLANETE`) ON DELETE NO ACTION) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Unité d''organisation des continents; InnoDB free: 6144 kB; ';
Ce script ordonne la création d'une table nommée 'continent', contenant 4
champs : 1 identifiant permettant de s'assurer de l'unicité complète de
l'enregistrement par le fait qu'il s'auto-augmente à chaque insertion, un champs clé
étrangère indiquant dans ce cas qu'un objet 'continent' possède / ou ne possède pas
un 'survol3d', enfin un autre champs clé étrangère indiquant qu'un objet 'continent'
appartient à un objet 'planete'.
Pendant l'execution du script, l'application crée un arbre syntaxique représenté
sous forme de tableau imbriqués :
fields [[NOM, TYPE, NULLIFY, DEFAULT_VALUE, COMMENT] ['ID_CONTINENT', 'int', 0, None, 'Identifiant d'un continent'],['NOM', 'string', 0, 'null', 'Nom du contient'],['ID_SURVOL3D', 'int', 0, 'null', '[0:1] Un continent a un survol'],['ID_PLANETE', 'int', 0, 0, '[1:1] Un continent appartient à une planète']
]
A cette étape, le script possède les informations nécessaires sur les champs de
la table.
Karobas.fr 37 / 93
Année 2004 - 2005
fk_feilds [[NOM, REF_TABLE, DEST_ID]['ID_SURVOL3D', 'SURVOL3D', 'ID_SURVOL3D'],['ID_PLANETE', 'PLANETE', 'ID_PLANETE']
]
Nous venons de créer une structure contenant les références aux tables
externes pour les inclusions de classes dans les fichiers générés.
Par la suite nous associons dans une structure de type dictionnaire (HashMap : clé
=> valeur), les informations traitées précédements.
Dict = [“PLANETE” : [fields, fk_feilds],“CONTINENT” : [fields, fk_feilds],....... (jusqu'à la fin du fichier script SQL)
]
Une fois la structure créée, il suffit de parcourir celle-ci pour générer les fichiers
classes associés. Les clés du dictionnaire correspondent aux noms des classes à
générer, ainsi chaque classes possèdent des propriétés, des accesseurs, des
méthodes générées en fonction de l'analyse du script.
Exemple d'une classe générée :
<?php/* Classe d'accès à la table Id_planete */require_once('planete.class.php');/* Classe d'accès à la table Id_survol3d */require_once('survol3d.class.php');/* Classe d'accès à la table Pays */require_once('pays.class.php');
class Continent{
/***************************************** * Propiétés *****************************************/
var $id_continent = -1;var $nom = '';var $id_survol3d = 0;var $id_planete = 0;var $survol3d = null;var $planete = null;
/***************************************** * Constructeurs
Karobas.fr 38 / 93
Année 2004 - 2005
*****************************************/
function Continent($id = -1){$this->id_continent = (int)$id;
}
/***************************************** * Accesseurs *****************************************/
function getId_continent() { return (int)$this->id_continent; }function getNom() { return (string)$this->nom; }function getId_survol3d() { return (int)$this->id_survol3d; }function getId_planete() { return (int)$this->id_planete; }
function getSurvol3d(){if($this->id_survol3d != 0) {
if($this->id_survol3d != null) { unset($this->survol3d); }$this->survol3d = new Survol3d((int)$this->id_survol3d);$this->survol3d->fill();return $this->survol3d;
}}
function getPlanete(){if($this->id_planete != 0) {
if($this->id_planete != null) { unset($this->planete); }$this->planete = new Planete((int)$this->id_planete);$this->planete->fill();return $this->planete;
}}
function getPaysCol($init = 0){$ret = array();
$sql = "SELECT ID_PAYS FROM pays WHERE ID_CONTINENT='".$this->id_continent."'";$res = DB_query($sql);while($row = DB_fetchArray($res)) {
$temp = new pays((int)$row[0]);$temp->setId_continent($this->id_continent);if($init != 0)
$temp->fill();$ret[] = $temp;
}if(count($ret) == 1)
return $temp;else
return $ret;}
function setNom($newvalue){if($newvalue != $this->nom && !empty($newvalue)){
$this->nom = (string)$newvalue;return (string)mysql_escape_string($newvalue);
} elsereturn (string)'';
}
function setId_survol3d($newvalue){if($newvalue != $this->id_survol3d && !empty($newvalue)){
$this->id_survol3d = (int)$newvalue;return (int)mysql_escape_string($newvalue);
} elsereturn (int)0;
Karobas.fr 39 / 93
Année 2004 - 2005
}
function setId_planete($newvalue){if($newvalue != $this->id_planete && !empty($newvalue)){
$this->id_planete = (int)$newvalue;return (int)mysql_escape_string($newvalue);
} elsereturn (int)0;
}
/***************************************** * Méthodes *****************************************/
/** * Ordonne le remplissage des propriétés de la classe en utilisant les champs de la
table ville3d * @param int L'identifiant de la table continent soit ID_CONTINENT. */function fill($autofill = 0){if($this->id_continent < 0)
return -1;$sql = "SELECT * FROM continent WHERE ID_CONTINENT='".(int)$this->id_continent."'";
$res = DB_query($sql);if(DB_numRows($res) == 1) {
$row = DB_fetchArray($res);
$this->nom = (string)$row['NOM'];$this->id_survol3d = (int)$row['ID_SURVOL3D'];$this->id_planete = (int)$row['ID_PLANETE'];
if($autofill != 0) {
/* Un continent a un survol */if($this->id_survol3d != 0) {
$this->survol3d = new Survol3d((int)$this->id_survol3d);$this->survol3d->fill($autofill);
}if($this->id_planete != 0) {
$this->planete = new Planete((int)$this->id_planete);$this->planete->fill($autofill);
}}
}}
/** * Ordonne la synchronisation des propriétés de la classe en cours d'utilisation avec la
base de données. */function commit(){if($this->id_continent == -1 || empty($this->id_continent)) {
$sql = "INSERT INTO continent VALUES ('' ,'".$this->nom."' ,'".$this->id_survol3d."' ,'".$this->id_planete."')";
} else$sql = "UPDATE continent SET NOM = '".$this->nom."', ID_SURVOL3D = '".$this-
>id_survol3d."', ID_PLANETE = '".$this->id_planete."' WHERE ID_CONTINENT = '" . $this->id_continent . "'";
$res = DB_query($sql);$this->id_continent= (int)DB_insertId($res);return $res;
}
/** * Ordonne la suppression de l'enregistrement de la base de données.*/function _delete() {
Karobas.fr 40 / 93
Année 2004 - 2005
$sql = "DELETE FROM continent WHERE ID_CONTINENT='".$this->id_continent."'";$res = DB_query($sql);return $res;
}
/***************************************** * Exportation *****************************************/
function toXML(){if($this->id_continent == -1 || empty($this->id_continent))
return null;
$xmlstr = "<continent id='".$this->id_continent."'>";$xmlstr .= "\t<nom>".$this->nom."</nom>\n";$xmlstr .= "\t<id_survol3d>".$this->id_survol3d."</id_survol3d>\n";$xmlstr .= "\t<id_planete>".$this->id_planete."</id_planete>\n";$xmlstr .= "</continent>";
return $xmlstr;}
}?>
Voici un exmple complet d'une classe générée par l'execution du script sur une
table nommée 'continent'. Chaque classes possèdent des méthodes communes
telles que :
• commit(), utilisée pour inserer les informations relatives à l'objet instancié
dans la base de données;
• update(), utilisée pour mettre à jour les informations de la base;
• toXML(), méthode de réalisation de l'arbre XML associé à la classe;
• _delete(), qui ordonne la suppression de l'enregistrement associé et de toutes
relations de la base.
Le script génère des méthodes en utilisant une nomenclature bien précise :
• get + Nom du champs : Accesseur en lecture du champs selectionné, retourne
la valeur transtypé dans son de base ( int, string, ... );
• set + Nom du champs : Accesseur en écriture du champs selectionné, chaque
valeur est verifiée avant insertion plus éviter les failles de sécurité comme les
sql injections;
• get + Obj relatif + Col : Retourne la collection d'objet étrangers à l'objet en
cours;
• create + Objet relatif : Permet la création d'un objet relatif à la classe en cours;
Concernant la protection des données de la base de données, vous pouvez voir
dans les accesseurs que tous les types sont forcés et protégés par l'execution
Karobas.fr 41 / 93
Année 2004 - 2005
intermédiaire de la méthode PHP mysql_escape_string($string), utiliser pour protéger
les caractères d'echappement, les quotes qui peuvent aparraitre dans le contenu des
variables.
Chaque variable est transtypé vers son vrai type afin d'eviter qu'une chaine de
caratère soit affectée à un entier, ce qui est possible en php car tous les types sont
dynamique mais peut représenter une menace pour la base de données.
Exemple :
<?php$var1 = 1;$var2 = (int)”salut”;
print $var1;// 1print $var2;// 0
?>
Avec cette méthode le programmeur n'a plus besoin de se soucier des accès à
la base de données, car il a accès à des objets permmettant de s'abstraire de la base
de données tout en exerçant des controles de types avant insertion.
Maintenant nous allons traiter de l'utilisation de ce système mis en place pour la
migration du site vers PHP. La méthode d'abstraction à la base de données permet
lors de modifications sur la base, d'appliquer les modifications sur le site sans être
obligé de se replonger dans le code de la page.
Utilisation de la méthode d'abstraction et concepts du site
Comme il a été expliqué précédement le programmeur à accès à toutes les
informations contenues dans les tables matérialisées par des objets. L'idée général
du site est d'avoir un objet persistent en session qui est en fait les informations de
l'utilisateur. Cet objet est instancié à la première connection de l'utilisateur puis l'objet
reste en session HTTP par le biais de la méthode de sérialisation d'objet ainsi il reste
accessible pendant la validité de la session HTTP, ce qui économise les accès à la
Karobas.fr 42 / 93
Année 2004 - 2005
base de données.
Après mesure effectuée entre l'ancien site et le nouveau, nous avons diminué
les accès redondant à la base, tout en augmentant le stockage d'informations dans la
base de données.
Le principe d'utilisation est simple, lorsque le programmeur necessite l'accès à un
objet (en général UTILISATEUR), il suffit de désérialiser l'objet stocké dans la
session. Ainsi le programmeur retrouve tout accès aux données associées à
l'utilisateur.
L'initialisation de l'utilisateur au niveau de la base de données s'effectue biensur
pendant l'inscription de celui-ci, par contre l'initialisation de l'objet s'effectue lors de
l'authentification de l'utilisateur, pour instancier puis stocker dans la session HTTP.
Concernant la synchronisation de l'objet et de la base de données, elle n'est
pas automatique; cependant il existe une méthode commit() dérivée du modèle
transactionnel permettant l'insertion et update() pour la mise à jour de la base de
données lors de son appel. Cette insertion/modification n'est pas faite
automatiquement pour eviter d'accéder à la base de données de trop nombreuses
fois : cela génère une requète d'insertion / modification incluant toutes les
informations.
Exemple : page d'authentification
<?php $pseudo = (string)$_POST['prenom']; $passwd = (string)$_POST['motdepasse']; $error = 0; if(!empty($_POST['play_x'])) { if(empty($pseudo) or empty($passwd)) { $error = 1; } else { $sql = "SELECT ID_Utilisateur, Activation FROM Utilisateur WHERE Pseudo='".strtolower($pseudo)."' AND Mdpc='".md5($passwd)."'"; $res = DB_query($sql); if(DB_numRows($res) == 1 ) { $row = DB_fetchArray($res); if ($row['Activation'] == 0) { $error = 2;
Karobas.fr 43 / 93
Année 2004 - 2005
} else { // Initialisation de l'objet avec l'identifiant trouvé
$temp_user = new Utilisateur((int)$row['ID_Utilisateur']); if($temp_user != null)
// Si l'initialisation s'est correctement effectuée { $_SESSION['CLEAN'] = 1; // Marquage de la session
// On rempli l'objet Utilisateur $temp_user->fill();
// Sérialisation pour stockage dans la variable de session $_SESSION['OBJUTIL'] = serialize($temp_user); refresh('?p=30'); } else {
// Affichage de l'erreur 3 : Initialisation échouée $error = 3; } } } else {
// Affichage de l'erreur 2 : Aucun utilisateur trouvé $error = 1; } } }?>
Dans ce script, nous effectuons une recherche correspondant au couple
[Pseudo, Mot de passe crypté]; cette selection retourne l'identifiant unique de
l'utilisateur, par la suite nous instancions un objet PHP Utilisateur avec l'identifiant
précédement selectioné. C'est une des seules pages qui ont été codées 'à la main'.
Les accès suivants sont fait par désérialisation de l'objet Utilisateur stocké en
session HTTP, puis utilisation des accesseurs en lecture / écriture. La
synchronisation ne s'effectue qu'en cas de modification de l'objet. Si l'objet n'est pas
modifié alors il n'y a pas de requète qui sont executées pour économiser les accès
BD.
Exemple de code :
<?// On déserialize l'objet de session$obj = unserialize($_SESSION['OBJUTIL']);
// Affichage des champsprint $obj->getNom();
Karobas.fr 44 / 93
Année 2004 - 2005
print $obj->getPrenom();
// Modifications et insertion dans la base$obj->setNom('Thibault');$obj->commit()
?>
Chaque accesseurs en écriture effectue un contrôle sur la validité de
l'informations en temps que type, c'est à dire : si le champs de la base attend un
entier et que l'utilisateur entre une chaine de caractères, la valeur entrée sera
transtypé puis traitée de manière à eviter les attaque par injection SQL.
Ainsi nous obtenons une abstraction à la base de données complète, puisque
l'utilisateur n'utilise plus de requêtes SQL de lui même dans le but d'obtenir / traiter
des informations. L'accès en devient simplifié, propre, et sécurisé. Cet ensemble de
classe est généré par le biais d'un script developpé en Python que nous allons
developper maintenant dans la prochaine partie.
Fonctionnalités du script
Le script permet de générer des classes d'abstractions à la base mais aussi du
fait de la complète représentation de la partie de gestion codée en php et de la base
de données; le script genère la partie de gestion de la base de données appelée
partie administration. La procédure permettant de créer la partie d'administration
s'appuie sur le script de création de la base de données. En fonction de cela, nous
obtenons une toile de pages contenant des pages types telles que la modification, la
création et recherche d'informations sur les entités selectionées.
Karobas.fr 45 / 93
Année 2004 - 2005
Options proposées par le script :
?> python sql2phpclass.py -hUsage :
sql2phpclass [-vh] table1, table2, ... -v, --verbose Mode verbeux. -h, --help Affiche ce message. -a, --build-adminpages Creer les pages d'administrations de la base. -c, --build-classfiles Creer les classes de la base. -m, --build-menu Creer le menu de la partie administration. -d, --database Nom de la base (si pas dans le script SQL). table1,table2, ... Liste des tables a traiter (par defaut toutes).
Exemple d'utilisation standart :
# Exporte la base de données dans un script SQL?> mysqldump -u root -p -B kidfrance -e > Creates.sqlpassword :# Execute le script pour générer les classes et les pages d'administration?> python sql2phpclass.py -acvm -d kidfrance ---------------------------------------------------------------------------Creation de la structure de traitement....-------------------------------------------------------------------------------------------------------------------Traitement de la table : affectation_affichage----------------------------------------['ID_AFFECTATION_AFFICHAGE', 'int', 'null', None, "Identifiant de gestion de l''objet affichage"]['ID_AFFICHAGE', 'int', 'null', None, "Identifiant d''un affichage"]['ID_UTILISATEUR', 'int', 'null', None, "Identifiant d''un utilisateur pouvant modifier l''affichage)"]----------------------------------------Traitement de la table : affectation_production----------------------------------------['ID_AFFECTATION_PRODUCTION', 'int', 'null', None, 'Identifiant de gestion des productions']['ID_PRODUCTION', 'int', 'null', None, 'Identifiant de la production']
Karobas.fr 46 / 93
Année 2004 - 2005
['ID_UTILISATEUR', 'int', 'null', None, "Identifiant de l''utilisateur (animateur)"]----------------------------------------Traitement de la table : affichage----------------------------------------['ID_AFFICHAGE', 'int', 'null', None, "Identifiant de l''affichage"]['IMAGE', 'string', 'null', None, 'Image ? afficher dans le cadrant']
...
---------------------------------------------------------------------------Generation des fichiers ....---------------------------------------------------------------------------Creation de la classe departementAdministration de la table departementCreation de la liste d'administration admin_departement_listCreation de la page de modification admin_departement_modifCreation de la page de modification admin_departement_createCreation de la page de recherche admin_departement_searchCreation de la classe planeteAdministration de la table planeteCreation de la liste d'administration admin_planete_listCreation de la page de modification admin_planete_modifCreation de la page de modification admin_planete_createCreation de la page de recherche admin_planete_search
...
?>
Nous venons de créer la structure d'abstraction à la base de données, et la
partie d'administration de la partie BD.
L'utilisation du script de génération a mis en évidence quelques problèmes
entre la partie générée par le script et la partie statique éditée par le developpeur;
dans le cas de modification de signature de table, qui influence par conséquent la
signature des fonctions générées par le script, ce qui abouti à un disfonctionnement
dans la partie non générée. Pour cela, nous avons mis en place une fonctionnalité
inexistante en PHP, qui consiste à passer des paramètres nommés, très courrament
Karobas.fr 47 / 93
Année 2004 - 2005
utilisé en environnement Python. Cela consiste à passer un nom de paramètre et sa
valeur, au lieu de mettre les valeurs au bonne endroit d'un appel de fonction.
Exemple :
<?phpfunction foo($a, $b, $c = 1) {return $a + $b + $c;
}?>
Pour faire appel à cette fonction, il suffit de passer les paramètres aux bons
endroits, c'est à dire : foo(1,2). Le troisième paramètre est un paramètre optionnel, il
a pour valeur par défaut 1 mais si on veut lui affecter une autre valeur il suffit
d'appeler la fonction de cette manière : foo(1,2,3). La fonctionnalité de paramètres
optionnels oblige de les mettre en fin de signature, ce qui implique aussi de remplir
tous les paramètres pour avoir un appel qui fonctionne.
Python:def foo(a,b,c=1):return a + b + c
Avec python, nous pouvons appeler cette fonction en utilisant le format standart
d'appel:
foo(1,2,3); mais aussi en utilisant les paramètres nommés : foo(a=2, b=3, c=40). Par
conséquent en imaginant une fonction contenant que des paramètres optionnels, il
est possible de modifier un paramètre sans pour autant être obligé de suivre la
signature.
Exemple:def foo(a=1, b=2, c=3):
return a + b + c
print foo()> 6
print foo(4, 5, 1)a = 4b = 5c = 1> 10
Karobas.fr 48 / 93
Année 2004 - 2005
print foo(b=8)a = 1b = 8c = 1> 10
Voila tout l'intêret de ce type de signature, elle peut s'adapter dynamiquement
aux paramètres de l'utilisateur.
Por pouvoir faire fonctionner cela en PHP, il suffit de remplir un dictionnaire de
clé=>valeur soit paramètre => valeur, puis de passer le tableau à la fonction.
<?phpfunction createStats(){
/*Tableau contenant les valeurs par défaut */$params = array(
'date_heure' => null,'id_type_event' => 1,'data' => null,'id_quartier3d' => null,'id_affichage' => null,'id_survol3d' => null,
);
/* récupération du tableau de paramètres */$gparams = func_get_args();if(!is_array($gparams[0])) {
print "Le paramètre doit être un tableau";return -1;
}
/* Parcours du tableau pour modification des valeurs par défaut */foreach($gparams[0] as $key => $value)
$params[$key] = $value;
/* Appel de la vrai fonction de création */return $this->real_createStats($params['date_heure'],
$params['id_type_event'],$params['data'],
$params['id_quartier3d'],$params['id_affichage'],$params['id_survol3d'] );
}
function real_createStats( &$s_date_heure, &$s_id_type_event, &$s_data, &$s_id_quartier3d, &$s_id_affichage, &$s_id_survol3d )
{$obj = new Stats();$obj->setId_utilisateur((int)$this->id_utilisateur);$obj->setDate_heure($s_date_heure);$obj->setId_type_event($s_id_type_event);$obj->setData($s_data);$obj->setId_quartier3d($s_id_quartier3d);
Karobas.fr 49 / 93
Année 2004 - 2005
$obj->setId_affichage($s_id_affichage);$obj->setId_survol3d($s_id_survol3d);$obj->commit();
return $obj;}
Exemple d'utilisation:
<?php$user->createStats(array(
'date_heure' => Date('Y-m-d H:i:s'), 'id_type_event' => 3)
); // Login?>
Pour utiliser le script Sql2phpclass convenablement, il est necessaire d'ajouter
quelques informations supplémentaires dans les espaces commentaires de la base
de données. Ils existe deux type de commentaires : ceux de la table, et ceux des
champs. Chacuns d'eux ont une influence lors de la génération des classes PHP.
➔ Commentaires de table:
Permettent de définir des options sur la table telles que son type :
Administrable [A], Liste [L];
[A] : Permet de rendre l'objet administrable ce qui a pour effet de créer la
section administration associée à la table. Ces objets seront des noeuds
racines du menu de l'administration.
[L] : Défini la table comme étant une liste d'identifiant / description; cela
pour effet d'ordonner la génération des composants liste déroulante HTML
associée stocké dans le fichier nommé 'lib-combo.php'
• Génère les méthodes combo + Objet Liste, pour l'affichage sous forme
de liste déroulante,
• getID_ + Objet Liste + Str qui retourne la description d'un champs
possédant l'ID passé en paramètre.
➔ Commentaires de champs:
Permettent de définir la nature du champs, ou ses propriétés telles que [b]
pour une case à cocher, [d] pour un controle date, [l] pour une liste deroulante
de la table associée, [r] pour un champs en read-only, [s] pour un nom de ville.
Karobas.fr 50 / 93
Année 2004 - 2005
[b] : Associé au type booléen. Affiche une case à cocher.
[d] : Associé à une date. Affiche le controle Date.
[l] : Associé à une liste déroulante de la table étrangère.
[r] : Définit le champs comme en lecture-seule.
[s] : Spécifique à un id de ville.
Le script utilise un système dynamique d'intégration de code dans les classes
pour ajouter des lignes qui ne peuvent être générées. Les fichiers patch doivent être
mis dans le repértoire 'custom_code' avec pour nomenclature le nom de la classe à
générer suivi de '.custom'.
Exemple de fichier :utilisateur.custom
<?php#!-- properties -->#/-- properties -->
#!-- constructors -->#/-- constructors -->
#!-- read_accessors --> function isAdmin() { return (bool)($this->id_type_statut == 1); } function isCitoyen() { return (bool)($this->id_type_statut == 2); } function isAnimateur() { return (bool)($this->id_type_statut == 3); }
#/-- read_accessors -->
#!-- write_accessors --> function setAdmin() { $this->id_type_statut = 1; } function setCitoyen() { $this->id_type_statut = 2; } function setAnimateur() { $this->id_type_statut = 3; }
#/-- write_accessors -->
#!-- methods -->#/-- methods -->
?>
Ce fichier est composé de 5 sections de code, qui correspondent aux
emplacements d'une classe tels que les propriétés, les constructeurs, les
accesseurs, et les méthodes. Ce fichier est analysé à l'étape de création du fichier
classe PHP, et les éléments à ajouter sont inscrits dans la classe aux endroits
indiqués après le code généré. Ce qui ajoute une flexibilité au code généré.
Nous avons mis en place un système de nettoyage d'informations et de
Karobas.fr 51 / 93
Année 2004 - 2005
sauvegarde, dans l'objectif de gardé une base saine.
Systême de traitements automatisés
Dans le but de maintenir en état la base de données, nous avons mis en place
des scripts de maintenance de base : défragmentation, sauvegarde journalière,
suppression des informations obsolètes. Ces tâches sont executées par le
gestionnaire de tâches du serveur d'applications (hébergé chez completel). Ils sont
au nombres de trois :
➔ DB_flush.py : un script python executant une sauvegarde de la base puis
supprime les utilisateurs et toutes entités associées, lorsque l'utilisateur est
marqué pour suppression. Ce script est executé tous les 1 du mois.
➔ DB_backup.bat : un autre script permettant la sauvegarde journalière
SET DATE=FOR /F "delims=/ tokens=1,2" %%i IN ('DATE /T') DO (SET DATE=%%i-%%j.sql.7z)mysqldump --default-character-set=latin1 -u root --password=55KS3E35 -e -B kidfrance | 7za a -si -m0=PPMd DB_SAVE_daily\kidfrance_%DATE%SET DATE=
➔ DB_backup_month.bat : un script de sauvegarde mensuelle, il supprime les sauvegardes journalières.
SET DATE=del /F /Q DB_SAVE_daily\*.*FOR /F "delims=/ tokens=2,3" %%i IN ('DATE /T') DO (SET DATE=%%i-%%j.sql.7z)mysqldump --default-character-set=latin1 -u root --password=XXXXXXXX -e -B kidfrance | 7za a -si -m0=PPMd DB_SAVE_monthly\kidfrance_%DATE%
SET DATE=
➔ site_save.bat : Sauvegarde tous les vendredi le portail d'inscription au
Karobas.fr 52 / 93
Année 2004 - 2005
format 7z et l'upload sur le Ftp de la société.
del karobas_net.7z.oldren karobas_net.7z karobas_net.7z.oldmysqldump --default-character-set=latin1 -u root --password=XXXXXXXX -e -B kidfrance > karobas_net.sql7za a -t7z karobas_net.7z ..\htdocs_karobas_net ./karobas_net.sql -m0=PPMddel karobas_net.sqlftp -s:ftp_script.ftp ftp.karobas.fr
Cet ensemble de scripts executés périodiquement veillent à la sauvegarde et le
maintient de la base de données représentant pour la société le coeur de leur jeu.
Nous venons de discuter de la conception de la base de données, de sa maintenance quotidienne, et des méthodes mis en place pour le developpemement et l'adaptation de la nouvelle architecture au site mais aussi au jeu. Reste encore quelques parties du site qui sont encore au stade minimal (travaux sur la mis en forme, esthétique, ....), qui doivent être developpée. Cela est dûe au temps restant et à la charge de travail croissante. Nous avons essayé d'apporter à l'entreprise un savoir nouveau, de nouvelles compétences, de nouvelles méthodes de travail avec l'apport de système informatique (Firewall Linux), de techniques nouvelles de programmation (Php & MySQL), mais surtout de répondre aux besoins de l'entreprise. Aujourd'hui, la société possède une base nouvelle répondant à leurs attentes actuelles, mais aussi futures dans la conception d'une base sensiblement évolutive et ouverte aux modifications, sans pour autant affecter directement les logiciels dépendants.
Karobas.fr 53 / 93
Année 2004 - 2005
Conclusion
Cette période passée dans l’entreprise Karobas, nous a permis de progresser
dans différents domaines :
- La mise en pratique des cours :
Il est valorisant de pouvoir utiliser les connaissances acquises au cours de cette
année d’étude pour aboutir à la mise en place d’outils pour faciliter et aider les
personnes à travailler d’en de bonnes conditions.
- Le niveau relationnel :
L’entreprise n’ayant pas d’administrateur nous dictant l’ensemble des tâches à
effectuer, les principaux travaux ont dût subir l’aval de l’entreprise. D’où une
présentation des avantages et des inconvénients de chacun en fonction des souhaits
et des possibilités de la société.
- Le travail en groupe et la prise de décision :
L’étude des différentes possibilités lors de la mise en place d’un produit, nous a
donné la possibilité de réfléchir et de s’aider profitant de l’expérience de chacun.
- L’ensemble des outils mis en place :
Partant d’un réseau peu développé, notre stage nous a permis d’implémenter et de
concevoir un nouveau réseau mieux adapté à l’entreprise. Ainsi, le passage en
domaine, la protection au niveau part-feux et formation du personnel au niveau des
risques, la mise en place du système de messagerie, l’énorme travail au niveau
conception et développement de la base de données et du site, permet d’obtenir un
stage diversifié.
Karobas.fr 54 / 93
Année 2004 - 2005
Annexes
Karobas.fr 55 / 93
Année 2004 - 2005
Index des annexes :
A.Sécurité de l'entreprise I. Schéma du réseau
B.Schéma relationnel de la nouvelle base de données I. Vue d'ensemble II. Zone 1 : Espace géographique III.Zone 2 : Utilisateur IV.Zone 4 : Objet 3D V. Zone 5 : Missions VI.Zone 6 : Quartiers modélisés VII.Zone 7 : Avatar
Karobas.fr 56 / 93
Année 2004 - 2005
Réseau de l'entreprise
Karobas.fr 57 / 93
Année 2004 - 2005
Karobas.fr 58 / 93
Année 2004 - 2005
Schéma conceptuel de la nouvelle base de données
Karobas.fr 59 / 93
Année 2004 - 2005
Karobas.fr 60 / 93
Année 2004 - 2005
Karobas.fr 61 / 93
Année 2004 - 2005
Karobas.fr 62 / 93
Année 2004 - 2005
Karobas.fr 63 / 93
Année 2004 - 2005
Karobas.fr 64 / 93
Année 2004 - 2005
Karobas.fr 65 / 93
Année 2004 - 2005
Karobas.fr 66 / 93
Année 2004 - 2005
Cahier des chargesObjectifs du developpement
(documents fournis par l'entreprise)
Karobas.fr 67 / 93
Année 2004 - 2005
Proposition d’architecture globale du Monde de Karobas
Lien par survol interplanétaire (plus tard…)
Lien par survol planète (interface spécifique : globe rotatif)
Interface spécifique SURVOL : prévoir boutons de changement d’altitude optionnel selon la présence ou non de niveaux intermédaires.
Lien par survol continental (ex. Europe)
Lien par survol national
Lien par survol régional
Lien par survol « moyenne altitude » : département
Lien par survol « basse altitude » : ville ou agglo
Interface « normale » avec avatar :
Lien par clic-pancarte, bus, etc.
Lien simple par « proximité »
Mondes avec salle(s) intérieure(s) Mondes simples
Karobas.fr 68 / 93
Année 2004 - 2005
Description des types d’ « objets dynamiques » du jeu
Panneau d’exposition- visible / invisible- vignette taille :- titre en roll-over (variables possibles :
langue, âge)- en cliquant (variables possibles : langue, âge) => production « format Karobas »,
fenêtre web, vidéo ?- administrateurs : Karobas + répartition pour chaque panneau
Panneau affichage texte- toujours visible- 1 affichage = 6 lignes de textes + 1 adresse URL- n affichages possibles (variables possibles : langue, âge)- administrateurs : Karobas + répartition pour chaque
affichage
Panneau abribus- toujours visible- 1 affichage = photo + 1 adresse URL- n affichages possibles (variables possibles : langue, âge)- administrateurs : Karobas + répartition pour chaque
affichage
Panneau pub 4x3- toujours visible- 1 affichage = photo + 1 adresse URL- n affichages possibles (variables possibles : langue, âge)- administrateurs : Karobas + répartition pour chaque
affichage
Panneau TV possible ?
Objet dynamique- visible / invisible- fixe ou mobile (parcours dynamique ?)- personnage (avatar), animal, véhicule ou objet quelconque - en cliquant (variables possibles : langue, âge) => bulle de texte, dialogue, son,
production « format Karobas », fenêtre web, vidéo ?- administrateurs : Karobas + répartition pour chaque objet
Karobas.fr 69 / 93
Année 2004 - 2005
Actions possibles à effectuer par un administrateur :
Administrateur KAROBAS
- Base de photos en gestion multicritères (prévoir achat base dédiée ?)
- Base de productions : affichage de type liste multicritère (nom production, origine, type, langue, âge, mots-clés, dates de mise à jours, compteurs, nom administrateur). En option ; affichage de la vignette générée automatiquement + modification interactive de la vignette (imposer la taille en pixels) et des autres critère (surtout le nom qui s’affiche en roll-over). Les dates de mise à jour et nom administrateur sont remplis automatiquement après validation ainsi que les compteurs dans le jeu.
- Base d’objets dynamiques : affichage de type liste multicritère (nom monde élémentaire, type d’objet, emplacement dans le monde, mots-clés, dates de mise à jour, compteurs, nom administrateur). Sur un objet sélectionné, afficher les n affichages en cours (dont « vide ») avec textes défilant ou vignette, liens web ou prod et critères « âge » et « langue » + date de mise en ligne + nom administrateur ayant fait la dernière mise à jour + historique des mises à jour. ? Base de vignettes des différents types (exposition, abribus, etc.) ou accès à une arborescence de dossiers images ? Changement dynamique de tous ces éléments sauf vignettes (à faire sous Photoshop ou équivalent avec des tailles imposées par type). Prévoir aussi 2 compteurs de consultation dans le jeu (mensuel et cumulé) pour chaque affichage de chaque objet dynamique (on doit pouvoir sortir des statistiques du type : nb de consultations sur tel mois ou depuis le début de l’affichage selon langue et par âge…). Ces compteurs sont remis à 0 à chaque modification du lien ou par volonté de l’administrateur.
- Base de missions : affichage de type liste multicritère + vignettes en option (nom mission, type, origine, âge, langue, code postal, mots-clés, dates de mise à jour, compteurs, liste d’objets dynamiques concernés). Sur une mission sélectionnée, afficher le contenu + tous les critères. Pour une nouvelle mission : choisir type + saisie de tous les éléments.
- Base d’expositions temporaires : affichage de type liste multicritère + vignettes en option (nom exposition, origine, âge, langue, mots-clés, dates de début et de fin, liste d’objets dynamiques concernés, compteurs avec historique détaillé). Sur une exposition sélectionnée, afficher le contenu + tous les critères. Pour une nouvelle exposition : choisir les objets dynamiques concernés dans liste multicritère + saisie date de fin + détails de l’historique.
- Base de campagnes d’affichage : affichage de type liste multicritère + textes ou vignette concernée (nom campagne, origine, âge, langue, mots-clés, dates de début et de fin, liste d’objets dynamiques concernés, compteurs avec historique détaillé). Sur une campagne sélectionnée, afficher le contenu + tous les critères. Pour une nouvelle campagne : choisir les objets dynamiques concernés dans liste multicritère + saisie date de fin + détails de l’historique.
Karobas.fr 70 / 93
Année 2004 - 2005
Administrateurs délégués (accès par mot de passe) :
Idem mais avec autorisations choisies dans une liste multicritère (par exemple, la Ville de Cannes pourrait modifier en direct 2 ou 3 affichages textes + 1 pub abribus + 2 pubs 4x3 + 8 panneaux d’exposition, etc. en donnant les possibilités d’agir sur tel ou tel monde appartenant à Cannes et sur l’un ou l’autre des critères liés à l’âge et à la langue (code postal possible uniquement pour les missions).
Karobas.fr 71 / 93
Année 2004 - 2005
Script SQL2Phpclass(disponible à l'adresse : http://thibault.normand.free.fr/projects/)
Karobas.fr 72 / 93
Année 2004 - 2005
#! /usr/bin/python# -*- coding: utf_8 -*-
import os, sys, time, getoptfrom xml.dom.minidom import *
''' Utilitaire de création de classe PHP à partir de scripts SQL, contenant les directives CREATE TABLE.'''
class sql2php: file = None buffer = '' classname = '' inclass = 0 fk_key = {} feilds = [] fk_table = [] def __init__(self): self.file = None self.sqlfile = open('Creates.sql', 'r') self.build_admin = False self.build_class = False self.build_menu = False self.build_stats = False self.verbose = False self.database_name = "" if not os.path.exists('./include'): os.mkdir('include') if not os.path.exists('./pages'): os.mkdir('pages') if not os.path.exists('./custom_code'): os.mkdir('custom_code') self.tables = {} self.rel = {} def __del__(self): self.sqlfile.close() def run(self): try: opts, args = getopt.getopt(sys.argv[1:], "ho:acvmsd:", ["help", "output=", "build-adminpages", "build-classfiles", "build-menu", "build-stats", "database-name"]) except getopt.GetoptError: usage() sys.exit(2) for o, a in opts: if o == "-v": self.verbose = True if o in ("-h", "--help"): usage() sys.exit() if o in ("-o", "--output"): pass if o in ("-a", "--build-adminpages"): self.build_admin = True if o in ("-c", "--build-classfiles"): self.build_class = True if o in ("-m", "--build-menu"): self.build_menu = True if o in ("-s", "--build-stats"): self.build_stats = True if o in ("-d", "--database-name"):
Karobas.fr 73 / 93
Année 2004 - 2005
self.database_name = a if not os.path.exists('./include/DB_' + self.database_name): os.chdir('include') os.mkdir('DB_' + self.database_name) os.chdir('..') print '-'*80 print 'Creation de la structure de traitement....\n' print '-'*80 self.buffer = self.sqlfile.readline() while self.buffer != '': self.processBuffer(self.buffer) self.buffer = self.sqlfile.readline()
print '\n' print '-'*80 print 'Generation des fichiers ....\n' print '-'*80
for name, class_ in self.tables.items(): self.in_custom_properties = 0 self.custom_properties_code = "" self.in_custom_constructors = 0 self.custom_constructors_code = "" self.in_custom_read_accessors = 0 self.custom_read_accessors_code = "" self.in_custom_write_accessors = 0 self.custom_write_accessors_code = "" self.in_custom_methods = 0 self.custom_methods_code = "" if os.path.exists('./custom_code/' + name.lower() + '.custom'): self.parseCustomCode(name) if self.build_class: print 'Creation de la classe ' + name ''' Crꢴion des classes de gestion de la base de donn꦳ ''' self.buildClassFile(name, class_[0], class_[1], class_[1], class_[4]) if self.build_admin: print "Administration de la table " + name if self.verbose: print 'Creation de la liste d\'administration admin_' + name + '_list' ''' Crꢴion des pages de listings ''' self.buildAdminListPages(name, class_[0], class_[1], class_[1], class_[4]) if self.verbose: print 'Creation de la page de modification admin_' + name + '_modif' ''' Crꢴion des pages de modification des objets ''' self.buildAdminModifPages(name, class_[0], class_[1], class_[1], class_[4]) if self.verbose: print 'Creation de la page de modification admin_' + name + '_create' ''' Crꢴion des pages d'enregistrement ''' self.buildAdminCreatePages(name, class_[0], class_[1], class_[1], class_[4]) if self.verbose: print 'Creation de la page de recherche admin_' + name + '_search' self.buildAdminSearchPages(name, class_[0], class_[1], class_[1], class_[4]) if self.build_menu: print "Creation du menu" self.buildAdminMenuPage() if self.build_stats: self.buildStatsPage() self.buildComboTypes()
Karobas.fr 74 / 93
Année 2004 - 2005
''' ------------------------------------------------------------------------------------------------------------------- ''' ''' Crꢴion de la structure de traitement / Parsing ''' ''' ------------------------------------------------------------------------------------------------------------------- ''' def processBuffer(self, str): if str.startswith(")"): self.inclass = 0 comm_pos = str.strip().find('COMMENT=') comment_table = str.strip()[comm_pos + 8:].strip('\'') obj_opt = 0 if comment_table.lower().startswith('[a]'): obj_opt = 1 if comment_table.lower().startswith('[l]'): obj_opt = 2 self.tables[self.classname] = [self.feilds, self.fk_key, self.fk_table, obj_opt, self.pk] self.feilds = [] self.fk_key = {} self.fk_table = [] if str.startswith("USE"): if self.database_name == "": self.database_name = str[4:] return if str.startswith("CREATE TABLE"): ''' Extraction du nom de la classe ''' str = str.split(' ',2) self.classname = str[2][1:-4] print "-"*40 print "Traitement de la table : " + self.classname print "-"*40 self.inclass = 1 return
if self.inclass == 1: ''' Extraction des champs ''' if str.strip().startswith("PRIMARY KEY"): temp = str.strip().split(' ', 2)[2][2:-2] self.pk = temp.replace('`', '').split(",") return elif str.strip().startswith("KEY"): temp = str.strip().split(' ', 2)[2][2:-2] self.idx = temp.replace('`', '').split(",") return elif str.strip().startswith("CONSTRAINT"): temp = str.strip().split(' ', 8) self.fk_key[temp[4].strip()[2:-2]] = [temp[6][1:-1], temp[7].strip(",")[2:-2]] self.fk_table.append(temp[6][1:-1]) if not self.rel.has_key(temp[6][1:-1]): self.rel[temp[6][1:-1]] = [] self.rel[temp[6][1:-1]].append([self.feilds[0][0], self.classname, temp[7].strip(",")[2:-2]]) return elif str.strip().startswith('UNIQUE'): return try: name, type, opts = str.strip().split(' ',2)
Karobas.fr 75 / 93
Année 2004 - 2005
except: name, type = str.strip().split(' ',1) opts = "" default = '0' popts = self.parseOptions(opts) if popts[0] == 0: if popts[1] is not None: default = popts[1].strip().strip(',').strip('\'') else: default = 'null' else: default = '0'
#~ try: typ, nullify = self.getValidphpTypes(type, popts[0], default) #~ except: #~ print "Erreur d'extraction d\'information : type = '"+ typ +"' nullify = '" + nullify +"'" #~ sys.exit(1) #~ if name[1:-1].startswith('ID_'): #~ ''' Si le champs est un identifiant, on fixe la valeur par dꧡut ''' #~ nullify = 1 field = [name[1:-1], typ, nullify, popts[3], popts[2]] if self.verbose: print field self.feilds.append(field) def parseOptions(self, options): nullify = 1 val = None comment = None card = None if options.startswith("NOT NULL"): nullify = 0 options = options[8:].strip() if options.startswith("default"): defo = options.split(" ") val = defo[1].strip('\'') options = options[9 + len(val):].strip('\'').strip() if val.lower() == 'null': nullify = 0 val = "null" if options.startswith("auto_increment"): options = options[15:] if options.startswith('COMMENT'): options = options[7:] comment = options.strip().strip(",")[1:-1] if comment.startswith('['): card = comment.split(" ",1)[0] comment = comment.split(" ",1)[1] return [nullify, val, comment, card] def getValidphpTypes(self, typ, Null, default = ''): typ = typ.lower() if typ.startswith('longtext') or typ.startswith('text'): return "string", not Null and "" or 'null' if typ.startswith('varchar') or typ.startswith('date') or typ.startswith('enum') or typ.startswith('char'): if default != 'null': default = "\'" + default + "\'" return "string", not Null and default or 'null' if typ.startswith('int') or typ.startswith('bigint') or typ.startswith('tinyint'): return "int", not Null and default or '0' if typ.startswith('double'):
Karobas.fr 76 / 93
Année 2004 - 2005
return "double", not Null and default or 'null' if typ.startswith('float'): return "float", not Null and default or '0.0' ''' ------------------------------------------------------------------------------------------------------------------- ''' ''' Traitement des fichiers custom ''' ''' ------------------------------------------------------------------------------------------------------------------- ''' def parseCustomCode(self, name): try: custom_file = open('./custom_code/' + name.lower() + '.custom' , 'r') except: print 'Erreur à l\'ouverture du fichier CusTOM : ' + name.lower() + '.custom' return -1 buffer = custom_file.readline() while buffer != '': self.processCustom(buffer) buffer = custom_file.readline() def processCustom(self, buffer): if buffer.startswith('#!-- properties -->'): self.in_custom_properties = 1 return if buffer.strip().startswith('#/-- properties -->'): self.in_custom_properties = 0 return if self.in_custom_properties: if buffer != "": self.custom_properties_code += buffer return if buffer.startswith('#!-- constructors -->'): self.in_custom_constructors = 1 return if buffer.strip().startswith('#/-- constructors -->'): self.in_custom_constructors = 0 return if self.in_custom_constructors: if buffer != "": self.custom_constructors_code += buffer return if buffer.startswith('#!-- read_accessors -->'): self.in_custom_read_accessors = 1 return if buffer.strip().startswith('#/-- read_accessors -->'): self.in_custom_read_accessors = 0 return if self.in_custom_read_accessors: if buffer != "": self.custom_read_accessors_code += buffer return if buffer.startswith('#!-- write_accessors -->'): self.in_custom_write_accessors = 1 return if buffer.strip().startswith('#/-- write_accessors -->'): self.in_custom_write_accessors = 0 return
Karobas.fr 77 / 93
Année 2004 - 2005
if self.in_custom_write_accessors: if buffer != "": self.custom_write_accessors_code += buffer return if buffer.startswith('#!-- methods -->'): self.in_custom_methods = 1 return if buffer.strip().startswith('#/-- methods -->'): self.in_custom_methods = 0 return if self.in_custom_methods: if buffer != "": self.custom_methods_code += buffer return ''' ------------------------------------------------------------------------------------------------------------------- ''' ''' Crꢴion des fichiers de classe ''' ''' ------------------------------------------------------------------------------------------------------------------- ''' def generateConstructor(self, classname, feilds, fk_key, fk_table, pk_key): ret = "" params = [] for f in feilds: if f[0] in pk_key: params.append('$def_' + f[0].lower() + ' = -1') ret += "\tfunction " + classname.capitalize() + "( " + ", ".join(params) + " )\n" ret += "\t{\n" for f in feilds: if f[0] in pk_key: ret += "\t\t$this->" + f[0].lower() + " = (int)$def_" + f[0].lower() + ";\n" ret += "\t}\n" if self.custom_constructors_code != "": ret += "\n" ret += self.custom_constructors_code return ret def createHeritedConstructors(self, classname): ret = "" if self.rel.has_key(classname): for p_id, table, c_id in self.rel[classname.lower()]: ret += "\t/**\n" ret += "\t * Constructeur hérité de la table " + table + "\n" ret += "\t */\n" ret += "\tfunction create" + table.capitalize() + "()\n" ret += "\t{\n" ret += "\t\t$params = array(\n" for f in self.tables[table.lower()][0][1:]: if not f[0].lower() == c_id.lower(): ret += "\t\t\t'" + f[0].lower() + "' => " + str(f[2]) + ",\n" ret += "\t\t);\n" ret += "\t\t$gparams = func_get_args();\n" ret += "\t\tif(!is_array($gparams[0])) {\n" ret += "\t\t\tprint \"Le paramètre doit être un tableau\";\n" ret += "\t\t\treturn -1;\n" ret += "\t\t}\n"
Karobas.fr 78 / 93
Année 2004 - 2005
ret += "\t\tforeach($gparams[0] as $key => $value)\n" ret += "\t\t\t$params[$key] = $value;\n" ret += "\t\treturn $this->real_create" + table.capitalize() params1 = "" params = "" for f in self.tables[table.lower()][0][1:]: if not f[0].lower() == c_id.lower(): params1 += ", $params['" + f[0].lower() + "']" params += ", &$s_" + f[0].lower() ret += "(" + params1[1:] + " );\n" ret += "\t}\n\n" ret += "\tfunction real_create" + table.capitalize() + "(" + params[1:] + " )\n" ret += "\t{\n" ret += "\t\t$obj = new " + table.capitalize() + "();\n" ret += "\t\t$obj->set" + c_id.capitalize() + "((int)$this->" + c_id.lower() + ");\n" for f in self.tables[table.lower()][0][1:]: if not f[0].lower() == c_id.lower(): ret += "\t\t$obj->set" + f[0].capitalize() + "($s_" + f[0].lower() + ");\n" ret += "\t\t$obj->commit();\n" ret += "\t\treturn $obj;\n" ret += "\t}\n" ret += "\n" return ret def generateProperties(self, classname, feilds, fk_key, fk_table, pk_key): ret = "" ret1 = "" ret2 = ""
for feild in feilds: if feild[0] in pk_key: ret += "\t/**\n" ret += "\t * Identifiant de la table " + classname.capitalize() + "\n" if feild[4] != None: ret += "\t * " + feild[4] + "\n" ret += "\t * @access private\n" ret += "\t * @var " + feild[1] + "\n" ret += "\t */\n" ret += "\tvar $" + feild[0].lower() + " = -1;\n\n" else: ret1 += "\t/**\n" ret1 += "\t * @access private\n" if feild[4] != None: ret1 += "\t * " + feild[4] + "\n" ret1 += "\t * @var " + ((feild[3] == '[b]') and "bool" or feild[1]) + "\n" ret1 += "\t */\n" ret1 += "\tvar $" + feild[0].lower() + " = " + str(feild[2]) + ";\n\n" if feild[0].upper() in fk_key.keys(): ret2 += "\t/**\n" ret2 += "\t * @access private\n" if feild[4] != None: ret2 += "\t * " + feild[4] + "\n" ret2 += "\t * @var " + ((feild[3] == '[b]') and "bool" or feild[1]) + "\n" ret2 += "\t */\n" ret2 += "\tvar $" + feild[0][3:].lower() + " = null;\n\n" return ret + ret1 + ret2 + "\n" + str(self.custom_properties_code) def generateAccessRead(self, classname, feilds, fk_key, fk_table, pk_key): ret = "" for feild in feilds: ret += "\t/**\n" ret += "\t * Accesseur en lecture du champs " + feild[0] + " \n" ret += "\t * @access public\n" ret += "\t * @return " + feild[1] + " Retourne la valeur actuelle du champs " + feild[0]
Karobas.fr 79 / 93
Année 2004 - 2005
+ "\n" ret += "\t */\n" ret += "\tfunction get" + feild[0].capitalize() + "() { return (is_null($this->" + feild[0].lower() + "))?(null):((" + ((feild[3] == '[b]') and "bool" or feild[1]) + ")$this->" + feild[0].lower() + "); }\n\n" for f in feilds: if f[0].upper() in fk_key.keys(): ret += "\t/**\n" ret += "\t * Accesseur en lecture du champs " + f[0] + " \n" ret += "\t * @access public\n" ret += "\t * @return " + f[1] + " Retourne l'objet " + f[0] + " initialisé" ret += "\t */\n" ret += "\tfunction get" + f[0][3:].capitalize() + "()\n" ret += "\t{\n" ret += "\t\tif($this->" + f[0].lower() + " > 0) {\n" ret += "\t\t\tif($this->" + f[0][3:].lower() + " != null) { unset($this->" + f[0][3:].lower() + "); }\n" ret += "\t\t\t$this->" + f[0][3:].lower() + " = new " + fk_key[f[0].upper()][0].capitalize() + "((int)$this->" + f[0].lower() + ");\n" ret += "\t\t\t$this->" + f[0][3:].lower() + "->fill();\n" ret += "\t\t\treturn $this->" + f[0][3:].lower() + ";\n" ret += "\t\t}\n" ret += "\t\treturn null;\n" ret += "\t}\n" ret += "\n" if self.rel.has_key(classname): for p_id, table, c_id in self.rel[classname]: ret += "\t/**\n" ret += "\t * Accesseur en lecture de la collection d'objet " + f[0] + " (d걥 nds de la cardinalit骜 n" ret += "\t * @param bool $init Dꧩnit le remplissage des objets de collections.\n" ret += "\t * @access public\n" ret += "\t * @return array Retourne la collection d'objets " + f[0] + " non initialisꝮ" ret += "\t */\n" ret += "\tfunction get" + table.capitalize() + "Col($init = 0)\n" ret += "\t{\n" ret += "\t\t$ret = array();\n\n" ret += "\t\t$sql = \"SELECT " + p_id + " FROM " + table + " WHERE " + c_id + "='\".$this->" + feilds[0][0].lower() + ".\"'\";\n" ret += "\t\t$res = DB_query($sql);\n" ret += "\t\twhile($row = DB_fetchArray($res)) {\n" ret += "\t\t\t$temp = new " + table + "((int)$row[0]);\n" ret += "\t\t\t$temp->set" + c_id.capitalize() + "($this->" + feilds[0][0].lower() + ");\n" ret += "\t\t\tif($init != 0)\n" ret += "\t\t\t\t$temp->fill();\n" ret += "\t\t\t$ret[] = $temp;\n" ret += "\t\t}\n" ret += "\t\treturn $ret;\n" ret += "\t}\n" ret += "\n" ret += "\n" if self.custom_read_accessors_code != "": ret += self.custom_read_accessors_code + "\n" return ret def generateAccessWrite(self, classname, feilds, fk_key, fk_table, pk_key): ret = "" for feild in feilds: ret += "\t/**\n" ret += "\t * Accesseur en ꤲiture du champs " + feild[0] + " \n" ret += "\t * @access public\n" ret += "\t * @param " + str(feild[1]) + " $newvalue Affecte une nouvelle valeur au champs " + str(feild[0]) + "\n" ret += "\t * @return " + str(feild[1]) + " Retourne $newvalue si succés sinon " + str(feild[2]) + " s'il n'y a pas de changement\n"
Karobas.fr 80 / 93
Année 2004 - 2005
ret += "\t */\n" ret += "\tfunction set" + feild[0].capitalize() + "($newvalue = " + feild[2].lower() + ")\n" ret += "\t{\n" if feild[2].lower() == 'null': ret += "\t\tif($newvalue == null) {\n" ret += "\t\t\t$this->" + feild[0].lower() + " = null;\n" ret += "\t\t\treturn null;\n" ret += "\t\t}\n" ret += "\t\n" ret += "\t\tif($newvalue != $this->" + feild[0].lower() + " && !empty($newvalue))\n" ret += "\t\t{\n"; ret += "\t\t\t$this->" + feild[0].lower() + " = (" + ((feild[3] == '[b]') and "bool" or feild[1]) + ")$newvalue;\n" ret += "\t\t\treturn (" + ((feild[3] == '[b]') and "bool" or str(feild[1])) + ")protectVar($newvalue);\n" ret += "\t\t }\n" ret += "\t\treturn (" + ((feild[3] == '[b]') and "bool" or str(feild[1])) + ")" + str(feild[2]) +";\n" ret += "\t}\n" if self.custom_write_accessors_code != "": ret += self.custom_write_accessors_code + "\n\n" return ret def generateMethod(self, classname, feilds, fk_key, fk_table, pk_key): ret = "" ret += "\t/**\n" ret += "\t * Ordonne le remplissage des propri굩 s de la classe en utilisant les champs de la table " + self.classname + "\n" ret += "\t * @param int L'identifiant de la table " + classname + " soit " + feilds[0][0] + ".\n" ret += "\t */\n" ret += "\tfunction fill($autofill = 0)\n" ret += "\t{\n" ret += "\t\tif($this->" + pk_key[0].lower() + " < 0)\n" ret += "\t\t\treturn -1;\n" for f in pk_key[1:]: ret += "\t\tif($this->" + f.lower() + " < 0)\n" ret += "\t\t\treturn -1;\n" ret += "\n" ret += "\t\t$sql = \"SELECT * FROM " + classname + " WHERE \";\n" ret += "\t\tif($this->" + pk_key[0].lower() + " > 0)\n" ret += "\t\t\t$sql .= \"" + feilds[0][0] + "='\".(int)$this->" + feilds[0][0].lower() + ".\"'\";\n" for f in pk_key[1:]: ret += "\t\tif($this->" + f.lower() + " > 0)\n" ret += "\t\t\t$sql .= \" AND " + f + "='\".(int)$this->" + f.lower() + ".\"'\";\n" ret += "\n" ret += "\t\t$res = DB_query($sql);\n" ret += "\t\tif(DB_numRows($res) == 1) {\n" ret += "\t\t\t$row = DB_fetchArray($res);\n\n" for f in feilds: ret += "\t\t\t$this->" + f[0].lower() + " = (is_null($row['" + f[0] + "']))?(null):((" + ((f[3] == '[b]') and "bool" or f[1]) + ")$row['" + f[0] + "']);\n" ret += "\n\t\t\tif($autofill != 0) {\n" for f in feilds: if not f[0] in pk_key: if f[0].upper() in fk_key.keys(): if f[4] != None: ret += "\n\t\t\t\t/* " + f[4] + " */\n" ret += "\t\t\t\tif($this->" + f[0].lower() + " != " + str(f[2]) + ") {\n" ret += "\t\t\t\t\t$this->" + f[0][3:].lower() + " = new " + fk_key[f[0].upper()][0].capitalize() + "((int)$this->" + f[0].lower() + ");\n"
Karobas.fr 81 / 93
Année 2004 - 2005
ret += "\t\t\t\t\t$this->" + f[0][3:].lower() + "->fill($autofill);\n" ret += "\t\t\t\t}\n" ret += "\t\t\t}\n" ret += "\t\t}\n" ret += "\t\telse return -1;\n" ret += "\n" ret += "\treturn 0;\n" ret += "\t}\n" ret += "\n" ret += "\t/**\n" ret += "\t * Ordonne la synchronisation des propri굩 s de la classe en cours d'utilisation avec la base de donn꦳.\n" ret += "\t */\n" ret += "\tfunction commit()\n" ret += "\t{\n" ret += "\t\t$sql = \"INSERT INTO " + classname + " VALUES (" if len(pk_key) == 1: ret += "''" else: ret += "\".protectVar($this->" + pk_key[0].lower() + ").\"" for f in pk_key[1:]: ret += " ,\".protectVar($this->" + f.lower() + ").\"" for f in feilds: if not f[0] in pk_key: ret += " ,\".protectVar($this->" + f[0].lower() + ").\"" ret += ")\";\n" ret += "\t\t$res = DB_query($sql);\n" ret += "\t\t$id = DB_insertId();\n" ret += "\t\t$this->" + feilds[0][0].lower() + " = $id;\n" ret += "\t\treturn $id;\n" ret += "\t}\n" ret += "\n" ret += "\tfunction update()\n" ret += "\t{\n" ret += "\t\t$sql = \"UPDATE " + classname + " SET " ret += feilds[len(pk_key)][0] + " = \".protectVar($this->" + feilds[len(pk_key)][0].lower() + ").\"" for f in feilds[len(pk_key) + 1:]: ret += ", " + f[0] + " = \".protectVar($this->" + f[0].lower() + ").\"" ret += " WHERE " + pk_key[0] + " = '\" . $this->" + pk_key[0].lower() + " . \"'" for f in pk_key[1:]: ret += " AND " + f + " = '\" . $this->" + f.lower() + " . \"'" ret += "\";\n" ret += "\t\t$res = DB_query($sql);\n" ret += "\t\treturn DB_affectedRows($res);\n" ret += "\t}\n" ret += "\n" ret += "\t/**\n" ret += "\t * Ordonne la suppression de l'enregistrement de la base de donn꦳.\n" ret += "\t*/\n" ret += "\tfunction _delete() {\n" ret += "\t\t$sql = \"DELETE FROM " + classname +" WHERE " + feilds[0][0] + "='\".$this->" + feilds[0][0].lower() + ".\"'\";\n" ret += "\t\t$res = DB_query($sql);\n" ret += "\t\treturn $res;\n" ret += "\t}\n" if self.custom_methods_code != "": ret += self.custom_methods_code + "\n" return ret def generateToXML(self, classname, feilds, fk_key, fk_table, pk_key): ret = "" ret += "\t/**\n" ret += "\t * Exportation de l'instance en cours vers un schꮡ XML exploitable\n"
Karobas.fr 82 / 93
Année 2004 - 2005
ret += "\t * @return string Le schꮡ XML compl굩 de l'instance en cours.\n" ret += "\t */\n" ret += "\tfunction toXML()\n" ret += "\t{\n" ret += "\t\tif($this->" + feilds[0][0].lower() + " == -1 || empty($this->" + feilds[0][0].lower() + "))\n" ret += "\t\t\treturn null;\n\n" ret += "\t\t$xmlstr = \"<" + classname.lower() + " id='\".$this->" + feilds[0][0].lower() + ".\"'>\";\n" for f in feilds[1:]: ret += "\t\t$xmlstr .= \"\\t<" + f[0].lower() + ">\".$this->" + f[0].lower() + ".\"</" + f[0].lower() + ">\\n\";\n" ret += "\t\t$xmlstr .= \"</" + classname.lower() + ">\";\n\n" ret += "\t\treturn $xmlstr;\n" ret += "\t}\n" return ret def buildClassFile(self, classname, feilds, fk_key, fk_table, pk_key): os.chdir('./include/DB_' + self.database_name) self.cfile = open(classname.lower() + '.class.php', 'w') content = "<?php\n" content += "/**\n" content += " * Classe de gestion de la Table " + classname + "\n" content += " * Gꯩr顬 e " + time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime()) + " avec sql2phpclass\n" content += " * Sql2Phpclass v 2.02 ([email protected])\n" content += " * @package KarobasDB\n" content += " */\n\n" if len(fk_table) > 0: for k, v in fk_table.items(): content += "/* Classe d'acc鳠 a table " + v[0].capitalize() +" R*/\n" content += "require_once('" + v[0] + ".class.php');\n" if self.rel.has_key(classname): for p_id, table, c_id in self.rel[classname]: content += "/* Classe d'acc鳠 a table " + table.capitalize() +" */\n" content += "require_once('" + table.lower() + ".class.php');\n" content += "\n" content += "class " + classname.capitalize() + "\n{\n" content += "\n" content += "\t/*" + "*"*40 + "\n" content += "\t * Propi굩 s\n" content += "\t " + "*"*40 + "*/\n\n" content += self.generateProperties(classname, feilds, fk_key, fk_table, pk_key) content += "\n\n" content += "\t/*" + "*"*40 + "\n" content += "\t * Constructeurs\n" content += "\t " + "*"*40 + "*/\n\n" content += self.generateConstructor(classname, feilds, fk_key, fk_table, pk_key) content += "\n\n" content += "\t/*" + "*"*40 + "\n" content += "\t * Constructeurs Hérités\n" content += "\t " + "*"*40 + "*/\n\n" content += self.createHeritedConstructors(classname) content += "\n\n" content += "\t/*" + "*"*40 + "\n" content += "\t * Accesseurs\n" content += "\t " + "*"*40 + "*/\n\n" content += self.generateAccessRead(classname, feilds, fk_key, fk_table, pk_key) content += "\n" content += self.generateAccessWrite(classname, feilds, fk_key, fk_table, pk_key) content += "\n" content += "\t/*" + "*"*40 + "\n" content += "\t * Méthodes\n" content += "\t " + "*"*40 + "*/\n\n" content += self.generateMethod(classname, feilds, fk_key, fk_table, pk_key)
Karobas.fr 83 / 93
Année 2004 - 2005
content += "\n" content += "\t/*" + "*"*40 + "\n" content += "\t * Exportation\n" content += "\t " + "*"*40 + "*/\n\n" content += self.generateToXML(classname, feilds, fk_key, fk_table, pk_key) content += "}\n" content += "?>\n" self.cfile.write(content) self.cfile.close() os.chdir('../../')
''' ------------------------------------------------------------------------------------------------------------------- ''' ''' Crꢴion des fichiers d'administration ''' ''' ------------------------------------------------------------------------------------------------------------------- ''' def buildAdminListPages(self, classname, feilds, fk_key, fk_table, pk_key): os.chdir('./pages') self.filename = 'admin_' + classname.lower() + '_list.php' self.cfile = open(self.filename, 'w')
tab = "$tri_tab = array(" var = "$vars = array(" idx = 0 content = "<? include('../header.inc');\n" content += "define ('GZIP', FALSE);\n" content += "require_once \"tpl.class.php\";\n" content += "require_once \"dcc.class.php\";\n" content += "\n" content += "$tpl = new dcc(\"" + classname.lower() + "\", \"listing\");\n" content += "if ($tpl->cache_start()) {\n" var += "\n\t\t'page_header' => \"<tr class=\\\"modif_header\\\"><td><a href=\\\"admin_" + classname.lower() + "_list.php\\\" target=\\\"content\\\"><b>Afficher</b></a></td><td><td><a href=\\\"admin_" + classname.lower() + "_create.php\\\" target=\\\"content\\\">Créer</a></td><td><td><a href=\\\"admin_" + classname.lower() + "_search.php\\\" target=\\\"content\\\">Rechercher</a></td></tr>\\n\"," ''' Crꢴion de l'ent뵥 du tableau ''' var += "\n\t\t'table_header' => \"<tr class=\\\"table_head\\\"><td>Actions</td>" for f in feilds[1:]: var += "<td>(<a href=\\\""+ self.filename+ "?t=-" + str(idx) + "\\\">A</a>/<a href=\\\""+ self.filename+ "?t=" + str(idx) + "\\\">D</a>) " + f[0].capitalize() + "</td>" tab += "" + str(idx) + " => \"" + f[0].lower() + "\", " idx = idx + 1 var += "</tr>\",\n" tab += ");" var += "\t);" content += "\t" + var + "\n" content += "\t$tpl->assign($vars);\n\n" content += "\t" + tab + "\n" content += "\t$sql = \"SELECT * FROM " + classname.capitalize() + "\";\n" content += "\t$sql .= \" ORDER BY \".$tri_tab[abs($tri) % count($tri_tab)];\n" content += "\t$sql .= ($tri>0)?(\" DESC\"):(\" ASC\");\n" content += "\t$sql .= \" LIMIT \".(((int)$pag)*$nb).\", \".$nb;\n" content += "\t$res= DB_query($sql);\n" content += "\t$table_rows = array();\n" content += "\tif(DB_numRows($res) > 0) {\n" content += "\t\t$idx = 0;\n" content += "\t\twhile ( $row = DB_fetchArray($res) ) {\n" content += "\t\t\t$ligne = array();\n" content += "\t\t\t$ligne['row_data'] = \"<tr class=\\\"alt\".$idx++ % 2 .\"\\\">\\n\";\n" content += "\t\t\t$ligne['row_data'] .= \"<td><a href=\\\"admin_" + classname.lower() + "_modif.php?u=\".$row[0].\"\\\" target=\\\"content\\\"><img src=\\\"../images/modifier.gif\\\"
Karobas.fr 84 / 93
Année 2004 - 2005
border=\\\"0\\\"></a><img src=\\\"../images/delete.gif\\\" border=\\\"0\\\"></td>\\n\";\n" content += "\t\t\t$ligne['row_data'] .= \"" for f in feilds[1:]: content += "<td>\".protectVar($row['"+ f[0] + "']).\"</td>" content += "\";\n" content += "\t\t\t$ligne['row_data'] .= \"</tr>\\n\";\n" content += "\t\t\t$table_rows[] = $ligne;\n" content += "\t\t}\n" content += "\t}\n" content += "\t$tpl->assign(\"table_rows\");\n" '''content += "\t$tpl->cache_stop();\n"''' content += "}\n" content += "$tpl->done();\n" content += "\n" content += "include('../footer.inc'); ?>\n" self.cfile.write(content) self.cfile.close() os.chdir('..') def buildAdminCreatePages(self, classname, feilds, fk_key, fk_table, pk_key): os.chdir('./pages') self.filename = 'admin_' + classname.lower() + '_create.php' self.cfile = open(self.filename, 'w') var = "$vars = array(" content = "<? include('../header.inc');\n" content += "\tdefine ('GZIP', FALSE);\n" content += "\trequire_once \"tpl.class.php\";\n" content += "\trequire_once \"dcc.class.php\";\n" content += "\n" content += "\t$feilds = array();\n" content += "\t$tpl = new dcc(\"" + classname.lower() + "\", \"modif\");\n" content += "\tif ($tpl->cache_start()) {\n" content += "\t\t$obj = new " + classname.capitalize() + "();\n" content += "\t\t$obj->fill();\n" content += "\n" content += "\t\tif($_POST['submit'] == 'Ajouter') {\n" for f in feilds[1:]: content += "\t\t\t$obj->set" + f[0].capitalize() + "($_POST['sub_" + f[0].lower() + "']);\n" content += "\t\t\t$obj->commit();\n" content += "\t\t}\n\n" var += "\n\t\t\t\t'page_header' => \"" var += "<tr class=\\\"modif_header\\\"><td><a href=\\\"admin_" + classname.lower() + "_list.php\\\" target=\\\"content\\\">Afficher</a></td><td><td><a href=\\\"admin_" + classname.lower() + "_create.php\\\" target=\\\"content\\\"><b>Créer</b></a></td><td><td><a href=\\\"admin_" + classname.lower() + "_search.php\\\" target=\\\"content\\\">Rechercher</a></td></tr>\",\n" var += "\t\t\t\t'uid' => (int)$uid," var += "\n\t\t\t\t'vid' => (int)$vid," var += "\n\t\t\t\t'tri' => (int)$tri," var += "\n\t\t\t\t'page' => (int)$pag," var += "\n\t\t\t\t'nbppage' => (int)$nb," var += "\n\t\t);\n\n" content += "\t\t" + var content += "\t\t$tpl->assign($vars);\n\n" ''' content += "<form action=\"?u=<?=$uid?>\" method=\"POST\">\n" ''' for f in feilds[1:]: opt_table = 0 if fk_table.has_key(f[0]): opt_table = self.tables[fk_table[f[0]][0]][3]
Karobas.fr 85 / 93
Année 2004 - 2005
#if opt_table in (0, 2): content += "\t\t$feilds[] = array('input' => \"<tr><td>" + f[0].capitalize() + "</td><td>" + self.createValidInput(f, opt_table) + "</td></tr>\");\n" content += "\t\t$feilds[] = array('input' => '<tr><td colspan=\"2\"><input type=\"submit\" id=\"submit\" name=\"submit\" value=\"Ajouter\" class=\"submit\"></td></tr>');\n" content += "\n" content += "\t\t$tpl->assign(\"feilds\");\n" content += "\t}\n" content += "\t$tpl->done();\n\n" content += "include('../footer.inc'); ?>\n" self.cfile.write(content) self.cfile.close() os.chdir('..') def buildAdminSearchPages(self, classname, feilds, fk_key, fk_table, pk_key): os.chdir('./pages') self.filename = 'admin_' + classname.lower() + '_search.php' self.cfile = open(self.filename, 'w') content = "<? include('../header.inc');\n" content += "\tdefine ('GZIP', FALSE);\n" content += "\trequire_once \"tpl.class.php\";\n" content += "\trequire_once \"dcc.class.php\";\n" content += "\n" content += "\t$feilds = array();\n" content += "\t$tpl = new dcc(\"" + classname.lower() + "\", \"search\");\n" content += "\tif ($tpl->cache_start()) {\n" content += "\t\tif($_POST['submit'] == 'Rechercher') {\n" content += "\t\t\t$sql = \"SELECT DISTINCT(" + feilds[0][0].lower() + ")" for f in feilds[1:]: content += ", " + f[0].upper() content += " FROM " + classname.capitalize() + " WHERE 1=1\";\n" for f in feilds[1:]: content += "\t\t\tif(isset($_POST['s_" + f[0].lower() + "']) && !empty($_POST['s_" + f[0].lower() + "'])) {\n" content += "\t\t\t\t$sql .= \" AND " + f[0].upper() + " \" . $_POST['sel_" + f[0].lower() + "'] . \" '\";\n" content += "\t\t\t\t$sql .= (strpos($_POST['sel_" + f[0].lower() + "'], 'LIKE') === FALSE)?(''):('%');\n" content += "\t\t\t\t$sql .= mysql_escape_string($_POST['s_" + f[0].lower() + "']);\n" content += "\t\t\t\t$sql .= (strpos($_POST['sel_" + f[0].lower() + "'], 'LIKE') === FALSE)?(''):('%');\n" content += "\t\t\t\t$sql .= \"'\";\n" content += "\t\t\t}\n\n" content += "\t\t\t$res = DB_query($sql);\n" content += "\t\t\t$_SESSION['SQLSCRIPT'] = $sql;\n" content += "\t\t\t$table_rows = array();\n" content += "\t\t\tif(DB_numRows($res) > 0) {\n" content += "\t\t\t\twhile($row = DB_fetchArray($res))\n" content += "\t\t\t\t\t$table_rows[] = array( 'row_data' => $row );\n" content += "\t\t\t}\n" content += "\t\t\t$tpl->assign(\"table_rows\");\n" content += "\t\t}\n" content += "\t\t$vars = array('search_header' => '<tr class=\"modif_header\"><td><a href=\"admin_" + classname.lower() + "_list.php\" target=\"content\">Afficher</a></td><td><td><a href=\"admin_" + classname.lower() + "_create.php\" target=\"content\">Créer</a></td><td><td><a href=\"admin_" + classname.lower() + "_search.php\" target=\"content\"><b>Rechercher</b></a></td></tr>');\n" content += "\t\t$tpl->assign($vars);\n\n" sfeilds = "" for f in feilds[1:]: sfeilds += "\t\t$feilds[] = array('search_parameter' => '" sfeilds += "<tr><td>" + f[0].capitalize() + "</td><td>" sfeilds += "<select name=\"sel_" + f[0].lower() + "\" class=\"sel_operator\">"
Karobas.fr 86 / 93
Année 2004 - 2005
sfeilds += "<option value=\"LIKE\">LIKE" sfeilds += "<option value=\"NOT LIKE\">NOT LIKE" sfeilds += "<option value=\"=\">=" sfeilds += "<option value=\"!=\">!=" sfeilds += "<option value=\">\">>" sfeilds += "<option value=\"<\"><" sfeilds += "<option value=\"<=\"><=" sfeilds += "<option value=\"=>\">=>" sfeilds += "</select></td>" sfeilds += "<td><input type=\"text\" name=\"s_" + f[0].lower() + "\" class=\"s_input\" value=\"'.$_POST['s_" + f[0].lower() + "'].'\"></td></tr>');\n" sfeilds += "\t\t$feilds[] = array('search_parameter' => '<tr><td colspan=\"3\"><input type=\"submit\" name=\"submit\" id=\"submit\" value=\"Rechercher\"></td></tr>');\n" content += sfeilds + "\n" content += "\t\t$tpl->assign(\"feilds\");\n" content += "\t};\n" content += "\t$tpl->done();\n\n" content += "include('../footer.inc'); ?>\n" self.cfile.write(content) self.cfile.close() os.chdir('..') def buildAdminModifPages(self, classname, feilds, fk_key, fk_table, pk_key): self.filename = 'admin_' + classname.lower() + '_modif.php' '''self.generateTemplate(classname.lower(), 'modif')''' os.chdir('./pages') self.cfile = open(self.filename, 'w') var = "$vars = array(" content = "<? include('../header.inc');\n" content += "\tdefine ('GZIP', FALSE);\n" content += "\trequire_once \"tpl.class.php\";\n" content += "\trequire_once \"dcc.class.php\";\n" content += "\n" content += "\t$feilds = array();\n" content += "\t$tpl = new dcc(\"" + classname.lower() + "\", \"modif\");\n" content += "\tif ($tpl->cache_start()) {\n" content += "\t\t$obj = new " + classname.capitalize() + "((int)$uid);\n" content += "\t\t$obj->fill();\n" content += "\n" content += "\t\tif($_POST['submit'] == 'Modifier') {\n" for f in feilds[1:]: content += "\t\t\t$obj->set" + f[0].capitalize() + "($_POST['sub_" + f[0].lower() + "']);\n" content += "\t\t\t$obj->update();\n" content += "\t\t}\n" var += "\n\t\t\t\t'uid' => (int)$uid," var += "\n\t\t\t\t'vid' => (int)$vid," var += "\n\t\t\t\t'tri' => (int)$tri," var += "\n\t\t\t\t'page' => (int)$pag," var += "\n\t\t\t\t'nbppage' => (int)$nb," var += "\n\t\t\t\t'page_header' => \"" if self.rel.has_key(classname): var += "<tr class=\\\"modif_header\\\">" var += "<td>" + classname.capitalize() + "</td>" idx = 0 for r in self.rel[classname]: var += "<td><a href=\\\""+ 'admin_' + classname.lower() + '_' + r[1].lower() + '_list.php?v=$uid' + "\\\">" + r[1].capitalize() + "</a></td>" self.buildAdminSubModifPages(classname, idx) idx = idx + 1 var += "</tr>\",\n"
Karobas.fr 87 / 93
Année 2004 - 2005
else: var += "\"\n" var += "\t\t);\n" content += "\t\t" + var content += "\n" content += "\t\t$tpl->assign($vars);\n\n" '''content += "<form action=\"?u=<?=$uid?>\" method=\"POST\">\n"''' for f in feilds: opt_table = 0 if fk_table.has_key(f[0]): opt_table = self.tables[fk_table[f[0]][0]][3] #if opt_table in (0, 2): content += "\t\t$feilds[] = array( \"input\" => \"<tr><td>" + f[0].capitalize() + "</td><td>" + self.createValidInput(f, opt_table) + "</td></tr>\");\n" content += "\t\t$feilds[] = array( \"input\" => \"<tr><td colspan=\\\"2\\\"><input type=\\\"submit\\\" name=\\\"submit\\\" id=\\\"submit\\\" value=\\\"Modifier\\\"></td></tr>\");\n" content += "\n" content += "\t\t$tpl->assign(\"feilds\");\n" content += "\t}\n" content += "\t$tpl->done();\n\n" content += "include('../footer.inc'); ?>\n" self.cfile.write(content) self.cfile.close() os.chdir('..') def buildAdminSubModifPages(self, classname, subclassname): temp_rel = self.rel[classname][subclassname] feilds = self.tables[temp_rel[1].lower()][0] self.sfilename = 'admin_' + classname.lower() + '_' + temp_rel[1].lower() + '_list.php' self.sfile = open(self.sfilename, 'w')
tab = "$tri_tab = array(" var = "$vars = array(" idx = 0 content = "<? include('../header.inc');\n" content += "define ('GZIP', FALSE);\n" content += "require_once \"tpl.class.php\";\n" content += "require_once \"dcc.class.php\";\n" content += "\n" content += "$tpl = new dcc(\"" + temp_rel[1].lower() + "\", \"listing\");\n" content += "if ($tpl->cache_start()) {\n" var += "\n\t\t\t\t'uid' => (int)$uid," var += "\n\t\t\t\t'vid' => (int)$vid," var += "\n\t\t\t\t'tri' => (int)$tri," var += "\n\t\t\t\t'page' => (int)$pag," var += "\n\t\t\t\t'nbppage' => (int)$nb," var += "\n\t\t'page_header' => \"<tr class=\\\"modif_header\\\"><td><a href=\\\"admin_" + temp_rel[1].lower() + "_list.php?v=$vid\\\" target=\\\"content\\\"><b>Afficher</b></a></td><td><td><a href=\\\"admin_" + temp_rel[1].lower() + "_create.php?v=$vid&" + temp_rel[2].lower() + "=$vid\\\" target=\\\"content\\\">Créer</a></td><td><td><a href=\\\"admin_" + temp_rel[1].lower() + "_search.php?v=$vid\\\" target=\\\"content\\\">Rechercher</a></td></tr>\\n\"," ''' Crꢴion de l'ent뵥 du tableau ''' var += "\n\t\t'table_header' => \"<tr class=\\\"table_head\\\"><td>Actions</td>" for f in feilds[1:]: var += "<td>(<a href=\\\""+ self.sfilename+ "?t=-" + str(idx) + "\\\">A</a>/<a href=\\\""+ self.sfilename+ "?t=" + str(idx) + "\\\">D</a>) " + f[0].capitalize() + "</td>" tab += "" + str(idx) + " => \"" + f[0].lower() + "\", " idx = idx + 1
Karobas.fr 88 / 93
Année 2004 - 2005
var += "</tr>\",\n" tab += ");" var += "\t);" content += "\t" + var + "\n" content += "\t$tpl->assign($vars);\n\n" content += "\t" + tab + "\n" content += "\t$sql = \"SELECT * FROM " + temp_rel[1].capitalize() + " WHERE " + temp_rel[2] + "=\".$vid.\" ORDER BY \".$tri_tab[(abs($tri)) % count($tri_tab)];\n" content += "\t$sql .= ($tri>0)?(\" DESC\"):(\" ASC\");\n" content += "\t$sql .= \" LIMIT \".(((int)$pag)*$nb).\", \".$nb;\n" content += "\t$res= DB_query($sql);\n" content += "\t$table_rows = array();\n" content += "\tif(DB_numRows($res) > 0) {\n" content += "\t\t$idx = 0;\n" content += "\t\twhile ( $row = DB_fetchArray($res) ) {\n" content += "\t\t\t$ligne = array();\n" content += "\t\t\t$ligne['row_data'] = \"<tr class=\\\"alt\".$idx++ % 2 .\"\\\">\\n\";\n" content += "\t\t\t$ligne['row_data'] .= \"<td><a href=\\\"admin_" + temp_rel[1].capitalize() + "_modif.php?u=\".$row[0].\"\\\" target=\\\"content\\\"><img src=\\\"../images/modifier.gif\\\" border=\\\"0\\\"></a><img src=\\\"../images/delete.gif\\\" border=\\\"0\\\"></td>\\n\";\n" content += "\t\t\t$ligne['row_data'] .= \"" for f in feilds[1:]: content += "<td>\".protectVar($row['"+ f[0] + "']).\"</td>" content += "\";\n" content += "\t\t\t$ligne['row_data'] .= \"</tr>\\n\";\n" content += "\t\t\t$table_rows[] = $ligne;\n" content += "\t\t}\n" content += "\t}\n" content += "\t$tpl->assign(\"table_rows\");\n" '''content += "\t$tpl->cache_stop();\n"''' content += "}\n" content += "$tpl->done();\n" content += "\n" content += "include('../footer.inc'); ?>\n" self.sfile.write(content) self.sfile.close() def buildAdminMenuPage(self): os.chdir('./pages') self.filename = 'admin_menu.php' self.cfile = open(self.filename, 'w') content = "<? include('../header.inc');\n" content += "define ('GZIP', FALSE);\n" content += "require_once \"tpl.class.php\";\n" content += "require_once \"dcc.class.php\";\n" content += "require_once \"TreeMenu.class.php\";\n" content += "\n" content += "$admin_tables = array();\n" content += "$tpl = new dcc(\"menu\", \"misc\");\n" content += "if ($tpl->cache_start()) {\n" xml_file = open('menu.xml', 'w') doc = Document() root = doc.createElement("menu") root.setAttribute('width', '300') root.setAttribute('target', 'content') root.setAttribute('indent', '2') doc.appendChild(root) list = doc.createElement('option') nblist = 0 for name, class_ in self.tables.items(): if class_[3] == 1: option = doc.createElement('option')
Karobas.fr 89 / 93
Année 2004 - 2005
rel = doc.createElement('option') rel.setAttribute('value', name.capitalize()) rel.setAttribute('url', 'admin_' + name.lower() + '_list.php') option.appendChild(rel) if self.rel.has_key(name): nbchild = 1 for r in self.rel[name]: rel = doc.createElement('option') rel.setAttribute('value', r[1].capitalize()) rel.setAttribute('url', 'admin_' + r[1].lower() + '_list.php') option.appendChild(rel) nbchild = nbchild + 1 option.setAttribute('length',str(nbchild)) option.setAttribute('value', name.capitalize()) root.appendChild(option) if class_[3] == 2: listitem = doc.createElement('option') listitem.setAttribute('value', name.capitalize()) listitem.setAttribute('url', 'admin_' + name.lower() + '_list.php') nblist = nblist + 1 list.appendChild(listitem) list.setAttribute('length',str(nblist)) list.setAttribute('value', 'Listes') root.appendChild(list) xml_file.write(doc.toprettyxml()) xml_file.close() content += "\t$vars = array(\n" content += "\t\t'show_all' => '<tr><td><a href=\"?v=1\">Afficher toutes les tables</a></td></tr>'," content += "\n\t\t'uid' => (int)$uid," content += "\n\t\t'gen_menu' => $menu->Generate()," content += "\n\t\t'menu' => $menu->PrintMenu()," content += "\n\t\t'vid' => (int)$vid," content += "\n\t\t'tri' => (int)$tri," content += "\n\t\t'page' => (int)$pag," content += "\n\t\t'nbppage' => (int)$nb," content += ");\n" content += "\t$tpl->assign($vars);\n" content += "}\n" content += "$tpl->done();\n\n" content += "include('../footer.inc'); ?>\n" self.cfile.write(content) self.cfile.close() os.chdir('..') def buildComboTypes(self): os.chdir('./include/DB_' + self.database_name) self.filename = 'lib-combo.php' self.cfile = open(self.filename, 'w') content = "<?php\n" content += "/**\n" content += " * Fichier de gestion des classes Type\n" content += " */\n\n" for name, class_ in self.tables.items(): if class_[3] == 2: content += "\tfunction combo" + name.capitalize() + "($default_id=1)\n" content += "\t{\n" content += "\t\t$ret = \"\";\n" content += "\t\t$sql = \"SELECT * FROM " + name.lower() + " ORDER BY " + str(class_[0][1][0].lower()) + "\";\n" content += "\t\t$res = DB_query($sql);\n" content += "\t\t$ret .= \"<select name=\\\"sub_id_" + name.lower() + "\\\" class=\\\"sel_operator\\\">\\n\";\n" content += "\t\twhile($row = DB_fetchArray($res)) {\n" content += "\t\t\t$ret .= \"<option value=\".$row[0].\" \".(($row[0]==$default_id)?(\"selected\"):(\"\")).\">\".$row[1].\"\\n\";\n"
Karobas.fr 90 / 93
Année 2004 - 2005
content += "\t\t}\n" content += "\t\t$ret .= \"</select>\";\n" content += "\t\treturn $ret;\n" content += "\t}\n\n" content += "\tfunction getID_" + name.capitalize() + "Str($default_id=1)\n" content += "\t{\n" content += "\t\t$ret = \"\";\n" content += "\t\t$sql = \"SELECT * FROM " + name.lower() + " WHERE " + str(class_[0][0][0].lower()) + "=\".$default_id;\n" content += "\t\t$res = DB_query($sql);\n" content += "\t\twhile($row = DB_fetchArray($res)) {\n" content += "\t\t\t$ret = $row[1];\n" content += "\t\t}\n" content += "\t\treturn $ret;\n" content += "\t}\n\n" self.cfile.write(content) self.cfile.close() os.chdir('..') def generateTemplate(self, table, type): os.chdir('templates') content = "" if not os.path.exists('./' + type.lower()): os.mkdir(type.lower()) os.chdir(type.lower()) if os.path.exists('generic.html'): gen_file = open('generic.html', 'r') gen_content = gen_file.read() gen_file.close() else: return -1 tmp_file = open(table.lower() + ".html", 'w') tmp_file.write(gen_content) tmp_file.close() os.chdir('..') def createValidInput(self, feild, opt =0, default_val = None): input = "" ''' feild = [name, type, nullify, comment options, cardinality] ''' if feild is None: return "" if default_val is None: default_val = "((!isset($_GET['" + feild[0].lower() + "']))?($obj->get"+feild[0].capitalize()+"()):($_GET['" + feild[0].lower() + "']))" input_type = "text" input_func = None input_readonly = 0 if opt == 2: input_type = None input_func = "combo" + feild[0][3:].capitalize() + "(" + default_val + ")" if feild[1].startswith('text') or feild[1].startswith('longtext'): input_type = "textarea" if feild[3] is not None: if feild[3].startswith('['): temp = feild[3][1:] if temp.startswith('b'): input_type = None input_func = "checkbox('"+ feild[0].capitalize() + "', " + default_val + ")" temp = temp[2:]
Karobas.fr 91 / 93
Année 2004 - 2005
if temp.startswith('d'): input_type = None input_func = "combobox('Ddn', " + default_val + ")" temp = temp[2:] if temp.startswith('l'): input_type = None input_func = "combo" + feild[0].capitalize() + "(" + default_val + ")" temp = temp[2:] if temp.startswith('r'): input_readonly = 1 temp = temp[2:] if temp.startswith('s'): input_func = "getNomVilleFromCP(" + default_val + ")" input_type = None temp = temp[2:] ''' crꢴion du contr孥 HTML ''' if input_func is not None: input = "\" ." + input_func + " . \"" else: input += "<input type=\\\"" + input_type + "\\\" name=\\\"sub_" + feild[0].lower() + "\\\" value=\\\"\"." + default_val + ".\"\\\" class=\\\"s_input\\\" " + (input_readonly and "DISABLED" or "") + ">" return input def buildStatsPage(self): if not self.tables.has_key('stats'): print 'Aucune table de stats detectee ! \n' return os.chdir('./stats') self.filename = 'stats_generator.php' self.cfile = open(self.filename, 'w') print self.tables['stats'][0] content = "" idx = 1 for k, v in self.tables['stats'][1].items(): content += '<p><input type="radio" onfocus="hide(' + str(idx-1) + ')" name="choice" value="' + str(idx) + '" id="choice' + str(idx) + '" />\n' content += '<label for="choice' + str(idx) + '">' + v[0].capitalize() + '</label>\n' content += '<span> \n' content += '<select name="select' + str(idx) + '" id="select' + str(idx) + '">\n' for f in self.tables[v[0]][0]: content += '\t<option value="sub_' + f[0].lower() + '">' + f[0].lower() + '</option>\n' content += '</select>\n' content += '</span></p>\n\n' idx = idx + 1 self.cfile.write(content) self.cfile.close() os.chdir('..') def usage(): print "Usage :" print "sql2phpclass [-vh] table1, table2, ..." print "-v, --verbose \t\t Mode verbeux." print "-h, --help \t\t Affiche ce message." print "-a, --build-adminpages \t Creer les pages d'administrations de la base." print "-c, --build-classfiles \t Creer les classes de la base." print "-m, --build-menu \t Creer le menu de la partie administration.\n" print "table1,table2, ... \tListe des tables a traiter (par defaut toutes)." def main(): App = sql2php() App.run() if __name__ == "__main__": main()
Karobas.fr 92 / 93
Année 2004 - 2005
Karobas.fr 93 / 93