Fr CreatePlugin084 » History » Version 39

Version 38 (yllen, 11/16/2013 06:09 PM) → Version 39/45 (maximeV, 03/27/2014 03:09 PM)

// documentation en cours de rédaction //
///////////////////////////////////////////////////////////////

{{toc}}

Nous allons voir comment créer pas à pas un plugin en 0.84

Mais avant de commencer, il faut avoir les règles de développement d'un plugin dans GLPI

h1. Qu'est-ce qu'un plugin ?

Le plugin est une extension permettant d'ajouter des fonctionnalités à GLPI.
Il se présente sous la forme d'un dossier à ajouter dans le répertoire _plugins_ de l'arborescence de GLPI.
Ce dossier va contenir tous les fichiers se rapportant au plugin.

*Ce nom de répertoire ne doit comprendre que des caractères alphanumériques (pas de - _ . ou autre)*

Un plugin ne modifie jamais la structure des tables de GLPI. Il se contente d'ajouter des tables dans la base de donnée MySQL, pour gérer ses propres donnée.

h1. Avant de commencer

* Il est toujours utile de :
> * passer en mode DEBUG (PARAMÉTRABLE DANS VOS PRÉFÉRENCES)
> * activer le tracage des logs (dans la configuration de GLPI), c'est d'une aide précieuse dans les développements et la vérification du bon fonctionnement
> * vérifier que MySQL est bien en mode strict (dans la section [mysqld] du fichier my.cnf ajouter :
<pre>
sql-mode = STRICT_ALL_TABLES
</pre>

et relancer MySQL)
> * récupérer le plugin Example qui contient toutes les fonctions utilisables dans un plugin https://forge.indepnet.net/projects/example/files

h1. Convention de programmation

h2. 1. Noms de tables

* Vos noms de tables doivent respecter la règle suivante : glpi_plugin_<plugin_name>objet
le nom de l'objet est toujours au pluriel (ex : pour la table des profiles du plugin => glpi_plugin_monplugin_profiles)

h2. 2. Arborescence des fichiers du plugin :

* A la racine :
> * en obligatoire :
> > * hook.php
> > * setup.php
> * en facultatif
> > * index.php

* Un répertoire "ajax" (facultatif) contenant les fichiers pour tout ce qui touche à l'ajax
* Un répertoire "front" contenant les formulaires
> * Les noms de fichiers devront être normalisés, par exemple :
> > * pour le formulaire de création/édition d'un objet : <objet>.form.php (nom de l'objet au singulier (ex : profile.form.php
> > * pour l'affichage du moteur de recherche associé à un objet : <objet>.php ( (nom de l'objet au singulier)
* Un répertoire "inc" contenant les classes et fonctions
> * Les noms de fichiers devront être normalisés pour les classes : plugin<plugin_name><nom_objet..class.php
Par exemple : pluginMonpluginProfile.class.php
> * Dans ces classes, les noms des fonctions devront être normalisées si la class étend un class mère
Par exemple :
- la fonction canCreate est définie en static dans le coeur, celle du plugin devra également l'être
- la fonction getTypeName() du coeur attend un paramètre $nb, celle du plugin devra également avoir un
- la fonction getTypeName() du coeur attend un paramètre $nb, celle du plugin devra également avoir un paramètre de défini, même s'il n'est pas utilisé
* Un répertoire "pics" (facultatif) contenant les images

En cas de plugin multi-langues, il faudra obligatoirement
* Un répertoire "locales" contenant les fichiers des différentes langues
* Un répertoire "tools" contenant les outils pour la génération des différentes langues

* Si votre plugin doit créer des fichiers temporaires :
> * ceux-ci doivent être créés dans le répertoire files de GLPI sous files/_plugins/<plugin_name>.
> * il faut bien penser à créer le répertoire files/_plugins/<plugin_name> lors de l'installation du plugin et à le supprimer lors de sa suppression

Ceci permet d'éviter la gestion des droits d'écriture à chaque release de votre plugin.
Les droits lors de l'installation ou la mise à jour de GLPI s'appliqueront.

h2. 3. Code à utiliser

Vous pouvez utiliser les fonctions du coeur de GLPI dans votre plugin.
Dans les principales, le coeur a prévu des points d'entrer afin que votre plugin puisse agir sur un objet du coeur.
Ces points d'entrer sont définir en Plugin::doHook(nomDuHookAutiliser).
Il faut bien penser à définir le hook utiliser dans l'initialisation de votre plugin.

