Les plugins

Dans MindFlow, de nombreuses fonctionnalités sont implémentées sous forme de plugins. Ces plugins peuvent être trouvés dans le dossier /mf/plugins/ pour les plugins systèmes (ceux publiés avec MindFlow). Depuis la version 1.6, il est recommandé de placer les plugins personnalisés dans le dossier /mf_websites/www.votresite.com/plugins/ de manière à ce que ceux-ci soient facilement sauvegardés / déplacés avec votre site web et qu’ils ne risquent pas de disparaître lors d’une mise à jour du dossier /mf avec une nouvelle version de MindFlow.

Les principaux plugins sont :

  • pages : fournit la fonctionnalité d’édition et d’affichage des pages.

  • urlRewriter : fournit les services de réécriture d’URL.

  • welcome : affiche un écran de bienvenue par défaut dans le backend

  • contacts : permet de publier un formulaire de contact sur le site web, et d’enregistrer la demande en base de données tout en envoyant un email au webmaster à chaque demande de contact.

  • mf_authentification : authentification sur le backend de MindFlow. Ce Plugin devrait toujours être actif, à moins d’être remplacé par un Plugin fournissant un service équivalent.

  • mf_searchEngine : service de moteur de recherche sur le site et d’indexation des enregistrement hérités de dbRecord

  • mf_users : gestion des utilisateurs et groupes d’utilisateurs du backend de MindFlow

  • news : publication d’actualités

  • events : calendrier d’événements (obsolète)

  • mf_countrydata : propose des tables renseignées et les classes pour les exploiter listant :

    • L’ensemble des pays du monde, avec les noms en Français, Anglais, Allemand ainsi que des expressions régulières pour vérifier les codes postaux
    • La liste des départements Français
    • La liste des communes françaises et leur principales informations
    • La liste des tarifs collisimo (pas forcément à jour)
  • myPlugin : exemple de Plugin minimaliste à la sauce MindFlow 1.6 avec une classe d’objet nommée ‘myContact’.

Important

La liste des plugins chargés au démarrage par MindFlow se trouve dans le fichier de configuration du site (ex : config-dev.php), dans la variable $config['load_plugins'].

Chaque clé spécifiée dans cette liste correspond au nom du dossier du Plugin qui sera chargé. Pour ajouter un plugin, ajoutez sa clef, puis allez dans l’install tool créer les tables du plugin concerné.

L’ordre dans lequel les plugins apparaissent dans cette liste détermine leur ordre de chargement et leur ordre d’affichage respectif.





Structure d’un plugin

Un plugin pour MindFlow comportera généralement plusieurs classes :

  • Une classe implémentant l’interface plugin : celle-ci fournira les fonctionnalités liées au plugin lui-même : initialisation du plugin, fourniture des informations du plugin (nom, description, numéro de version etc.)

  • Une ou plusieurs classes implémentant l’interface module : un module pourrait être vu comme un programme dédié à la réalisation d’une tache particulière au sein du plugin, par exemple lister et permettre l’édition de vidéos en backend, ou alors affichage d’une vidéo en frontend.

  • Zéro ou plusieurs classes héritées de la classe dbRecord : ce sont les objets qui seront écrits ou lus depuis la base de données et sur lesquels travaille le plugin, par exemple les informations concernant une vidéo.

  • Zéro ou plusieurs classes héritées de la classe dbForm : ce sont des formulaires utilisés pour les besoins de collecte d’informations nécessaires au traitement des données, mais qui ne donnent pas lieu a la création d’un enregistrement en base de données. Par exemple un flitre d’affichage multicritères pour trier les vidéos par genre, auteur, etc.




Organisation des dossiers

L’organisation des fichiers dans le dossier d’un plugin est relativement libre, néanmoins nous avons mis en place la convention suivante que nous vous recommandons d’adopter. Respecter celle-ci permettra à d’autres développeurs ou intégrateurs de s’y retrouver plus facilement dans la structure de votre plugin.

Organisation des dossiers dans un plugin

Il est tout d’abord recommandé de créer un dossier /plugins dans le dossier du site web sur lequel il sera exploité. Ainsi, si vous déplacez le site ou mettez à jour le dossier /mf de MindFlow, les plugins employés par le site resteront toujours disponibles avec celui-ci. Vous indiquerez à MindFlow ou sont stockés les plugins au moyen de la variable $config['website_pluginsdir'] du fichier de configuration. Tous les plugins associés au site devront être situés sous ce dossier. Dans le cas d’une installation multi-sites, plusieurs sites peuvent très bien pointer sur le même dossier de plugins : il ne sera donc pas nécessaire de dupliquer le dossier des plugins sur chaque site.

Note

Le dossier de votre plugin sera nommé selon la clef de votre plugin (plugin key). Cette clef ne doit pas comporter de caractère espace (on mettra un underscore à la place) et doit être unique (ne pas reprendre la clef d’un plugin existant).

Une bonne manière de forger un identifiant unique est d’y ajouter un préfixe qui vous est personnel, par exemple les plugins “officiels” livrés avec MindFlow sont souvent préfixés par “mf_”.





Mise en pratique avec l’exemple “my_plugin”

Une fois les classes du plugin ajoutées, voici ce que peut donner plus concrètement un plugin avec l’exemple my_plugin proposé dans le dossier mf_websites/www.sample-website.com/plugins/ de la distribution de MindFlow, exemple que nous reprendrons plus loin dans cette documentation.

