Aller au contenu principal


Le bloc "likes"

Ce billet est la suite de la page "autopsie d'un module" dont la lecture aidera à la compréhension de ce billet.
Le bloc "likes" est l'élément central de mon module personnalisé "dh_like". Sa  finalité, illustrée ci-dessous, est de permettre au visiteur dire s'il aime ou non la page affichée. 

Cliquer pour agrandir l'image

 

Billet créé le :
07 Mar 2023

Ce bloc doit être ajouté aux pages pour lesquelles l'administrateur du site souhaite obtenir un avis du lecteur soit :

Ce billet explique les différentes parties du code fournit dans l'onglet en fin de page.

  1. Le code principal ;

  2. Les formulaires contenus dans le bloc ;

  3. La gestion de la table "like" dans la base de données du site.

1. Le programme principal

La documentation de base : https://www.drupal.org/docs/creating-modules/creating-custom-blocks/create-a-custom-block est à connaître pour une bonne compréhension de ce billet.

Le code de ce programme figure dans l'onglet "dh_like_block" du paragraphe "le code" présenté ci-dessous et figure dans le fichier "dh_like_block.php" du dossier "src/Plugin/Block" du module.

Il définit la classe "dh_like_block" comme extension de la classe abstraite "BlockBase"  qui offre la fonction générique "buid()" pour construire et renvoyer le "renderable array" qui affichera le bloc "dh_like_block".

 

 La classe mère "BlockBase" offre la possibilité au développeur d'ajouter au bloc, ses propres éléments de configuration. On parle alors d'un bloc "configurable" et par opposition à bloc non configurable. Une version configurable de ce bloc est présentée dans le billet : "le bloc des likes version configurable".   

Dans ce code, la fonction "buid()" récupère dans ses variables "$like_button_form" et "$dislike_button_form" les références des objets formulaire créés par l'appel de la fonction "formBuilder" de la classe Drupal ( "\Drupal::formBuilder()") ;