h1. Création d'un plugin pas à pas

h2. Faire apparaitre le nom du plugin dans la liste des plugins de GLPI

h3. Définition de la version du plugin et de sa compatibilité avec la version du coeur

- créer un dossier portant le nom de votre plugin dans glpi/plugins/ (dans mon exemple, il se nomme devplugin)

- dans ce dossier, créer un fichier *setup.php* qui contiendra :

* la version de votre plugin et sa compatibilité

<pre>
function plugin_version_devplugin() {

return array('name' => "Mon Plugin",
'version' => '1.0.0',
'author' => 'Moi',
'license' => 'GPLv2+',
'homepage' => 'https://forge.indepnet.net/repositories/show/monplugin',
'minGlpiVersion' => '0.84');// For compatibility / no install in version < 0.80

}
</pre>
name => nom qui apparaitra
homepage => correspond à la page de votre plugin dans la forge des plugins, si vous souhaitez le rendre disponible
minGlpiVersion => la version de GLPI à compter de laquelle votre plugin sera compatible
Toutes ces informations sont visibles dans le tableau Configuration > Plugins

Dans l'exemple, le nom est mis en chaine de texte directement dans le code. Nous verrons plus tard comment faire un plugin multi-langues

* le blocage à une version spécifique de GLPI.
GLPI évoluant constamment au niveau des fonctions du coeur, il est conseillé de créer un plugin en le bloquant à la version courante, quite à modifier la fonction pour une version ultérieure de GLPI. Dans mon exemple, le plugin ne sera opérationnel qu'avec la version 0.84 de GLPI

<pre>
function plugin_devplugin_check_prerequisites() {

if (version_compare(GLPI_VERSION,'0.84','lt') || version_compare(GLPI_VERSION,'0.85','gt')) {
echo "This plugin requires GLPI >= 0.84 and GLPI < 0.85";
return false;
}
return true;
}
</pre>

* le controle de la configuration

<pre>
function plugin_devplugin_check_config($verbose=false) {
if (true) { // Your configuration check
return true;
}

if ($verbose) {
echo 'Installed / not configured';
}
return false;
}
</pre>

* l'initialisation du plugin

<pre>
function plugin_init_devplugin() {
global $PLUGIN_HOOKS;

$PLUGIN_HOOKS['csrf_compliant']['devplugin'] = true;
}
</pre>

h3. Création du squelette d'installation et de désinstallation du plugin

- dans le dossier du plugin, créer un fichier *hook.php* qui contiendra :

<pre>
function plugin_devplugin_install() {
return true;
}

function plugin_devplugin_uninstall() {
return true;
}
</pre>

C'est dans ces fonctions que vous devrez mettre vos requetes SQL servant à la création de vos tables spécifiques.

Voilà, vous pouvez maintenant voir votre plugin dans la liste des plugins.

h2. Gérer les droits d'utilisation du plugin

h3. Créer les tables du plugin

Si vous autorisez l'accès à votre plugin, vous devez définir quel profil aura ce droit.

dans le fichier *hook.php* créé précedemment, ajouter dans la fonction plugin_devplugin_install()
(laisser le return true à la fin)

<pre>
global $DB;

$migration = new Migration(100);

// Création de la table uniquement lors de la première installation
if (!TableExists("glpi_plugin_devplugin_profiles")) {

// requete de création de la table
$query = "CREATE TABLE `glpi_plugin_devplugin_profiles` (
`id` int(11) NOT NULL default '0' COMMENT 'RELATION to glpi_profiles (id)',
`right` char(1) collate utf8_unicode_ci default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";

$DB->queryOrDie($query, $DB->error());

$migration->executeMigration();

//creation du premier accès nécessaire lors de l'installation du plugin
include_once(GLPI_ROOT."/plugins/devplugin/inc/profile.class.php");
PluginDevpluginProfile::createAdminAccess($_SESSION['glpiactiveprofile']['id']);
}
</pre>
Concernant la dernière ligne, elle correspond à l'appel d'une fonction statique dans une class que nous créérons ultérieurement
(une fonction statique est toujours appelée avec NomDeLaClass::NomDeLaFonction)

- Dans le dossier du plugin, créer un dossier inc

- Dans ce dossier inc, créer un fichier *profile.class.php* qui contiendra la fonction appelée dans le fichier hook.php
ainsi que les fonctions de creation et de vision.