Organisation des dossiers dans l'exemple my_plugin




Création de la structure d’un nouveau plugin

Depuis MindFlow 1.8.2, vous disposez de scripts pour générer automatiquement la structure de nouveaux plugins. Ceux-ci se trouvent dans le dossier /mf/cli_scripts.

On distingue les scripts suivants :

  • plugin_create.php Création des fichiers de base d’un plugin très simple, sans module ni enregistrement présents.

  • plugin_add_module Ajoute un module à un plugin précédemment créé avec plugin_create.php

  • plugin_add_record Ajoute un enregistrement de base de données à un plugin. Un enregistrement est généralement associé à un module, ce dernier permettant de créer des enregistrements, les éditer et les supprimer. Aussi on exécutera généralement ce script avoir créé un module.

  • plugin_add_form Ajoute un formulaire à un plugin, utilisé dans le but de filter l’affichage fourni par un module.

Important

  • Les scripts de génération de plugins en sont actuellement à leur première version. Ils n’ont pas encore été testé extensivement pour tous les cas de figure. Pour un bon résultat, veillez à les exécuter dans l’ordre indiqué. Ceux-ci vous feront gagner un temps certain.

  • Si vous voulez ajouter un module, un enregistrement ou un formulaire a un plugin existant, les scripts sont supposés opérer correctement. Néanmoins, si vous avez saisi du code personnalisé, faites une copie de sauvegarde de tout le dossier de votre plugin votre plugin avant d’exécuter un script dessus : il y a de nombreuses raisons qui peuvent causer l’écrasement d’un code existant ! Et une nouvelle fois, ce code est jeune !

  • Pour chacun de ces scripts, plutôt que d’utiliser le mode interactif, il est possible de spécifier un fichier de configuration en entrée. Il suffit de spécifier le chemin vers ce fichier de configuration en argument du script. Un exemple pour d’un tel fichier est spécifié dans le sous dossier sample-configs. Dans le même fichier de configuration, vous pouvez saisir les paramètres de tous les scrips. Les paramètres sont organisés par section : plugin, module, record, form. Ne saisissez les paramètres que dans les sections dont vous avez l’usage. La saisie de ces paramètres équivaut à préremplir les réponses aux questions posées par le script. Ensuite, lors de l’exécution du script, si des fichiers doivent être écrits ou écrasés, votre autorisation vous sera toujours demandée de manière interactive.


Pour utiliser le fichier de configuration d’exemple sur les 4 scripts et générer un plugin d’exemple complet nommé “AG records” ( “AG” pour “Auto Généré”), utilisez les commandes suivantes :

cd mf/cli_scripts
php plugin_create.php sample-configs/agPlugin_config.php
php plugin_add_module.php sample-configs/agPlugin_config.php
php plugin_add_record.php sample-configs/agPlugin_config.php
php plugin_add_form.php sample-configs/agPlugin_config.php


Avertissement

Une fois votre plugin généré, n’oubliez pas de :

  • L’ajouter à la variable $config[‘load_plugins’] dans le fichier de configuration de votre site/application

  • Aller dans l’install tool créer les tables d’enregistrements dans la base de données s’il y a lieu.




L’objet plugin

Tout plugin doit disposer à sa racine d’un fichier index.php contenant une classe implémentant l’interface plugin. C’est le fichier qui sera appellé par MindFlow pour charger le plugin, d’autre part, aussi son rôle est d’informer MindFlow de toutes les caractéristiques du plugin et des éléments à charger.

require_once DOC_ROOT.SUB_DIR.'/mf/core/interfaces/plugin.php';

class myPlugin implements plugin{
        ...
}

Dans le fichier index.php, il est également nécessaire d’informer le pluginManager de l’existence de ce plugin en lui fournissant une instance :

$mf->pluginManager->addPluginInstance(new myPlugin());

Note

Une fois MindFlow en cours d’exécution, il sera possible d’accéder à nouveau à cette instance au moyen de la fonction getPluginInstance() en lui spécifiant la clef du plugin :

$myPlugin = $mf->pluginManager->getPluginInstance('my_plugin');

Les variables suivantes devront être obligatoirement être définies :

private $pluginName = 'My plugin';      //nom du plugin
private $pluginVersion = '1.0'; //version du plugin

Les fonctions suivantes devront être obligatoirement surchargées :

  • function getName() : renvoie le nom du plugin.
  • function getKey() : renvoi la clef du plugin (plugin key), identifiant unique et nom du dossier du plugin.
  • function getVersion() : renvoie le numéro de version du plugin.
  • function getPath() : retourne le chemin vers le dossier du plugin, relatif à la racine de l’hébergement.
  • function setup() : permet d’exécuter des opérations complémentaires à la création des tables lors de l’installation d’un plugin. N’est pas encore effectif.
  • function getDependencies() : liste des clés des plugins tiers requis pour le bon fonctionnement de ce plugin. N’est pas encore effectif.




Les modules

Si vous n’avez pas déjà lu la section Le pipeline de rendu, nous vous conseillons de le faire à ce stade.

Dans MindFlow, la plupart des objets qui participent au rendu de la page, c’est à dire son affichage, disposent d’une fonction prepareData() et d’une fonction render(). C’est nottament le cas des modules, qui sont les programmes réalisant les traitement de données au coeur des plugins. En backend, un module réalisera le plus souvent la gestion de l’interface graphique du plugin et l’exécution des opérations commandées par l’administrateur du site. En frontend, il fera de même, mais sur le site Internet et pour l’internaute.

