paheko-fork/doc/admin/modules.md
2024-01-19 16:39:49 +01:00

14 KiB

Title: Développer des modules pour Paheko

{{{.nav

<>

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 :

  • Reçu de don simple
  • Reçu de paiement simple
  • Reçu fiscal
  • Cartes de membres
  • Heures d'ouverture
  • Modèles d'écritures comptables

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 :

  • Auto-remplissage de la déclaration de la liste des dirigeants à la préfecture
  • Compte de résultat et bilan conforme au modèle du plan comptable
  • Formulaires partagés entre la partie privée, et le site web (voir par exemple le module "heures d'ouverture")
  • Gestion de matériel prêté par l'association

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

  • Utilisation de la syntaxe Brindille
  • Les modules peuvent utiliser toutes les fonctions et boucles de Brindille
  • Les modules peuvent stocker et récupérer des données dans la base SQLite dans une table clé-valeur spécifique à chaque module
  • Les données du module sont stockées en JSON, on peut faire des requêtes complètes avec l'extension JSON de SQLite
  • Les données peuvent être validées avant enregistrement en utilisant JSON Schema
  • Un module peut également accéder aux données des autres modules
  • Un module peut aussi accéder à toutes les données de la base de données, sauf certaines données à risque (voir plus bas)
  • Un module ne peut pas modifier les données de la base de données
  • Paheko crée automatiquement des index sur les requêtes SQL des modules, permettant de rendre les requêtes rapides

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 :

  • module.ini : contient les informations sur le module, voir ci-dessous pour les détails
  • config.html : si ce squelette existe, un bouton "Configurer" apparaîtra dans la liste des modules (Configuration -> Modules) et affichera ce squelette dans un dialogue
  • icon.svg : icône du module, qui sera utilisée sur la page d'accueil, si le bouton est activé, et dans la liste des modules. Attention l'élément racine du fichier doit porter l'id img pour que l'icône fonctionne (<svg id="img"...>), notamment pour que les couleurs du thème s'appliquent à l'icône.
  • README.md : si ce fichier existe, son contenu sera affiché dans les détails du module

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/transaction_details.html : sera inclus en dessous de la fiche d'une écriture comptable
  • snippets/transaction_new.html : sera inclus au début du formulaire de saisie d'écriture
  • snippets/user_details.html : sera inclus en dessous de la fiche d'un membre
  • snippets/my_details.html : sera inclus en dessous de la page "Mes informations personnelles"
  • snippets/my_services.html : sera inclus en dessous de la page "Mes inscriptions et cotisations"
  • snippets/home_button.html : sera inclus dans la liste des boutons de la page d'accueil (ce fichier ne sera pas appelé si home_button est à true dans module.ini, il le remplace)

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 :

  • $params : les paramètres du tag
  • $block : booléen, TRUE si le tag est seul sur une ligne, ou FALSE s'il se situe à l'intérieur d'un texte
  • $content : le contenu du bloc, si celui-ci est sur plusieurs lignes

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 :

  • name (obligatoire) : nom du module
  • description : courte description de la fonctionnalité apportée par le module
  • author : nom de l'auteur
  • author_url : adresse web HTTP menant au site de l'auteur
  • home_button : indique si un bouton pour ce module doit être affiché sur la page d'accueil (true ou false)
  • menu : indique si ce module doit être listé dans le menu de gauche (true ou false)
  • restrict_section : indique la section auquel le membre doit avoir accès pour pouvoir voir le menu de ce module, parmi web, documents, users, accounting, connect, config
  • restrict_level : indique le niveau d'accès que le membre doit avoir dans la section indiquée pour pouvoir voir le menu de ce module, parmi read, write, admin.

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.

Variables spéciales

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

  • $module.name contient le nom unique (recu_don par exemple)
  • $module.label le libellé du module
  • $module.description la description
  • $module.config la configuration du module
  • $module.url l'adresse URL du module (https://site-association.tld/m/recu_don/ par exemple)

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

  • Il n'est pas possible de télécharger ou envoyer des données depuis un autre serveur
  • Il n'est pas possible d'écrire un fichier local

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 :

  • password
  • pgp_key
  • otp_secret

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

  • emails
  • emails_queue
  • compromised_passwords_cache
  • compromised_passwords_cache_ranges
  • api_credentials
  • plugins_signals
  • config
  • users_sessions
  • logs