Creating blocks using plugins

In Drupal, a block is a piece of content that can be placed in a region provided by a theme. Blocks are used to present specific kinds of content, such as a user login form, a snippet of text, and many more.

Blocks are configuration entities, and the block module uses the Drupal plugin system as a way to define blocks for modules. Custom blocks are defined in the PHP code in the module's Plugin class namespace. Each class in the Plugin/Block namespace will be discovered by the block module's plugin manager.

In this recipe, we will define a block that will display a copyright snippet and the current year, and place it in the footer region.

Getting ready

Create a new module like the one shown in this recipe. We will refer to the module as mymodule throughout the recipe. Use your module's appropriate name.

How to do it…

  1. Create the src/Plugin/Block directory in your module. This will translate the DrupalmymodulePluginBlock namespace and allow a block plugin discovery.
  2. Create a Copyright.php file in the newly created folder so that we can define the Copyright class for our block:
    How to do it…
  3. The Copyright class will extend DrupalCoreBlockBlockBase:
    <?php
    
    /**
     * @file
     * Contains DrupalmymodulePluginBlockCopyright.
     */
    namespace DrupalmymodulePluginBlock;
    use DrupalCoreBlockBlockBase;
    class Copyright extends BlockBase {
    }
  4. We extend the BlockBase class, which implements DrupalCoreBlockBlockPluginInterface and provides us with an implementation of nearly all of its methods.
  5. Blocks are annotated plugins. Annotated plugins use documentation blocks to provide details of the plugin. We will provide the block's identifier, administrative label, and category:
    <?php
    
    /**
     * @file
     * Contains DrupalmymodulePluginBlockCopyright.
     */
    
    namespace DrupalmymodulePluginBlock;
    
    use DrupalCoreBlockBlockBase;
    /**
     * @Block(
     *   id = "copyright_block",
     *   admin_label = @Translation("Copyright"),
     *   category = @Translation("Custom")
     * )
     */
    class Copyright extends BlockBase {
    
    }
  6. The annotation document block of the class identifies the type of plugin through @Block. Drupal will parse this and initiate the plugin with the properties defined inside it. The id is the internal machine name, the admin_label is displayed on the block listing page, and category shows up in the block select list.
  7. We need to provide a build method to satisfy the DrupalCoreBlockBlockPluginInterface interface. This creates the output to be displayed:
    <?php
    /**
     * @file
     * Contains DrupalmymodulePluginBlockCopyright
     */
    
    namespace DrupalmymodulePluginBlock;
    
    use DrupalCoreBlockBlockBase;
    
    /**
     * @Block(
     *   id = "copyright_block",
     *   admin_label = @Translation("Copyright"),
     *   category = @Translation("Custom")
     * )
     */
    class Copyright extends BlockBase {
      /**
       * {@inheritdoc}
       */
      public function build() {
        $date = new DateTime();
        return [
          '#markup' => t('Copyright @year&copy; My Company', [
              '@year' => $date->format('Y'),
          ]),
        ];
      }
    }

    The build method returns a render array that uses Drupal's t function to substitute @year for the DateTime object's output that is formatted as a full year.

    Tip

    Since PHP 5.4, a warning will be displayed if you have not explicitly set a timezone in your PHP's configuration.

  8. Rebuild Drupal's cache so that the new plugin can be discovered.
  9. In the Footer fourth region, click on Place block.
  10. Review the block list and add the custom block to your regions, for instance, the footer region. Find the Copyright block, and click on Place block:
    How to do it…
  11. Uncheck the Display title checkbox so that only our block's content can be rendered.
  12. Review the copyright statement that will always keep the year dynamic:
    How to do it…

How it works...

The plugin system works through plugin definitions and plugin managers for those definitions. The DrupalCoreBlockBlockManager class defines the block plugins that need be located in the Plugin/Block namespace. It also defines the base interface that needs to be implemented along with the Annotation class, which is to be used, when parsing the class's document block.

When Drupal's cache is rebuilt, all available namespaces are scanned to check whether classes exist in the given plugin namespace. The definitions, via annotation, will be processed and the information will be cached.

Blocks are then retrieved from the manager, manipulated, and their methods are invoked. When viewing the Block layout page to manage blocks, the DrupalCoreBlockBlockBase class's label method is invoked to display the human-readable name. When a block is displayed on a rendered page, the build method is invoked and passed to the theming layer to be output.

There's more...

