# Manuel de NOALYSS

## Chapitre 45. Développement d’extension (plugin)

### 45.1. L’API

Toute la documentation se trouve sur http://wiki.noalyss.eu/doc/; seules les classes nous intéressent. La documentation est générée à partir du code avec Doxygen. Le but de cet article n’est pas de revisiter la documentation technique mais de la mettre en oeuvre afin d’écrire une extension ou plugin.

### 45.2. Installation de l’extension

Les extensions se trouvent toujours dans un sous-répertoire de `/noalyss/include/ext`, pour notre plugin que nous appelerons DUMMY, le répertoire correspondant sera `/noalyss/include/ext/dummy`. La première étape est donc de décompresser le plugin dans le répertoire `/noalyss/include` et vérifier si on a bien `/noalyss/include/ext/DUMMY` ensuite l’activer.

Le plus simple est de retrouver le fichier dummy.php, si le chemin est `/chemin/ext/dummy/dummy.php` alors /chemin/ext est l’endroit où les plugins doivent être décompressés. Le chemin quand on ajoute le plugin dans le menu [C1MENU] est donné dans le wiki , pour chaque plugin, on donne le paramètre "chemin" qui est en fait le chemin à partir de /ext/

### 45.3. Comment l’activer

Grâce à l’accès direct allez sur [C1MENU], puis ajoutez plugin, les champs sont les mêmes, il faut ajouter ce plugin dans le profil des utilisateurs qui doivent l’utiliser (CFGPRO)

Pour la version 6.8 et plus haut, il suffit d’aller dans C0PLG et cochez qui peut utiliser le plugin qui se trouvera dans le module "Extension". Avec [C1MENU], on peut placer l’extension dans un autre module.

#### 45.3.1. Explication des champs

- Label, est le nom de menu de votre extension

- Code est utilisé pour inclure le fichier du plugin, il correspond au champs caché plugin_code

- Fichier est le chemin complet vers l’extension

Pour notre extension, les valeurs suivantes sont données

- Label : Mon dummy à moi

- code : dum

- Fichier : dummy/dummy.php

Vous sauvez, cliquer sur Extension et vous verrez apparaître un nouveau plugin appelé "Mon dummy à moi"; si vous cliquez dessus, la page ext/dummy/dummy.php sera exécutée; voyez le code du fichier noalyss/html/extension.php pour comprendre.

#### 45.3.2. Tutoriel vidéo

Installation d’un plugin sur Noalyss

#### 45.3.3. Exemple de base

Dans les plugins de base de NOALYSS, il y a skel qui est le squelette d’un plugin. Vous pouvez l’utiliser comme base.

https://gitlab.com/noalyss/noalyss-plugins/-/tree/stable/skel?ref_type=heads

#### 45.3.4. Connection à la base de données

Certaines valeurs doivent toujours être passées à chaque page, par exemple gDossier qui est l’identifiant du dossier. Pour se connecter c’est assez facile, il faut utiliser la classe Database et la classe Dossier.

Donc on écrit dans dummy.php

      echo "L' identifiant de mon dossier est ".dossier::id()."<br>";
      echo "Son nom réel est ".DOMAIN."dossier".dossier::id()."<br>";
      echo "Son nom est ".dossier::name()."<br>";
     // Je me connecte à présent à ce dossier
      $cn_db=new Database(dossier::id());
      // On peut aussi se connecter ainsi (développement plus récent)
      $cn= Dossier::connect();

Je veux afficher toutes les fiches qui concernent le matériel à amortir, donc j’ai besoin de savoir ce que vaut son FICHE_DEF_REF:FRD_ID dans constant.php; la FICHE_TYPE_XX n’existe pas, je vois dans la table fiche_def_ref qu’il s’agit de "7 Matériel à amortir"

Pour avoir toutes les fiches,

      $fiche= new Fiche($cn_db);
      $aFicheMateriel=$fiche->getByDef(7);

Pour chaque fiche, je veux afficher son nom, son prix, le nombre d’années à amortir et la date d’achat. Je peux en trouver les valeurs dans la table attr_def ou le fichier constant.php,