<pre>
class PluginDevpluginProfile extends CommonDBTM {

static function canCreate() {

if (isset($_SESSION["glpi_plugin_devplugin_profile"])) {
return ($_SESSION["glpi_plugin_devplugin_profile"]['devplugin'] == 'w');
}
return false;
}

static function canView() {

if (isset($_SESSION["glpi_plugin_devplugin_profile"])) {
return ($_SESSION["glpi_plugin_devplugin_profile"]['devplugin'] == 'w'
|| $_SESSION["glpi_plugin_devplugin_profile"]['devplugin'] == 'r');
}
return false;
}

static function createAdminAccess($ID) {

$myProf = new self();
// si le profile n'existe pas déjà dans la table profile de mon plugin
if (!$myProf->getFromDB($ID)) {
// ajouter un champ dans la table comprenant l'ID du profil d la personne connecté et le droit d'écriture
$myProf->add(array('id' => $ID,
'right' => 'w'));
}
}
}
</pre>

Déclarer cette nouvelle class en modifiant le fonction plugin_init_devplugin() du fichier *setup.php*

<pre>
Plugin::registerClass('PluginDevpluginProfile');
</pre>

h3. Gérer la désinstallation des tables du plugin

Vu que nous venons de créer une table, il ne faut pas oublier de la détruire si on désintalle le plugin.

Donc, modification de la fonction plugin_devplugin_uninstall du fichier *hook.php*
(laisser le return true à la fin)

<pre>
global $DB;

$tables = array("glpi_plugin_devplugin_profiles");

foreach($tables as $table) {
$DB->query("DROP TABLE IF EXISTS `$table`;");
}
</pre>

h3. Création d'un formulaire dans l'objet Profil

Nous allons maintenant créer le formulaire pour donner les droits.

Ajouter dans le fichier *profile.class*

<pre>
function showForm($id, $options=array()) {

$target = $this->getFormURL();
if (isset($options['target'])) {
$target = $options['target'];
}

if (!Session::haveRight("profile","r")) {
return false;
}

$canedit = Session::haveRight("profile", "w");
$prof = new Profile();
if ($id){
$this->getFromDB($id);
$prof->getFromDB($id);
}

echo "<form action='".$target."' method='post'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr><th colspan='2' class='center b'>".sprintf(__('%1$s %2$s'), ('gestion des droits :'),
Dropdown::getDropdownName("glpi_profiles",
$this->fields["id"]));
echo "</th></tr>";

echo "<tr class='tab_bg_2'>";
echo "<td>Utiliser Mon Plugin</td><td>";
Profile::dropdownNoneReadWrite("right", $this->fields["right"], 1, 1, 1);
echo "</td></tr>";

if ($canedit) {
echo "<tr class='tab_bg_1'>";
echo "<td class='center' colspan='2'>";
echo "<input type='hidden' name='id' value=$id>";
echo "<input type='submit' name='update_user_profile' value='Mettre à jour'
class='submit'>";
echo "</td></tr>";
}
echo "</table>";
Html::closeForm();
}
</pre>

h3. Ajout d'un onglet dans un objet du coeur

Je veux que les droits de mon profil soit gérer dans les profils du coeur, donc j'y ajoute un onglet.

Modifier la fonction plugin_init_devplugin du fichier setup.php

<pre>
Plugin::registerClass('PluginDevpluginProfile', array('addtabon' => array('Profile')));
</pre>

Définition du nom de l'onglet

Ajouter dans le fichier *profile.class.php*
<pre>
function getTabNameForItem(CommonGLPI $item, $withtemplate=0) {

if ($item->getType() == 'Profile') {
return "Mon plugin";
}
return '';
}
</pre>

Définition du contenu de l'onglet

Ajouter dans le fichier *profile.class.php*

<pre>
static function displayTabContentForItem(CommonGLPI $item, $tabnum=1, $withtemplate=0) {

if ($item->getType() == 'Profile') {
$prof = new self();
$ID = $item->getField('id');
// j'affiche le formulaire
$prof->showForm($ID);
}
return true;
}
</pre>

Voilà, un onglet est apparu dans Administration > Profil pour le profil courant

Mais le formulaire ne fait toujours rien

h3. Gestion des données du formulaire

Pour cela, créer un dossier front dans le répertoire de votre plugin
Dans ce dossier, créer un fichier *profile.form.php*

Ajouter dans ce fichier
<pre>
include ("../../../inc/includes.php");