Les fonctions suivantes devront être obligatoirement surchargées :

  • function prepareData() : préparation des données pour l’affichage
  • function render(&$mainTemplate) : rendu des données pour affichage.
  • function getType() : renvoie le contexte d’execution (‘backend,frontend,authentification’) du plugin.



La fonction prepareData()

Une fois toutes les données prêtes pour l’exécution et tous les plugins chargés, il est temps de passer au traitement de données proprement dit. Dans MindFlow, nous avons souhaité que le maximum de traitements soit réalisé sous forme de plugins de manière à avoir la conception la plus modulaire possible. Ainsi la gestion et l’affichage des pages web est réalisé via le plugin Pages, la gestion de la réécriture d’URLs via le plugin URLRewriter, etc.

A cette fin, chaque plugin dispose d’une fonction prepareData(). Celle-ci est toujours exécutée avant la fonction render(). Son rôle est d’offrir une fenêtre de temps pour préparer les données qui seront affichées plus tard par la fonction render().

Durant l’étape de préparation des données, le pluginManager de MindFlow va donc appeller la fonction ``prepareData()`` de chaque plugin, l’un après l’autre.

Note

Les fonctions prepareData() des différents plugins sont exécutées les unes après les autres, dans l’ordre défini par la variable $config['load_plugins']. Cette étape permet au développeur d’un plugin d’accéder accéder aux données produites par les autres plugins. S’il souhaite donc accéder aux données d’un autre Plugin, par exemple l’URL actuelle qui a été analysée et décortiquée sous forme de tableau de chaînes par le Plugin URLRewriter, il faudra s’assurer que le Plugin URLRewriter soit mentionné avant le Plugin du développeur dans la variable $config['load_plugins'] du fichier de configuration de MindFlow, de manière à ce que ses données aient préalablement été préparées par sa fonction prepareData() quand le plugin souhaitera y accéder.




La fonction render(&$mainTemplate)

Normalement, cette fonction ne devrait traiter que les opérations d’affichage, c’est à dire la production de code HTML, XML, JSON, SVG, créer un fichier texte etc...

Important

On ne doit donc pas réaliser d’opération de modification des données dans une fonction render(). Néanmoins, si un plugin donné devait accéder à des données produites par un plugin tiers qui serait chargé après celui-ci et qu’il n’y a aucune possibilité de déterminer dans quel ordre les plugins seront exécutés, il reste possible de traiter des données exceptionnellement en début de fonction render(). Ces données ne seront alors pas disponible pour les autres plugins puisque traitées / générées après la phase d’exécution des fonctions prepareData(). Elles ne seraient également pas disponibles pour des scripts qui n’utiliseraient pas la fonction render(). C’est donc à éviter autant que possible.

Dans sa fonction render(), un plugin va généralement substituer les marqueurs entre crochets du type {nom_marqueur} qui sont présents dans la variable $mainTemplate passée en argument de l’appel de fonction. $mainTemplate étant passé par référence avec l’opérateur &, toute modification de la variable opérée au sein de la fonction a des répercutions au niveau global : la variable $mainTemplate est immédiatement modifiée. Généralement, la variable $mainTemplate contient le gabarit HTML de la page courante, qu’il s’agisse d’une page frontend ou backend. Vous pouvez savoir de quel type de page il s’agit en interrogeant la variable $mf->mode.





Contexte d’exécution

Un plugin peut être exécuté en Frontend et/ou en Backend. Il peut être également exécuté, de manière beaucoup plus rare en mode authentification. Afin d’optimiser les performances et ne pas évaluer inutilement du code prévu pour gérer le backend alors qu’on produit un affichage en frontend, chaque plugin doit définir la variable $pluginType qui spécifie dans quel contexte d’exécution il doit être appellé.

private $pluginType = 'frontend'; //valeurs possibles : 'frontend', 'backend' ou 'frontend,backend'

Si le $pluginType de votre plugin est de type ‘frontend,backend’, il est également courant et conseillé d’insérer dans vos fonctions render() et prepareData() la structure de contrôle suivante, toujours en vue d’optimiser les performances :

if($mf->mode == 'frontend'){
        //traitement si on est en frontend
}
else if($mf->mode == 'backend'){
        //traitement si on est en backend
}




Optimisation de l’exécution

A chaque execution, MindFlow évalue tous les plugins. Dans le cas de nombreux plugins présents sur le site, ou de plugins réalisant des traitements lourds, ceci peut impacter les performances. Si vous développez un plugin qui n’a pas besoin d’être exécuté à chaque requête de MindFlow, mais uniquement pour certaines pages, il est fortement recommandé d’ajouter les contrôles suivants qui vous permettront de vous assurer que votre code ne sera évalué que lorsque c’est nécessaire :

En backend

//évaluation d'un module uniquement lorsque celui-ci est appelé spécifiquement par l'utilisateur

if(isset($_REQUEST['module']) && ($_REQUEST['module'] == "my_module_key")){

        //execution du module

}

En frontend

//évaluation du module uniquement sur les pages pour lesquelles il est configuré

