Cet article présente une possibilité sous-exploitée de Wordpress : combiner site édito & intranet. Nous abordons les bases d'un point de vue fonctionnel et technique.

Utiliser WordPress pour un intranet complet

Le couteau suisse international

WordPress est un système de gestion de contenu (CMS) gratuit, libre et open-source. Particulièrement populaire (il représenterait 40% des sites web dans le monde), il permet de créer et gérer des sites vitrine, blogs, portfolios etc. Le monde des agences et même le grand public connait les utilisations classiques de WordPress, mais il peut être décliné de beaucoup d'autres manières.

Utiliser Wordpress en tant qu'espace sécurisé

Nous n'allons aborder ici qu’une de ces utilisations méconnues : la création d’espaces connectés et sécurisés. Nous pouvons considérer cela comme un intranet, un extranet ou même un espace membre. C’est une suite logique de fonctionnalités qui va correspondre aux besoins finaux, comme par exemple un espace de gestion de stock, ou un espace de collecte et visualisation de données sur l’activité.

La création d’un espace sécurisé n’est bien sûr pas exclusive à WordPress, loin de là, mais ce dernier apporte tout son bagage qui pousse à le choisir lui plutôt qu’un autre. WordPress permet déjà de faire un site vitrine, un blog et plein d’autres choses, et bien ces éléments-là seront toujours utilisables en plus de l’espace connecté sécurisé.

En plus d’avoir un extranet vous pourriez avoir au même endroit votre blog, vos pages entreprises, vos pages de contacts, de devis, etc. Ce qui en fait un véritable atout en comparaison au développement d’un espace connecté sur un autre Framework.

WordPress avec toutes ses fonctionnalités « clé en main » présentes dans le cœur du CMS, avec les outils et modules développés qui sont réutilisables entre les différents projets, tout en passant par des plugins populaires, et avec sa communauté qui est parmi les plus importantes dans le développement Web, en fait un choix de prédilection.

Fonctionnalités courantes dans un intranet

Une liste non-exhaustive de ces fonctionnalités pourrait être :

  • la gestion de chaîne de production (workflow)
  • la gestion de plannings
  • le partage de ressources
  • la gestion de documents internes
  • un espace de visioconférences
  • formulaire de collecte de données
  • dashboard d’affichage de données agglomérées

Nous avons été amenés à proposer et développer un grand nombre de fonctionnalités au fil de nos différents projets, et nous allons présenter 2 enjeux primordiaux lorsqu'il s'agit d'intranet : la gestion des la sécurité, et la création de dashboards / rapports.

Comment générer un dashboard dans Wordpress ?

Voici quelques exemples de fonctionnalités que l’on peut envisager dans un espace connecté. Mais il est évident que le nombre de fonctionnalités est illimité.

Collecte de données

Créer une section dans l’espace connecté où vos utilisateurs auront accès à un ensemble de moyen afin de renseigner des données qui pourront être affichées ou utilisées pour autre chose.

Par exemple, un utilisateur peut renseigner des informations sur son groupe/pôle, comme une description, une liste des adhérents, le logo, etc. Et ensuite ces informations seront directement visibles en front sur la partie publique du site internet.

Ou encore de simplement collecter des chiffres sur des ventes de produits, et ces données seront ensuite agglomérées, traitées et des rapports seront générés afin d’être visionner dans une section dédiée de l’espace connecter.

Tant dans le principe que dans le développement, ce n’est pas très compliqué. Il faut juste être minutieux et ne rien laisser au hasard, surtout vis à vis de la sécurité.

Il va donc falloir créer un template qui contient un formulaire et qui permet de renseigner ces éléments. Il faut gérer les accès à ces pages lors de leur affichage, mais aussi faire des vérifications à l’envoi du formulaire.

En plus d’un simple formulaire html on peut créer un template supplémentaire qui permet à l’utilisateur de procéder à un import (csv ou équivalent) pour accélérer l’enregistrement d’un grand nombre de données.

Côté back-office on peut créer des CPT ("Custom Post Type" = Types de contenus personnalisés dans Wordpress) afin de gérer plus précisément ces formulaires ou ces données.

Rapport de données

En complément de la collecte de données, avec ces dernières on peut créer et générer des rapports qui mettent en scène ces mêmes données.