Altering blocks

Blocks can be altered in two different ways: the plugin definition can be altered, the build array, or the view array out.

A module can implement hook_block_alter in its .module file and modify the annotation definitions of all the discovered blocks. The will allow a module to change the default user_login_block from user login to Login:

/**
 * Implements hook_block_alter().
 */
function mymodule_block_alter(&$definitions) {
  $definitions['user_login_block']['admin_label'] = t('Login');
}

A module can implement hook_block_build_alter and modify the build information of a block. The hook is passed the build array and the DrupalCoreBlockBlockPluginInterface instance for the current block. Module developers can use this to add cache contexts or alter the cache ability of metadata:

/**
 * Implements hook_block_build_alter().
 */
function hook_block_build_alter(array &$build, DrupalCoreBlockBlockPluginInterface $block) {
  // Add the 'url' cache the block per URL.
  if ($block->getBaseId() == 'myblock') {
    $build['#contexts'][] = 'url';
  }
}

Note

You can test the modification of cache metadata by altering the recipe's block to output a timestamp. With caching enabled, you will see that the value persists on the same URL, but it will be different across each page.

Finally, a module can implement hook_block_view_alter in order to modify the output to be rendered. A module can add content to be rendered or remove content. This can be used to remove the contextual links item, which allows inline editing from the front page of a site:

/**
 * Implements hook_block_view_alter().
 */
function hook_block_view_alter(array &$build, DrupalCoreBlockBlockPluginInterface $block) {
  // Remove the contextual links on all blocks that provide them.
  if (isset($build['#contextual_links'])) {
    unset($build['#contextual_links']);
  }
}

Block settings forms

Blocks can provide a setting form. This recipe provides the text My Company for the copyright text. Instead, this can be defined through a text field in the block's setting form.

Let's revisit the Copyright.php file that contained our block's class. A block can override the default defaultConfiguration method, which returns an array of setting keys and their default values. The blockForm method can then override the DrupalCoreBlockBlockBase empty array implementation to return a Form API array to represent the settings form:

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'company_name' => '',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, DrupalCoreFormFormStateInterface $form_state) {
    $form['company_name'] = [
      '#type' => 'textfield',
      '#title' => t('Company name'),
      '#default_value' => $this->configuration['company_name'],
    ];
    return $form;
  }

The blockSubmit method must then be implemented, which updates the block's configuration:

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, DrupalCoreFormFormStateInterface $form_state) {
    $this->configuration['company_name'] = $form_state->getValue('company_name');
  }

Finally, the build method can be updated to use the new configuration item:

  /**
   * {@inheritdoc}
   */
  public function build() {
    $date = new DateTime();
    return [
      '#markup' => t('Copyright @year&copy; @company', [
        '@year' => $date->format('Y'),
        '@company' => $this->configuration['company_name'],
      ]),
    ];
  }

You can now go back and visit the Block layout form, and click on Configure in the Copyright block. The new setting will be available in the block instance's configuration form.

Defining access to a block

Blocks, by default, are rendered for all users. The default access method can be overridden. This allows a block to only be displayed to authenticated users or based on a specific permission:

  /**
   * {@inheritdoc}
   */
  protected function blockAccess(AccountInterface $account) {
    $route_name = $this->routeMatch->getRouteName();
    if ($account->isAnonymous() && !in_array($route_name, array('user.login', 'user.logout'))) {
      return AccessResult::allowed()
        ->addCacheContexts(['route.name', 'user.roles:anonymous']);
    }
    return AccessResult::forbidden();
  }

The preceding code is taken from the user_login_block. It allows access to the block if the user is logged out and is not on the login or logout page. The access is cached based on the current route name and the user's current role being anonymous. If these are not passed, the access returned is forbidden and the block is not built.

Other modules can implement hook_block_access to override the access of a block:

/**
 * Implements hook_block_access().
 */
function mymodule_block_access(DrupallockEntityBlock $block, $operation, DrupalCoreSessionAccountInterface $account) {
  // Example code that would prevent displaying the Copyright' block in
  // a region different than the footer.
  if ($operation == 'view' && $block->getPluginId() == 'copyright') {
    return DrupalCoreAccessAccessResult::forbiddenIf($block->getRegion() != 'footer');
  }

  // No opinion.
  return DrupalCoreAccessAccessResult::neutral();
}

A module implementing the preceding hook will deny access to our Copyright block if it is not placed in the footer region.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset