Comme déjà évoqué dans un précédent article, il est possible d’utiliser le moteur de template FLUID dans ses propres développements.
Une particularité intéressante de FLUID est le fait qu’il est possible d’utiliser, dans les templates HTML, des « objets » déclarés par d’autres plugins.
Ces objets, ou « viewhelper », rendent accessibles des fonctionnalités spécifiques par le biais de nouvelles balises.
Il existe des viewhelper fournis par le CMS TYPO3, et il est possible d’en créer de nouveaux.
Jusqu’à maintenant l’ajout de fonctionnalités dans un plugin nécessitait que ce plugin soit conçu dans cette optique d’élargissement.
Il est possible de prévoir des hooks à différents endroits du plugin ou de mettre en place un retraitement typoscript sur certains éléments (permettant l’appel d’une fonction PHP externe).
Si le développeur ne l’a pas prévu, il n’est pas possible intervenir dans le plugin sans passer par une surcharge du code via XCLASS (pas top).
A) Mise en situation
Présentation d’un cas concret.
Nous allons essayer d’ajouter une fonctionnalité « simple » à différents types de plugin.
La fonctionnalité sera d’ajouter un texte/lien à un enregistrement en fonction d’un paramètre propre à chaque enregistrement.
Dans un vrai cas de figure cela pourrait être :
- d’ajouter un lien d’inscription à un évènement en fonction des places disponibles
- d’ajouter un lien pour contacter une personne en fonction de la disponibilité de cette personne
Les différents types de plugin :
- un plugin lambda qui n’a pas de hook ou de retraitement stdWrap
- un plugin avec des hooks (ex le plugin TT_news basé sur pibase)
- un plugin utilisant FLUID (ex le développement de l’exemple « Fluid dans les développements »)
B) Mise en place pour le plugin sans Hook
Dans le cas où le développeur n’a pas prévu de système d’ouverture (par hook ou typoscript) alors la seule solution est de « remplacer » le code d’origine par un code l’évolution attendue.
Cela peut générer des problèmes dans le cas ou le plugin d’origine serait mis à jour, il faudrait dans ce cas reprendre son propre code.
Le principe de fonctionnement du XCLASS est de regarder, au moment d’initialiser un objet, s’il n’existe pas une classe spécifique qui surchargerait l’objet.
Si oui, c’est cette classe spécifique qui est chargée au lieu de l’objet d’origine.
Ce principe rend impossible la surcharge multiple simple, car il n’est possible de surcharger qu’une seule fois un même objet.
Plugin tx_lambda_pi1 :
class tx_lambda_pi1 extends tslib_pibase { //..... function main($content, $conf) { $enregistrements=$this->getListe(); $content=$this->getListe($enregistrements); return $content; } function getListe($enregistrements){ $retour='<ul>'.chr(10); for($i=0;$i<10;$i++){ $retour.='<li>item'.inval($i).' : '.$htmlspecialchars($enregistrements[$i]['nom']).'</li>'.chr(10); } $retour.='</ul>'; return $retour; } }
Xclass : ux_tx_lambda_pi1 :
class ux_tx_lambda_pi1 extends tx_lambda_pi1 { //on reprend la fonction que l'on doit modifier function getListe($enregistrements){ $retour='<ul>'.chr(10); for($i=0;$i<10;$i++){ //on adapte la ligne pour ajouter la nouvelle fonctionnalité $retour.='<li>item'.inval($i).' : '.$htmlspecialchars($enregistrements[$i]['nom']) . $this->reservation($enregistrements[$i]).'</li>'.chr(10); } $retour.='</ul>'; return $retour; } //on ajoute une nouvelle fonction pour generer le lien de reservation function reservation($enregistrement){ //on verifie que l'on peut reserver en testant un champ de l'enregistrement //si l'evenement n'est pas complet if (trim($enregistrement['etat'])!=='complet'){ //id page destination $idPage=18; //tableaux des parametres a inserer dans le lien $confLink=array( 'parameter' => $idPage, 'additionalParams' => '&etape=reservation&idEvent='.intval($enregistrement['id']) ); //generation de l'url $texte=$this->cObj->typoLink('Reservation', $confLink); }else{ //texte en cas de reservation non disponible $texte= 'Complet'; } //on retourne le resultat return $texte; } }
En fonction de l’organisation du plugin d’origine (utilisation d’un tableau de marqueurs et de templates, concaténation directe de code HTML, etc..) il peut être nécessaire de modifie d’autres éléments comme les templates HTML ou ajouter du typoscript.
Mise en garde :
Si le développeur a oublié de prévoir la déclaration XCLASS pour son plugin, son plugin ne peut pas être étendu.
De plus, il ne peut y avoir qu’un seul XCLASS par fichier, donc si plusieurs personnes doivent étendre les fonctionnalités d’un même plugin cela devient encore plus compliqué.
Enfin, si le développeur n’a pas respecté certaines règles (ex : déclarer un objet avec l’instruction « new … » au lieu de l’api « makeInstance »), il ne sera pas possible de prendre en compte le XCLASS sur l’ensemble du développement.
Il est donc fortement déconseillé de passer par un XCLASS… mieux vaut contacter le responsable de l’extension et essayer de voir si il est possible de prévoir une autre solution (comme l’ajout d’un hook dans le code d’origine).
C) Mise en place pour le plugin avec Hook
Dans le cas où le développeur a prévu de système d’ouverture (par hook ) alors le traitement est simplifié.
Un hook est un embranchement dans le code du plugin, prévu par l’auteur, et qui permet d’appeler une ou des fonctions PHP.
Voir l’article sur l’utilisation des hooks dans ses extensions sur le site typo3.org.
Il suffit donc d’effectuer la déclaration de la fonction pour ce hook et de mettre en place le code qui sera appelée.
Le plugin tt_news met en place différents hook comme par exemple « extraItemMarkerHook » qui permet de rajouter des marqueurs pour chaque tt_news de la liste.
Extrait du code du plugin tt_news
// Adds hook for processing of extra item markers if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['tt_news']['extraItemMarkerHook'])) { foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['tt_news']['extraItemMarkerHook'] as $_classRef) { $_procObj = & t3lib_div::getUserObj($_classRef); $markerArray = $_procObj->extraItemMarkerProcessor($markerArray, $row, $lConf, $this); } }
On va donc déclarer son hook (dans le fichier ext_localconf.php) en indiquant quel fichier/classe php doit être appelée par ce hook.
Déclaration du hook :
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['tt_news']['extraItemMarkerHook'][] = 'EXT:monextension/lib/class.tx_monextension_ttnewshook.php:tx_monextension_ttnewshook';
Fichier de la classe appellée par le hook :
class tx_monextension_ttnewshook { //on declare la fonction appellee dans le hook function extraItemMarkerProcessor($markerArray, $row, $lConf, $ttnews){ //si les valeurs ne sont pas conformes if ((!is_array($row))&&(!isset(row['uid']))&&(intval(row['uid'])===0){ //on ne rajoute rien, et on renvoie le tableau des marqueurs return $markerArray; } //si l'evenement n'est pas passe if (intval($row['datetime'])>time()) //id page destination $idPage=18; //tableaux des parametres a inserer dans le lien $confLink=array( 'parameter' => $idPage, 'additionalParams' => '&etape=reservation&idEvent='.intval(row['uid']) ); //generation de l'url $markerArray['###LIEN_RESERVATION###']=$this->cObj->typoLink('Reservation', $confLink); }else{ //sinon (si la reservation n'est pas possible // on met un texte non disponible $markerArray['###LIEN_RESERVATION###']='Non disponible'; } //on renvoie le tableau des marqueurs complété return $markerArray; } }
Il ne reste plus qu’à ajouter le marqueur « ###LIEN_RESERVATION### » dans le template HTML du plugin d’origine.
Cette solution, comparée au XCLASS, est beaucoup plus stable, car les hooks ne sont pas sensés évoluer dans le temps, donc le développement n’a pas à être repris à chaque mise à jour du plugin d’origine.
Malheureusement, il n’y a pas de hook partout dans les plugins, et certaines modifications peuvent nécessiter de repasser par les XCLASS.
D) Mise en place pour le plugin avec FLUID
Dans le cas où le développeur a utilisé FLUID pour les templates, alors le développement est encore plus simple.
En effet, il est possible d’utiliser directement des objets typoscript dans les templates FLUID.
Il est aussi possible de créer des « viewhelper », c’est à dire créer une fonction qui va s’ajouter aux fonctionnalités d’origine de Fluid.
Comme base de travail, nous allons repartir du chapitre D-3) de l’article précédent sur Fluid.
Voici le template HTML d’origine :
<ul> <f:for each="{taches}" as="tache" key="tachekey" iteration="tacheiteration"> <li class="classe<f:cycle values="{0: '1', 1: '0'}" as="cycle">{cycle}</f:cycle>">{tacheiteration.cycle} - {tache.titre} ( {tache.responsable} )</li> </f:for> </ul>
Le but de l’exemple sera de rajouté un lien dans cet liste pour contacter la personne, sans toucher au développement PHP d’origine, sans présence de Hook, et sans XCLASS.
Seul le template sera adapté pour inclure les nouveaux marqueurs.
1) exemple avec un objet typoscript
On ajoute le marqueur typoscript dans le template FLUID auquel on transmet les données de la tache actuelle.
Objet typoscript dans FLUID :
<f:cObject typoscriptObjectPath="lib.contacter" data="{tache}"/>
Le template HTML modifié :
<ul> <f:for each="{taches}" as="tache" key="tachekey" iteration="tacheiteration"> <li class="classe<f:cycle values="{0: '1', 1: '0'}" as="cycle">{cycle}</f:cycle>">{tacheiteration.cycle} - {tache.titre} ( {tache.responsable} <!--appel du viewhelper cObject--> <f:cObject typoscriptObjectPath="lib.contacter" data="{tache}"/> ) </li> </f:for> </ul>
Il suffit alors de créer le typoscript correspondant à « lib.contacter ».
Typoscript de déclaration de la fonction :
#on declare un objet de type USER lib.contacter=USER #on indique quelle classe/fonction doit etre appellee lib.contacter.userFunc = tx_evolutionfluid_lib->contacter
On ne s’attardera pas sur les problèmes d’inclusions de cette classe (via includeLibs, via l’autoloader de TYPO3, ou via les règles d’écriture d’extBase).
Le paramètre « data » de l’objet typoscript sera transmis au code PHP via la propriété $this->cObj->data.
Le code PHP appelé :
class tx_evolutionfluid_lib{ function contacter($content = '', $conf = array()) { //on ne fait qu'une verification simple, dans un developpement propre il faudrait faire plus de verification sous peine de risque de warning if (is_array($this->cObj->data)){ //si l'utilisateur est disponible if($this->cObj->data['disponible']==='OUI')){ //on renvoie son telephone //idem ici il faudrait verifier la validité du telephone return ' : '.htmlspecialchars($this->cObj->data['telephone']); }else{ //sinon on renvoie un lien vers son email return ' : '.$this->cObj->getTypoLink('Il est à la pêche, spammez son email', $this->cObj->data['email']); } }else{ //sinon (si data non disponible) //on ne retourne rien return ''; } } }
Le résultat généré :
Le résultat est équivalent au chapitre D-4) de l’article précédent sur Fluid, c’est à dire avec la modification du template FLUID pour ajouter une condition sur la disponibilité de l’intervenant.
2) exemple de viewhelper
Pour rappel, les viewhelpers sont des balises spéciales dans FLUID dont le but est de mettre à disposition un traitement spécifique.
Il en existe déjà plusieurs comme par exemple :
- la génération de lien : f:link.action, f:link.email, f:link.externe, f:link.page
- la gestion des boucles : f:for
- la gestion des conditions : f:if, f:then, f:else
- la gestion du typoscript (vu juste au dessus) : f:cObject
Au niveau écriture il est composé d’un namespace et du nom du viewhelper.
ex : f:cObject avec f qui correspond au namespace générique de FLUID, et cObject qui correspond au viewhelper pour les objets typoscript.
Il est assez facile de créer son propre viewhelper. On va un créer un et l’appeler « contacter ».
Pour qu’ils soient chargé automatiquement, on peut appliquer la configuration suivante (voir les règles de nommage) :
- le nom du fichier : « /typo3conf/ext/evolutionfluid/Classes/ViewHelpers/ContacterViewHelper.php »
- le nom de la classe : « Tx_Evolutionfluid_ViewHelpers_ContacterViewHelper »
- le namespace dans le template : « Tx_Evolutionfluid_ViewHelpers »
Le template devient donc :
<!--declaration du namespace--> {namespace evolutionfluid=Tx_Evolutionfluid_ViewHelpers} <ul> <f:for each="{taches}" as="tache" key="tachekey" iteration="tacheiteration"> <li class="classe<f:cycle values="{0: '1', 1: '0'}" as="cycle">{cycle}</f:cycle>">{tacheiteration.cycle} - {tache.titre} ( {tache.responsable} <!--appel du viewhelper--> <evolutionfluid:contacter tache="{tache}"/> ) </li> </f:for> </ul>
et la classe PHP (/typo3conf/ext/evolutionfluid/Classes/ViewHelpers/ContacterViewHelper.php ) :
class Tx_Evolutionfluid_ViewHelpers_ContacterViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper { /** * genere un texte/lien en fonction des parametres d'une tache. * * @param array $tache la tache dans la boucle d'affichage * @return string lien en fonction de la disponibilite de la personne * @author demo <demo@demo.tld> */ public function render($tache) { //on ne fait qu'une verification simple, dans un developpement propre il faudrait faire plus de verification sous peine de risque de warning if (is_array($tache)){ //si l'utilisateur est disponible if($tache['disponible']==='OUI'){ //on renvoie son telephone //idem ici il faudrait verifier la validité du telephone return ' : '.htmlspecialchars($tache['telephone']); }else{ //sinon on renvoie un lien vers son email //on declare la classe pour le cObj et le typolink $this->cObj= t3lib_div::makeInstance('tslib_cObj'); //on genere le texte de retour base sur un typolink return ' : '.$this->cObj->getTypoLink('Il est à la pêche, spammez son email', $tache['email']); } }else{ //sinon (si pas un array) return ''; } } }
E) règles de nommage extBase
1) Concernant les fichiers/dossiers/classes PHP :
Le principe est que le nom de la classe à charger reflète l’arborescence utilisée en prenant en compte les règles suivantes :
- les underscores dans le nom de l’extensions sont retirés et transformés en UpperCamelCase (« mon_extension » s’écrit « MonExtension »)
- chaque underscore apres le nom de l’extension indique la descente dans un sous dossier ( « _demo » signifie sous dossier « demo » )
- le répertoire de base des classes extBase est [extension]/Classes/
- Chaque mot doit commencer par une majuscule (règle « UpperCamelCase »)
- le nom de la classe doit commenter par « Tx_ »
- la dernière partie de la classe (apres le dernier _) correspond au nom du fichier PHP (avec l’ajout de « .php » )
2) Concernant les namespaces dans fluid
- le namespace reprend le chemin d’accès à la classe PHP (sans le nom du fichier PHP)
3) Concernant les ViewHelpers
- le fichier PHP du viewhelper doit se situer dans le dossier correspondant au namespace et se composer du nom du viewhelper+ »ViewHelper.php »
- le nom de la classe PHP du viewhelper doit être composé du namemespace et du nom du viewhelper et étendre la classe « Tx_Fluid_Core_ViewHelper_AbstractViewHelper »
- la classe du viewhelper doit contenir la fonction « render » et retourner le texte HTML qui sera utilisé par FLUID
- si la fonction render doit accepter des paramètres, il faut les déclarer en mode « docComment « .
=> exemple si la fonction doit accepter un tableau : /** .... * @param array $variable tableau des informations d'un enregistrement ... */
4) Complément pour TYPO3 V6
- il faut utiliser la déclaration du namespace de PHP ex : Vendeur\Evolutionfluid\ViewHelpers
- le nom de la classe ne contient plus l’arborescence mais uniquement le nom du viewhelper
- la classe a étendre pour les ViewHelpers est \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper (écriture en mode « namespaced » de TYPO3 v6)
- le viewhelper a utiliser dans FLUID est celui déclaré dans PHP
5) Exemples :
- un namespace nommé « Tx_Evolutionfluid_Fonctions » doit avoir son code PHP dans le dossier « /typo3conf/ext/evolutionfluid/Classes/Fonctions/«
- pour bien reconnaitre un dossier contenant des ViewHelper, il est conseillé d’utiliser le nom « ViewHelpers » ce qui donne un namespace nommé « Tx_Evolutionfluid_ViewHelpers » et doit avoir son code PHP dans le dossier « /typo3conf/ext/evolutionfluid/Classes/ViewHelpers/«
- un viewhelper nommé « contacter » (avec le namespace « Tx_Evolutionfluid_ViewHelpers ») doit être dans le fichier « /typo3conf/ext/evolutionfluid/Classes/ViewHelpers/ContacterViewHelper.php«
- la déclaration de la classe du ViewHelper « contacter » sera : class Tx_Evolutionfluid_ViewHelpers_ContacterViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper {…}
- le code minimum de la classe du ViewHelper « contacter » sera : public function render(){ return ‘Ca marche’; }
F) Complément sur les ViewHelpers
Par défaut, le noyau de TYPO3 fournit plusieurs ViewHelpers comme par exemple pour générer des liens ou pour gérer les boucles. Mais il existe beaucoup d’autres.
Evoqué pendant l’université TYPO3, le site Fedext.net répertorie un grand nombre de ces balises spéciales.
Un travail extra-ordinaire a été réalisé par l’auteur du site (Claus Due aka. @NamelessCoder) pour répertorier et rendre compréhensible un grand nombre de collections de ViewHerlpers.
On y trouve les éléments d’extensions propres à l’auteur (vhs, flux, tool, et biens d’autres) mais aussi ceux du Core de TYPO3 (fluid) et d’autres extensions (news ou powermail)
G) Conclusions
En utilisant les principes de fonctionnement de FLUID, il est possible de rajouter une fonctionnalité dans n’importe quel endroit d’une présentation.
Il n’est plus nécessaire que le plugin d’origine soit imaginé dans l’optique d’être ouvert, et il n’y a plus besoin de hook, de typoscript complexe ou pire… de XCLASS.
Imaginez prendre un plugin d’actualité, y rajouter des éléments propres à votre site comme par exemple la réservation à un évènement en fonction de critères dans la base, tout en conservant le fonctionnement général du plugin d’actualité…. et sans trop de crainte lors de la prochaine mise à jour… au pire cela se limitera à une nouvelle petite modification dans le template FLUID, le top non?
Un grand merci pour ce bien bel article Olivier !!!