Session::checkRight("profile", "r");

$prof = new PluginDevpluginProfile();

if (isset($_POST['update_user_profile'])) {
$prof->update($_POST);
Html::back();
}
</pre>

Il faut ajouter le profil dans la table du plugin si ce n'est pas le profil super-admin

Modification de la fonction displayTabContentForItem du fichier *profile.class.php*

<pre>
$ID = $item->getField('id');
// si le profil n'existe pas dans la base, je l'ajoute
if (!$prof->GetfromDB($ID)) {
$prof->createAccess($ID);
}
// j'affiche le formulaire
</pre>

Définition de la fonction createAccess appelée

toujours dans le fichier *profile.class.php*
<pre>
function createAccess($ID) {
$this->add(array('id' => $ID));
}
</pre>

Voilà, le profil est créé dans la table mais nous ne pouvons pas le modifier
Pour pouvoir le faire il faut ajouter dans le fichier *profile.class.php*
<pre>
static function changeProfile() {

$prof = new self();
if ($prof->getFromDB($_SESSION['glpiactiveprofile']['id'])) {
$_SESSION["glpi_plugin_devplugin_profile"] = $prof->fields;
} else {
unset($_SESSION["glpi_plugin_devplugin_profile"]);
}
}
</pre>

et déclarer ce hook dans la fonction plugin_init_devplugin du fichier *setup.php*
<pre>
$PLUGIN_HOOKS['change_profile']['devplugin'] = array('PluginDevpluginProfile','changeProfile');
</pre>

h2. Paramétrage action

Vous pouvez avoir besoin de paramétrer le plugin sans pour autant donner une interface de saisie à vos utilisateurs.

Nous allons voir le cas de rendre obligatoire le champ solution dans un ticket.

h3. Création d'un formulaire pour le plugin

Ajouter dans la function plugin_init_devplugin() du fichier *setup.php*
<pre>
if (Session::haveRight("config", "w")) {
$PLUGIN_HOOKS['config_page']['devplugin'] = 'front/config.form.php';
}
</pre>
Dans Configuration > Plugins, le nom de mon plugin est maintenant un lien cliquable

Nous allons mainteant crééer le fichier que le lien appelle.

Pour cela, dans le répertoire front, créer un fichier config.form.php qui contiendra
<pre>


include("../../../inc/includes.php");
require_once("../inc/config.class.php");

$plugin = new Plugin();
if ($plugin->isActivated("devplugin")) {
$config = new PluginDevpluginConfig();

if (isset($_POST["update"])) {
Session::checkRight("config", "w");
$config->update($_POST);
Html::back();

} else {
Html::header('Mon Plugin', $_SERVER["PHP_SELF"], "config", "plugins");
$config->showConfigForm();
}

} else {
Html::header('configuration', '', "config", "plugins");
echo "<div class='center'><br><br>".
"<img src=\"".$CFG_GLPI["root_doc"]."/pics/warning.png\" alt='warning'><br><br>";
echo "<b>Merci d'activer le plugin</b></div>";
Html::footer();
}

Html::footer();
</pre>

Nous allons maintenant créer la class contenenant le fomulaire.

h3. Contrôle d'un droit du coeur

Pour cela, créer un fichier config.class.php dans le dossier inc qui contiendra
<pre>
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access directly to this file");
}
class PluginDevpluginConfig extends CommonDBTM {


static function canCreate() {
return plugin_devplugin_haveRight('config', 'w');
}

static function canView() {
return plugin_devplugin_haveRight('config', 'r');
}

/**
* Configuration form
**/
function showConfigForm() {

$id = $this->getFromDB(1);
echo "<form method='post' action='./config.form.php' method='post'>";
echo "<table class='tab_cadre' cellpadding='5'>";
echo "<tr><th colspan='2'>Configuration de mon plugin</th></tr>";

echo "<tr class='tab_bg_1'>";
echo "<td>Solution oligatoire</td>";
echo "<td><select name='solution'>";
echo "<option value='0' ".(($this->fields["solution"] == 0)?" selected ":"").">Non</option>";
echo "<option value='1' ".(($this->fields["solution"] == 1)?" selected ":"").">Oui</option>";
echo "</select></td></tr>";

echo "<tr class='tab_bg_1'><td class='center' colspan='2'>";
echo "<input type='hidden' name='id' value='1' class='submit'>";
echo "<input type='submit' name='update' value='modifier' class='submit'>";
echo "</td></tr>";
echo "</table>";
Html::closeForm();
}
}
</pre>

