diff --git a/htmx.routing.yml b/htmx.routing.yml index 0f69664b34baff7b4d1bec355cd0408cc0d56014..bf4c1ef0d712d133ec3026ddf7c4dd7e3553eb7d 100644 --- a/htmx.routing.yml +++ b/htmx.routing.yml @@ -52,6 +52,19 @@ htmx_blocks.htmx_system_block_library: _admin_route: true _htmx_route: true +htmx.htmx_entity_view: + path: '/htmx/{entityType}/{entity}/{viewMode}' + defaults: + _title: 'HTMX Entity View' + _controller: '\Drupal\htmx\Controller\HtmxEntityViewController::view' + requirements: + _entity_access: entity.view + options: + _htmx_route: true + parameters: + entity: + type: entity:{entityType} + htmx_blocks.view: path: '/htmx/blocks/view/{block}' defaults: diff --git a/src/Controller/HtmxEntityViewController.php b/src/Controller/HtmxEntityViewController.php new file mode 100644 index 0000000000000000000000000000000000000000..96d3405599014259c257f1c1bf626642af63639c --- /dev/null +++ b/src/Controller/HtmxEntityViewController.php @@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\htmx\Controller; + +use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Utility\Error; + +/** + * Returns an entity rendered in a view mode. + */ +final class HtmxEntityViewController extends ControllerBase { + + /** + * Builds the response. + * + * @param string $entityType + * The id of the entity type requested. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity upcast from the entity id in the request path. + * @param string $viewMode + * The view mode to use in rendering the entity. + * + * @return mixed[] + * The render array. + */ + public function view(string $entityType, EntityInterface $entity, string $viewMode = 'default'): array { + $build = []; + try { + /** @var \Drupal\Core\Entity\EntityViewBuilderInterface $viewBuilder */ + $viewBuilder = $this->entityTypeManager()->getViewBuilder($entityType); + $build = $viewBuilder->view($entity, $viewMode); + } + catch (InvalidPluginDefinitionException $e) { + /* + * \Drupal\Core\Entity\EntityTypeManager::getHandler() throws this + * exception when it cannot get the requested view builder. + */ + Error::logException($this->getLogger('htmx'), $e); + } + return $build; + } + +} diff --git a/tests/src/Kernel/HtmxEntityViewTest.php b/tests/src/Kernel/HtmxEntityViewTest.php new file mode 100644 index 0000000000000000000000000000000000000000..994c182ade84daae1fcef8ba8befef56869eae17 --- /dev/null +++ b/tests/src/Kernel/HtmxEntityViewTest.php @@ -0,0 +1,73 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\htmx\Kernel; + +use Drupal\htmx\Controller\HtmxEntityViewController; +use Drupal\KernelTests\KernelTestBase; +use Drupal\node\Entity\Node; +use Drupal\node\NodeViewBuilder; +use Drupal\Tests\node\Traits\NodeCreationTrait; + +/** + * Test description. + * + * @group htmx + */ +final class HtmxEntityViewTest extends KernelTestBase { + + use NodeCreationTrait; + + /** + * The class under test. + */ + protected HtmxEntityViewController $controller; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'htmx', + 'node', + 'datetime', + 'user', + 'system', + 'filter', + 'field', + 'text', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->installConfig('filter'); + $this->installConfig('node'); + $this->installSchema('node', 'node_access'); + $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->controller = HtmxEntityViewController::create($this->container); + } + + /** + * Verify the render array has the appropriate properties. + */ + public function testRenderArray(): void { + $node = $this->createNode(['type' => 'page']); + $renderArray = $this->controller->view('node', $node, 'teaser'); + $this->assertArrayHasKey('#node', $renderArray); + $this->assertInstanceOf(Node::class, $renderArray['#node']); + $this->assertEquals($node->id(), $renderArray['#node']->id()); + $this->assertArrayHasKey('#view_mode', $renderArray); + $this->assertEquals('teaser', $renderArray['#view_mode']); + $this->assertArrayHasKey('#theme', $renderArray); + $this->assertEquals('node', $renderArray['#theme']); + $this->assertArrayHasKey('#pre_render', $renderArray); + [$callbackObject, $method] = reset($renderArray['#pre_render']); + $this->assertInstanceOf(NodeViewBuilder::class, $callbackObject); + $this->assertEquals('build', $method); + } + +}