Loading README.md +30 −0 Changes for README.md: 30 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading js/ajax_blocks.js +0 −4 Changes for js/ajax_blocks.js: 0 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -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, Loading src/Controller/AjaxBlockController.php +55 −89 Changes for src/Controller/AjaxBlockController.php: 55 added lines, 89 removed lines. Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } /** Loading Loading @@ -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. * Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading
README.md +30 −0 Changes for README.md: 30 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading
js/ajax_blocks.js +0 −4 Changes for js/ajax_blocks.js: 0 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -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, Loading
src/Controller/AjaxBlockController.php +55 −89 Changes for src/Controller/AjaxBlockController.php: 55 added lines, 89 removed lines. Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } /** Loading Loading @@ -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. * Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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. Loading