# Contribution

Comme tout projet libre qui se respecte, les contributions à LdapSaisie sont les bienvenues. Ce chapitre explique les possibilités de contribution.

## LSaddons

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…) par le biais de méthodes de génération de la valeur de ces attributs par exemple (paramètre `generate_function`) ;

- des tâches communes et génériques (envoi de mails, connexion FTP/SSH…) ;

- l'implémentation de déclencheurs spécifiques à votre environnement : création automatique du dossier client sur le serveur de fichiers de l'entreprise, création de la boite mail de l'utilisateur… ;

- l'implémentation de vues personnalisées proposées dans l'interface

- l'implémentation d'action personnalisée sur les objets (synchronisation, archivage…) ou sur les résultats de recherches (export, rapport personnalisé…) ;

### Structure d'écriture

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 :

conf/LSaddons/config.LSaddons.[addon name].php

  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.

includes/addons/LSaddons.[addon name].php

  Ce fichier contiendra le code à proprement dit de votre &LSaddon;.

    Structure du fichier includes/addons/LSaddons.[addon name].php
    <?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.

Par convention, la structure de ce fichier est toujours à peu près la même:

- 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 `LSerror :: defineError()` 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;.

- On déclare ensuite une fonction `LSaddon_[myaddon]_support` qui sera exécutée lors du chargement de l'addon et qui permettra de s'assurer du support de celui-ci. Cette fonction devra retourner `True` si c'est le cas ou `False` dans le cas contraire.

  Cette fonction s'assura notamment :

  - que les librairies dont l'addon dépends sont bien chargées et fonctionnelles ;

  - que ses variables et constantes de configuration sont bien définies ;

  - de déclarer les vues personnalisées fournies par cet &LSaddon; ;

  - de déclarer les commandes CLI personnalisées fournies par cet &LSaddon; ;

- On déclare ensuite les fonctions, classes et éléments fournis et manipulés par l'addon.

- Si notre addon offre des commandes CLI personnalisées, les fonctions les implémentant 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 `CLI`. Pour cela, nous utilisons communément la fonction `php_sapi_name` pour déterminer le contexte d'exécution et si celui-ci vaut `cli`, nous stoppons l'exécution du reste du code du fichier via un `return true`.

  Il est important dans ce contexte de ne jamais retourner autre chose que `True` pour éviter tout message d'erreur inutile dans les logs.

- On déclare, pour finir, les fonctions implémentant les commandes CLI personnalisées et leur éventuelle fonction gérant l'autocomplétion des arguments qu'elles acceptent.

### Les vues personnalisées

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 racine courante de l'annuaire LDAP.

Pour mettre en place une telle vue personnalisée, il est nécessaire de :

- Déclarer cette vue dans la fonction `LSaddon_[addon]_support` de l'addon à l'aide de la méthode `LSsession :: registerLSaddonView` ;

- 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 CSS & JS, variables...).

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 exportSearchResultAsCSV).

      Structure du fichier includes/addons/LSaddons.[addon name].php
      <?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');
      }

### Les commandes CLI personnalisées

Les &LSaddons; peuvent fournir des commandes CLI personnalisées qui seront accessibles via la commande `ldapsaisie` 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.

Pour mettre en place une telle commande CLI personnalisée, il est nécessaire de :

- Déclarer cette vue dans la fonction `LSaddon_[addon]_support` de l'addon à l'aide de la méthode `LScli :: add_command` ;

- Déclarer la fonction implémentant cette commande CLI 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 `True` ou `False` en cas de succès/d'erreur d'exécution de la commande. Cette valeur de retour influencera le code retourné par la commande : `0` en cas de succès, `1` en cas d'erreur.

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

  Cette méthode recevra en paramètre:

  $command_args

    Un tableau des arguments déjà reçus par la commande.

  $comp_word_num

    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 `$command_args` 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.

  $comp_word

    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.

  $opts

    Un tableau des potentiels arguments globaux acceptés par LScli dans le contexte actuel (par exemple, `-d` ou `--debug` 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).

  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.

  Pour vous aider dans l'écrire d'une telle méthode d'autocomplétion, des méthodes statiques sont fournies par la classe `LScli` pour les autocomplétions les plus courantes:

  LScli :: autocomplete_class_name()

    Autocomplétion du nom d'une classe PHP.

  LScli :: autocomplete_addon_name()

    Autocomplétion du nom d'un &LSaddon;.

  LScli :: autocomplete_int()

    Autocomplétion d'un nombre entier.

  LScli :: autocomplete_LSobject_types()

    Autocomplétion du nom d'un type d'&LSobject;.

  LScli :: autocomplete_LSobject_dn()

    Autocomplétion du DN d'un type précis d'&LSobject; de l'annuaire.

  Par ailleurs, la méthode `LScli :: autocomplete_opts()` 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 `$comp_word`). 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.

Pour implémenter une telle commande CLI personnalisée, vous pouvez vous inspirer de l'exemple fourni ci-dessous ou encore des commandes CLI fournies par les autres &LSaddons; ou classes PHP de l'application.

      Structure du fichier includes/addons/LSaddons.[addon name].php
      <?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);
      }

## LSformElements

Les &LSformElements; sont les types de champs de formulaire supportés par l'application.

Pour chaque type implémenté, on devra trouver :

- Une classe PHP dérivée de la classe `LSattr_html` et devant s'appeler `LSattr_html_[nom du type d'attribut HTML]`. Dans celle-ci, il devra être défini à minima la variable de classe `LSformElement_type` permettant de référencer le type d'&LSformElement; à utiliser ;

- Une classe PHP dérivée de la classe `LSformElement` et devant s'appeler `LSformElement_[nom du type d'LSformElement]`. Cette classe implémentera tout ce qui concerne l'affichage du champ dans le formulaire et le traitement d'une valeur retournée par ce dernier. Cela concerne notamment les méthodes suivantes :

  getDisplay()

    Retourne les informations d'affichage du champ dans un formulaire sous la forme d'un tableau (implémentation obligatoire, pas de méthode par défaut). Il sera possible de s'appuyer sur la méthode `getLabelInfos()` permettant de générer et récupérer tout ce qui concerne le label du champ du formulaire. Il faudra cependant à minima fournir également la clé `html` 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 `fetchTemplate()`.

  fetchTemplate()

    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 `$template` 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 `$fieldTemplate`.

    La variable de classe `$fieldTemplate` est également utilisée par la méthode `LSformElement :: getEmptyField()` 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.

  getPostData()

    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.

    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 `LSformElement`.

  setValueFromPostData()

    Définit les valeurs de l'attribut à partir des données reçues du formulaire (et récupérées par la méthode `getPostData`). 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).

  autocomplete_attr_values()

    Génère de la liste des valeurs possibles de l'attribut dans un contexte CLI.

    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 `LSformElement`. Vous pouvez également vous inspirer des exemples d'implémentations fournies avec les autres type d'&LSformElement;.

- Un (ou plusieurs) fichier template pour la génération du code HTML du champ du formulaire. Communément, le fichier `LSformElement.tpl` 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 `$fieldTemplate` 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;.

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; `pre` qui s'inspire du type `textarea`, ou encore du type `url` dérivé du type `text`.

## LSformRules

Les &LSformRules; sont les règles syntaxiques applicables aux champs des formulaires. Ces règles serviront à s'assurer que les valeurs des champs récupérées des formulaires sont syntaxiquement correctes. Elles seront configurables via le paramètre `check_data` des attributs des &LSobjects;.

Pour chaque type implémenté, on trouvera une classe PHP dérivée de la classe `LSformRule` et devant s'appeler `LSattr_rule_[nom du type]`. Dans celle-ci, il devra être défini la méthode statique `validate()` qui implémentera le contrôle syntaxique. Cette méthode prendra en paramètres :

$value

  La valeur à tester.

$options

  Un tableau des options définies dans la configuration pour ce contrôle syntaxique.

$formElement

  Une référence au champ du formulaire (objet &LSformElement;).

Cette méthode devra retourner `True` ou `False` si la valeur testée est respectivement valide ou invalide. Elle pourra également déclencher une exception `LSformRuleException` qui lui permettra de donner des messages d'erreurs elle-même sur le(s) problème(s) detecté(s) durant l'analyse de la valeur passée. Le constructeur de ce type d'exception prend en tant que premier paramètre un tableau de messages d'erreurs (ou un simple message d'erreur) qui seront retournés à l'utilisateur.

Par défaut, les valeurs de l'attribut sont testées une à une via la méthode `validate()`. Cependant, il est possible d'implémenter une méthode de validation pour toutes les valeurs de l'attribut en une seule fois en affectant la valeur `false` à la constante de classe `validate_one_by_one`. Dans ce cas, l'ensemble des valeurs de l'attribut seront passées via le paramètre `$value` à la méthode `validate()` (sous la forme d'un tableau). Cela pourra par exemple être utile pour implémenter une validation de la cohérence des valeurs les unes vis à vis des autres (unicité, nombre maximum de valeurs, …).