Commit 909df8a6 authored by Joshua Sedler's avatar Joshua Sedler 🤸🏼
Browse files

Adjust block rendering

parent 12f89a7a
Loading
Loading
Loading
Loading
Loading
+30 −0
Changes for README.md: 30 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -34,6 +34,36 @@ drush en block_ajax
* Place block in the region.
* Test page where the block was placed, and it should load via Ajax.

### Rendering an Ajax block in a template

Only blocks that are stored as block configuration entities (i.e. placed via
Structure -> Block layout) can be loaded via Ajax. Blocks created on the fly
from a plugin id and configuration, e.g. with the Twig Tweak `drupal_block()`
function, are not supported. The block configuration is never taken from the
request, as that would allow visitors to instantiate arbitrary block plugins
with arbitrary configuration. For details see the related issue:
https://git.drupalcode.org/project/block_ajax/-/work_items/3315618.

As a workaround, to output an Ajax block somewhere in a template:

* Place the block and enable "Load block via Ajax" as described above, but put
  it in a region that the theme does not output (for example a region which is
  declared in the theme's `.info.yml` but not printed in any template). Do not
  use the "Disabled" region: disabled blocks are never rendered.
* Render the block entity in the template via Twig Tweak:

  ```twig
  {{ drupal_entity('block', 'my_block_id') }}
  ```

  or any other way that renders the block entity through Drupal core's block
  view builder, for example in a preprocess function:

  ```php
  $block = \Drupal::entityTypeManager()->getStorage('block')->load('my_block_id');
  $variables['my_block'] = \Drupal::entityTypeManager()->getViewBuilder('block')->view($block);
  ```

### Maintainers

George Anderson (geoanders) - https://www.drupal.org/u/geoanders
+0 −4
Changes for js/ajax_blocks.js: 0 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -49,10 +49,6 @@
        const request = $.ajax({
          type: drupalSettings.block_ajax.config.type,
          url: Drupal.url(ajaxUrl),
          data: {
            plugin_id: pluginId,
            config: drupalSettings.block_ajax.blocks[blockId],
          },
          timeout: drupalSettings.block_ajax.config.timeout,
          cache: drupalSettings.block_ajax.config.cache,
          async: drupalSettings.block_ajax.config.async,
+55 −89
Changes for src/Controller/AjaxBlockController.php: 55 added lines, 89 removed lines.
Original line number Diff line number Diff line
@@ -4,8 +4,6 @@ namespace Drupal\block_ajax\Controller;

use Drupal\block\BlockInterface;
use Drupal\block_ajax\BlockViewBuilder;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\block_ajax\Response\AjaxBlockResponse;
use Drupal\Core\Block\BlockManager;
@@ -116,58 +114,43 @@ class AjaxBlockController extends ControllerBase {
  /**
   * Get the block instance.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   * @param string $block_id
   *   The block/plugin id.
   * @param array $configuration
   *   The block configuration.
   * @param \Drupal\block\BlockInterface $block
   *   The block config entity.
   *
   * @return array|null
   *   Returns the block render array/instance.
   *
   * @throws \Drupal\Component\Plugin\Exception\ContextException
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\MissingValueContextException
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getBlockInstance(Request $request, string $block_id, array $configuration): ?array {
    $blockInstance = NULL;

    // Load block by block_id.
    $block = $this->entityTypeManager->getStorage('block')->load($block_id);
    if (!$block instanceof BlockInterface) {
      // Attempt to load block plugin.
      $block_plugin = $this->blockManager->createInstance($block_id, $configuration);
      if ($block_plugin instanceof BlockPluginInterface && 'broken' !== $block_plugin->getPluginId()) {
        // Check access.
        $access = $block_plugin->access($this->account, TRUE);
        if ($access->isAllowed()) {
          $blockInstance = $block_plugin->build();
          $blockInstance['#cache']['tags'] = array_merge($block_plugin->getCacheTags(), ['block_ajax']);
        }
      }
  protected function getBlockInstance(BlockInterface $block): ?array {
    $plugin = $block->getPlugin();
    if ('broken' === $plugin->getPluginId()) {
      return NULL;
    }
    else {

    // Add in custom cache tag.
    $block->addCacheTags(['block_ajax']);

      // Make sure we have plugin id.
      $plugin_id = $request->get('plugin_id', '');
      if (!empty($plugin_id)) {
        // Check if block has special plugin and add it to dependency.
        $plugin = $block->getPlugin();
        if (is_object($plugin) && $plugin->getPluginId() == $plugin_id) {
          $configuration = $plugin->getConfiguration();
        }

    // Construct and render the block.
        $blockInstance = $this->blockViewBuilder->build($plugin_id, $configuration, TRUE);
      }
    return $this->blockViewBuilder->build($plugin->getPluginId(), $plugin->getConfiguration(), TRUE);
  }

    return $blockInstance;
  /**
   * Get the block config entity for the given block id.
   *
   * @param string $block_id
   *   The block config entity id.
   *
   * @return \Drupal\block\BlockInterface|null
   *   The block config entity, or NULL if there is none.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getBlockEntity(string $block_id): ?BlockInterface {
    $block = $this->entityTypeManager->getStorage('block')->load($block_id);
    return $block instanceof BlockInterface ? $block : NULL;
  }

  /**
@@ -201,43 +184,6 @@ class AjaxBlockController extends ControllerBase {
    $response->setSharedMaxAge($max_age);
  }

  /**
   * Get block configuration.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   *
   * @return array
   *   The merged and sanitized configuration.
   */
  protected function getBlockConfiguration(Request $request): array {
    // The default configuration.
    $configuration = $request->get('config', []);
    // Filter/sanitize configuration data.
    return $this->filterConfiguration($configuration);
  }

  /**
   * Get filtered block configuration data.
   *
   * @param array $data
   *   The configuration data.
   *
   * @return array
   *   The filtered configuration data.
   */
  protected function filterConfiguration(array $data): array {
    foreach ($data as $key => $value) {
      if (is_array($value)) {
        $this->filterConfiguration($value);
      }
      else {
        $data[$key] = Xss::filter($value);
      }
    }
    return $data;
  }

  /**
   * Implements ajax block update request handler.
   *
@@ -266,11 +212,16 @@ class AjaxBlockController extends ControllerBase {
      return $response;
    }

    // Block configuration.
    $configuration = $this->getBlockConfiguration($request);
    // Only blocks stored as block config entities can be loaded, using their
    // stored configuration.
    $block = $this->getBlockEntity($block_id);
    if (!$block instanceof BlockInterface) {
      return $response;
    }
    $configuration = $block->getPlugin()->getConfiguration();

    // Get block instance.
    if ($blockInstance = $this->getBlockInstance($request, $block_id, $configuration)) {
    if ($blockInstance = $this->getBlockInstance($block)) {
      $renderedBlock = $this->renderer->renderRoot($blockInstance);

      // Apply token replacement.
@@ -327,11 +278,16 @@ class AjaxBlockController extends ControllerBase {
      return $response;
    }

    // Block configuration.
    $configuration = $this->getBlockConfiguration($request);
    // Only blocks stored as block config entities can be loaded, using their
    // stored configuration.
    $block = $this->getBlockEntity($block_id);
    if (!$block instanceof BlockInterface) {
      return $response;
    }
    $configuration = $block->getPlugin()->getConfiguration();

    // Get block instance.
    if ($blockInstance = $this->getBlockInstance($request, $block_id, $configuration)) {
    if ($blockInstance = $this->getBlockInstance($block)) {
      $renderedBlock = $this->renderer->renderRoot($blockInstance);

      // Apply token replacement.
@@ -384,11 +340,16 @@ class AjaxBlockController extends ControllerBase {
      return $response;
    }

    // Block configuration.
    $configuration = $this->getBlockConfiguration($request);
    // Only blocks stored as block config entities can be loaded, using their
    // stored configuration.
    $block = $this->getBlockEntity($block_id);
    if (!$block instanceof BlockInterface) {
      return $response;
    }
    $configuration = $block->getPlugin()->getConfiguration();

    // Get block instance.
    if ($blockInstance = $this->getBlockInstance($request, $block_id, $configuration)) {
    if ($blockInstance = $this->getBlockInstance($block)) {
      $renderedBlock = $this->renderer->renderRoot($blockInstance);

      // Apply token replacement.
@@ -441,11 +402,16 @@ class AjaxBlockController extends ControllerBase {
      return $response;
    }

    // Block configuration.
    $configuration = $this->getBlockConfiguration($request);
    // Only blocks stored as block config entities can be loaded, using their
    // stored configuration.
    $block = $this->getBlockEntity($block_id);
    if (!$block instanceof BlockInterface) {
      return $response;
    }
    $configuration = $block->getPlugin()->getConfiguration();

    // Get block instance.
    if ($blockInstance = $this->getBlockInstance($request, $block_id, $configuration)) {
    if ($blockInstance = $this->getBlockInstance($block)) {
      $renderedBlock = $this->renderer->renderRoot($blockInstance);

      // Apply token replacement.