Introduction

Depuis la version 1.3, Paheko dispose d'extensions modifiables, nommées Modules.

Les modules permettent de créer et modifier des formulaires, des modèles de documents simples, à imprimer, mais aussi de créer des "mini-applications" directement dans l'administration de l'association, avec le minimum de code, sans avoir à apprendre à programmer PHP.

Les modules utilisent le langage Brindille, aussi utilisé pour le site web (qui est lui-même un module). Avec Brindille on parle d'un squelette pour un fichier texte contenant du code Brindille.

Les modules ne permettent pas d'exécuter du code PHP, ni de modifier la base de données en dehors des données du module, contrairement aux plugins. Grâce à Brindille, les administrateurs de l'association peuvent modifier ou créer de nouveaux modules sans risques pour le serveur, car le code Brindille ne permet pas d'exécuter de fonctions dangereuses. Les plugins eux sont écrits en PHP et ne peuvent pas être modifiés par une association. Du fait des risques de sécurité, seuls les plugins officiels sont proposés sur Paheko.cloud.

Exemples

Paheko fournit quelques modules par défaut, qui peuvent être modifiés ou servir d'inspiration pour de nouveaux modules :

Ces exemples sont développés directement avec Brindille et peuvent être modifiés ou lus depuis le menu Configuration, onglet Extensions.

Un module fourni dans Paheko peut être modifié, et en cas de problème il peut être remis à son état d'origine.

D'autres exemples d'utilisation sont imaginables :

Pré-requis

Une connaissance de la programmation informatique est souhaitable pour commencer à modifier ou créer des modules, mais cela n'est pas requis, il est possible d'apprendre progressivement.

Résumé technique

Structure des répertoires

Chaque module a un nom unique (composé uniquement de lettres minuscules, de tirets bas et de chiffres) et dispose d'un sous-répertoire dans le dossier modules. Ainsi le module recu_don serait dans le répertoire modules/recu_don.

Dans ce répertoire le module peut avoir autant de fichiers qu'il veut, mais certains fichiers ont une fonction spéciale :

Snippets

Les modules peuvent également avoir des snippets, ce sont des squelettes qui seront inclus à des endroits précis de l'interface, permettant de rajouter des fonctionnalités, ils sont situés dans le sous-répertoire snippets du module :

Snippets MarkDown

Il est également possible, depuis Paheko 1.3.2, d'étendre les fonctionnalités Markdown du site web en créant un snippet dans le répertoire snippets/markdown/, par exemple snippets/markdown/map.html.

Le snippet sera appelé quand on utilise le tag du même nom dans le contenu du site web. Ici par exemple ça serait <<map>>.

Le nom du snippet doit commencer par une lettre minuscule et peut être suivi de lettres minuscules, de chiffres, ou de tirets bas. Exemples : map2024 map_openstreetmap, etc.

Le snippet reçoit ces variables :

Exemple :

<<map center="Auckland, New Zealand"

Ceci est la capitale de Nouvelle-Zélande !
>>

Voici un marqueur : <<map marker>>

Dans le premier appel, map.html recevra ces variables :

$params = ['center' => 'Auckland, New Zealand']
$content = "Ceci est la capitale de Nouvelle-Zélande !"
$block = TRUE

Dans le second appel, le snippet recevra celles-ci :

$params = [0 => 'marker']
$content = NULL
$block = FALSE

Fichier module.ini

Ce fichier décrit le module, au format INI (clé=valeur), en utilisant les clés suivantes :

Attention : les directives restrict_section et restrict_level ne contrôlent que l'affichage du lien vers le module dans le menu et dans les boutons de la page d'accueil, mais pas l'accès aux pages du module.

Il est possible d'ajouter un commentaire dans ce fichier, pour cela il faut que la ligne commence par un point virgule.

Exemple de module.ini

; Exemple de commentaire
name="Reçu de don"
description="Reçu de don simple, sans valeur fiscale"
author="Paheko"
author_url="https://paheko.cloud/"
restrict_section="accounting"
restrict_level="read"
doc_url="https://paheko.cloud/extension-recu-don"

Variables spéciales

Toutes les pages d'un module disposent de la variable $module qui contient l'entité du module en cours :

Stockage de données

Un module peut stocker des données de deux manières : dans sa configuration, ou dans son stockage de documents JSON.

Configuration

La première manière est de stocker des informations dans la configuration du module. Pour cela on utilise la fonction save et la clé config :

{{:save key="config" accounts_list="512A,512B" check_boxes=true}}

On pourra retrouver ces valeurs dans la variable $module.config :

{{if $module.config.check_boxes}}
  {{$module.config.accounts_list}}
{{/if}}