if(in_array($mf->info['currentPageUid'], $config['plugins']['my_plugin']['pages-uids']){

        //execution du module

}

La variable $config['plugins']['my_plugin']['pages-uids'] étant définie dans le fichier de configuration du site comme suit :

$config['plugins']['my_plugin'] = array(
        // on spécifie une liste d'UIDs correspondant aux pages sur lesquelles
        // on executera le plugin
        'pages-uids' => array(25,64,72)
);


Note

Pour connaitre l’UID d’une page donnée sur le site, dans la liste des pages du site, passez avec votre souris en rollover sur l’icône précédant le titre de la page, et l’UID apparaitra sous forme d’infobulle :

Obtention de l'UID d'une page MindFlow

Ou alors, éditez la page, et consultez l’onglet “Propriétés de la page” :

Obtention de l'UID d'une page MindFlow




Ajout du module à un menu du backend

Pour ajouter un lien vers votre module dans la navigation du backend de MindFlow, au début de la fonction prepareData() de votre module, utilisez la commande suivante :

//add the plugin to the backend menus
$mf->pluginManager->addEntryToBackendMenu('<a href="{subdir}/mf/index.php?module=my_module_key">'.$l10n->getLabel('my_module_key','menu_title').'</a>','settings', LOW_PRIORITY);
  • Le premier argument '<a href="{subdir}/mf/index.php?module=my_module_key">'.$l10n->getLabel('my_module_key','menu_title').'</a>' est le lien sur lequel envoie votre entrée de menu. Celui-ci doit passer dans l’URL un paramètre ?module= et spécifier la clef de votre module. Celui-ci comporte également un titre localisé $l10n->getLabel('my_module_key','menu_title')
  • Le second argument 'settings' est la clef du menu concerné, pour insérer l’entrée dans un menu existant. Si la clef est déjà définie pour un menu existant, votre entrée sera insérée dans ce menu. Sinon le menu sera créé, toutefois son niveau de priorité d’apparition dans le sens horizontal ne sera pas défini. Il est préférable de définir préalablement votre menu au moyen de la fonction addBackendMenu() à cette fin (voir section suivante).

Important

L’ajout du menu doit être réalisé après que le droit d’accès au module de l’utilisateur courant aura été validé, sinon le menu apparaitra même si l’utilisateur n’y a pas accès !

function prepareData(){

    // (...)

    $this->userGroup = $mf->currentUser->getUserGroup();
    $this->moduleAccess = ($mf->currentUser->isAdmin() || (class_exists('mfUserGroup') && ($this->userGroup && $this->userGroup->getUserRight('dyn_core', 'allowEditGestionDossiers') == 1)));

    if ($this->moduleAccess) {
        // insertion du menu ici
    }

    // (...)
}

Note

Les différentes clefs de menu définies en standard par MindFlow sont :

  • 'content' => Menu “Publication”
  • 'datas' => Menu “Données”
  • 'administration' => Menu “Administration CMS”
  • 'settings' => Menu “Paramètres”

Vous pouvez également définir votre propre menu. Il vous faudra alors spécifier dans le second paramètre de l’appel à addEntryToBackendMenu() une nouvelle clef, par exemple my_menu. Il vous faudra alors également créer dans le dossier languages/ de votre plugin un fichier nommé backendMenus_l10n.php définissant la traduction de votre clef de menu dans les différentes langues pour lesquels votre plugin est susceptible d’être utilisé :

$l10nText = array(

        'fr' => array(
                'my_menu' => 'Mon menu',
        ),
        'en' => array(
                'my_menu' => 'My menu',
        ),
);
  • Le troisième argument LOW_PRIORITY est le niveau de priorité selon lequel l’entrée spécifiée apparaitra dans le menu. Plus le niveau de priorité sera élevé, et plus votre entrée apparaîtra haut dans le menu, tout en se retrouvant en concurrence avec les plugins ayant requis le même niveau de priorité.

Les niveaux de priorité disponibles sont les suivants :

define("TOP_PRIORITY",     0);
define("HIGH_PRIORITY",    100);
define("MEDIUM_PRIORITY",  200);
define("LOW_PRIORITY",     300);
define("LOOSE_PRIORITY",   400);

Chaque constante est définie par un entier : le niveau de priorité de l’entrée est inversement proportionnel à la valeur de l’entier. Ainsi 0 est le niveau de priorité le plus fort et 400 le plus faible. Notez que les intervales entre les constantes définies vont de 100 en 100. Si la granularité entre 2 niveaux de priorité n’est pas suffisante, vous pouvez saisir une valeur entière : la valeur 250 permet de définir un niveau de priorité entre MEDIUM_PRIORITY et LOW_PRIORITY par exemple.





Création d’un menu backend

Pour ajouter un menu supplémentaire à l’interface, utilisez la fonction addBackendMenu() de la classe pluginManager :

//ajout du menu backend
if(!$mf->pluginManager->backendMenuExists('prod'))$mf->pluginManager->addBackendMenu('prod', MEDIUM_PRIORITY);

//ajout du plugin au menu backend
$mf->pluginManager->addEntryToBackendMenu('<a href="{subdir}/mf/index.php?module=gestionDossiers">' . $l10n->getLabel('gestionDossiers', 'menu_title') . '</a>', 'prod', MEDIUM_PRIORITY);

La fonction addBackendMenu() dispose elle aussi d’un argument de priorité. Celui-ci définit la priorité d’apparition du menu dans l’ordre d’affichage horizontal des menus, tandis que l’argument de priorité de la fonction addEntryToBackendMenu() définit la priorité d’affichage de l’entrée dans le sens vertical, parmis la liste des entrées au sein du menu donné.





Gestion des droits d’accès

Il est possible d’associer des droits d’accès aux plugins ou aux actions effectuées au sein d’un plugin. Cette fonctionnalité est apportée par le plugin mf_users. Ces droits d’accès apparaissent alors dans l’édition des objets “Groupes d’utilisateurs” (mfUserGroup) de MindFlow. Tout utilisateur backend associé à un groupe donné héritera alors des droits définis pour le groupe. Actuellement, il n’est pas encore possible de définir des droits au niveau utilisateur. Si vous voulez assigner des droits particuliers à un seul utilisateur, il vous faudra créer un groupe juste pour cette utilisateur.

En créant ou éditant un groupe d’utilisateurs, vous verrez apparaître les droits définis par les différents plugins :

Les droits d'utilisateur


Important

Notez que les utilisateurs qui ont la case “Administrateur” cochée dans leur profil utilisateur (à ne pas confondre avec leur groupe d’utilisateurs) ont normalement accès à tous les modules, quel que soient les droits définis dans leur groupe.



Pour ajouter un droit d’accès à votre plugin, il vous faudra ajouter un appel à mfUserGroup::defineUserRight($moduleKey, $keyName, $formFieldDefinition, $l10nFile) depuis la fonction init() de votre classe implémentant l’interface plugin, par exemple :

if(class_exists('mfUserGroup')) mfUserGroup::defineUserRight('my_plugin', 'allowEditMyContacts',
        array(
                'value' => '0',
                'dataType' => 'checkbox',
                'valueType' => 'number',
                'processor' => '',
                'div_attributes' => array('class' => 'col-lg-2'),
        ),
        $config['website_pluginsdir'].'/my_plugin/languages/userRights_l10n.php'
);

Dans l’exemple ci-dessus, on voit que pour l’argument $formFieldDefinition nous avons ajouté une définition de champ conforme à celle qu’on trouve dans les formulaires. En utilisant la syntaxe habituelle, vous serez donc à même d’insérer tout type de champ offert par MindFlow : input, select, radio etc.

Le paramètre 'allowEditMyContacts' correspond, lui, à la clef associée à votre champ et qui vous permettra de rapatrier la valeur du droit d’accès saisie dans le champ.

Pour rapatrier la valeur de ce droit d’accès dans votre plugin, procédez comme suit :

// on rapatrie d'abord l'objet userGroup de l'utilisateur actuellement connecté
$this->userGroup = $mf->currentUser->getUserGroup();

// puis on peut consulter la valeur de notre droit d'accès :
if($this->userGroup->getUserRight('my_plugin','allowEditMyContacts')==1){

        // action si le droit est accordé

}

// on pourra adopter une approche plus sophistiquée,
// en forçant le droit d'accès pour les utilisateurs administrateurs
// et en vérifiant l'existence de la classe mfUserGroup pour éviter l'affichage d'erreurs.

$this->moduleAccess = ($mf->currentUser->isAdmin() || (class_exists('mfUserGroup') && ($this->userGroup && $this->userGroup->getUserRight('my_plugin','allowEditMyContacts')==1)));

if($this->moduleAccess){

        // action si le droit est accordé

}
else {

        //action si le droit est refusé

}




Utilisation des gabarits HTML

En backend

Pour assurer la mise en page de votre plugin en backend, le plus simple est de définir un gabarit HTML externe dans lequel vous placerez des balises de {marqueurs} auxquelles vous pourrez substituez les données que vous souhaitez afficher.

Dans l’exemple my_plugin, on trouve le gabarit HTML de base pour un plugin backend. En voici une version simplifiée pour l’exemple :

<div id="modulePadder">
        <div class="row">
                <div class="module-menu col-lg-2">
                        <div class="well" style="max-width: 340px; padding: 8px 0;">
                                <ul id="mfNavModule" class="nav nav-list">
                                        {local-menu}
                                </ul>
                        </div>
                <div>
        </div>

        <div class="module-body col-lg-10">
        {module-body}
        </div>
</div>

Dans la fonction render() de votre module, vous pourrez substituer les marqueurs par les données à afficher que vous aurrez préalablement préparées dans la fonction prepareData() :

function render(&$mainTemplate){
        global $mf,l10n;

        if($mf->mode == 'backend'){
                if(isset($_REQUEST['module']) && ($this->moduleKey == $_REQUEST['module'])){

                        // on charge le gabarit HTML du module
                        $moduleBody = file_get_contents(DOC_ROOT.
                        SUB_DIR.$config['website_pluginsdir'].
                        '/my_plugin/ressources/templates/module.html');

                        // on substitue nos marqueurs dans le gabarit HTML qu'on a chargé
                        $moduleBody = str_replace("{module-body}",$this->pageOutput,$moduleBody);
                        $moduleBody = str_replace("{local-menu}",$this->localMenu,$moduleBody);

                        // Enfin on insère le corps de notre module dans l'objet $mainTemplate
                        // mis à disposition par MindFlow en argument de la fonction render()
                        $mainTemplate = str_replace("{current_module}",$moduleBody,$mainTemplate);

                }

        }
}



En frontend

En frontend, la stratégie sera peu différente. On insèrera tout d’abord un marqueur unique à notre plugin dans le gabarit d’une page du site :

<div>
        <h5>Consultez la liste des contacts :</h5>
        {my-plugin}
</div>

Puis il nous suffira ensuite de le remplacer le code produit par notre plugin :

function render(&$mainTemplate){
        global $mf,$l10n;

        if($mf->mode == 'backend'){

                //...

        }

        else if($mf->mode == 'frontend'){

                $mainTemplate = str_replace("{my-plugin}",$this->pageOutput,$mainTemplate);

        }
}



La fonction showRecordEditTable()

Vous vous êtes peut-être demandé comment générer ce genre de vue dans votre module :

_images/showRecordEditTable.png

La fonction showRecordEditTable() héritée de la classe dbRecord permet de générer facilement ce type de vue pour tous les types d’enregistrements. Voici son profil :

function showRecordEditTable(
        $request,               // requête SQL de sélection des enregistrements affichés dans le recordEditTable
        $moduleName,    // module
        $subModuleName, // sous-module
        $mainKey,       // colonne dont le titre clicable
        $keyProcessors = array(), //functions pour prétraiter l'affichage des valeurs de certaines colonnes
        $page = NULL,   // page actuellement affichée si la sélection s'étend sur plusieurs pages
        $availableActions = array(
                'create'=>1, // affiche un bouton de création de nouvel enregistrement au dessus du tableau recordEditTable
                'view'=>0,       // affiche une icône de visualisation de l'enregistrement sur chaque ligne
                'edit'=>1,   // affiche une icône d'édition de l'enregistrement sur chaque ligne
                'delete'=>1, // affiche une icône de suppression de l'enregistrement sur chaque ligne
                'clone'=>1   // affiche une icône de duplication de l'enregistrement sur chaque ligne
        ),
        $advancedOptions=array(
                'ajaxActions' => 1,     // Prise en charge des actions 100% AJAX (actif par défaut)
                'showPrint' => true,  // affiche un bouton pour imprimer la sélection affichée
                'showResultsCount' => true //affiche le nombre de résultats de la sélection en cours
        )
){}

Retrouvez une documentation complète de l’ensemble de ses paramètres dans la documentation phpDoc de la classe dbRecord.

Voici un exemple d’utilisation dans notre plugin d’exemple my_plugin :

//on crée une instance de notre enregistrement
$contact = new myContact();

//Insertion d'un bouton personnalisé pour réaliser un export CSV
$buttons = '<button type="button" class="btn-sm mf-btn-new" id="exportCSV" name="exportCSV" onclick="document.location=\''.getHTTPHost().SUB_DIR.'/mf/core/csv-exporter.php?sec='.md5(microtime()).'\';"><span class="glyphicon glyphicon-th"></span> '.$l10n->getLabel('backend','export_to_csv').'</button>';


//génération du code HTML du showRecordEditTable au moyen de la fonction
return $contact->showRecordEditTable(
        array(
                //requête SQL de sélection des enregistrements à afficher
                'SELECT' => 'creation_date,name, email,deleted',
                'FROM' => '',
                'JOIN' => '',
                'WHERE' => '1=1'.$dateConditions.$sqlConditions.$deletedCondition,
                'ORDER BY' => 'name',
                'ORDER DIRECTION' => 'ASC',
        ),
        //clé du module
        'my_plugin',
        //clé du sous module s'il y a lieu
        '',
        //champ principal (celui-ci sera cliquable)
        'name',
        //fonction de pré-traitement pour certains champs avant affichage
        $keyProcessors = array(
                'creation_date' => 'dbRecord::formatDate',
                'deleted' => 'dbRecord::getDeletedName',
        ),
        //index de pagination à afficher
        $page = NULL,
        //actions activées
        array(
                'create' => 1,
                'view' => 1,
                'edit' => 1,
                'delete'=> 1
        ),
        //options avancées
        array(
                //Indique si les enregistrement doivent être ouverts en popup AJAX ou dans leur propre page
                'ajaxActions' => true,
                //ici on ajoute le code de notre bouton personnalisé "Export CSV"
                'buttons' => $buttons,
                //classes associées aux colonnes, utiles notamment pour les tableaux responsive
                'columnClasses' => array(
                        'creation_date' => 'hidden-xs',
                        'email' => 'hidden-xs hidden-sm',
                        'deleted' => 'hidden-xs hidden-sm',
                ),
                //afficher les requêtes SQL générées
                'debugSQL' => 0,
                //boutons disponibles pour la validation du formulaire d'édition d'un enregistrement ouvert à partir du recordEditTable
                'editRecordButtons' => array(
                        'showSaveButton' => true,
                        'showSaveCloseButton' => true,
                        'showPreviewButton' => false,
                        'showCloseButton' => true
                )
        ),
        'contact'       // spécifiez un recordEditTableID si vous ne voulez pas qu'il soit spécifié de manière aléatoire.
                                // Ceci vous permet d'acceder au div au moyen de #recordEditTable_contact plutôt que #recordEditTable_78546642 (nombre aléatoire)
                                // MindFlow nécessite un ID unique pour chaque recordEditTable au cas ou plusieurs seraient affichés simultanément dans la page
);

Appels sécurisés

Un problème de sécurité courant est le détournement de formulaires par des personnes mal intentionnées.

Par exemple nous avons un formulaire d’édition de profil utilisateur sur notre site. Celui-ci fait figurer l’action à réaliser sur l’objet en cours d’édition, le nom de sa classe et son uid. En effet, le script ajax-core-json.php a besoin de ces 2 informations pour identifier l’enregistrement qu’il va devoir mettre à jour :

<form id="form_1496842627" method="POST" role="form" class="form-horizontal formNoEdit" enctype="x-www-form-urlencoded" action="/mf/core/ajax-core-json.php">
        <!-- champs cachés -->
        <input type="hidden" name="formID" id="formID" value="form_1496842627">
        <input type="hidden" name="mode" id="mode" value="backend">
        <input type="hidden" name="action" id="action" value="saveRecord">
        <input type="hidden" name="class" id="class" value="vzUserBackend">
        <input type="hidden" name="uid" id="uid" value="33590">

        <!-- champs affichés -->
        <input type="text" value="" name="record|first_name" id="record|first_name" class="input form-control  input-md text">
        <input type="text" value="" name="record|last_name" id="record|last_name" class="input form-control  input-md text">
</form>

Il serait alors simple pour un hacker d’altérer la valeur du champ uid ou de substituer un autre nom de classe de manière à pouvoir éditer les profils d’autres utilisateurs, ou d’autres objets du CMS comme la table des utilisateurs backend. Ce problème de sécurité est également susceptible d’affecter vos plugins.

Pour prévenir ce type de hack, nous devons pouvoir contrôler que certains champs n’ont pas été altérés. Pas tous évidemment, puisque l’essence d’un formulaire est de recueillir des données de la part de l’utilisateur, données qui sont par essence variables et imprévisibles. En revanche pour les autres champs qui sont générés par le plugin, on doit pouvoir vérifier leur absence de modification.

La solution consiste à générer un Hash basé sur la valeur de ces champs. Nous allons donc définir un tableau dans lequel nous allons indiquer les clés des champs concernés ainsi que leurs valeurs :

Nouvelle méthode

Depuis Mindflow 1.8, la génération des hashs a été simplifiée. Voici son fonctionnement.

Tout d’abord, on génère un objet $sec comportant les titres et valeurs des champs qui ne doivent être modifiés sous aucun prétexte :

$sec = getSec(
    array(
        'action' => 'saveRecord',
        'class' => 'my_record_classname',
        'uid' => $uid
    )
);

On pourra ensuite insérer le hash et la liste des champs dans la prochaine requête faite à MindFlow. Si celle-ci est réalisée via un formulaire, on poura ajouter 2 champs nommés ‘sec’ et ‘fields’ auxquels on assignera respectivement les valeurs $sec->hash et $sec->fields de notre objet $sec

    $html[] = '<input type="hidden" name="action" id="action" value="saveRecord" />';
    $html[] = '<input type="hidden" name="class" id="class" value="my_record_classname" />';
    $html[] = '<input type="hidden" name="uid" id="uid" value="'.$uid.'" />';
    $html[] = '<input type="hidden" name="sec" id="sec" value="'.$sec->hash.'" />';
    $html[] = '<input type="hidden" name="fields" id="fields" value="'.$sec->fields.'" />';

S’il s’agit plutôt d’une requête AJAX, on aura :

    var jqxhr = $.post("' . SUB_DIR . '/mf/core/ajax-core-json.php",
    {
            action: "saveRecord",
            uid: $uid,
            class: "my_record_classname",
            sec: "' . $sec->hash . '",
            fields: "' . $sec->fields . '",
    });

S’il s’agit d’une URL, on pourra aussi utiliser $sec->parameters pour injecter dans l’URL les paramètres déjà spécifiés dans la variable $sec. Dans l’exemple ci-dessous, $sec->parameters contiendra automatiquement la chaine suivante :

'action=saveRecord&class=my_record_classname&uid='.$uid

On l’utilisera ainsi :

    $url = SUB_DIR.$config['website_pluginsdir'].'/vz_orders/ajax-vzOrders-html-backend.php?'.$sec->parameters.'&useAJAX=1&mode=backend&header=0&footer=0&sec='.$sec->hash.'&fields='.$sec->fields;

Enfin, dans notre script recevant la requête émise, nous allons appeler la fonction getSec qui va générer un nouveau hash portant sur la valeur des champs reçus et les comparer au hash envoyé avec le formulaire :

// Vérification du hash de sécurité. La requête ne sera pas honorée si le hash de sécurité est erroné ou manquant
if(checkSec()){

        // traitement de vos données de formulaire
    $uid = $_REQUEST['uid']
    // ...

}
else {
        $mf->db->closeDB();
        die('MindFlow : Request was not accepted. Check the "sec" and "fields" values must be present in the request and properly set.');
}

Important

A ce stade vous pourriez-vous demander : qu’est-ce qui empêche un hacker de forger son propre hash et de l’envoyer avec le formulaire, contournant ainsi la protection ?

La résponse est simple, c’est le “sel” ! Souvenez-vous, lors de l’installation, il vous a été vivement recommandé de personnaliser le paramètre de sel nommé $config['salt'].

Lorsqu’un hash est généré par MindFlow, celui-ci concatène la valeur du sel avec la valeur des champs ajoutés. Il réalise la même opération lors de la vérification du Hash. Or la valeur du sel est supposée être être unique à votre installation et secrête.

Aussi, si le hacker tente de forger un hash, il ne possèdera pas la valeur du sel et sa requête échouera. Il est donc essentiel pour la sécurité de bien personnaliser le paramètre de définition de la valeur du sel lors de l’installation.

Ancienne méthode

$secKeys = array(
        'action' => 'saveRecord',
        'class' => 'my_record_classname',
        'uid' => $uid
);

Ensuite on génère notre hash :

$secHash = formsManager::makeSecValue($secKeys);

Enfin, on génère une liste des champs concernés :

$secFields = implode(',',array_keys($secKeys));

On pourra ensuite insérer le hash et la liste des champs dans la prochaine requête faite à MindFlow. Si celle-ci est réalisée via un formulaire, on poura ajouter 2 champs nommés ‘sec’ et ‘fields’ (ou tout autre nom souhaité, notez que Mindflow utilise déjà les noms ‘sec’ et ‘fields’ pour contrôler les champs standard de formulaires) :

$html[] = '<input type="hidden" name="action" id="action" value="saveRecord" />';
$html[] = '<input type="hidden" name="class" id="class" value="my_record_classname" />';
$html[] = '<input type="hidden" name="uid" id="uid" value="'.$uid.'" />';
$html[] = '<input type="hidden" name="sec" id="sec" value="'.$secHash.'" />';
$html[] = '<input type="hidden" name="fields" id="fields" value="'.$secFields.'" />';

S’il s’agit plutôt d’une requête AJAX, on aura :

var jqxhr = $.post("' . SUB_DIR . '/mf/core/ajax-core-json.php",
{
        action:"saveRecord",
        uid: $uid,
        class: "my_record_classname",
        sec: "' . $secHash . '",
        fields: "' . $secFields . '",
});

Enfin, dans notre script recevant la requête émise, nous allons générer un nouveau hash portant sur la valeur des champs reçus et le comparer au hash envoyé avec le formulaire :

//Vérification du hash de sécurité. La requête ne sera pas honorée sie le hash de sécurité est erroné ou manquant
if((isset($_REQUEST['sec']) && isset($_REQUEST['fields']) )&& formsManager::checkSecValue($_REQUEST['sec'],explode(',',$_REQUEST['fields']))){

        // traitement du formulaire
    $uid = $_REQUEST['uid']
// ...
}
else {
        $mf->db->closeDB();
        die('MindFlow : Request was not accepted. Check the "sec" and "fields" values must be present in the request and properly set.');
}

Export CSV

MindFlow propose en standard un script dédié à l’export CSV. Aussi pour réaliser un export CSV, il suffit de l’appeler en spécifiant les bons paramètres :

// Définition d'un identifiant unique pour la session contenant les paramètres a exécuter lors de notre export CSV
// Celui-ci sera stocké dans la session et supprimé à l'expiration de celle-ci.
// Aussi l'utilisateur pourra rapeller ce lien tant que sa session n'aura pas expirée, mais il ne pourra pas le communiquer à un tiers.
$sec = md5(microtime());

//on vérifie que $_SESSION['actions'] existe bien, sinon erreur
if(!isset($_SESSION['actions']))$_SESSION['actions']=array();

//requête pour l'export CSV stockée en $_SESSION
$_SESSION['actions'][$sec]=array(

    //déclenche l'execution de l'export CSV
    'action' => 'exportCSV',

    //paramètre optionnel : 'utf-8' ou 'windows-1252' ('windows-1252' est la valeur par défaut si le paramètre n'est pas spécifié)
    'CSV_charset' => 'utf-8',

    //caractère séparateur de colonnes
    'CSV_separator' => ';',

    //requête SQL a exécuter pour l'export. Tous les champs spécifiés seront exportés. Il est également possible de spécifier SELECT *
    'sql' => 'SELECT uid,language,organization,description,address_3,address_1,address_2,address_4,city,country,latitude,longitude,zip,emergency_phone,email,booking_link,code_alliance_reseau,website,picture,type_prestataire FROM '.vzPrestataire::getTableName().' WHERE type_utilisateur="Prestataire" AND deleted=0'.$sqlConditions,

    //classe de l'enregistrement exporté, requise pour l'interprétation des données en fonction des entrées 'valueType' spécifiées dans le tableau data de l'objet.
    'record_class' => 'vzPrestataire',

    //pour ne pas exporter certaines colonnes. Utile notamment quand on a réalisé un SELECT * pour éviter de spécifier chaque colonne et qu'on veut en supprimer certaines quand même
    'skipKeys' => array('address_1', 'address_2', 'address_4'),

    //afficher les noms de colonnes en première ligne si true
    'print_column_names' => false,

    //Il est possible de spécifier des fonctions keyProcessor pour réinterpreter les données lors de l'export.
    //Le profil de ces fonctions est identique à celui utilisé pour les keyProcessors employés avec la fonction showRecordEditTable
    'keyProcessors' => array(
        'address_3'=>'vzPrestataire::adressePrestataire',
        'picture'=>'vzPrestataire::getPictureURL',
        'country'=>'vzUser::getPays',
    ),
);

//définition d'un boutton et son lien d'appel de l'exporteur CSV. Ce code HTML sera ensuite affiché dans votre page.
//L'appel de ce lien déclenchera le téléchargement d'un fichier CSV par le navigateur de l'utilisateur
$buttons = '<button type="button" class="btn-sm mf-btn-new" id="exportCSV" name="exportCSV" onclick="document.location=\''.getHTTPHost().SUB_DIR.'/mf/core/csv-exporter.php?sec='.$sec.'\';"><span class="glyphicon glyphicon-th"></span> '.$l10n->getLabel('gestionPrestataires','export_mhikes').'</button>';