Maintenant, en cliquant sur le nom de notre plugin depuis le menu configuration > plugin, le formulaire apparait à l'écran.

Seulement, même si nous avons défini dans le fichier config.form.php les actions à réaliser, rien ne se passera car nous n'avons pas de table où stocker ces informations.

Nous allons créer la table correspondante.

Dans le fichier hook.php, la fonction plugin_devplugin_install() contient désormais
<pre>
$migration = new Migration(100);

// Création de la table uniquement lors de la première installation
if (!TableExists("glpi_plugin_devplugin_profiles")) {
// requete de création de la table pour les droits
$query = "CREATE TABLE `glpi_plugin_devplugin_profiles` (
`id` int(11) NOT NULL default '0' COMMENT 'RELATION to glpi_profiles (id)',
`right` char(1) collate utf8_unicode_ci default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";

$DB->queryOrDie($query, "erreur lors de la création de la table des droits ".$DB->error());

// création de la table indispensable avant création du premier accès
$migration->migrationOneTable("glpi_plugin_devplugin_profiles");

//creation du premier accès nécessaire lors de l'installation du plugin
include_once(GLPI_ROOT."/plugins/devplugin/inc/profile.class.php");
PluginDevpluginProfile::createAdminAccess($_SESSION['glpiactiveprofile']['id']);

}
if (!TableExists("glpi_plugin_devplugin_configs")) {
// requete de création de la table pour les droits
$query = "CREATE TABLE `glpi_plugin_devplugin_configs` (
`id` int(11) NOT NULL auto_increment,
`solutionmandatory` tinyint(1) default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";

$DB->queryOrDie($query, "erreur lors de la création de la table de configuration ".$DB->error());

$query = "INSERT INTO `glpi_plugin_devplugin_configs`
(`id`, `solutionmandatory`)
VALUES (NULL, '0')";
$DB->queryOrDie($query,
"erreur lors de l'insertion des valeurs par défaut dans la table de configuration ".$DB->error());
}
// se fait toujours à la fin pour grouper les migration par table
$migration->executeMigration();
return true;

</pre>

J'ai ajouté un peu de commentaire, notamment en cas d'erreur

Voilà, nous pouvons configurer notre plugin pour savoir si nous activons ou non la solution obligatoire.

Maintenant nous allons rendre obligatoire la solution en cas de changement manuel du statut du ticket.

Dans le répertoire inc, créer un fichier ticket.class.php dans lequel seront mis les contrôles.
<pre>
class PluginDevpluginTicket {

/**
* Controles avant mise à jour
*
* @param $ticket Objet Ticket
**/
static function beforeUpdateTicket(Ticket $ticket) {
// global $DB;

$config = new PluginDevpluginConfig();

// si j'ai activé la solution obligatoire
if ($config->getField('solutionmandatory')) {

// je regarde que le statut soit modifié et que la modification soit Résolu
if (isset($ticket->input['status'])
&& in_array($ticket->input['status'],
array(implode("','",Ticket::getSolvedStatusArray())))) {

// je regarde si la solution est remplie
if (!isset($ticket->input['solution'])
|| empty($ticket->input['solution'])) {

h3. Ajout d'un message d'erreur

// je vide uniquement le champ Statut, les autres champs modifiés en même temps seront mis à jour
unset($ticket->input['status']);
Session::addMessageAfterRedirect("Description de la solution obligatoire", true, ERROR);
}
}
}
}
}
</pre>

Initialiser cette fonction dans la fonction plugin_init_devplugin() du fichier *setup.php*
<pre>
$PLUGIN_HOOKS['pre_item_update']['devplugin'] = array('Ticket' => array('PluginDevpluginTicket',
'beforeUpdateTicket'));
</pre>

h1. FAQ

Cette partie correspond aux différentes questions que l'on m'a posé sur IRC

h2. Comment ajouter un onglet

h3. A un objet du coeur

Modifier la fonction plugin_init_xxx du fichier setup.php

<pre>
Plugin::registerClass('NomDeMaClass', array('addtabon' => array('ObjetDuCoeur')));
</pre>

Ajouter dans votre fichier *xxx.class.php*
<pre>
/**
* Définition du nom de l'onglet
**/
function getTabNameForItem(CommonGLPI $item, $withtemplate=0) {

if ($item->getType() == 'ObjetDuCoeur') {
return "Mon plugin";
}
return '';
}