Fonctionnement technique : On va créer un CPT de rapport qui permettra de les identifier. Dans ce CPT on va gérer côté back-office la génération en ajoutant un bouton sur un post de rapport qui permet de lancer une action custom.

Dans cette action nous pouvons y mettre beaucoup de choses. On peut récupérer par exemple les données de ventes des produits de type “nourriture” de tous les adhérents et créer à partir de ça un graph qui permet de visualiser cela. Encore une fois ici les possibilités sont presque infinies.

A ce CPT on peut aussi imaginer y ajouter un système qui permet d’ouvrir et fermer l’accès au rapport en combinant un choix de plage de date + des cases à cocher. Et également une fonctionnalité d’édition et d’envoi de mail automatique aux adhérents concernés.

L’affichage de ces rapports sera un autre template de l’espace connecté, et il faudra sécuriser l’accès à ce template.

Export sécurisés (excel, csv)

Dans l’espace connecté on peut également ajouter une fonctionnalité d’export en fichier excel des données et/ou des rapports.

Mise en place technique

Pour générer un fichier excel sans trop de difficulté et avec tout ce dont vous avez besoin nous conseillons fortement d’utiliser PhpSpreadsheet. C’est une librairie écrite en PHP et elle permet de lire et d'écrire des fichiers excel (marche aussi avec divers formats de fichiers de tableur).

Sans trop rentrer dans les détails (et sans répéter les éternels check de sécurité), pour faire un export excel il faut d’abord créer une route comme on a pu le faire pour les uploads sécurisés ou pour la gestion invisible des pages de l’espace connecté.

Ensuite dans le template appelé via la route nouvellement créée on va récupérer les données à mettre dans le fichier excel, puis créer une instance de PhpSpeadsheet pour commencer la création du fichier excel : $spreadsheet = new Spreadsheet();

On met les données dedans, on stylise l’excel, et tout ce dont on a besoin. Et à la fin de tout ça on lit le fichier afin que l’utilisateur qui appelle la route puisse avoir le téléchargement du fichier qui se lance (un adhérent qui clique le lien de téléchargement).

Le fichier se termine comme suit :

// force download
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");

// file
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename="'.$export_excel_file_name.'"');
header('Content-Length: ' . filesize($export_excel_file_path));

// echo to download
readfile($export_excel_file_path);

die();

Comment gérer la sécurité d'un intranet construit avec Wordpress ?

Un cliché très souvent entendu à propos de WordPress est qu’il n’est pas assez sécurisé, qu’il n’est pas optimisé et performant… Nous allons vous démontrer ci-dessous que bien conçu et structuré, WordPress peut s'avérer un outil fiable et bien protégé.

Sécurité des pages

Pour gérer la sécurité de toutes les routes et pages il faut pouvoir les identifier facilement afin de pouvoir appliquer des conditions de sécurité.

On peut procéder de deux manières :

  • Soit faire une gestion complètement invisible dans le code
  • Soit catégoriser une liste de page

Dans les deux cas, il est impératif de définir un listing en dur des pages qui composeront l’espace connecté. On pourrait dire que ce listing est inutile, mais comme il faut absolument, à un moment ou un autre, pouvoir identifier les pages qui doivent être accessibles, celles qui existent et celles qui n’existent pas, il faut forcément passer par la case « listing des pages ».

Gestion invisible

Dans ce cas-ci il faut que ça reste lisible, donc il est fortement conseillé (si ce n’est obligatoire) d’avoir un dossier dans son thème et des fichiers dédiés avec une nomenclature facilement identifiable.

On utilise ensuite le combo classique query_vars, add_rewrite_rule et template_include. Voici un exemple basique :

// Create a new route, and make a redirection to it
add_filter('query_vars', function ($vars) {
   $vars[] = 'secured_area_page';
   return $vars;
});
add_rewrite_rule(
   '^namespace-de-lespace-connecte/([a-z_]+)/?',
   'index.php?secured_area_page=$matches[1]',
   'top'
);
add_filter( 'template_include', 'template_redirect_for_secured_area', 99 );
function template_redirect_for_secured_area( $template ): string {
   if ( ! empty( get_query_var( 'secured_area_page' ) ) ) {
   return get_template_directory() . '/inc/secured_area_controllers/'.get_query_var( 'secured_area_page' ).'.php';
   }

   return $template;
}