Donc cela devient

      for ($i=0; $i < count($aFicheMateriel);$i++) {
        echo "<ul>
         echo "<li> ";
         echo "Nom";
         echo $aFicheMateriel[$i]->strAttribut(ATTR_DEF_NAME);
         echo "</li>";
         echo "<li> ";
         echo "Prix achat";
         echo $aFicheMateriel[$i]->strAttribut(ATTR_DEF_PRIX_ACHAT);
         echo "</li>";
         echo "<li> ";
         echo "Durée amortissement";
         echo $aFicheMateriel[$i]->strAttribut(8);
         echo "</li>";
         echo "<li> ";
         echo "Date de début";
         echo $aFicheMateriel[$i]->strAttribut(10);
         echo "</li>";
         echo "</ul>
      }

#### 45.3.5. Soumettre des requêtes

Uniquement pour l’exercice, nous allons ajouter un FORM. Les données nécessaires dans le FORM sont toujours au minimum: l’id du dossier, le code du plugin.

Dans le FORM, on demandera juste à afficher le solde de chaque élément. On aura alors le code suivant, on utilisera la technique des templates

      $year=new IText('year');

      $str_year=$year->input();

      $str_submit=HtmlInput::submit('year_left','Appliquer');

      require_once('template1.php');

    template1.php

      <FORM METHOD="GET" ACTION="extension.php">

      <?=dossier::hidden()?>

      <?=HtmlInput::extension()?>

      Solde pour l'année : <?=$str_year?>

      <?=$str_submit?>

      </form>

Puis dans le début du fichier noalyss/include/ext/dummy/dummy.php, on ajoutera un test pour savoir un FORM a été soumis et on affichera une boîte de dialogue.

    $http= new HttpInput();
     if (isset($http->get('year_left','number'))){

        alert('Vous avez demandé le nombre d\'années restantes');

     }

(1) permet de retrouver des variables passées par get , post ou request (voir documentation code^) (2) remplace `$_GET['year_left']`, HttpInput vérifie aussi le type de données et peut donner une valeur par défaut si cette donnée n’est pas dans $_GET

### 45.4. Plugin plus avancé

Ma première extension, intégrer un fichier de client dans une catégorie de fiche, ce fichier est en CSV. Le code est simple et compréhensible, normalement on devrait avoir une meilleure gestion des erreurs, vérifier les attaques SQL Inject,…​ Ce code n’est là QUE pour expliquer le concept. On n’a pas utilisé plusieurs pages, ni de templates

Tout d’abord, il faut se connecter à la base de données

     // se connecter au dossier courant

     $cn=Dossier::connect();

Dans extension.php on vérifie la sécurité, en ajoutez une dans l’extension n’est en général pas nécessaire mais vous pourriez avoir votre propre système de sécurité si votre extension est fort complexe

En premier lieu, il est nécessaire de choisir dans quelle catégorie de fiche je veux intégrer les enregistrements. Donc on utilise un petit form

     echo '<form METHOD="get" action="extension.php">';

     echo dossier::hidden();

     // Ceci vous permet de revenir ici (voir extension.php). Cet élément caché permet d'include cette page-ci

     // Donc si votre plugin contient plusieurs pages, vous allez devoir ajouter une seconde variable pour

     // inclure la page que vous voulez (voir méthode de développement de PhpCompta )

     echo HtmlInput::extension();

     echo "Choix de la catégorie de fiche";

     $select_cat=new ISelect('fd_id');

     $select_cat->value=$cn->make_array('select fd_id,fd_label from fiche_def where frd_id='.

         FICHE_TYPE_CLIENT);

     echo $select_cat->input();

     echo HtmlInput::submit('display_prop','Afficher les propriétés');

     echo '</FORM>';

Il faut remarquer 2 choses dans ce FORM, primo, on utilise les objets HtmlInput et ISelect, secundo on doit avoir absolument en variables cachées, le n° de dossier sur lequel on est connecté, le code de l’extension, qui permettra à extension.php d’include le bon fichier. On utilise ici le protocole GET puisqu’on interroge, le protocole POST est réservé aux sauvegardes, c’est une convention assez répandue. La différence, est que les requêtes GET se voient dans l’URL, les requêtes POST ne sont jamais dans l’url.

