Signaux dans les plugins
Les plugins peuvent se « brancher » (connecter) sur des signaux (événements) émis par Paheko lors de certaines actions.
Utilisation
Se connecter à un signal
Il faut dans le fichier install.php du plugin enregistrer son callback associé au signal souhaité, avec la méthode registerSignal de l'entité Plugin (fournie par défaut dans la variable $plugin) :
$plugin->registerSignal('home.banner', 'Paheko\Plugin\Test\MyTest::banner');
Le premier paramètre est le nom du signal (voir ci-dessous pour la liste), le second est le nom du callback, qui doit être une fonction statique publique d'une classe.
Les méthodes de callback des signaux doivent obligatoirement commencer par le namespace Paheko\Plugin\NomDuPlugin (où NomDuPlugin correspond à l'identifiant du plugin), Paheko utilisera l'auto-chargement des classes (selon PSR-0) pour appeler la méthode. Par exemple en enregistrant un callback sur Paheko\Plugin\Caisse\Signaux::addMember, Paheko chargera le fichier lib/Signaux.php du plugin caisse (notez les minuscules pour le nom du répertoire) et appellera la méthode statique addMember de la classe Signaux située dans le namespace Paheko\Plugin\Caisse.
Réagir à un signal
Quand un signal est lancé, la méthode spécifie comme callback est exécutée. Le premier paramètre passé est un objet Signal, et le second est l'objet Plugin correspondant au plugin qui a enregistré le signal.
Paramètres d'entrée, paramètres de retour
Un signal peut être appelé avec des paramètres en entrée, dans ce cas on appelle la méthode Signal->getIn(?string $name). On ne peut pas modifier ces paramètres.
Un signal peut aussi renvoyer des valeurs. Dans ce cas on peut appeler la méthode Signal->setOut(string $name, mixed $value).
Les paramètres possibles diffèrent d'un signal à l'autre.
namespace Paheko\Plugin\Test;
use Paheko\Entities\Signal;
class MyTest
{
static public function banner(Signal $signal, Plugin $plugin)
{
$signal->setOut('my_banner', '<h1>Test !!!</h1>');
}
}
Arrêter une exécution
Un plugin peut décider de stopper l'exécution en appelant la méthode Signal->stop(). Dans ce cas, les callbacks suivants dans la liste ne seront pas appelés, et la méthode déclenchant le signal arrête son exécution également.
Si le signal n'est pas stoppable, une exception LogicException sera levée.
Exemple, si vous souhaitez gérer la file d'envoi de mails dans un plugin :
$plugin->registerSignal('email.queue.insert', 'Paheko\Plugin\CustomEmailQueue\Queue::insert');
…
static public function insert(Signal $signal, Plugin $plugin)
{
…
$db->insert('my_queue', ['recipient' => $signal->getIn('recipient')]);
}
Liste des signaux
Squelettes et templates
render.extensions.init(depuis 1.2.7) permet d'enregistrer des extensions Skriv/MarkDown depuis les pluginsusertemplate.init(depuis 1.1.0) est exécuté lors de l'initialisation d'un User Template (voir la documentation développeur des extensions), pour pouvoir étendre le langage Brindille utilisé dans les modèles de documents et les squelettes du site web public
Entités
Il existe des signaux spécifiques contenant le nom de l'entité :
entity.NAME.save.before(depuis 1.1.0) est exécuté AVANT l'enregistrement d'une entité (qu'elle ait été modifiée ou non)entity.NAME.save.after(depuis 1.1.0) est exécuté APRÈS l'enregistrement d'une entitéentity.NAME.create.before(depuis 1.3.0) est exécuté AVANT la création d'une nouvelle entitéentity.NAME.create.after(depuis 1.3.0) est exécuté APRÈS la création d'une nouvelle entitéentity.NAME.modify.before(depuis 1.3.0) est exécuté AVANT la modification d'une nouvelle entité (seulement si une propriété de l'entité à été modifiée)entity.NAME.modify.after(depuis 1.3.0) est exécuté APRÈS la modification d'une nouvelle entitéentity.NAME.delete.before(depuis 1.1.0) est exécuté AVANT la suppression d'une entitéentity.NAME.delete.after(depuis 1.1.0) est exécuté APRÈS la suppression d'une entité
Ce nom contient le namespace partiel de l'entité : la classe Paheko\Entities\Users\User donnera lieu à l'appel des signaux entity.Users\User....
Et des signaux génériques (depuis 1.1.12) qui concernent toutes les entités (quand on veut cibler toutes les créations ou suppressions d'entités par exemple). Ces signaux sont exécutés après le signal spécifique (contenant le nom de l'entité) :
entity.save.beforeentity.save.afterentity.create.beforeentity.create.afterentity.modify.beforeentity.modify.afterentity.delete.beforeentity.delete.after
Les signaux before peuvent interrompre l'action, mais pas after.
Membres
password.check(depuis 1.3.0) : exécuté AVANT la vérification de compromission d'un mot de passe. Appelé pour vérifier si un mot de passe est compromis ou non. Le plugin doit renvoyerTRUEpour arrêter la chaîne d'exécution, et positionner le booléenis_compromisedà vrai ou faux dans le tableau des paramètres de retour.user.login.after(depuis 1.3.0) : exécuté après une tentative de connexion. Voir la clésuccesspour savoir si la connexion a fonctionné ou non.successpeut êtrefalse(mauvais login ou mot de passe),true(connexion OK) ou la chaîneOTPsi la connexion a fonctionné mais que le membre nécessite du 2FA en OTPuser.login.otp.after(depuis 1.3.0) : exécuté après une tentative de 2FA avec OTP. Voir la clésuccess(booléen) pour savoir si le code OTP était bon ou non.user.change.login.after(depuis 1.3.0) : exécuté après le changement d'identifiant de connexion d'un membreuser.change.password.after(depuis 1.3.0) : exécuté après le changement de mot de passe de connexion d'un membre
Fichiers
file.store(depuis 1.3.0) exécuté après l'enregistrement d'un fichier (création ou modification), avec le paramètrefilecontenant l'objetFilecorrespondantfile.mkdir(depuis 1.3.0) exécuté à la création d'un répertoire, paramètre idemfile.delete(depuis 1.3.0) exécuté après la suppression d'un fichier, paramètre idemfile.rename(depuis 1.3.0) exécuté après le déplacement d'un fichier, paramètre idem, avec un paramètrenew_pathsupplémentaire pointant sur le chemin de destinationfile.create(depuis 1.3.0) exécuté après la création d'un nouveau fichier non-videfile.overwrite(depuis 1.3.0) exécuté après l'écrasement du contenu d'un fichier existant (modification du contenu)file.trash(depuis 1.3.0) exécuté après le déplacement d'un fichier vers la corbeillefile.restore(depuis 1.3.0) exécuté après le déplacement d'un fichier de la corbeille vers son chemin d'originefile.thumbnail.create(depuis 1.3.12) exécuté pour savoir si un plugin sait créer une miniature à partir d'un fichier (cf.FileThumbnailTrait)file.thumbnail.create(depuis 1.3.12) exécuté pour créer une miniature à partir d'un fichier (cf.FileThumbnailTrait)
Requêtes HTTP
http.request.file.before(depuis 1.1.11) est exécuté AVANT le traitement d'une requête HTTP qui correspond à un fichier. Paramètres fournis :uri,session(contenant la session de l'utilisateur courant, s'il est connecté, sinonNULL), etfile(contenant une entitéFile). RenvoyerTRUEinterrompt l'exécution.http.request.file.after(depuis 1.1.11) : identique, mais exécuté APRÈS le traitement de la requête, renvoyerTRUEne change rien.web.request.before(depuis 1.3.0) est exécuté AVANT le traitement d'une requête HTTP qui correspond à un squelette. Paramètres fournis :uri,skeleton(contient le nom du squelette correspondant à la requête) etpagequi contient une entitéPageouNULLsi l'URI fournie ne correspond à aucune page connue. RenvoyerTRUEinterrompt l'exécution.web.request.after(depuis 1.3.0) : identique, mais exécuté APRÈS le traitement de la requête, renvoyerTRUEne change rien.
Création de PDF
Ces signaux sont réservés à une extension qui fournirait un moyen de créer des PDF à partir de fichiers HTML. Le but est de fournir une alternative aux associations auto-hébergées dont le serveur ne dispose pas de programme de création de PDF. Une extension Dompdf est disponible à cette fin.
pdf.stream(depuis 1.1.12) est utilisé quand un PDF doit être envoyé directement au navigateur. Dans ce cas un seul paramètrestringest fourni, il contient le code HTML à transformer en PDF.pdf.create(depuis 1.1.12) est utilisé quand un PDF doit être stocké dans un fichier. Deux paramètres sont fournis :sourceest le chemin complet du fichier HTML source, ettargetest le chemin complet du PDF qui doit être créé.
L'extension doit renvoyer TRUE dans ces signaux pour interrompre la recherche automatique de programme de création de PDF : si l'extension ne renvoie pas TRUE, Paheko continuera à chercher un programme et à éventuellement l'exécuter.
Attention, ces signaux ne sont pas déclenchés si la constante de configuration PDF_COMMAND n'a pas null comme valeur, car on considère alors que le PDF est créé par la commande donnée, et appeler l'extension n'a donc aucun sens.
E-mails et rappels
reminder.send.after(depuis 1.1.25) appelé après l'envoi automatique d'un e-mail de rappel (un appel par message)email.send.before(depuis 1.1.25) appelé avant l'envoi effectif d'un email, permet à un plugin d'envoyer les emails à la place Paheko (par exemple pour utiliser une API d'envoi etc.), si la méthode du plugin renvoieTRUEalors Paheko considérera que le mail a été envoyé et n'essaiera pas de l'envoyer lui-même.email.send.after(depuis 1.1.25)email.queue.before(depuis 1.1.25) appelé avant le début de mise en queue d'un message à un ou plusieurs destinataires. C'est ici qu'il faut intervenir si on veut empêcher l'utilisateur d'envoyer un message, l'appel àemail.send.*pouvant se faire de manière asynchrone.email.queue.after(depuis 1.1.25)email.queue.insert(depuis 1.1.25) appelé pour l'insertion de chaque message la file d'attente, avec le message personnalisé pour le destinataireemail.bounce(depuis 1.1.28) est appelé quand un message de bounce est reçu
Autres
cron(depuis 1.1.12) est exécuté de manière périodique en même temps que les autres tâches périodiques. Malheureusement il n'est pas possible de prévoir la régularité d'une telle exécution, car en auto-hébergement par défaut cette fonction est lancée à la connexion, à moins d'avoir activé la constanteUSE_CRONdans la configuration et d'exécuter le scriptwww/cron.phpdirectement depuis un cron système.home.banner(depuis 1.1.25) permet d'afficher une bannière en haut de la page d'accueil (affichée après la connexion). La fonction doit simplement renvoyer une chaîne HTML qui sera affichée en haut de la page (dans le second paramètre reçu par référence).menu.item(depuis 1.1.25) permet d'insérer des items dans le menu principal, en dessous de l'item "page d'accueil".home.button(depuis 1.3.0) permet d'insérer des boutons sur la page d'accueilweb.page.version.new(depuis 1.3.0) appelé après la modification du contenu d'une page web (nouvelle version du texte)
Signaux système
Il est possible de se connecter à un signal sans utiliser un plugin. Pour cela il faut configurer les signaux dans la constante SYSTEM_SIGNALS de config.local.php :
const SYSTEM_SIGNALS = [
['entity.Users\User.modify.before' => 'detect_user_changes'],
];
Il faut donc inclure dans le même fichier le code de la fonction qu'on veut exécuter. Exemple :
function detect_user_changes(\Paheko\Entities\Signal $signal)
{
$entity = $signal->getIn('entity');
$modified = $entity->getModifiedProperties();
if (isset($modified['mon_champ'])) {
$old_value = $modified['mon_champ'];
$new_value = $entity->get('mon_champ');
// ... do something
}
}