Stockage de documents JSON

Chaque module peut stocker ses données dans une base de données clé-document qui stockera les données dans des documents au format JSON dans une table SQLite.

Grâce aux fonctions JSON de SQLite on pourra ensuite effectuer des recherches sur ces documents.

Pour enregistrer il suffit d'utiliser la fonction save :

{{:save key="facture001" type="facture" date="2022-01-01" label="Vente de petits pains au chocolat" total="42"}}

Si la clé indiquée (dans le paramètre key) n'existe pas, l'enregistrement sera créé, sinon il sera mis à jour avec les valeurs données.

Validation

On peut utiliser un schéma JSON pour valider que le document qu'on enregistre est valide :

{{:save validate_schema="./document.schema.json" type="facture" date="2022-01-01" label="Vente de petits pains au chocolat" total="42"}}

Le fichier document.schema.json devra être dans le même répertoire que le squelette et devra contenir un schéma valide. Voici un exemple :

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
        "date": {
            "description": "Date d'émission",
            "type": "string",
            "format": "date"
        },
        "type": {
            "description": "Type de document",
            "type": "string",
            "enum": ["devis", "facture"]
        },
        "total": {
            "description": "Montant total",
            "type": "integer",
            "minimum": 0
        },
        "label": {
            "description": "Libellé",
            "type": "string"
        },
        "description": {
            "description": "Description",
            "type": ["string", "null"]
        }
    },
    "required": [ "type", "date", "total", "label"]
}

Si le document fourni n'est pas conforme au schéma, il ne sera pas enregistré et une erreur sera affichée.

Propriété non requise

Si vous souhaitez utiliser dans votre document une propriété non requise, il ne faut pas la fournir en paramètre de la fonction save.

Si elle est fournie mais vide, il faut aussi autoriser le type null (en minuscules) au type de votre propriété.

Exemple :

[...]
    "description": {
        "description": "Description",
        "type": ["string", "null"]
    }
[...]

Stockage JSON dans SQLite (pour information)

Explication du fonctionnement technique derrière la fonction save.

En pratique chaque enregistrement sera placé dans une table SQL dont le nom commence par module_data_. Ici la table sera donc nommée module_data_factures si le nom unique du module est factures.

Le schéma de cette table est le suivant :

CREATE TABLE module_data_factures (
  id INTEGER PRIMARY KEY NOT NULL,
  key TEXT NULL,
  document TEXT NOT NULL
);

CREATE UNIQUE INDEX module_data_factures_key ON module_data_factures (key);

Comme on peut le voir, chaque ligne dans la table peut avoir une clé unique (key), et un ID ou juste un ID auto-incrémenté. La clé unique n'est pas obligatoire, mais peut être utile pour différencier certains documents.

Par exemple le code suivant :

{{:save key="facture_43" nom="Facture de courses"}}

Est l'équivalent de la requête SQL suivante :

INSERT OR REPLACE INTO module_data_factures (key, document) VALUES ('facture_43', '{"nom": "Facture de courses"}');

Récupération et liste de documents

Il sera ensuite possible d'utiliser la boucle load pour récupérer les données :

{{#load id=42}}
    Ce document est de type {{$type}} créé le {{$date}}.
    <h2>{{$label}}</h2>
    À payer : {{$total}} €
    {{else}}
    Le document numéro 42 n'a pas été trouvé.
{{/load}}

Cette boucle load permet aussi de faire des recherches sur les valeurs du document :

<ul>
{{#load where="$$.type = 'facture'" order="date DESC"}}
    <li>{{$label}} ({{$total}} €)</li>
{{/load}}
</ul>

La syntaxe $$.type indique d'aller extraire la clé type du document JSON.

C'est un raccourci pour la syntaxe SQLite json_extract(document, '$.type').

Export et import de modules

Il est possible d'exporter un module modifié. Cela créera un fichier ZIP contenant à la fois le code modifié et le code non modifié.

De la même manière il est possible d'importer un module à partir d'un fichier ZIP d'export. Si vous créez votre fichier ZIP manuellement, attention à respecter le fait que le code du module doit se situer dans le répertoire modules/nom_du_module du fichier ZIP. Tout fichier ou répertoire situé en dehors de cette arborescence provoquera une erreur et l'impossibilité d'importer le module.

Restrictions

Envoi d'e-mail

Voir la documentation de la fonction {{:mail}}

Tables et colonnes de la base de données

Pour des raisons de sécurité, les modules ne peuvent pas accéder à toutes les données de la base de données.

Les colonnes suivantes de la table users (liste des membres) renverront toujours NULL :

Tenter de lire les données des tables suivantes résultera également en une erreur :