/**
* Définition du contenu de l'onglet
**/
static function displayTabContentForItem(CommonGLPI $item, $tabnum=1, $withtemplate=0) {

if ($item->getType() == 'ObjetDuCoeur') {
$monplugin = new self();
$ID = $item->getField('id');
// j'affiche le formulaire
$monplugin->nomDeLaFonctionQuiAfficheraLeContenuDeMonOnglet();
}
return true;
}
</pre>

h3. A mon plugin

Ajouter dans votre fichier *xxx.class.php*

<pre>
/**
* Définition des onglets
**/
function defineTabs($options=array()) {

$ong = array();
$this->addStandardTab('NomDeMaClass', $ong, $options);
$this->addStandardTab('Document', $ong, $options); => ajoute l'onglet Document standard
return $ong;
}

/**
* Définition du nom de l'onglet
**/
function getTabNameForItem(CommonGLPI $item, $withtemplate=0) {

if ($item->getType() == 'NomDeMaClass') {
return "Mon plugin";
}
return '';
}

/**
* Définition du contenu de l'onglet
**/
static function displayTabContentForItem(CommonGLPI $item, $tabnum=1, $withtemplate=0) {

if ($item->getType() == 'NomDeMaClass') {
self::NomDeMaFonctionStatic()
ou
$monplugin = new self();
$monplugin->nomDeLaFonctionQuiAfficheraLeContenuDeMonOnglet();
}
return true;
}
</pre>

h2. Comment ajouter plusieurs onglets

Modifier les fonctions
<pre>
function getTabNameForItem(CommonGLPI $item, $withtemplate=0) {

$ong = array();
$ong[1] = 'titre de mon premier onglet';
$ong[2] = 'titre de mon second onglet';
return $ong;
}
</pre>

h3. A un objet du coeur

<pre>
static function displayTabContentForItem(CommonGLPI $item, $tabnum=1, $withtemplate=0) {

switch ($tabnum) {
case 1 : // mon premier onglet
$item->nomDeLaFonctionQuiAfficheraLeContenuDeMonPremierOnglet();
break;

case 2 : // mon second onglet
$item->nomDeLaFonctionQuiAfficheraLeContenuDeMonSecondOnglet();
break;
}
return true;
}
</pre>

h3. A mon plugin

<pre>
static function displayTabContentForItem(CommonGLPI $item, $tabnum=1, $withtemplate=0) {

$monplugin = new self();

switch ($tabnum) {
case 1 : // mon premier onglet
$monplugin->nomDeLaFonctionQuiAfficheraLeContenuDeMonPremierOnglet();
break;

case 2 : // mon second onglet
$monplugin->nomDeLaFonctionQuiAfficheraLeContenuDeMonSecondOnglet();
break;
}
return true;
}
</pre>

h2. Comment voir la liste des objets de mon plugin

Déjà, il faut créer un fichier NomClass.php qui contiendra

<pre>
include ("../../../inc/includes.php");

$plugin = new Plugin();

// Creation du fil d'ariane
Html::header('devplugin', $_SERVER['PHP_SELF'], "plugins", "devplugin");
}

// contrôle si droit de lecture de mon plugin
if (plugin_devplugin_haveRight("devplugin","r")) {
Search::show('NomDeMaClass');

} else {
Html::displayRightError();
}
Html::footer();
</pre>

Vous bénéficierez ainsi des fonctionnalités du coeur.

Mais par défaut vous n'aurez que la colonne "nom".
Pour afficher les colonnes que vous souhaitez, il faut avoir défini les options d'affichage dans la table glpi_displaypreferences.
Cela se fait lors de l'installation de votre plugin, à l'endroit où vous créez vos tables en faisant

<pre>
INSERT INTO `glpi_displaypreferences` ( `id` , `itemtype` , `num` , `rank` , `users_id` ) VALUES (NULL,'NomClassDuPlugin','2','2','0');
</pre>

La valeur `num` correspond à l'ID du $tab défini dans le getSearchOptions() de votre class.
La valeur `rank` correspond à l'ordre d'affichage de la colonne `num`

Ces données seront ensuite surchargées par l'utilisateur dans ses préférences.

h2. Comment mettre dans mon tableau un objet avec un lien

Il suffit de définir le type de la colonne dans le getSearchOptions()

<pre>
$tab[votre numéro]['datatype'] = 'itemlink';
</pre>