ldapsaisie/doc/contrib/contrib.docbook
2020-08-26 11:25:56 +02:00

607 lines
28 KiB
XML

<?xml version="1.0" encoding="UTF-8" ?>
<chapter>
<title>Contribution</title>
<para>Comme tout projet libre qui se respecte, les contributions à LdapSaisie sont les bienvenues. Ce chapitre explique les possibilités de contribution.</para>
<sect1 id="contrib-LSaddons">
<title>LSaddons</title>
<para>Les &LSaddons; sont utilisés pour implémenter dans &LdapSaisie; des fonctionnalités spécifiques tel que le support d'une famille d'attributs spécifiques (POSIX, Samba, SUPANN…) ou encore des tâches communes et génériques (envoi de mails, connexion FTP…). Les &LSaddons; vous permettront également d'adapter &LdapSaisie; à vos besoins spécifiques en écrivant par exemple les fonctions appelées par les déclencheurs ou encore écrire des &customSearchActions; sur des recherches ou des &customActions; sur des &LSobjects;.</para>
<sect2 id="contrib-LSaddons-structure">
<title>Structure d'écriture</title>
<para>L'écriture d'un &LSaddon; doit respecter une structure suffisamment souple afin de ne pas être un frein à vos contributions, tout en permettant d'assurer la bonne intégration de votre contribution au projet. Le code que vous écrirez sera réparti dans deux fichiers :</para>
<variablelist>
<varlistentry>
<term>conf/LSaddons/config.LSaddons.[addon name].php</term>
<listitem><simpara>Ce fichier contiendra la configuration de votre &LSaddon;. On y retrouvera la déclaration de constances et/ou variables de configuration permettant d'adapter votre &LSaddon; à une installation et à un environnement.</simpara></listitem>
</varlistentry>
<varlistentry>
<term>includes/addons/LSaddons.[addon name].php</term>
<listitem><simpara>Ce fichier contiendra le code à proprement dit de votre &LSaddon;.</simpara></listitem>
</varlistentry>
</variablelist>
<programlisting linenumbering="unnumbered">
<citetitle>Structure du fichier includes/addons/LSaddons.[addon name].php</citetitle>
<![CDATA[<?php
/*
* Error messages
*/
// Support error messages
LSerror :: defineError('MYADDON_SUPPORT_01',
___("MYADDON Support : Unable to load %{dep}.")
);
LSerror :: defineError('MYADDON_SUPPORT_02',
___("MYADDON Support : The constant %{const} is not defined.")
);
// Other orror messages
LSerror :: defineError('MYADDON_01',
___("An error : %{msg}.")
);
LSerror :: defineError('MYADDON_02',
___("An other error about %{about} : %{msg}")
);
LSerror :: defineError('MYADDON_03',
___("Unknown error.")
);
/**
* Verify support of my addon by LdapSaisie
*
* @author My Name <my.email@example.com>
*
* @retval boolean true if my addon is totaly supported, false in other cases
**/
function LSaddon_myaddon_support() {
$retval=true;
// Check/load dependencies
if ( !class_exists('mylib') ) {
if ( !LSsession::includeFile(LS_LIB_DIR . 'class.mylib.php') ) {
LSerror :: addErrorCode('MYADDON_SUPPORT_01', 'mylib');
$retval=false;
}
}
$MUST_DEFINE_CONST= array(
'LS_MYADDON_CONF_O1',
'LS_MYADDON_CONF_O2',
...
);
foreach($MUST_DEFINE_CONST as $const) {
if ( (!defined($const)) || (constant($const) == "")) {
LSerror :: addErrorCode('MYADDON_SUPPORT_02',$const);
$retval=false;
}
}
if ($retval) {
// Register LSaddon view using LSsession :: registerLSaddonView()
if (php_sapi_name() == 'cli') {
// Register LSaddon CLI command using LScli :: add_command()
}
}
return $retval;
}
/**
* My first function
*
* Description of this wonderfull function
*
* @author My Name <my.email@example.com>
*
* @retval [type(s) of returned values (pipe separator)] Description of the return of this function
**/
function myaddon_first_function($arg1, $arg2) {
// Do some stuff
if (something) {
LSerror :: addErrorCode(
'MYADDON_01',
'something went wrong' // Error LSformat unique argument
);
return false;
}
if (something else) {
LSerror :: addErrorCode(
'MYADDON_02',
array( // Error LSformat arguments
'about' => 'second step',
'msg' => 'something went wrong'
)
);
return false;
}
if (still something else) {
LSerror :: addErrorCode('MYADDON_03'); // Error without argument
return false;
}
return true;
}
[...]
// Defined custom CLI commands functions only on CLI context
if (php_sapi_name() != 'cli')
return true; // Always return true to avoid some warning in log
// Defined functions handling custom CLI commands and optionnaly
// their arguments autocompleter functions.
]]>
</programlisting>
<para>Par convention, la structure de ce fichier est toujours à peu près la même:
<itemizedlist>
<listitem><para>On déclare tout d'abord les messages d'erreurs qui seront potentiellement émis par notre &LSaddon; en commençant par
les messages d'erreurs liés au support de cet &LSaddon;. On utilise pour cela la méthode <literal>LSerror :: defineError()</literal> qui
attends en premier argument, l'identifiant du message d'erreur et en tant que second argument, le &LSformat; du message d'erreur. Par
convention, les identifiants des messages d'erreurs seront en majuscule et préfixés du nom du &LSaddon;.</para></listitem>
<listitem><para>On déclare ensuite une fonction <literal>LSaddon_[myaddon]_support</literal> qui sera exécuté lors du chargement de
l'addon et qui permettra de s'assurer du support de celui-ci. Cette fonction devra retourner <literal>True</literal> si c'est le cas ou
<literal>False</literal> dans le cas contraire.</para>
<para>Cette fonction s'assura notamment :
<itemizedlist>
<listitem><simpara>que les librairies dont l'addon dépends sont bien chargées et fonctionnelles ;</simpara></listitem>
<listitem><simpara>que les variables et constantes de configuration sont bien définies ;</simpara></listitem>
<listitem><simpara>de déclarer <link linkend='contrib-LSaddon-views'>les vues personnalisées fournies</link> par cet &LSaddon; ;</simpara></listitem>
<listitem><simpara>de déclarer <link linkend='contrib-LSaddon-CLI-commands'>les commandes <emphasis>CLI</emphasis> personnalisées</link> fournies par cet &LSaddon; ;</simpara></listitem>
</itemizedlist>
</para></listitem>
<listitem><para>On déclare ensuite les fonctions, classes et éléments fournies et manipulés par l'addon.</para></listitem>
<listitem><para>Si notre addon offre des <link linkend='contrib-LSaddon-CLI-commands'>commandes <emphasis>CLI</emphasis>
personnalisées</link>, les fonctions les implémentants ne seront définies, dans un souci de performance, que dans un contexte
ou elles seraient potentiellement appelables, c'est à dire dans un contexte d'exécution <literal>CLI</literal>. Pour cela,
nous utilisons communément la fonction <literal>php_sapi_name</literal> pour déterminer le contexte d'exécution et si celui-ci
vaut <literal>cli</literal>, nous stoppons l'exécution du reste du code du fichier via un <literal>return true</literal>.
<note><simpara>Il est important dans ce contexte de ne jamais retourner autre chose que <literal>True</literal> pour éviter tout message
d'erreur inutile dans les logs.</simpara></note>
</para></listitem>
<listitem><para>On déclare, pour finir, les fonctions implémentant les <link linkend='contrib-LSaddon-CLI-commands'>commandes
<emphasis>CLI</emphasis> personnalisées</link> et leur éventuelle fonction gérant l'autocomplétion des arguments qu'elles acceptent.
</para></listitem>
</itemizedlist>
</para>
</sect2>
<sect2 id="contrib-LSaddon-views">
<title>Les vues personnalisées</title>
<para>Les &LSaddons; peuvent fournir des vues personnalisées qui seront accessibles à tout ou parties des utilisateurs de l'application.
Ce filtrage d'accès sera fait en utilisant les &LSprofiles; de l'utilisateur connecté sur la <link linkend="config-subDn">racine
courante de l'annuaire LDAP</link>.</para>
<para>Pour mettre en place une telle vue personnalisée, il est nécessaire de :
<itemizedlist>
<listitem><simpara>Déclarer cette vue dans la fonction <literal>LSaddon_[addon]_support</literal> de l'addon à l'aide de la méthode
<literal>LSsession :: registerLSaddonView</literal> ;</simpara></listitem>
<listitem><simpara>Déclarer la fonction implémentant cette vue. Cette fonction n'acceptera aucun paramètre et ne retournera rien.
Elle devra en outre s'occuper de définir son fichier template et charger les dépendances de ce dernier (fichiers <emphasis>CSS
&amp; JS</emphasis>, variables...).</simpara></listitem>
</itemizedlist>
</para>
<para>Pour implémenter une telle vue personnalisée, vous pouvez vous inspirer de l'exemple fourni ci-dessous ou encore des vues fournies
par les autres &LSaddons; (par exemple, l'addon <link linkend="config-LSaddon_exportSearchResultAsCSV">exportSearchResultAsCSV</link>).
</para>
<programlisting linenumbering="unnumbered">
<citetitle>Structure du fichier includes/addons/LSaddons.[addon name].php</citetitle>
<![CDATA[<?php
function LSaddon_myaddon_support() {
$retval=true;
// Some check
if ($retval) {
$retval = LSsession :: registerLSaddonView(
'myaddon', // addon name
'myaddon_view', // addon view ID
__('MyAddon view'), // addon view label
'myaddon_view', // callable (ex: function name) that implement addon view
array('user'), // array listing allowed LSprofiles
true // Show/hide this addon view in user menu
);
}
return $retval;
}
[...]
/**
* My addon view handler function
*
* Description of this view
*
* @author My Name <my.email@example.com>
*
* @retval void
**/
function myaddon_view() {
// Do some stuff and set some template variables
$list = array ([...]);
LStemplate :: assign('list', $list);
// Load some CSS & JS files need on this view
LStemplate :: addCssFile('LSaddon_myadon.css');
LStemplate :: addJSscript('LSaddon_myadon.js');
// Set template file of the view
LSsession :: setTemplate('LSaddon_myadon_view.tpl');
}
]]>
</programlisting>
</sect2>
<sect2 id="contrib-LSaddon-CLI-commands">
<title>Les commandes <emphasis>CLI</emphasis> personnalisées</title>
<para>Les &LSaddons; peuvent fournir des commandes <emphasis>CLI</emphasis> personnalisées qui seront accessibles via la commande
<literal>ldapsaisie</literal> fournie avec l'application. Cela peut, par exemple, vous permettre de rendre accessible en ligne de commandes
une procédure implémentée dans le code d'LdapSaisie et vous permettre de mettre en place une tâche planifiée exécutant cette procédure
régulièrement.</para>
<para>Pour mettre en place une telle commande <emphasis>CLI</emphasis> personnalisée, il est nécessaire de :
<itemizedlist>
<listitem><simpara>Déclarer cette vue dans la fonction <literal>LSaddon_[addon]_support</literal> de l'addon à l'aide de la méthode
<literal>LScli :: add_command</literal> ;</simpara></listitem>
<listitem><simpara>Déclarer la fonction implémentant cette commande <emphasis>CLI</emphasis> personnalisée. Cette fonction acceptera,
en tant qu'unique paramètre, un tableau des arguments reçus lors de l'exécution de la commande et retournera <literal>True</literal>
ou <literal>False</literal> en cas de succès/d'erreur d'exécution de la commande. Cette valeur de retour influencera le code retourné
par la commande : <literal>0</literal> en cas de succès, <literal>1</literal> en cas d'erreur.</simpara></listitem>
<listitem><para>Bien que cela ne soit pas obligatoire, il sera également possible de déclarer une fonction permettant l'autocomplétion
des arguments acceptés par la commande.</para>
<para>Cette méthode recevra en paramètre:
<variablelist>
<varlistentry>
<term>$command_args</term>
<listitem>
<simpara>Un tableau des arguments déjà reçus par la commande.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>$comp_word_num</term>
<listitem>
<simpara>Un entier indiquant le rang de l'argument que l'autocomplétion tente de compléter. Il peut s'agir du rang d'un
paramètre déjà fourni et présent dans le tableau <literal>$command_args</literal> ou bien d'un rang supérieur aux nombres
d'arguments déjà fournis à la commande et dans ce cas il s'agira d'autocompléter tous potentiels autre argument que pourrait
accepter cette commande.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>$comp_word</term>
<listitem>
<simpara>Une chaîne de caractères correspondant à ce qu'a déjà saisi l'utilisateur de l'argument que l'on tente
d'autocompléter. Cette chaîne de caractères peut être vide ou non, en fonction de s'il s'agit d'un nouvel argument à
autocompléter ou non.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>$opts</term>
<listitem>
<simpara>Un tableau des potentiels arguments globaux acceptés par <emphasis>LScli</emphasis> dans le contexte actuel (par
exemple, <literal>-d</literal> ou <literal>--debug</literal> pour l'activation du mode debug). La réponse de cette fonction
devra inclure ces potentiels arguments si le contexte d'autocomplétion si prête (nouvel argument par exemple).</simpara>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>Pour finir, cette fonction devra retourner un tableau des potentielles valeurs que pourrait prendre l'argument autocomplété. Si
une unique proposition est faite à l'utilisateur, celle-ci sera automatiquement proposée à l'utilisateur et à défaut, la liste des
valeurs possibles lui seront affichées.</para>
<note><para>Pour vous aider dans l'écrire d'une telle méthode d'autocomplétion, des méthodes statiques sont fournies par la classe
<literal>LScli</literal> pour les autocomplétions les plus courantes:
<variablelist>
<varlistentry>
<term>LScli :: autocomplete_class_name()</term>
<listitem>
<simpara>Autocomplétion du nom d'une classe PHP.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>LScli :: autocomplete_addon_name()</term>
<listitem>
<simpara>Autocomplétion du nom d'un &LSaddon;.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>LScli :: autocomplete_int()</term>
<listitem>
<simpara>Autocomplétion d'un nombre entier.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>LScli :: autocomplete_LSobject_types()</term>
<listitem>
<simpara>Autocomplétion du nom d'un type d'&LSobject;.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>LScli :: autocomplete_LSobject_dn()</term>
<listitem>
<simpara>Autocomplétion du DN d'un type précis d'&LSobject; de l'annuaire.</simpara>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>Par ailleurs, la méthode <literal>LScli :: autocomplete_opts()</literal> vous facilitera la construction de la liste des valeurs
d'autocomplétion de l'argument courant en fonction de ce qui a déjà été saisi par l'utilisateur (paramètre
<literal>$comp_word</literal>). Cette méthode s'occupera en l'occurrence de filtrer parmi toutes les valeurs contextuelles possibles,
celles qui correspondent au préfixe fourni par l'utilisateur.</para></note>
</listitem>
</itemizedlist>
</para>
<para>Pour implémenter une telle commande <emphasis>CLI</emphasis> personnalisée, vous pouvez vous inspirer de l'exemple fourni ci-dessous
ou encore des commandes <emphasis>CLI</emphasis> fournies par les autres &LSaddons; ou classes PHP de l'application.</para>
<programlisting linenumbering="unnumbered">
<citetitle>Structure du fichier includes/addons/LSaddons.[addon name].php</citetitle>
<![CDATA[<?php
function LSaddon_myaddon_support() {
$retval=true;
// Some check
if ($retval) {
if (php_sapi_name() == 'cli') {
LScli :: add_command(
'my_custom_cli_cmd', // The CLI command name (required)
'cli_my_custom_cli_cmd', // The CLI command handler (must be callable, required)
'My custom CLI command', // A short description of what this command does (required)
'[arg1] [arg2] [...]', // A short list of commands available arguments show in usage message
// (optional, default: false)
'This command permit to ...', // A long description of what this command does (optional, default:
// false)
true, // Permit to define if this command need connection to LDAP server
// (optional, default: true)
'cli_my_custom_cli_cmd_autocompleter', // Callable of the CLI command arguments autocompleter (optional,
// default: null)
true // Allow override if a command already exists with the same name
// (optional, default: null)
);
}
}
return $retval;
}
[...]
// Defined CLI commands functions only on CLI context
if (php_sapi_name() != 'cli')
return true; // Always return true to avoid some warning in log
/**
* My addon CLI command my_custom_cli_cmd handler function
*
* Description of this CLI command.
*
* @param[in] $command_args array Command arguments
* - Positional arguments :
* - LSobject
* - dn
* - Optional arguments :
* - -f|--force : Force mode
*
* @author My Name <my.email@example.com>
*
* @retval boolean True on succes, false otherwise
**/
function cli_my_custom_cli_cmd($command_args) {
$objType = null;
$dn = null;
$force_mode = false;
foreach ($command_args as $arg) {
if ($arg == '-f' || $arg == '--force')
$force_mode = true;
elseif (is_null($objType)) {
$objType = $arg;
}
elseif (is_null($dn)) {
$dn = $arg;
}
else
LScli :: usage("Invalid $arg parameter.");
}
if (is_null($objType) || is_null($dn))
LScli :: usage('You must provide LSobject type and DN.');
if (!LSsession :: loadLSobject($objType))
return false;
$obj = new $objType();
if (!$obj->loadData($dn)) {
self :: log_fatal("Fail to load object $dn data from LDAP");
return false;
}
// Do some stuff on loaded object
[...]
return true;
}
/**
* Args autocompleter for CLI my_custom_cli_cmd command
*
* @param[in] $command_args array List of already typed words of the command
* @param[in] $comp_word_num int The command word number to autocomplete
* @param[in] $comp_word string The command word to autocomplete
* @param[in] $opts array List of global available options
*
* @retval array List of available options for the word to autocomplete
**/
public static function cli_my_custom_cli_cmd_autocompleter($command_args, $comp_word_num, $comp_word, $opts) {
$opts = array_merge($opts, array ('-f', '--force'));
// Handle positional args
$objType = null;
$objType_arg_num = null;
$dn = null;
$dn_arg_num = null;
for ($i=0; $i < count($command_args); $i++) {
if (!in_array($command_args[$i], $opts)) {
// If object type not defined
if (is_null($objType)) {
// Defined it
$objType = $command_args[$i];
LScli :: unquote_word($objType);
$objType_arg_num = $i;
// Check object type exists
$objTypes = LScli :: autocomplete_LSobject_types($objType);
// Load it if exist and not trying to complete it
if (in_array($objType, $objTypes) && $i != $comp_word_num) {
LSsession :: loadLSobject($objType, false);
}
}
elseif (is_null($dn)) {
$dn = $command_args[$i];
LScli :: unquote_word($dn);
$dn_arg_num = $i;
}
}
}
// If objType not already choiced (or currently autocomplete), add LSobject types to available options
if (!$objType || $objType_arg_num == $comp_word_num)
$opts = array_merge($opts, LScli :: autocomplete_LSobject_types($comp_word));
// If dn not alreay choiced (or currently autocomplete), try autocomplete it
elseif (!$dn || $dn_arg_num == $comp_word_num)
$opts = array_merge($opts, LScli :: autocomplete_LSobject_dn($objType, $comp_word));
return LScli :: autocomplete_opts($opts, $comp_word);
}
]]>
</programlisting>
</sect2>
</sect1>
<sect1 id="contrib-LSformElements">
<title>LSformElements</title>
<para>Les &LSformElements; sont les types de champs de formulaire supportés par l'application.</para>
<para>Pour chaque type implémenté, on devra trouver :
<itemizedlist>
<listitem><simpara>Une classe PHP dérivée de la classe <literal>LSattr_html</literal> et devant s'appeler
<literal>LSattr_html_[nom du type d'attribut HTML]</literal>. Dans celle-ci, il devra être défini à minima la variable de classe
<literal>LSformElement_type</literal> permettant de référencer le type d'&LSformElement; à utiliser ;</simpara>
</listitem>
<listitem><para>Une classe PHP dérivée de la classe <literal>LSformElement</literal> et devant s'appeler
<literal>LSformElement_[nom du type d'LSformElement]</literal>. Cette classe implémentera tous ce qui concerne l'affichage du champ
dans le formulaire et le traitement d'une valeur retourné par ce dernier. Cela concerne notamment les méthodes suivantes:
<variablelist>
<varlistentry>
<term>getDisplay()</term>
<listitem>
<simpara>Retourne les informations d'affichage du champ dans un formulaire sous la forme d'un tableau <emphasis>(implémentation
obligatoire, pas de méthode par défaut)</emphasis>. Il sera possible de s'appuyer sur la méthode <literal>getLabelInfos()</literal>
permettant de générer et récupérer tous ce qui concerne le label du champ du formulaire. Il faudra cependant à minima fournir
également la clé <literal>html</literal> dans le tableau retourné qui devra contenir le bout de code HTML correspondant au champ
du formulaire. Communément, ce code HTML est généré en appelant la méthode <literal>fetchTemplate()</literal>.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>fetchTemplate()</term>
<listitem>
<simpara>Retourne le code HTML du champ dans le formulaire. L'implémentation de cette méthode est facultative et par défaut, cette
méthode utilisera la variable de classe <literal>$template</literal> pour connaître le fichier de template à utiliser. Ce fichier
de template permettra la génération de la liste de tous les champs associés à chacune des valeurs de l'attribut. Individuellement,
le champ d'une des valeurs de l'attribut est généré à l'aide du fichier de template référencé dans la variable de class
<literal>$fieldTemplate</literal>.</simpara>
<note><simpara>La variable de classe <literal>$fieldTemplate</literal> est également utilisée par la méthode
<literal>LSformElement :: getEmptyField()</literal> qui sert à générer le code HTML d'un champ du formulaire pour une nouvelle
valeur de l'attribut. Cette méthode est notamment utilisée lorsque l'on clique sur le bouton permettant d'ajouter une valeur à
un champ du formulaire.</simpara></note>
</listitem>
</varlistentry>
<varlistentry>
<term>getPostData()</term>
<listitem>
<simpara>Récupère dans les données postées par le formulaire, celle concernant ce champ. Cette méthode devra potentiellement
traiter l'ensemble des valeurs de l'attribut envoyées par le formulaire et les définir dans le tableau passé en référence en tant
que premier argument, les valeurs de l'attribut. L'implémentation de cette méthode est facultative et par défaut, un tableau de
valeurs portant le nom de l'attribut LDAP correspondant sera récupérée comme valeur de l'attribut.</simpara>
<note><simpara>Pour plus d'informations sur le rôle et fonctionnement de cette méthode, référer à la méthode par défaut, définie
dans la classe PHP parente <literal>LSformElement</literal>.</simpara></note>
</listitem>
</varlistentry>
<varlistentry>
<term>setValueFromPostData()</term>
<listitem>
<simpara>Défini les valeurs de l'attribut à partir des données reçus du formulaire (et récupérée par la méthode
<literal>getPostData</literal>). L'implémentation de cette méthode est facultative et par défaut, aucune transformation ne sera
faites à cette étape sur les données récupérées depuis le formulaire. Implémenter cette méthode pourra cependant se révéler utile
en cas de champs de formulaire complexe (attribut composite par exemple).</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>autocomplete_attr_values()</term>
<listitem>
<simpara>Génère de la liste des valeurs possibles de l'attribut dans un contexte <emphasis>CLI</emphasis>.</simpara>
<note><simpara>Pour plus d'informations sur le rôle et fonctionnement de cette méthode, référer aux commentaires de la méthode par
défaut, définie dans la classe PHP parente <literal>LSformElement</literal>. Vous pouvez également vous inspirer des exemples
d'implémentations fournies avec les autres type d'&LSformElement;.</simpara></note>
</listitem>
</varlistentry>
</variablelist>
</para></listitem>
<listitem><simpara>Un (ou plusieurs) fichier template pour la génération du code HTML du champ du formulaire. Communément, le fichier
<literal>LSformElement.tpl</literal> est utilisé pour générer la structure de la liste des champs correspondant aux différentes valeurs
de l'attribut. Ce template utilise une variable <literal>$fieldTemplate</literal> pour définir quel fichier template devra être utilisé
pour générer le code HTML de chaque champ associés à une valeur. C'est ce second fichier de template qui est en général à fournir à
minima avec votre &LSformElement;.</simpara></listitem>
</itemizedlist>
</para>
<note><simpara>Il peut être utile d'étendre un type d'&LSformElement; existant pour faciliter l'implémentation d'un nouveau type. Pour
cela, vous devez utiliser l'héritage de classe PHP en faisant dériver vos nouvelles classes des classes du &LSformElement; dont vous vous
inspirer, plutôt que les classes génériques. Vous pouvez prendre exemple sur le type d'&LSformElement; <literal>pre</literal> qui s'inspire
du type <literal>textarea</literal>, ou encore du type <literal>url</literal> dérivé du type <literal>text</literal>.</simpara></note>
</sect1>
</chapter>