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 de "liker" ou "disliker" la page affichée. 

Cliquer pour agrandir l'image

 

Billet créé le :
07 Mar 2023

Ce bloc doit être ajouté aux pages dont l'administrateur du site souhaite l'évaluation 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 code principal

La documentation de base : https://www.drupal.org/docs/creating-modules/creating-custom-blocks/create-a-custom-block à connaître.

Ce code figure dans l'onglet "dh_like_block" ci-dessous, c'est le contenu du 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 ses propres éléments de configuration au bloc. 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 notre 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()") ;

buid() renvoie 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 figure dans les onglets "like_form" et "dislike_form" ci-dessous, 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" obtenu par l'emploi de la fonction "getCurrentEntity()" ;

  •  'like_count' : champ caché qui mémorise le nombre actuel de likes pour cette page 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 par 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 accessibles dans les classes LikeForm et DisLikeForm, grâce  à la déclaration "use GestionLikeTrait" lors de la définition de la classe.

 

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" , affichage mis en forme pour la classe "dh_like_block" de la bibliothèque "dh_like.css" attachée à ce bloc.