Créer un bloc personnalisé

Posté le: mar 06/11/2018 - 13:52 Par: rcowebdev
custom block

Et bien et bien, ca m'a pris plus de temps que je ne le pensais. Mon idée était de rendre administrable la zone de la page <head>, pour y ajouter des scripts mais tout ne s'est pas vraiment passé comme prévu ...

Les custom blocks dans Drupal 8 sont décorés par des div, autant dire que dans la balise head ca ne marche pas des masses et cette décoration n'est pas administrable /!\.

{#
/**
 * @file
 * Default theme implementation to display a block.
 *
 * Available variables:
 * - plugin_id: The ID of the block implementation.
 * - label: The configured label of the block if visible.
 * - configuration: A list of the block's configuration values.
 *   - label: The configured label for the block.
 *   - label_display: The display settings for the label.
 *   - provider: The module or other provider that provided this block plugin.
 *   - Block plugin specific settings will also be stored here.
 * - content: The content of this block.
 * - attributes: array of HTML attributes populated by modules, intended to
 *   be added to the main container tag of this template.
 *   - id: A valid HTML ID and guaranteed unique.
 * - title_attributes: Same as attributes, except applied to the main title
 *   tag that appears in the template.
 * - title_prefix: Additional output populated by modules, intended to be
 *   displayed in front of the main title tag that appears in the template.
 * - title_suffix: Additional output populated by modules, intended to be
 *   displayed after the main title tag that appears in the template.
 *
 * @see template_preprocess_block()
 *
 * @ingroup themeable
 */
#}
<!-- block -->
<div{{ attributes }}>
  {{ title_prefix }}
  {% if label %}
    <h2{{ title_attributes }}>{{ label }}</h2>
  {% endif %}
  {{ title_suffix }}
  {% block content %}
    {{ content }}
  {% endblock %}
</div>
<!-- /block -->

J'ai regardé si des modules existaient, il y avait bien un add to head mais il n'est pas disponible sur Drupal 8. J'ai ensuite croisé pas mal de tutoriels proposant de surcharger le thème et y incorporer les ajouts (script ou meta) mais ce n'est pas administrable. 

Bon bha c'est parti pour la création d'un petit module qui va nous gérer tout ca.

On commence par modifier le thème afin d'y ajouter la région Head (Je suis actuellement sur le thème Nexus en passant, mais le principe est toujours le même).

themes/nexus/nexus.info.yml

name: Nexus Theme
type: theme
description: 'Nexus Theme is a premium Drupal 7 theme. Developed by <a href="http://www.devsaran.com">Devsaran.com</a>.'
# core: 8.x

# Regions
regions:
  head: 'Head'
  header: 'Header'
  main_navigation: 'Main Navigation'
  preface_first: 'Preface First'
  preface_second: 'Preface Second'
  preface_third: 'Preface Third'
  highlighted: 'Highlighted'
  sidebar_first: 'First Sidebar'
  content_top: 'Content Top'
  help: 'Help'
  content: 'Content'
  footer: 'Footer'
  footer_first: 'First Bottom'
  footer_second: 'Second Bottom'
  footer_third: 'Third Bottom'
  footer_fourth: 'Fourth Bottom'

# Assets
libraries:
  - nexus/global-css
  - nexus/global-js

# Information added by Drupal.org packaging script on 2018-06-08
version: '8.x-1.4'
core: '8.x'
project: 'nexus'
datestamp: 1528444991

On vide le cache et on retrouve notre nouvelle région

head region

 

Puis on place la nouvelle région sur la page  {{ page.head }}

themes/nexus/html.html.twig

{#
/**
 * @file
 * Default theme implementation for the basic structure of a single Drupal page.
 *
 * Variables:
 * - logged_in: A flag indicating if user is logged in.
 * - root_path: The root path of the current page (e.g., node, admin, user).
 * - node_type: The content type for the current node, if the page is a node.
 * - head_title: List of text elements that make up the head_title variable.
 *   May contain or more of the following:
 *   - title: The title of the page.
 *   - name: The name of the site.
 *   - slogan: The slogan of the site.
 * - page_top: Initial rendered markup. This should be printed before 'page'.
 * - page: The rendered page markup.
 * - page_bottom: Closing rendered markup. This variable should be printed after
 *   'page'.
 * - page.head: Add head tags
 * - db_offline: A flag indicating if the database is offline.
 * - placeholder_token: The token for generating head, css, js and js-bottom
 *   placeholders.
 *
 * @see template_preprocess_html()
 *
 * @ingroup themeable
 */
#}

<!DOCTYPE html>
<html{{ html_attributes }}>
  <head>
    <head-placeholder token="{{ placeholder_token|raw }}">
    <title>{{ head_title|safe_join(' | ') }}</title>
    <css-placeholder token="{{ placeholder_token|raw }}">
    <js-placeholder token="{{ placeholder_token|raw }}">
    {{page.head}}
  </head>
  <body{{ attributes }}>
    {{ page_top }}
    {{ page }}
    {{ page_bottom }}
    <js-bottom-placeholder token="{{ placeholder_token|raw }}">
  </body>
</html>

Maintenant il faut créer un bloc custom, un peu plus que custom d'ailleurs car aucune décoration n'est désirée; je l'ai nommé light_block dont la structure est la suivante 

/root
-- modules
---- custom
------ light_block
--------- src
----------- Plugin
------------- Block
--------------- LightBlock.php
-------- light_block.info.yml
-------- light_block.module

light_block/src/Plugin/Block/LightBlock.php

<?php

namespace Drupal\light_block\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Provides a 'Light' Block.
 *
 * @Block(
 *   id = "light_block",
 *   admin_label = @Translation("Light block")
 * )
 */
class LightBlock extends BlockBase implements BlockPluginInterface {

  /**
   * {@inheritdoc}
   */
  public function build() {
  	$config = $this->getConfiguration();

    return [
      '#markup' => $config['light_block_content'],
      '#allowed_tags' => ['script'],
    ];
  }

	/**
	* {@inheritdoc}
	*/
  public function blockForm($form, FormStateInterface $form_state) {
    $form = parent::blockForm($form, $form_state);

    $config = $this->getConfiguration();

    $form['light_block_content'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Content'),
      '#description' => $this->t('The content above will appear without any HTML decoration or strip tags functionnality'),
      '#default_value' => isset($config['light_block_content']) ? $config['light_block_content'] : '',
    ];

    return $form;
  }

   /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    parent::blockSubmit($form, $form_state);
    $values = $form_state->getValues();
    $this->configuration['light_block_content'] = $values['light_block_content'];
  }
}

Rien de fou fou, on déclare le bloc et son formulaire de configuration pour le BO mais il y a quand même quelques points à noter :

  • l'implémentation de l'interface BlockPluginInterface afin de rendre le bloc administrable
  • dans la méthode build(), l'entrée '#allowed_tags' => ['script'], car Drupal 8 échappe les tags pour des raisons de sécurité (attaque XSS notamment) et il faut lui dire que "non non, tout va bien tu peux avoir confiance !"

On peut maintenant créer un bloc de type Light Block.

ligth block

 

light_block/light_block.info.yml

name: Light block Module
description: Provide a light block for head content
package: Custom

type: module
core: 8.x

Et non ce n'est pas encore terminé, il nous faut un template, en twig depuis que Drupal 8 est passé à Symfony.

La convention de nommage est la suivante pour un bloc:

block--(nom-du-module).html.twig

Donc pour le module Light Block ca donne ca:

block--light-block.html.twig 

Un point super important quand on travail sur Drupal 8 - remarque ca se retrouve partout - le nommage ! Les logs ne sont pas toujours super explicites si bien qu'on peut rester bloqué longtemps pour une simple majuscule de trop. Moi je copie/colle tout le temps, au moins je suis sur de ne pas faire de fautes de frappes :)

 

Là on est bon et on va tester tout de suite le résultat; dans la région head, y créer un bloc de type Light Block dont le contenu sera 

<script>
alert('bobby');
</script>

En rechargeant la page, un certain Bobby devrait se manifester.

Pour ce module, j'ai du debugger les templates Twig et sur ma nouvelle installation du site, rien n'était configuré.

Donc pour les curieux

  1. Renommer sites/default/default.services.yml en sites/default/services.yml
  2. Modifier le fichier en activant le debug de Twig
  twig.config:
    # Twig debugging:
    #
    # When debugging is enabled:
    # - The markup of each Twig template is surrounded by HTML comments that
    #   contain theming information, such as template file name suggestions.
    # - Note that this debugging markup will cause automated tests that directly
    #   check rendered HTML to fail. When running automated tests, 'debug'
    #   should be set to FALSE.
    # - The dump() function can be used in Twig templates to output information
    #   about template variables.
    # - Twig templates are automatically recompiled whenever the source code
    #   changes (see auto_reload below).
    #
    # For more information about debugging Twig templates, see
    # https://www.drupal.org/node/1906392.
    #
    # Not recommended in production environments
    # @default false
    debug: true 
Inutile de préciser que ce mode de debug ne doit être activé que sur un environnement de DEV

 

Mots clés
Twig
Drupal 8