La fonction buid() rend un "renderable array" qui affichera ces formulaires en utilisant :

  • le template "dh-like-block.html.twig" (grâce à l'attribut "#theme")  qui affichera le contenu de l'attribut "#data" à savoir les valeurs des variables "like_button" et "dislike_button" ;

  • les styles présents dans le fichier attaché à la hiérarchie "dh_like/custom" de la libraire CSS du module  (par l'attribut "#attached") (lire la hiérarchie custom dans le fichier "dh_like.libraries.yml" et le §8 : le dossier "template" du billet "autopsie d'un module") ;

  • le vidage du cache pour une mise à jour  immédiate de l'affichage (par l'attribut "#cache").

  La classe mère BlockBase permet d'ajouter la fonction "getCacheMaxAge()" suivante dans le code de déclaration de la classe au lieu de l'attribut "#cache" du "renderable array" de la fonction "build()"

  public function getCacheMaxAge() {      
   return 0;      
 }    

2. Les formulaires

La documentation de base https://www.drupal.org/docs/drupal-apis/form-api/intoduction-to-form-api est à connaitre.
Le code étudié ici, figure dans les onglets "like_form" et "dislike_form" ci-dessous et se trouve contenu respectivement dans les fichiers "LikeForm.php"  et "DislikeForm.php" du dossier "src/Form" du module.

Ces fichiers décrivent les classes "LikeForm" et "DislikeForm" comme extension de la classe FormBase qui offre les fonctions génériques :

 "buidForm()" : le constructeur du formulaire ;

"validForm()" : pour la validation des données saisies dans le formulaire ;

"submitForm()" : pour l'envoi du formulaire.

Dans chaque fichier, la fonction "buidForm()" définit le formulaire avec 3 champs :

  • 'entity_id' : champ caché qui mémorise le "numéro de la page" qui est obtenu par l'emploi de la fonction "getCurrentEntity()" ;

  •  'like_count' : champ caché qui mémorise le nombre actuel de "likes" pour cette page qui est obtenu grâce à getLikeCount() ;

  • 'submit' : bouton de validation du formulaire.

Un clic sur le bouton "submit" appellera la fonction "submitForm()" qui met à jour la table des likes de la base de données lors de l'appel à la fonction "updateDbLike()".

La fonction getFromDbLike()  interroge la table des likes de la base de données et rend le nombre de like (resp. dislike) enregistré en base de données pour cette page.

Les fonctions "getFromDbLike()" et "updateDbLike()", qui manipulent la table "like" de la base de données, sont détaillées dans le billet 'gérer la table like". Elles sont définies dans le "trait"  "GestionLikeTrait" et sont accessibles dans les classes LikeForm et DisLikeForm, grâce  à la déclaration "use GestionLikeTrait" lors de la définition de ces classes.

 

Cliquez sur le bouton pour copier le code        
dans le presse papier  
<?php
namespace Drupal\dhlike\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
//pour compléter le formulaire de config dans le menu d'administration
use Drupal\Core\Block\BlockPluginInterface;
/**
* Provides a 'dh_like' Block.
*
* @Block(
* id = "dhlike_block",
* admin_label = @Translation("Le bloc Like (dh)"),
* category = @Translation("Social network"),
* )
*/
class DhlikeBlock extends BlockBase {
/**
* @return int
*/
public function getCacheMaxAge() {
return 0;
}   
/**
* {@inheritdoc}
*/
public function build() {    
   //Génére le sous titre du bloc
if ($this->configuration['isSubtitle']=='Oui' && strlen($this->configuration['subtitle'])>0){
       $sousTitre = [
           '#type' => 'html_tag',
           '#tag' => 'h4',
           '#value' => $this->configuration['subtitle'],
       ];
}
   // Get the like/dislike buttons.
$like_button_form = \Drupal::formBuilder()->getForm(\Drupal\dhlike\Form\LikeForm::class);
   $dislike_button_form = \Drupal::formBuilder()->getForm(\Drupal\dhlike\Form\DisLikeForm::class);
   //
   $like_count = $like_button_form['like_count']['#default_value'];
   $dislike_count = $dislike_button_form['dislike_count']['#default_value'];
   $nbVotes = $like_count + $dislike_count;
   if ($nbVotes > 0){
       $taux = $like_count / $nbVotes;
   }
   else {
       $taux=0.5;
   }
   // renderable array   
   $build= [];
   $build['1']= [
'#theme' => 'dhlike_block',
'#data' => [
        'like_button' => $like_button_form,
        'dislike_button' => $dislike_button_form,
       ],
       '#sousTitre' =>$sousTitre,
       '#cache' => [
'max-age' => 0,
],
       '#attached' => [
           'library' => [
               'dhlike/custom',
           ],
       ],
   ];
   return $build;
}
/**
* {@inheritdoc}
* Pour compléter le formulaire de configuration affiché lorsqu'on ajoute le bloc à une région
*/
public function blockForm($form, FormStateInterface $form_state) {
$form = parent::blockForm($form, $form_state);
   //
$form['isSubtitle'] = [
'#type' => 'radios',
'#title' => $this->t('Voulez-vous une sous-titre ?'),
'#default_value' => isset($this->configuration['isSubtitle']) ? $this->configuration['isSubtitle'] : "Oui",
'#options' => [
"Oui"=>$this->t('Oui'),
"Non"=>$this->t('Non')
],
'#required'=>true,
'#attributes' => [
'data-n' => 'isSubtitle',
],
];
   //
$form['subtitle'] = [
'#type' => 'textfield',
'#title' => $this->t('Ajout d\' un sous-titre'),
'#description' => $this->t('Indiquez votre sous-titre !'),
'#default_value' => isset($this->configuration['subtitle']) ? $this->configuration['subtitle'] : '',
'#states' => [
//show this textfield only if the radio 'Oui' is selected above
'visible' => [
':input[data-n="asksubtitle"]' => ['value' => "Oui"],
],
],
];
return $form;
}
/**
* {@inheritdoc}
* Vérification de la validité des paramètres saisis
*/
public function blockValidate($form, FormStateInterface $form_state) {
   // récupération des paramètres
$subtitle = $form_state->getValue('subtitle');
$isSubtitle = $form_state->getValue('isSubtitle');
   //
if ($isSubtitle=="Oui" && strlen($subtitle)==0) {
       // pas de sous-titre saisi donc erreeur
$form_state->setErrorByName('subtitle', t('Saisissez au moins un caractère !'));
}
}
/**
* {@inheritdoc}
* Elle renregistre automatiquement en BdD la configuration les valeurs saisie par le formulaire
*/
public function blockSubmit($form, FormStateInterface $form_state) {
   // sauver les paramètres des éléments standards du formulaire
parent::blockSubmit($form, $form_state);
   // sauver les paramètres complémentaires propre au module
$values = $form_state->getValues();
$this->configuration['subtitle'] = $values['subtitle'];
$this->configuration['isSubtitle'] = $values['isSubtitle'];
   // pour éventuel debug affichage dans status
foreach ($form_state->getValues() as $key => $value) {
if (!is_array($value)){
\Drupal::messenger()->addMessage($key . ': ' . $value);
}else{
foreach ($value as $k=>$v){
\Drupal::messenger()->addMessage($k . ': ' . $v);
}
}
}
}
}
Cliquez sur le bouton pour copier le code        
dans le presse papier  
 <?php
namespace Drupal\dhlike\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\dhlike\Utility\GestionLikeTrait;
/**
* Class LikeForm.
*/
class LikeForm extends FormBase {
use GestionLikeTrait;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'dh_like_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
   //rend est un tableau associatif dont les clés correspondent aux éléments du formulaire.
$current_entity = $this->getCurrentEntity();
// If current page is no entity, return null.
if(!$current_entity){
return null;
}
$entity_id = $current_entity->id();
//$like_count = $this->getLikeCount($entity_id);
   $like_count = $this->getFromDbLike($entity_id, "uplike");
//
$form['entity_id'] = [
'#type' => 'hidden',
'#required' => true,
'#default_value' => $entity_id,
];
//   
   $form['like_count'] = [
'#type' => 'hidden',
'#required' => true,
'#default_value' => $like_count,
];
//
   $form['submit'] = [
       '#type' => 'submit',
       '#value' => ' ',
       '#attributes' => [
           'style' => 'width:27px;',
           'id' =>'liked-form-button',
       ],
       //'#prefix' => ' ',
       //'#suffix' => '<span>'.$button_text.'</span>',
];
//
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
   // retrieve data from form
   $form_values = $form_state->getValues();
   $entity_id = $form_values['entity_id'];
   $like_count = $form_values['like_count'];
   //
   $like_count = $like_count + 1;
$this->updateDbLike($like_count,$entity_id,'uplike' );
return;
}
/**
* Get current entity, if any.
*/
private function getCurrentEntity(){
$currentRouteParameters = \Drupal::routeMatch()->getParameters();
foreach ($currentRouteParameters as $param) {
if ($param instanceof \Drupal\Core\Entity\EntityInterface) {
$entity = $param;
return $entity;
}
}
return NULL;
}
}
Cliquez sur le bouton pour copier le code        
dans le presse papier  
<?php
namespace Drupal\dhlike\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\dhlike\Utility\GestionLikeTrait;
/**
* Class DisLikeForm.
*/
class DisLikeForm extends FormBase {
use GestionLikeTrait;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'dh_dislike_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$current_entity = $this->getCurrentEntity();
//$logged_in = \Drupal::currentUser()->isAuthenticated();
// If current page is no entity, return null.
if(!$current_entity){
return null;
}
$entity_id = $current_entity->id();
$uid = \Drupal::currentUser()->id();
$dislike_count = $this->getFromDbLike($entity_id, "downlike");
   $button_text = $dislike_count;
//
$form['entity_id'] = [
'#type' => 'hidden',
'#required' => true,
'#default_value' => $entity_id,
];
   //
   $form['submit'] = [
       '#type' => 'submit',
       '#value' => ' ',
       '#attributes' => [
           'style' => 'width:30px;',
           'id' =>'disliked-form-button',
       ],
];
   //
   $form['dislike_count'] = [
'#type' => 'hidden',
'#required' => true,
'#default_value' => $dislike_count,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
   $form_values = $form_state->getValues();
   $entity_id = $form_values['entity_id'];
   $dislike_count = $form_values['dislike_count'];
   $dislike_count = $dislike_count + 1;
   $this->updateDbLike($dislike_count,$entity_id,'downlike' );
return;
}
/**
* Get current entity, if any.
*/
private function getCurrentEntity(){
$currentRouteParameters = \Drupal::routeMatch()->getParameters();
foreach ($currentRouteParameters as $param) {
if ($param instanceof \Drupal\Core\Entity\EntityInterface) {
$entity = $param;
return $entity;
}
}
return NULL;
}
}

3. L'affichage du bloc "like"

Comme indiqué par le "renderable array" de la fonction "build" définissant le bloc like, l'affichage est produit par le template "dh-like-block.html.twig" (voir à ce propos le §6 du billet "autopsie d'un module").

Le code de ce fichier, illustré par l'image figurant dans l'introduction de ce billet, est reproduit ci-dessous :

<div class="dh_like_block">
    <div>
        {{ data.like_button }}
    </div>
    <div>
        {{ data.dislike_button }} 
    </div>
</div>

Il affiche le contenu des variables (du tableau data) : les formulaires "like_button" et "dislike_button". Cet affichage est mis en forme par la classe "dh_like_block" de la bibliothèque "dh_like.css" attachée à ce bloc.