PAGE OUTDATED

The current page is out-of-date. Please refer to the new documentation:
http://glpi-developer-documentation.readthedocs.io/en/master/plugins/index.html

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

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

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.

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 :
sql-mode        = STRICT_ALL_TABLES

et relancer MySQL)

Convention de programmation

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)

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.

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.

Création d'un plugin pas à pas

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

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é
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

}

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
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;
}
  • le controle de la configuration
function plugin_devplugin_check_config($verbose=false) {
   if (true) { // Your configuration check
      return true;
   }

   if ($verbose) {
     echo 'Installed / not configured';
   }
   return false;
}
  • l'initialisation du plugin
function plugin_init_devplugin() {
   global $PLUGIN_HOOKS;

   $PLUGIN_HOOKS['csrf_compliant']['devplugin'] = true;
}

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 :

function plugin_devplugin_install() {
    return true;
}

function plugin_devplugin_uninstall() {
    return true;
}

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.

Gérer les droits d'utilisation du plugin

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)

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']);
}

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.

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'));
      }
   }
}

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

 Plugin::registerClass('PluginDevpluginProfile');

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)

   global $DB;

   $tables = array("glpi_plugin_devplugin_profiles");

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

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.php

   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();
   }

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

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

Définition du nom de l'onglet

Ajouter dans le fichier profile.class.php

   function getTabNameForItem(CommonGLPI $item, $withtemplate=0) {

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

Définition du contenu de l'onglet

Ajouter dans le fichier profile.class.php

   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;
   }

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

Mais le formulaire ne fait toujours rien

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

include ("../../../inc/includes.php");

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

$prof = new PluginDevpluginProfile();

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

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

         $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

Définition de la fonction createAccess appelée

toujours dans le fichier profile.class.php

   function createAccess($ID) {
      $this->add(array('id' => $ID));
   }

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

   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"]);
      }
   }

et déclarer ce hook dans la fonction plugin_init_devplugin du fichier setup.php

   $PLUGIN_HOOKS['change_profile']['devplugin'] = array('PluginDevpluginProfile','changeProfile');

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.

Création d'un formulaire pour le plugin

Ajouter dans la function plugin_init_devplugin() du fichier setup.php

   if (Session::haveRight("config", "w")) {
      $PLUGIN_HOOKS['config_page']['devplugin'] = 'front/config.form.php';
   }

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


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();

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

Contrôle d'un droit du coeur

Pour cela, créer un fichier config.class.php dans le dossier inc qui contiendra

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();
   }
}

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

   $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;

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.

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);
            }
         }
      }
   }
}

Initialiser cette fonction dans la fonction plugin_init_devplugin() du fichier setup.php

   $PLUGIN_HOOKS['pre_item_update']['devplugin'] = array('Ticket' => array('PluginDevpluginTicket',
                                                                           'beforeUpdateTicket'));

FAQ

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

Comment ajouter un onglet

A un objet du coeur

Modifier la fonction plugin_init_xxx du fichier setup.php

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

Ajouter dans votre fichier xxx.class.php

   /**
    * 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;
   }

A mon plugin

Ajouter dans votre fichier xxx.class.php

   /**
    * 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;
   }

Comment ajouter plusieurs onglets

Modifier les fonctions

 function getTabNameForItem(CommonGLPI $item, $withtemplate=0) {

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

A un objet du coeur

Si les données du second onglet ne sont pas dans la même class que celles du premier onglet, il faut penser à ajouter
Plugin::registerClass('NomDeMaClass', array('addtabon' => array('ObjetDuCoeur')));
dans la fonction plugin_init_xxx du fichier setup.php

 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;
}     

A mon plugin

 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;
}     

Comme pour un seul onglet, la fonction de définition des onglets est indispensable

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

      $ong = array();
      $this->addStandardTab('NomDeMaClass', $ong, $options);
      return $ong;
   }

Comment voir la liste des objets de mon plugin

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

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();

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

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

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.

Comment mettre dans mon tableau un objet avec un lien

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

$tab[votre numéro]['datatype'] = 'itemlink';

Comment gérer les droits sur mon formulaire

Les droits sont visibles, en mode debug, dans la session (glpi_plugin_monPlugin_profile)
Les fonctions correspondantes doivent être défini dans la class correspondant dans votre plugin.
A minimam, ces 2 fonctions sont nécessaires, en static

 static function canCreate() {
      return plugin_monplugin_haveRight('droitDeMonPlugin', 'w');
   }

   static function canView() {
      return plugin_monplugin_haveRight('droitDeMonPlugin', 'r');
   }

Comment Ajouter mon objet dans le menu Configuration > Intitulés

Il faut ajouter une fonction dans le fichier hook.php où on va déclarer les objets à afficher

function plugin_devplugin_getDropdown(){
   return array('PluginDevpluginProfile'     => __('Gestion du profile', 'devpluginb'));
}