La Gestion de Contenu Simplifiée
Construire un module - part V
Dans l'épisode précédent...
Nous avions finit le dernier article avec un module qui possédait tous les accès depuis l'administration, mais restait quelques peu inactif...
Du coup, au menu du jour : nous allons intégrer tout le code du nettoyeur + gérer le multilangue + ré-aborder un peu de sécurité en SQL + tester notre module finit !
Du nouveau code dans le moduleje
J'ai donc commencé par modifier le fichier CmsModuleCleaner.module.php afin d'ajouter toutes les fonctions que je vais devoir utiliser :
/**
* Renvoi la liste possible des langues supprimable
*/
function _getLang()
{
$langs = array();
$langs[""ca_ES""] = array(""ca_ES"");
$langs[""cs_CZ""] = array(""cs_CZ"");
$langs[""da_DK""] = array(""da_DK"");
$langs[""de_DE""] = array(""de_DE"");
$langs[""es_ES""] = array(""es_ES"");
$langs[""en_CY""] = array(""en_CY"");
$langs[""et_EE""] = array(""et_EE"");
$langs[""fa_FA""] = array(""fa_FA"");
$langs[""fi_FI""] = array(""fi_FI"");
$langs[""fr_FR""] = array(""fr_FR"",""fr"");
$langs[""hr_HR""] = array(""hr_HR"");
$langs[""hu_HU""] = array(""hu_HU"");
$langs[""it_IT""] = array(""it_IT"");
$langs[""iw_IL""] = array(""iw_IL"");
$langs[""ja_JP""] = array(""ja_JP"");
$langs[""lt_LT""] = array(""lt_LT"");
$langs[""nb_NO""] = array(""nb_NO"");
$langs[""nl_NL""] = array(""nl_NL"",""nl"");
$langs[""pl_PL""] = array(""pl_PL"");
$langs[""pt_PT""] = array(""pt_PT"");
$langs[""ro_RO""] = array(""ro_RO"");
$langs[""ru_RU""] = array(""ru_RU"");
$langs[""sk_SK""] = array(""sk_SK"");
$langs[""sl_SI""] = array(""sl_SI"");
$langs[""so_SO""] = array(""so_SO"");
$langs[""sr_YU""] = array(""sr_YU"");
$langs[""sv_SE""] = array(""sv_SE"");
$langs[""tr_TR""] = array(""tr_TR"");
return $langs;
}
function _getModulesDirectoriesToPattern()
{
$dirs = array();
global $gCms;
$config = $gCms->GetConfig();
$path = $config['root_path'].'/modules';
if (!is_dir($path) || !$handle = @dir($path)) {
trigger_error('''.$path.'' doesn't exists or is not a valid directory', E_USER_ERROR);
} else {
while ($entry = $handle->read()) {
if ($entry !== ""."" && $entry !== "".."") {
$path_to_entry = $path.'/'.$entry;
if (@is_dir($path_to_entry)) {
$dirs[] = $path_to_entry;
}
}
}
}
$patterns = array();
foreach($dirs as $dir){
$patterns[] = array(FILE,$dir.'/lang/ext/%1$s.php');
}
return $patterns;
}
function _getPatterns()
{
define (""DIR"", ""D"");
define (""FILE"", ""F"");
$patterns = array();
$patterns = $this->_getModulesDirectoriesToPattern();
$patterns[] = array(FILE,'/admin/lang/%1$s.nls.php');
$patterns[] = array(FILE,'/admin/lang/ext/%1$s/admin.inc.php');
$patterns[] = array(FILE,'/admin/lang/ext/%1$s/index.html');
$patterns[] = array(DIR,'/admin/lang/ext/%1$s');
$patterns[] = array(FILE,'/lib/lang/cms_selflink/ext/%1$s.php');
$patterns[] = array(FILE,'/lib/lang/tasks/ext/%1$s.php');
$patterns[] = array(FILE,'/lib/filemanager/ImageManager/lang/%2$s.js');
return $patterns;
}
function _cleaner($delete)
{
$temps_debut = microtime(true);
define (""DELETE"", $delete);
$pipe = '|';
$lang_to_delete = $this->GetPreference('liste_langue_a_supprimer');
$lang_to_delete = explode($pipe,$lang_to_delete);
$patterns = $this->_getPatterns();
$langs = array();
foreach ( $this->_getLang() as $key => $item)
{
if(in_array($key, $lang_to_delete))
{
$langs[] = $item;
}
}
$size=0;
//Pour chaque patterns
$liste_objet = array();
foreach($patterns as $pattern){
//pour chaque langue
foreach($langs as $lang){
$objet = new stdclass;
//On remplace les %1$ et %2$ par la langue
$path = sprintf($pattern[1], $lang[0], (count($lang)>1?$lang[1]:null));
//Si le pattern désigne un fichier qui existe
if($pattern[0] == FILE && @is_file($path)){
//on récupère son poids
$stat = stat($path);
$size += $stat[7];
$objet->path = $path;
$objet->size = $stat[7];
//Si à supprimer, on supprime
if(DELETE)
unlink($path);
//Si le pattern est un répertoire qui existe
} else if($pattern[0] == DIR && @is_dir($path)){
$objet->path = $path;
//Si à supprimer, on supprime
if(DELETE)
rmdir($path);
}
if(isset($objet->path))
{
$liste_objet[] = $objet;
}
}
}
if(DELETE)
{
$this->_makeLog($this->Lang('numberFileDeleted',count($liste_objet),$size));
} else
{
$this->_makeLog($this->Lang('numberFileDeletable',count($liste_objet),$size));
}
return $liste_objet;
}
function _makeLog($str)
{
global $gCms;
$db =& $gCms->GetDb();
// Récupération d'un nouvel identifiant depuis la séquence
$sid = $db->GenID(cms_db_prefix().'module_cmsModuleCleaner_log_seq');
$queryInsert = 'INSERT INTO '.cms_db_prefix().'module_cmsModuleCleaner_log (log_id,log_date,log_texte) values (?,?,?)';
$param = array($sid, trim($db->DBTimeStamp(time()), ""'""), $str);
$result = $db->Execute($queryInsert, $param);
if ($result === false){die(""Database error durant l'insert de la donnée! $queryInsert"");}
}
Alors évidement en soit ce n'est pas très intéressant de se pencher sur le contenu des fonctions, c'est simplement une adaptation de ma toute première création : l'UDT de nettoyage des fichiers de langues. Ce qui est par contre plus intéressant c'est le fait que je nomme toujours mes noms de fonction par un underscore.
En effet rien ne m'assure que demain une fonction ""makeLog()"" ne sera pas implémentée dans le noyau des modules de cmsmadesimple. Quelle assurance alors que mon code fonctionne encore ? Ainsi il a été acté par les développeurs de cmsmadesimple que toutes les fonctions ajoutées dans les fichiers xxx.module.php devront, par sécurité, commencer par un underscore.
Notez également que :
- Pour faire appel à une fonction depuis une autre fonction j'utilise $this->autreFonction(), c'est la programmation objet qui nécessite cela.
- Dans mes fonctions, je place parfois un global $gCms; avant toute chose, c'est pour récupérer la valeur de $gCms dans la fonction. Sans ce code cette variable vaudrait null. On appel ce principe la ""portée d'une variable""
Une fois ce code ajouté, je vais évidement pouvoir y faire appel depuis les pages de l'administration en y ajoutant un bouton de traitement, ainsi qu'une nouvelle option dans la partie paramétrage du module.
Gestion SQL
On reviendra peut être rapidement sur la fonction _makeLog($str) qui fait des insertions en bases. On avait déjà vu le select dans les précédents billets, voici son pendant : l'Insert SQL. Notez que je vous conseille énormement (impérativement?) de passer les paramètres tel que je l'ai fait :
$queryInsert = 'INSERT INTO '.cms_db_prefix().'module_cmsModuleCleaner_log (log_id,log_date,log_texte) values (?,?,?)';
$param = array($sid, trim($db->DBTimeStamp(time()), ""'""), $str);
$result = $db->Execute($queryInsert, $param);
et non comme certains le font :
$sql = ""select * from table where id = "" + $id;
$result = $db->Execute($sql);
La raison déjà évoquée dans d'autres articles du blog porte sur la sécurité de votre module contre les SQL-injections !
Donc attention !
Page d'action
Nous avions jusque maintenant peu de page php, et pour dire en fait la seule page appelée était action.defaultadmin.php
Passons un peu de temps sur la façon dont CmsMadeSimple permet de naviguer entre les différentes actions. Dans notre cas je veux un bouton sur l'admin qui amène à une page de traitement que je nommerais arbitrairement ""execute"" (nettoyage dans notre cas) avant de revenir sur la page d'admin.
on aura donc : action.defaultadmin.php => action.execute.php => action.defaultadmin.php
La création du lien dans l'administration se fait ainsi :
$this->CreateLink($id, 'execute', $returnid, $this->Lang('execute'))
On retrouve en second paramètre le nom de l'action qui permet à CmsMadeSimple de naviguer de fichier action en fichier action. Le 4ème paramètre étant simplement le libellé à afficher (français ou anglais selon la langue du back office)
De l'autre côté nous avons le fichier d'exécution : action.execute.php qui contient finalement très peu de code, et tant mieux pour sa lisibilité !
<?php
//Securite
if (!isset($gCms)) exit;
// Verification de la permission
if (! $this->VisibleToAdminUser()) {
echo $this->ShowErrors($this->Lang('accessdenied'));
return;
}
$delete = $this->GetPreference('suppression');
//Appel au nettoyage, fonction présente dans le noyau du module : cmsModuleCleaner.module.php
$listeResultat = $this->_cleaner($delete);
[...]
$smarty->assign('return_link',$this->CreateLink ($id, 'defaultadmin', $returnid, $this->Lang('return')));
echo $this->ProcessTemplate('executeadmin.tpl');
?>
On retrouve les principes de sécurité dont j'ai déjà parlé dans les précédents articles, suivit d'un appel aux fonctions contenues dans le noyaux du module que l'on a vu en début de cet article. Enfin la création d'un lien pointant vers defaultadmin, la page d'accueil de l'administration du module.
La boucle est bouclée : action.defaultadmin.php => action.execute.php => action.defaultadmin.php
Notez que l'API de CmsMadeSimple permet des redirections post-traitement évitant de dépenser un clic pour passer de action.execute.php à action.defaultadmin.php.
Gestion de l'internationnalisation du module
Attention : ici je ne parle pas forcement de configurer votre module pour gérer 34 langues sur le front-office avec toutes les astuces SEO qui vont avec, je n'aborderais ici que les notions de multilangue envers les utilisateurs du module : tant les webmasters pour le back-office, que les éventuels visiteurs si notre module possédait un front-office.
Dans cette version, l'intégralité des phrases et mots sont traduits en langues FR et EN (Français et Anglais). Maintenant que nous avons besoin de ces doubles fichiers Anglais ET Français il est intéressant de se pencher sur la manière dont CmsMadeSimple gère les fichiers de langues, comme à son habitude : très simplement.
En premier point créez les fichiers et répertoires suivant à la racine de votre module :
- ./lang/ext
- ./lang/ext/fr_FR.php
Configuration Terminée (oui je sais : déjà) ! Il vous reste maintenant à dédoubler les messages en anglais dans le fichier d'origine et leur correspondances françaises dans ce nouveau fichier.
Et comment laisser un étranger traduire mon module ?
Attention : Dans notre cas nous allons un peu à l'envers du fonctionnement de la traduction d'un module dans CmsMadeSimple. En effet, dans un cas normal, un module se code simplement en Anglais, s'envoie sur la Forge de CmsMadeSimple, s'inscrit sur le centre de traduction, et enfin se traduit en français, espagnol, russe, ... DEPUIS le centre de traduction.
Disons que dans cet article, j'aborde ce point surtout pour que vous puissiez comprendre le fonctionnement d'un module dans ses grandes lignes, nous reviendrons certainement sur la manière dont un développeur doit procéder pour mettre son module au service de tous sur la Forge un peu plus tard ;)
Exécution du module
Je lance le module en cliquant sur le lien ""Procéder au nettoyage"". J'ai pris soin de décocher la checkbox ""Suppression définitive""
On y retrouve le traitement de simulation de la suppression des fichiers comme le proposait l'UDT, avec une belle couche de design en plus ! Enfin vous allez voir que le log est bien enrichit.
Allez un second essai : je recoche la checkbox et je relance successivement 2 fois le traitement.
Hop un second nettoyage ?
Notre travail est donc terminé : nous avons bien une suppression propre des fichiers de langues !
Bilan de la 5ème édition et pistes pour creuser vos connaissances
A vous maintenant de me dire si ce petit module disponible en version 1.3.0 fonctionne bien chez vous :)
Alors certains thèmes de programmation n'ont pas encore été abordés. Je peux en citer quelques uns
- L'automatisme des taches plannfiées (même si vous avez pu voir qu'un paramétre était déjà prévu dans l'administration du module)
- La gestion des evènements
- Les Pretty-URL pour un module
- L'affiche de donnée en front-office
- ....
Autant de points important à connaitre pour celui qui désire développer de solides modules sur CmsMadeSimple. Je ne sais pas encore si je vais pouvoir vous faire d'autres articles sur ces sujets précisement mais si c'est le cas je penses partir de notre petit module autant que faire se peut ! Affaire à suivre donc...
Si vous souhaitez maintenant creuser vos connaissances (bandes d'affamés) je vous conseille deux choses :
Installez l'excellant module d'apprentissage Skeleton qui est certes en Anglais mais est surtout un excellant outils de travail pour comprendre toutes les subtilités que peut contenir un module sur CmsMadeSimple... Un must donc.
Le second conseil que je vous donnerais si vous devez vous mettre à coder : lisez lisez et relisez cette précieuse documentation qui est la bible pour tous les développeurs sur CmsMadeSimple!
Voilà pour aujourd'hui, N'hésitez pas à me dire si vous avez des questions sur le fonctionnement de notre petit module et si vous avez eu des idées pour l'améliorer !