1

I am building a module that contains a route and a controller that only renders a specific theme region, without any surrounding HTML.

How can I load and render the region programmatically within the controller output method?

The region output from the controller contains one or more blocks and the entire region needs to be embedded on another site. The site will retrieve the markup using a custom route (e.g. example.com/partials/header or example.com/partials/footer).

I don't know which blocks are going to be in the region; the list of blocks in the region is not hard-coded in the module.

0

2 Answers 2

2

Using the block.repository service you can get an instance of the BlockRepository class. You can then call the getVisibleBlocksPerRegion method on it which will return an associative array BlockInterface[][] (first key is the region name, second key is the block ID).

You will probably need to change the active context if some of these blocks only appear certain conditions.

2

Coming back to this nearly five years later, because we now have to output an entire HTML (in our case, Smarty) template incl. assets that can be consumed by another service. Might be useful to someone, or myself when I google this in another five years.

We are rendering selected regions into a custom theme, that we then pass into an HTML render element.

We then call some hooks on the render element to allow modules to alter it.

We use the HTMLResponse class to process attachments and add them to the response.

We use getContent() on the response to get the final template and return it as text.

Note: the template becomes invalid when the processed asset URLs change! Drupal's absolute path URLs /sites/default/... work for use because we are using a reverse proxy, but they are easy to str_replace in the rendered template.

<?php

namespace Drupal\my_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\HtmlResponse;
use Drupal\user\Entity\User;
use Symfony\Component\HttpFoundation\Response;

/**
 * Returns template.
 */
class MyController extends ControllerBase
{

    /**
     * Returns the template.
     */
    public function template()
    {
        $content = $this->generateTemplate();

        $response = new Response(
            $content,
            Response::HTTP_OK,
            [
                'Content-Type' => 'text/plain; charset=UTF-8',
                'X-Robots-Tag' => 'noindex, nofollow',
            ]
        );

        return $response;
    }

    /**
     * Generates and saves the Smarty template.
     *
     * @return string
     *   The rendered template.
     */
    private function generateTemplate()
    {
        // Switch to anon.
        /** @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
        $account_switcher = \Drupal::service('account_switcher');
        $account_switcher->switchTo(User::getAnonymousUser());

        // Create a theme that renders the required regions.
        $build = [
            '#theme' => 'regions_template',
            '#preheader' => $this->renderRegion('preheader'),
            '#header' => $this->renderRegion('header'),
            '#prefooter' => $this->renderRegion('prefooter'),
            '#footer' => $this->renderRegion('footer'),
        ];

        // Add the regions to a page.
        $html = [
            '#type' => 'html',
            'page' => [
                '#type' => 'page',
                'content' => $build,
            ],
        ];

        // Call some hooks.
        system_page_attachments($html['page']);
        \Drupal::service('module_handler')->alter('page_attachments', $html['page']);

        // Render the render array (this modifies it, as it is passed by ref).
        \Drupal::service('renderer')->renderRoot($html);

        // Use a response to process the attachments in the render array.
        $response = new HtmlResponse();
        $response->setContent($html);

        $response = \Drupal::service('html_response.attachments_processor')
            ->processAttachments($response);

        // Get the content from the response.
        $rendered = $response->getContent();

        // Switch back.
        $account_switcher->switchBack();

        return $rendered;
    }

    /**
     * @param string $region
     */
    private function renderRegion($region)
    {
        $build = [];
        foreach ($this->getBlocksInRegion($region) as $block) {
            $build[] = \Drupal::entityTypeManager()
                ->getViewBuilder('block')
                ->view($block);
        }
        return $build;
    }

    /**
     * @param string $region
     * 
     * @return array
     */
    private function getBlocksInRegion($region)
    {
        return \Drupal::entityTypeManager()
            ->getStorage('block')
            ->loadByProperties([
                'status' => 1,
                'theme' => 'mytheme',
                'region' => $region,
            ]);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.