Ce script récupère automatiquement le fichier PHP demandé en paramètre de l’url. Dans cet exemple, aucune sécurité n’est visible. Nous pouvons ajouter la sécurité soit dans la regex de la rewrite rule en y forçant la liste des slugs des pages définies, soit le vérifier dans le filtre template_include. 

 Gestion par pages catégorisées

Dans le cas où l’on catégorise les pages, il faut créer des templates de page. Nous pouvons en créer un seul qui servira de « dispatch » vers tous les controllers correspondants, ou créer autant de template que de page souhaitée.

Ensuite nous créons un statut de post avec la fonction register_post_status() que l’on va assigner aux pages concernées. Pour cela on récupère les pages avec une WP_Query en filtrant par 'meta_key' => '_wp_page_template' et dans la clé 'meta_value' on met un array des templates PHP créés pour l’occasion. On loop donc dessus et on wp_update_post() les pages en assignant le ‘post_status’ avec celui créé plus haut. On complète cela avec le fitlre display_post_states()afin que le statut de page soit visible dans le tableau qui liste les pages.

Maintenant toutes nos pages sont facilement identifiables dans le backoffice et l’on peut gérer la sécurité en se basant sur le post_status de la page. On gérera cette sécurité via l’action template_redirect.

 Comparatif des 2 options de sécurité proposées

En conclusion ces deux techniques ont leurs avantages et inconvénients. L’une permet un nombre réduit du total d’action à effectuer pour créer une nouvelle page dans l’espace connecté et peut donc réduire les erreurs d’oublis, mais la visibilité de l’existence de ces pages n’est que dans le code. La gestion qui passe par la création d’un statut de page et de multiples templates PHP permet d’avoir une vraie visibilité de ces pages dans le backoffice et éventuellement d’y ajouter des champs ACF pour avoir des metas, mais l’ajout d’une page spécifique pour l’espace connecté demande une action dans le code et côté backoffice WP.

Sécurité des utilisateurs

WordPress vient avec son lot de type d’utilisateur et leurs droits : administrateur, éditeur, auteur, contributeur, abonné. Mais pour gérer au mieux les accès à l’espace sécurisé, il est conseillé de créer un nouveau type d’utilisateur. En créant cet utilisateur on lui assigne les “capabilities” que l’on souhaite, chez Sooyoos nous préférons n’en mettre aucune afin de bloquer entièrement l’accès au back-office par défaut et de gérer soi-même les accès de ce type d’utilisateur et qu’il n’ait un accès qu’à une section connecté côté front entièrement géré à la main.

Droits customs pour un nouveau type d’utilisateur

Bien évidemment comme nous créons un espace connecté entièrement dédié, les “capabilities” par défaut de WordPress ne permettent pas de gérer toutes les spécificités. C’est pourquoi nous allons créer des champs customs avec le plugin ACF afin de gérer ces droits. 

Comme par exemple :

  • Droit de renseignement des données (Aucun droit / Collecte par formulaire / Collecte par formulaire & import csv)
  • Droit d’accès aux fichiers du centre de ressources (Aucun droit / Catégorie de fichier 1 / Catégorie de fichier 2 / [...])
  • Droit à la gestion des plannings (Admin de planning / Editeur de planning / Lecteur de planning)
  • Droit d’accès aux visioconférences (Liste des catégories/groupes de visioconférence)

Ensuite tout au long du développement, dans le code, nous pourrons récupérer les valeurs de ces champs pour l’utilisateur connecté et vérifier ses droits.

Droits customs pour l’administrateur

Pour laisser entièrement la main aux administrateurs par rapport aux champs customs qui ont été créés afin de gérer des droits et paramètres d'utilisateurs, on va devoir donner les accès à tous les administrateurs. 

Pour cela il faut logiquement, à tous les endroits où l’on vérifie dans le code avec des conditions/checks les droits d’accès d’un utilisateur, check si l’utilisateur est administrateur et si oui donner l’accès. Mais ça peut rendre compliqué la lecture du code et de penser à le faire partout s’il y a besoin de beaucoup de ces conditions. Comme nous utilisons le plugin ACF pour les champs customs on va pouvoir utiliser le filtre :

apply_filters( 'acf/format_value/key={$key}', $value, $post_id, $field ); 

