rap_karobas_2005

93
Année 2004 - 2005 Julien Bauduf Thibault Normand Institut Universitaire des Technologies de Nice Licence Professionnelle Administration des Sytêmes en Réseaux KAROBAS Mise en place d'une infrastucture commerciale et migration technologique. Durée : 4 Mois Tuteur de stage : M. Michel SYSKA Tuteur d'entreprise : M. Patrice CHEREAU Karobas.fr 1 / 93

description

Rapport de stage, Licence Pro ASR, Karobas, 2005

Transcript of rap_karobas_2005

Page 1: 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

Page 2: rap_karobas_2005

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

Page 3: rap_karobas_2005

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

Page 4: rap_karobas_2005

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

Page 5: rap_karobas_2005

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

Page 6: rap_karobas_2005

Année 2004 - 2005

I – La sécurité

Karobas.fr 6 / 93

Page 7: rap_karobas_2005

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

Page 8: rap_karobas_2005

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

Page 9: rap_karobas_2005

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

Page 10: rap_karobas_2005

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

Page 11: rap_karobas_2005

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

Page 12: rap_karobas_2005

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

Page 13: rap_karobas_2005

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

Page 14: rap_karobas_2005

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

Page 15: rap_karobas_2005

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

Page 16: rap_karobas_2005

Année 2004 - 2005

II – L’Informatique au service de la Communication

Karobas.fr 16 / 93

Page 17: rap_karobas_2005

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

Page 18: rap_karobas_2005

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

Page 19: rap_karobas_2005

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

Page 20: rap_karobas_2005

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

Page 21: rap_karobas_2005

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

Page 22: rap_karobas_2005

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

Page 23: rap_karobas_2005

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

Page 24: rap_karobas_2005

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

Page 25: rap_karobas_2005

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

Page 26: rap_karobas_2005

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

Page 27: rap_karobas_2005

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

Page 28: rap_karobas_2005

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

Page 29: rap_karobas_2005

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

Page 30: rap_karobas_2005

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

Page 31: rap_karobas_2005

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

Page 32: rap_karobas_2005

Année 2004 - 2005

III – Le développement : La base de données et le site

Karobas.fr 32 / 93

Page 33: rap_karobas_2005

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

Page 34: rap_karobas_2005

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

Page 35: rap_karobas_2005

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

Page 36: rap_karobas_2005

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

Page 37: rap_karobas_2005

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

Page 38: rap_karobas_2005

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

Page 39: rap_karobas_2005

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

Page 40: rap_karobas_2005

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

Page 41: rap_karobas_2005

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

Page 42: rap_karobas_2005

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

Page 43: rap_karobas_2005

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

Page 44: rap_karobas_2005

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

Page 45: rap_karobas_2005

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

Page 46: rap_karobas_2005

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

Page 47: rap_karobas_2005

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

Page 48: rap_karobas_2005

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

Page 49: rap_karobas_2005

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

Page 50: rap_karobas_2005

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

Page 51: rap_karobas_2005

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

Page 52: rap_karobas_2005

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

Page 53: rap_karobas_2005

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

Page 54: rap_karobas_2005

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

Page 55: rap_karobas_2005

Année 2004 - 2005

Annexes

Karobas.fr 55 / 93

Page 56: rap_karobas_2005

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

Page 57: rap_karobas_2005

Année 2004 - 2005

Réseau de l'entreprise

Karobas.fr 57 / 93

Page 58: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 58 / 93

Page 59: rap_karobas_2005

Année 2004 - 2005

Schéma conceptuel de la nouvelle base de données

Karobas.fr 59 / 93

Page 60: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 60 / 93

Page 61: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 61 / 93

Page 62: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 62 / 93

Page 63: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 63 / 93

Page 64: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 64 / 93

Page 65: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 65 / 93

Page 66: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 66 / 93

Page 67: rap_karobas_2005

Année 2004 - 2005

Cahier des chargesObjectifs du developpement

(documents fournis par l'entreprise)

Karobas.fr 67 / 93

Page 68: rap_karobas_2005

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

Page 69: rap_karobas_2005

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

Page 70: rap_karobas_2005

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

Page 71: rap_karobas_2005

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

Page 72: rap_karobas_2005

Année 2004 - 2005

Script SQL2Phpclass(disponible à l'adresse : http://thibault.normand.free.fr/projects/)

Karobas.fr 72 / 93

Page 73: rap_karobas_2005

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

Page 74: rap_karobas_2005

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

Page 75: rap_karobas_2005

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

Page 76: rap_karobas_2005

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

Page 77: rap_karobas_2005

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

Page 78: rap_karobas_2005

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

Page 79: rap_karobas_2005

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

Page 80: rap_karobas_2005

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

Page 81: rap_karobas_2005

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

Page 82: rap_karobas_2005

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

Page 83: rap_karobas_2005

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

Page 84: rap_karobas_2005

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&eacute;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

Page 85: rap_karobas_2005

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&eacute;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

Page 86: rap_karobas_2005

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&eacute;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

Page 87: rap_karobas_2005

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

Page 88: rap_karobas_2005

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&eacute;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

Page 89: rap_karobas_2005

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

Page 90: rap_karobas_2005

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

Page 91: rap_karobas_2005

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

Page 92: rap_karobas_2005

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>&nbsp;\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

Page 93: rap_karobas_2005

Année 2004 - 2005

Karobas.fr 93 / 93