L’utilisateur soumet le FORM, donc la feuille se recharge et on arrive à cette partie du code

On choisit d’afficher les propriétés avant de confirmer l’import

     if ( isset($_GET['display_prop'])){
        $http=new HttpInput();
         $a=new Fiche($cn);

         $prop=$a->toArray($http->get('fd_id'));

         foreach ($prop as $key=>$value)     echo "Index : $key valeur $value <br/>";

         echo '<form method="POST" action="extension.php"  enctype="multipart/form-data">';

         echo dossier::hidden();

         echo HtmlInput::extension();

         echo HtmlInput::hidden('fd_id',$http->get('fd_id');

         $file=new IFile('fichier_csv');

         echo $file->input();

         echo HtmlInput::submit('start_import','Démarrez importation');

         echo '</form>';

         exit;

     }

Voilà, si l’utilisateur clique sur le bouton SUBMIT, l’importation va démarrer. Dans notre exemple, on imaginera que le fichier CSV n’a que 4 champs "nom client","prenom client", "numero client","adresse client"

Le code qui suit est très simplifié.

     if ( isset($_POST['start_import'])){
    $http=new HttpInput();
         $fd_id=$http->post('fd_id');

         $tmp_file=$_FILE['fichier_csv']['tmp_name'];

         if ( ! is_uploaded_file($tmp_file))

             die 'Je ne peux charger ce fichier';

         // on ouvre le fichier

         $f=fopen($tmp_file,'r');

         // On récupère les propriétés de cette catégorie de fiche

         $client=new Fiche($cn);

         // $array contient toutes les valeurs nécessaires à Fiche::insert,

         $array=$client->toArray($http->post('fd_id'));

         while ( $data=fgetcsv($f)) {

             // remarque : on a éliminé les traitements d'erreur

             // On  remet tous les attributs (propriétés) à vide

             foreach(array_keys($array) as $key) $array[$key]="";

             // Nom et prénom

             $array['av_text1']=$data[0].' '.$data[1];

             // Numéro de client

             $array['av_text30']=$data[2];

             // Adresse

             $array['av_text14']=$data[3];

             // Quickcode

             $array['av_text23']="CLI".$data[2];

             $client->insert($fd_id,$array);

         }

         exit;

     }

Voici le fichier client.txt

     "Nom client1","Prénom","C1","Rue de la boite,55"

     "Nom client2","Prénom","C2","Rue du couvercle,55"

     "Nom client3","Prénom","C3","Rue de la chaussure,55"

     "Nom client4","Prénom","C4","Rue de la couleur,55"

Si vous vérifiez dans VW_CLIENT, vous verrez que toutes vos fiches ont été ajoutées. Dans l’exemple, il fatraitement d’erreur plus élaboré; le fait que si une fiche echoue , l’opération est annulée (Database::rollback) ou alors création d’un fichier avec les enregistrements "ratés"…​

### 45.5. Ajax

Afin d’utiliser des fonctions avec ajax, prototype.js devrait être utilisé.Vous devez appeler le fichier ajax.php, ce fichier dans html va simplement vérfier la sécurité et appeler le fichier ajax.php du répertoire où se trouve le plugin avec tous les arguments donnés.

Exemple

dans

dummy/javascript.js, vous avez

    function show_detail(pop_id){
        $('detail_invoice_content').innerHTML=loading();
        showIPopup('detail_invoice');
        try {
        var gDossier=$('gDossier').value;
        var phpsessid=$('phpsessid').value;
        var code=$('code').value;
        var obj={"op_id":pop_id,"gDossier":gDossier,"phpsessid":phpsessid,"code":code,'act':'detail_invoice'};
        var queryString=encodeJSON(obj);
        var action=new Ajax.Request ( 'ajax.php',
                  {
                     method:'get',
                     parameters:queryString,
                     onFailure:show_detail_error,
                     onSuccess:show_detail_success
                   }
                   );
        } catch (e){alert('show_detail'+e.message);}

     }

et dans dummy/ajax.php

    <?php
    // Met correctement la langue

    set_language();
    //retrouve le dossier courant et s y connecte

    $gDossier=dossier::id();
    $cn=new Database($gDossier);

    // action

    $action=(isset($_REQUEST['act']))?$_REQUEST['act']:'sh';

    // Véridfie la sécurité

    require_once ('class_user.php');
    $User=new User(new Database());
    $User->Check();

    /* Suivant l action demandé, on executera tel ou tel partie de code

    /* Show the document */
    if ( $action == 'sh') {
    /*

    * Votre code

    */}
    /* remove the document */
    if ( $action == 'rm' ) {
    /*votre code et la réponse

    */

        header('Content-type: text/xml; charset=UTF-8');
        header ('<?xml version="1.0" encoding="UTF-8"?>');
        echo '<answer>';
        echo '<ctl>detail_invoice</ctl>';
        echo '<html>'.escape_xml($html).'</html>';
        echo '</answer>';
    }
    ?>

### 45.6. Les données

Si votre extension nécessite de sauver ses propres données, il faut impérativement les mettres dans un schéma séparé. Prévoyez une table version, afin d’appliquer des mises à jour si nécessaire et un fichier install.php afin de créer les schémas nécessaire

exemple

    create schema plugin_tva;

    create table version (val integer);

### 45.7. Comment faire un rapport en PDF

Si vous décidez d’ajouter un menu qui renvoit un PDF.

Tout d’abord comme dans le développement des plugins, il faut d’abord ajouter un menu dans les menu et les [menu/cfgpro[profils.

Les menus de type impression ne sont jamais affichés, ils ne servent qu’à la sécurité et commence toujours par `CSV:`,`PDF:`,…​ voir [C1MENU]

Le code , vous devez créér une nouvelle classe depuis la classe PDF (en mode portrait) ou la classe PDFLand en mode paysage

Exemple

    require_once('class_pdf.php');

    class Mon_Impression_PDF extends PDF
    {

        function header()
        {
            parent::header();

        }

        function export()
        {

        }
    }

La fonction header : est ce qui est affiché comme en tête sur chaque page, La fonction footer : est ce qui est affiché comme en pied de page sur chaque page,

La fonction la plus importante est export qui remplira le listing.

Il y a peu de fonctions à connaître : Cell, export , SetFillColor…​

Voici par exemple le morceau de code pour le listing dans amortissements

    class Amortissement_Material_PDF extends PDF
    {

        function header()
        {
            parent::header();
            $this->setFont('DejaVu', 'B', 14);
            $this->Cell(190, 10, _('Amortissement : Liste de biens'), 1, 2, 'C');
            $this->ln();
            $this->col_size=array('qcode'=>20, 'name'=>35,'desc'=>80, 'date.purch'=>20, 'year.purch'=>20,  '#amort'=>10,'amount.purch'=>30, 'amount.amort'=>30, '%'=>20, 'amount.remain'=>20,'amount.delta'=>20);

            $this->setFont('DejaVu', 'B', 7);
            $this->Cell($this->col_size['qcode'], 8, _('QCode'));
            $this->Cell($this->col_size['name'], 8, _('Nom'));
            $this->Cell($this->col_size['date.purch'], 8, _('Date achat'));
            $this->Cell($this->col_size['year.purch'], 8, _('Année achat'));
            $this->Cell($this->col_size['#amort'], 8, _('Nbre'), 0, 0, 'R');
            $this->Cell($this->col_size['amount.purch'], 8, _('Montant'), 0, 0, 'R');
            $this->Cell($this->col_size['amount.amort'], 8, _('A amortir'), 0, 0, 'R');
            $this->Cell($this->col_size['amount.delta'], 8, _('A amortir'), 0, 0, 'R');
            $this->Ln();
        }

        function export()
        {
            global  $cn;
            $this->SetFont('DejaVu', '', 7);
            $ret=$cn->get_array("select * from amortissement.v_amortissement_summary where a_visible='Y' order by a_start,a_date");
            bcscale(2);
            for ($i=0;$i<count($ret);$i++)
            {
                if ($i%2==0)
                {
                    $this->SetFillColor(220, 221, 255);
                    $fill=1;
                }
                else
                {
                    $this->SetFillColor(0, 0, 0);
                    $fill=0;
                }
                $this->Cell($this->col_size['qcode'], 8, $ret[$i]['quick_code'], 0, 0, 'L', $fill);
                $this->Cell($this->col_size['name'], 8, $ret[$i]['vw_name'], 0, 0, 'L', $fill);
                $this->Cell($this->col_size['date.purch'], 8, format_date($ret[$i]['a_date']), 0, 0, 'L', $fill);
                $this->Cell($this->col_size['year.purch'], 8, $ret[$i]['a_start'], 0, 0, 'C', $fill);
                $this->Cell($this->col_size['#amort'], 8, round($ret[$i]['a_nb_year']), 0, 0, 'R', $fill);
                $this->Cell($this->col_size['amount.purch'], 8, nb($ret[$i]['a_amount']), 0, 0, 'R', $fill);
                $this->Cell($this->col_size['amount.amort'], 8, nb($ret[$i]['amort_done']), 0, 0, 'R', $fill);
                $delta=bcsub($ret[$i]['a_amount'],$ret[$i]['amort_done']);
                $this->Cell($this->col_size['amount.delta'], 8, nb($delta), 0, 0, 'R', $fill);

            $this->Ln();
            }

            $this->Output('listing-amort.pdf', 'I');
        }

    }

Il est conseillé de travailler avec des vues plutôt qu’avec des requêtes SQL directes, plus facile de réutiliser une vue que de dupliquer à plusieurs endroits une requête SQL, surtout pour la maintenance si vous décidez de changer la requête ou la structure de la base de données, avec une vue, vous retrouverez plus vite les endroits à modifier.

Il y a des tutoriaux dans noalyss/doc/fpdf et tfpdf, vous pouvez les lire en ligne sur le site de fpdf

1. Voir annexe : comment contribuer ou le wiki
1. souvent latin-1 pour Excel et unicode pour libreOffice
1. vous pouvez aussi utiliser directement les postes comptables, certains postes sont si rarement utilisés qu’on peut décider de ne pas créer de fiche comme par exemple le capital souscrit
1. Voir annexe : comment contribuer ou le wiki
1. A la condition que l’administrateur lui a envoyé le QRCode à scanner
1. C’est l’accès direct AD
2. c’est le nombre avant la catégorie
1. https://wiki.noalyss.eu
1. Vérifie que vous pouvez encore déduire une facture passée pour la TVA et l’impôt
1. https://wiki.noalyss.eu
2. le 31 décembre mais cela pourrait être une autre date
3. Pour imprimer les bilans il y a aussi l’extension "Bilan Interne" ou un rapport avancé
1. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
2. https://wiki.noalyss.eu
3. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
1. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
2. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
3. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
1. Il existe aussi les actifs inversés, charges inversées…​
2. pour extourner il faut entrer une date valide. Une date valide est une date qui est dans une période non fermée et dans une période du dossier
3. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
4. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
1. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
1. Il faut d’abord créer la nouvelle année.
1. extrait de compte
2. extrait de compte
1. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
1. Plugins standards de NOALYSS, https://gitlab.com/noalyss/noalyss-plugins
1. ces fichiers ont l’extension ods ou odt
1. PHP4 existait déjà, cependant on est toujours en retard sur les versions PHP afin d’être plus compatible, la partie orienté objet était très basique
2. Pour l’ajax, il existe plusieurs fichiers, ajax_misc.php est en général utilisé,ajax.php est utilisé pour les plugins.
3. NOALYSS n’est pas à 100% documenté, on l’améliore à chaque version
4. voir noalyss/html/ajax_misc.php.
5. paramètrer avec la variable $_ENV['TMP']
6. sous linux il s’agit de /tmp
7. static function test_me()