ne pas oublier de mettre au début de la fonction :

if ( wp_doing_ajax() ) {
  return $value;
}

Pour éviter des problèmes lors de call ajax.

Dans cette fonction, nous vérifions si l’utilisateur est administrateur et nous remplissons les champs de toutes les valeurs possibles afin d’octroyer tous les droits. Ainsi à chaque fois que l’on récupère les champs ACF dans le code pour vérifier les droits customs aucune logique supplémentaire pour un administrateur ne sera à faire.

Helpers pour administrer les “adhérents”

Avec ces champs customs de droits d’accès, il peut être compliqué ou non souhaité de rajouter ces champs au formulaire par défaut de création d’utilisateur WordPress. Surtout si on a un montage en multisite. C’est pourquoi pour le client il sera nettement plus facile d’avoir une section dédiée à la création de ses adhérents. 

Nous pourrons donc penser à créer une page d’ajout/création d’adhérents en uploadant un csv contenant toutes les informations indispensables à cela. Un administrateur pourra donc rapidement créer beaucoup de compte d’adhérent sans grands efforts.

En plus de cela on crée une page d’ajout/création d’adhérent à l’unité. En utilisant une simple page d’option ACF en y mettant tous les champs customs c’est simple et rapide. On peut vider le formulaire à chaque envoi si c’est la création est un succès. Ne pas oublier d’ajouter les messages d’erreurs et de succès, et d’envoyer un mail à l’utilisateur créé afin qu’il puisse créer son mot de passe.

Sécurité des uploads

Dans un espace connecté nous avons besoin d’avoir des fichiers uploadés via le back-office de WordPress qui doivent être uniquement accessible aux utilisateurs connectés et ayant les droits correspondants. Ce n’est pas l’unique solution possible à ce problème, mais voici une solution simple et efficace.

On commence par utiliser le filtre suivant :

apply_filters( 'pre_move_uploaded_file', mixed $move_new_file, string[] $file, string $new_file, string $type );

La particularité de ce filtre est que si une valeur non null est retournée, WordPress ne va plus bouger le fichier ni rapporter/afficher d’erreur. Le but va donc être de retourner une valeur, un booléen par exemple, et donc bien évidemment de bouger et setup le fichier nous-même. Ça tombe bien c’est exactement ce que l’on souhaite faire : gérer à la main l’upload de certains fichiers pour restreindre et sécuriser leur accès.

Dedans nous allons commencer par vérifier où l’on se trouve lors de l’upload du fichier. Par exemple, une page d’édition d’un CPT ou une page de l’espace connecté. Ainsi lorsque ça passe dans le filtre on cible précisément ce que l’on souhaite traiter comme upload.

Ensuite on définit l’endroit où l’on va stocker tous nos fichiers uploadés qui auront un accès restreint. De préférence dans un sous-dossier du dossier uploads/ et en gardant à l’intérieur une architecture équivalente au dossier uploads/ classique : {année}/{mois}/{fichier}.

$uploads = wp_upload_dir();
$destination_folder = $uploads['basedir'].'/restricted'.$uploads['subdir'];

Ne pas oublier de vérifier si le dossier existe, et de le créer si ce n’est pas le cas.

if ( ! is_dir($destination_folder) ) {
  mkdir( $destination_folder, 0777, true );
}
$move_new_file = move_uploaded_file( $file['tmp_name'], $new_file_path )

Ensuite nous allons déplacer le fichier uploadé dans le dossier de destination dédié. A sooyoos nous préférons également modifier le nom du fichier en y ajoutant un ID qui s’incrémente : nom_du_fichier_{ID}.ext Comme ça nous pouvons gérer plusieurs fichiers ayant potentiellement le même nom sans avoir de conflit.

Habituellement WordPress gère les permissions d’accès sur le fichier lui-même, mais comme on modifie la destination d’upload du fichier WordPress ne le fera pas, nous le faisons donc nous-même :

$stat  = stat( dirname( $new_file_path ) );
$perms = $stat['mode'] & 0000666;
chmod( $new_file_path, $perms );


Dans un second temps nous utiliserons le filtre :Voilà pour le contenu de ce filtre. Il est à préciser que vu le traitement que l’on applique à cet upload custom, WordPress ne génèrera pas les différentes tailles d’image, on ne garde que les fichiers originaux. Ce n’est pas forcément pour déplaire mais est tout de même à mentionner.

