diff --git a/src/EventSubscriber/CustomElementsFormatSubscriber.php b/src/EventSubscriber/CustomElementsFormatSubscriber.php index e1a26d2dc33fac4ce01413873448a5d0c7d86133..62516f3829d26beced67c3edc20b6e8031f356f5 100644 --- a/src/EventSubscriber/CustomElementsFormatSubscriber.php +++ b/src/EventSubscriber/CustomElementsFormatSubscriber.php @@ -2,10 +2,10 @@ namespace Drupal\lupus_ce_renderer\EventSubscriber; -use Symfony\Component\HttpKernel\Event\ResponseEvent; use Drupal\Core\Site\Settings; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; use Symfony\Component\HttpKernel\KernelEvents; @@ -29,10 +29,15 @@ class CustomElementsFormatSubscriber implements EventSubscriberInterface { $response = $event->getResponse(); $request = $event->getRequest(); - // Dis-allow html format when custom elements rendering is enabled. Other - // formats like 'json' should be allowed, so custom APIs can be added. + // Dis-allow html responses when custom elements rendering is enabled. + // Other formats like 'json' are kept allowed for backwards compatibility. // @todo Make this alterable somehow. - if ($request->attributes->get('lupus_ce_renderer') && $request->getRequestFormat('custom_elements') == 'html') { + // Block responses that would result in HTML output. Check both the + // request format and whether the response has no explicit content type + // set (which defaults to text/html via Response::prepare()). + $is_html = $request->getRequestFormat('html') === 'html' + || !$response->headers->has('Content-Type'); + if ($request->attributes->get('lupus_ce_renderer') && $is_html && !$response instanceof RedirectResponse) { // Throw status 406 http exception, but take care to not run in an // end-less loop here. if (!$event->getRequest()->attributes->get('exception') || !$event->getRequest()->attributes->get('exception') instanceof NotAcceptableHttpException) { diff --git a/tests/modules/lupus_ce_renderer_test/lupus_ce_renderer_test.routing.yml b/tests/modules/lupus_ce_renderer_test/lupus_ce_renderer_test.routing.yml index b0970b3f6ae896932d11914759b40dc60d800a9b..1f4daddbd44f1e812d08ffa038fe611223c4211d 100644 --- a/tests/modules/lupus_ce_renderer_test/lupus_ce_renderer_test.routing.yml +++ b/tests/modules/lupus_ce_renderer_test/lupus_ce_renderer_test.routing.yml @@ -29,6 +29,14 @@ lupus_ce_renderer_test.admin_render_array: options: _admin_route: TRUE +lupus_ce_renderer_test.render_array: + path: '/lupus-ce-renderer-test/render-array' + defaults: + _controller: '\Drupal\lupus_ce_renderer_test\Controller\TestController::renderArray' + _title: 'Render array test page' + requirements: + _access: 'TRUE' + lupus_ce_renderer_test.html_response_with_render: path: '/lupus-ce-renderer-test/html-response-with-render' defaults: diff --git a/tests/src/Kernel/LupusCeRendererControllerTest.php b/tests/src/Kernel/LupusCeRendererControllerTest.php index 8a6612365eeb4b7c876799585ec49b75b3a216b4..75e712df2ebc1b02ee9889643ce4b5e5b5f6bf51 100644 --- a/tests/src/Kernel/LupusCeRendererControllerTest.php +++ b/tests/src/Kernel/LupusCeRendererControllerTest.php @@ -2,7 +2,6 @@ namespace Drupal\Tests\lupus_ce_renderer\Kernel; -use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\Request; /** @@ -32,35 +31,46 @@ class LupusCeRendererControllerTest extends LupusCeRendererKernelTestBase { ]; /** - * Tests that controllers calling render() work with custom_elements format. + * Tests non-entity controllers with custom_elements format. * - * Regression test: CustomElementsRouteSubscriber previously stripped - * EarlyRenderingControllerWrapperSubscriber for all non-entity routes when - * the format was custom_elements. This removed the render context, causing - * controllers that call render() (like Canvas) to fail with "Render context - * is empty" LogicException. + * Render array controllers should work and return CE JSON responses. + * Controllers returning HtmlResponse should be blocked with 406. + * Other formats like JSON should still be allowed for custom APIs. */ public function testControllerWithRenderCallWorksWithCustomElements() { - // Test with lupus_ce_renderer enabled globally. - new Settings(Settings::getAll() + ['lupus_ce_renderer_enable' => TRUE]); - - $request = Request::create('/lupus-ce-renderer-test/html-response-with-render'); + // Render array controller works with custom_elements format. + $request = Request::create('/lupus-ce-renderer-test/render-array', 'GET', [ + '_format' => 'custom_elements', + ]); $response = $this->container->get('http_kernel')->handle($request); - - // The controller returns an HtmlResponse, which should pass through - // without error. The key assertion is that no LogicException about - // empty render context is thrown. $this->assertEquals(200, $response->getStatusCode()); - $this->assertStringContainsString('Test content from render call', $response->getContent()); + $this->assertStringContainsString('application/json', $response->headers->get('Content-Type')); + $data = $this->decodeResponse($response); + $this->assertNotEmpty($data['content']); - // Also test with explicit _format parameter. + // HtmlResponse controller is blocked with 406 when lupus_ce_renderer + // is active (as it would be via /ce-api). $request = Request::create('/lupus-ce-renderer-test/html-response-with-render', 'GET', [ '_format' => 'custom_elements', ]); + $request->attributes->set('lupus_ce_renderer', TRUE); $response = $this->container->get('http_kernel')->handle($request); + $this->assertEquals(406, $response->getStatusCode()); + // Without lupus_ce_renderer, the HTML route works normally. + $request = Request::create('/lupus-ce-renderer-test/html-response-with-render'); + $response = $this->container->get('http_kernel')->handle($request); $this->assertEquals(200, $response->getStatusCode()); $this->assertStringContainsString('Test content from render call', $response->getContent()); + + // JSON responses should still be allowed (BC for custom APIs). + $request = Request::create('/user/login_status', 'GET', [ + '_format' => 'json', + ]); + $request->attributes->set('lupus_ce_renderer', TRUE); + $response = $this->container->get('http_kernel')->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('0', $response->getContent()); } /** @@ -72,9 +82,9 @@ class LupusCeRendererControllerTest extends LupusCeRendererKernelTestBase { * pipeline should then include this metadata in the final JSON response. */ public function testEarlyRenderingCacheMetadataPreservedForRenderArray() { - new Settings(Settings::getAll() + ['lupus_ce_renderer_enable' => TRUE]); - - $request = Request::create('/lupus-ce-renderer-test/render-array-with-early-rendering'); + $request = Request::create('/lupus-ce-renderer-test/render-array-with-early-rendering', 'GET', [ + '_format' => 'custom_elements', + ]); $response = $this->container->get('http_kernel')->handle($request); $this->assertEquals(200, $response->getStatusCode()); @@ -96,9 +106,9 @@ class LupusCeRendererControllerTest extends LupusCeRendererKernelTestBase { * Without the decorator, core would throw a LogicException. */ public function testEarlyRenderingCacheMetadataPreservedForCustomElement() { - new Settings(Settings::getAll() + ['lupus_ce_renderer_enable' => TRUE]); - - $request = Request::create('/lupus-ce-renderer-test/custom-element-with-early-rendering'); + $request = Request::create('/lupus-ce-renderer-test/custom-element-with-early-rendering', 'GET', [ + '_format' => 'custom_elements', + ]); $response = $this->container->get('http_kernel')->handle($request); $this->assertEquals(200, $response->getStatusCode()); @@ -121,8 +131,8 @@ class LupusCeRendererControllerTest extends LupusCeRendererKernelTestBase { * * Entity canonical routes have their controller replaced by * CustomElementsControllerSubscriber. The decorated early rendering - * subscriber should still - * provide a render context and handle any leaked metadata. + * subscriber should still provide a render context and handle any leaked + * metadata. */ public function testEntityRouteEarlyRenderingHasRenderContext() { $response = $this->request($this->nodePath, ['_format' => 'custom_elements']);