apply_filters( 'wp_handle_upload', array $upload, string $context );


Ensuite on va ajouter une meta à notre fichier uploadé afin de mieux l’identifier lors d’une requête. Pour cela on utilise la même fonction appelée avec les deux actions edit_attachment et add_attachment. Dans la fonction on récupère le guid de l’upload pour check s’il contient le fameux restricted dans son path, si oui on fait un update_post_meta() pour lui assigner une meta, par exemple attachment_is_restricted.Qui servira lui à filtrer les données de l’array du fichiers uploadé qui seront enregistrés en base de données (son chemin, son nom, etc.). Il suffit simplement de reprendre les informations renseignées dans le filtre précédent et de les search & replace dans l’array retourné de ce filtre
wp_handle_upload.

Grâce à cette meta on peut utiliser le filtre :

apply_filters( 'ajax_query_attachments_args', array $query );

Où dedans en fonction de où l’on se trouve on va modifier la query avec une 'meta_query' en jouant avec la meta attachment_is_restricted.

Une fois l’upload customisé on va devoir créer une route qui permet d’accéder à ces uploads restreints et sécurisés. Pour cela rien de plus simple, on combine le filtre query_vars, la fonction add_rewrite_rule() et le filtre template_include comme suit :

add_filter('query_vars', function ($vars) {
  $vars[] = 'restricted-uploads';
  return $vars;
});
add_rewrite_rule(
  '^restricted-uploads/(.+)?$',
  'index.php?restricted-uploads=$matches[1]',
  'top'
);
add_filter( 'template_include', 'restricted_uploads_template', 99 );
function restricted_uploads_template( $template ) {
  if ( get_query_var( 'restricted-uploads' ) == false || get_query_var( 'restricted-uploads' ) == '' ) {
    return $template;
  }
  return get_template_directory() . '/restricted-uploads.php';
}


https://mon-site.com/restricted-uploads/354On peut donc créer un template qui récupère soit l’ID du fichier soit le path du fichier dans le dossier des uploads comme suit : 

  • https://mon-site.com/restricted-uploads/2021/01/mon-pdf.pdf

Retourner l’image via le template

Tout ça va se passer dans le fichier du template que l’on a arbitrairement défini dans le filtre template_include.

Tout le début de ce template se compose de check de sécurité :

  • Check si la variable du fichier à récupérer :
    • existe
    • est dans le bon format
  • Check si l’utilisateur :
    • est connecté
    • a les droits d’accès au fichier
  • Et autant d’autres vérifications que vous souhaitez

Si erreur il y a, il faut afficher ou rediriger sur un template d’erreur.

Sinon ensuite on va récupérer l’upload. Si c’est un ID on fait simplement un get_post().

Mais si on a le path du fichier dans le dossier des uploads on va chercher avec le guid avant de faire un get_post(). En faisant une requête sql c’est très simple :

global $wpdb;
$post_id = $wpdb->get_var( $wpdb->prepare(
  "SELECT ID
  FROM $wpdb->posts
  WHERE guid=%s",
  $guid
) );
$attachment = get_post( $post_id );

Il ne reste plus qu’à faire un echo file_get_contents( $attachment_path ); et c’est terminé.

 

En conclusion, WordPress est une excellente solution pour combiner site vitrine et espace partagé sécurisé.

Comme on a pu le voir dans cet article, avec un socle Wordpress bien structuré, il est tout à fait pertinent de construire des espaces de gestion de données sécurisés, flexibles pour correspondre à beaucoup de besoins clients. 

En plus d’être flexible, cette solution est également maintenable et adaptable dans le temps. C’est ainsi que l’on conçoit et développe nos projets chez Sooyoos et c’est ce que l’on propose à nos clients : des solutions sur-mesure. C’est en ça que réside la force de savoir comment utiliser pleinement l’outil qu’est WordPress.

Article écrit par Willy , Lead WordPress @Sooyoos

    Comment pouvons-nous vous aider ?

    Dites nous quelles sont vos problématiques ou besoins actuels.

  • Votre projet

  • Vous contacter

  • Ce champ n’est utilisé qu’à des fins de validation et devrait rester inchangé.