Commit 4997192b authored by larowlan's avatar larowlan

Issue #2449143 by damiankloip, tedbow, Wim Leers, dawehner, Pavan B S, Tybor,...

Issue #2449143 by damiankloip, tedbow, Wim Leers, dawehner, Pavan B S, Tybor, effulgentsia, larowlan: REST views specify HTML as a possible request format, so if there is a "regular" HTML view on the same path, it will serve JSON
parent 342b4921
...@@ -70,7 +70,7 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac ...@@ -70,7 +70,7 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
* *
* @var string * @var string
*/ */
protected $mimeType; protected $mimeType = 'application/json';
/** /**
* The renderer. * The renderer.
...@@ -93,6 +93,13 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac ...@@ -93,6 +93,13 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
*/ */
protected $authenticationProviders; protected $authenticationProviders;
/**
* The serialization format providers, keyed by format.
*
* @var string[]
*/
protected $formatProviders;
/** /**
* Constructs a RestExport object. * Constructs a RestExport object.
* *
...@@ -110,12 +117,15 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac ...@@ -110,12 +117,15 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
* The renderer. * The renderer.
* @param string[] $authentication_providers * @param string[] $authentication_providers
* The authentication providers, keyed by ID. * The authentication providers, keyed by ID.
* @param string[] $serializer_format_providers
* The serialization format providers, keyed by format.
*/ */
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, RendererInterface $renderer, array $authentication_providers) { public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, RendererInterface $renderer, array $authentication_providers, array $serializer_format_providers) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state); parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state);
$this->renderer = $renderer; $this->renderer = $renderer;
$this->authenticationProviders = $authentication_providers; $this->authenticationProviders = $authentication_providers;
$this->formatProviders = $serializer_format_providers;
} }
/** /**
...@@ -129,8 +139,8 @@ public static function create(ContainerInterface $container, array $configuratio ...@@ -129,8 +139,8 @@ public static function create(ContainerInterface $container, array $configuratio
$container->get('router.route_provider'), $container->get('router.route_provider'),
$container->get('state'), $container->get('state'),
$container->get('renderer'), $container->get('renderer'),
$container->getParameter('authentication_providers') $container->getParameter('authentication_providers'),
$container->getParameter('serializer.format_providers')
); );
} }
/** /**
...@@ -139,21 +149,22 @@ public static function create(ContainerInterface $container, array $configuratio ...@@ -139,21 +149,22 @@ public static function create(ContainerInterface $container, array $configuratio
public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) { public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
parent::initDisplay($view, $display, $options); parent::initDisplay($view, $display, $options);
// If the default 'json' format is not selected as a format option in the
// view display, fallback to the first format available for the default.
if (!empty($options['style']['options']['formats']) && !isset($options['style']['options']['formats'][$this->getContentType()])) {
$default_format = reset($options['style']['options']['formats']);
$this->setContentType($default_format);
}
// Only use the requested content type if it's not 'html'. This allows
// still falling back to the default for things like views preview.
$request_content_type = $this->view->getRequest()->getRequestFormat(); $request_content_type = $this->view->getRequest()->getRequestFormat();
// Only use the requested content type if it's not 'html'. If it is then
// default to 'json' to aid debugging. if ($request_content_type !== 'html') {
// @todo Remove the need for this when we have better content negotiation.
if ($request_content_type != 'html') {
$this->setContentType($request_content_type); $this->setContentType($request_content_type);
} }
// If the requested content type is 'html' and the default 'json' is not
// selected as a format option in the view display, fallback to the first
// format in the array.
elseif (!empty($options['style']['options']['formats']) && !isset($options['style']['options']['formats'][$this->getContentType()])) {
$this->setContentType(reset($options['style']['options']['formats']));
}
$this->setMimeType($this->view->getRequest()->getMimeType($this->contentType)); $this->setMimeType($this->view->getRequest()->getMimeType($this->getContentType()));
} }
/** /**
...@@ -327,17 +338,21 @@ public function collectRoutes(RouteCollection $collection) { ...@@ -327,17 +338,21 @@ public function collectRoutes(RouteCollection $collection) {
if ($route = $collection->get("view.$view_id.$display_id")) { if ($route = $collection->get("view.$view_id.$display_id")) {
$style_plugin = $this->getPlugin('style'); $style_plugin = $this->getPlugin('style');
// REST exports should only respond to get methods.
// REST exports should only respond to GET methods.
$route->setMethods(['GET']); $route->setMethods(['GET']);
// Format as a string using pipes as a delimiter. $formats = $style_plugin->getFormats();
if ($formats = $style_plugin->getFormats()) {
// Allow a REST Export View to be returned with an HTML-only accept // If there are no configured formats, add all formats that serialization
// format. That allows browsers or other non-compliant systems to access // is known to support.
// the view, as it is unlikely to have a conflicting HTML representation if (!$formats) {
// anyway. $formats = $this->getFormatOptions();
$route->setRequirement('_format', implode('|', $formats + ['html']));
} }
// Format as a string using pipes as a delimiter.
$route->setRequirement('_format', implode('|', $formats));
// Add authentication to the route if it was set. If no authentication was // Add authentication to the route if it was set. If no authentication was
// set, the default authentication will be used, which is cookie based by // set, the default authentication will be used, which is cookie based by
// default. // default.
...@@ -421,15 +436,15 @@ public function render() { ...@@ -421,15 +436,15 @@ public function render() {
$build['#suffix'] = '</pre>'; $build['#suffix'] = '</pre>';
unset($build['#markup']); unset($build['#markup']);
} }
elseif ($this->view->getRequest()->getFormat($this->view->element['#content_type']) !== 'html') { else {
// This display plugin is primarily for returning non-HTML formats. // This display plugin is for returning non-HTML formats. However, we
// However, we still invoke the renderer to collect cacheability metadata. // still invoke the renderer to collect cacheability metadata. Because the
// Because the renderer is designed for HTML rendering, it filters // renderer is designed for HTML rendering, it filters #markup for XSS
// #markup for XSS unless it is already known to be safe, but that filter // unless it is already known to be safe, but that filter only works for
// only works for HTML. Therefore, we mark the contents as safe to bypass // HTML. Therefore, we mark the contents as safe to bypass the filter. So
// the filter. So long as we are returning this in a non-HTML response // long as we are returning this in a non-HTML response,
// (checked above), this is safe, because an XSS attack only works when // this is safe, because an XSS attack only works when executed by an HTML
// executed by an HTML agent. // agent.
// @todo Decide how to support non-HTML in the render API in // @todo Decide how to support non-HTML in the render API in
// https://www.drupal.org/node/2501313. // https://www.drupal.org/node/2501313.
$build['#markup'] = ViewsRenderPipelineMarkup::create($build['#markup']); $build['#markup'] = ViewsRenderPipelineMarkup::create($build['#markup']);
...@@ -465,4 +480,15 @@ public function calculateDependencies() { ...@@ -465,4 +480,15 @@ public function calculateDependencies() {
return $dependencies; return $dependencies;
} }
/**
* Returns an array of format options.
*
* @return string[]
* An array of format options. Both key and value are the same.
*/
protected function getFormatOptions() {
$formats = array_keys($this->formatProviders);
return array_combine($formats, $formats);
}
} }
langcode: en
status: true
dependencies:
module:
- rest
- user
id: test_serializer_shared_path
label: 'Test serializer shared path'
module: rest
description: ''
tag: ''
base_table: entity_test
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: null
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
query:
type: views_query
exposed_form:
type: basic
style:
type: serializer
row:
type: data_entity
sorts:
id:
id: standard
table: entity_test
field: id
order: DESC
plugin_id: date
entity_type: entity_test
entity_field: id
title: 'Test serialize'
arguments: { }
rest_export_1:
display_plugin: rest_export
id: rest_export_1
display_title: serializer
position: null
display_options:
defaults:
access: false
path: test/serialize/shared
page_1:
display_plugin: page
id: page_1
display_title: page
position: null
display_options:
defaults:
access: false
style: false
row: false
style:
type: default
row:
type: entity:entity_test
path: test/serialize/shared
...@@ -43,7 +43,7 @@ class StyleSerializerTest extends ViewTestBase { ...@@ -43,7 +43,7 @@ class StyleSerializerTest extends ViewTestBase {
* *
* @var array * @var array
*/ */
public static $testViews = ['test_serializer_display_field', 'test_serializer_display_entity', 'test_serializer_display_entity_translated', 'test_serializer_node_display_field', 'test_serializer_node_exposed_filter']; public static $testViews = ['test_serializer_display_field', 'test_serializer_display_entity', 'test_serializer_display_entity_translated', 'test_serializer_node_display_field', 'test_serializer_node_exposed_filter', 'test_serializer_shared_path'];
/** /**
* A user with administrative privileges to look at test entity and configure views. * A user with administrative privileges to look at test entity and configure views.
...@@ -85,6 +85,9 @@ public function testRestViewsAuthentication() { ...@@ -85,6 +85,9 @@ public function testRestViewsAuthentication() {
$url = $this->buildUrl('test/serialize/auth_with_perm'); $url = $this->buildUrl('test/serialize/auth_with_perm');
$response = \Drupal::httpClient()->get($url, [ $response = \Drupal::httpClient()->get($url, [
'auth' => [$this->adminUser->getUsername(), $this->adminUser->pass_raw], 'auth' => [$this->adminUser->getUsername(), $this->adminUser->pass_raw],
'query' => [
'_format' => 'json',
],
]); ]);
// Ensure that any changes to variables in the other thread are picked up. // Ensure that any changes to variables in the other thread are picked up.
...@@ -134,7 +137,7 @@ public function testSerializerResponses() { ...@@ -134,7 +137,7 @@ public function testSerializerResponses() {
$this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.'); $this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.');
// Test a 403 callback. // Test a 403 callback.
$this->drupalGet('test/serialize/denied'); $this->drupalGet('test/serialize/denied', ['query' => ['_format' => 'json']]);
$this->assertResponse(403); $this->assertResponse(403);
// Test the entity rows. // Test the entity rows.
...@@ -169,7 +172,7 @@ public function testSerializerResponses() { ...@@ -169,7 +172,7 @@ public function testSerializerResponses() {
$this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.'); $this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
$this->assertCacheTags($expected_cache_tags); $this->assertCacheTags($expected_cache_tags);
// Change the default format to xml. // Change the format to xml.
$view->setDisplay('rest_export_1'); $view->setDisplay('rest_export_1');
$view->getDisplay()->setOption('style', [ $view->getDisplay()->setOption('style', [
'type' => 'serializer', 'type' => 'serializer',
...@@ -207,6 +210,28 @@ public function testSerializerResponses() { ...@@ -207,6 +210,28 @@ public function testSerializerResponses() {
$this->assertSame(trim($expected), $actual_xml); $this->assertSame(trim($expected), $actual_xml);
} }
/**
* Verifies REST export views work on the same path as a page display.
*/
public function testSharedPagePath() {
// Test with no format as well as html explicitly.
$this->drupalGet('test/serialize/shared');
$this->assertResponse(200);
$this->assertHeader('content-type', 'text/html; charset=UTF-8');
$this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'html']]);
$this->assertResponse(200);
$this->assertHeader('content-type', 'text/html; charset=UTF-8');
$this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'json']]);
$this->assertResponse(200);
$this->assertHeader('content-type', 'application/json');
$this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'xml']]);
$this->assertResponse(200);
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
}
/** /**
* Verifies site maintenance mode functionality. * Verifies site maintenance mode functionality.
*/ */
...@@ -336,6 +361,11 @@ public function testResponseFormatConfiguration() { ...@@ -336,6 +361,11 @@ public function testResponseFormatConfiguration() {
$style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options'; $style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options';
// Test with no format.
$this->drupalGet('test/serialize/field');
$this->assertHeader('content-type', 'text/html; charset=UTF-8');
$this->assertResponse(406, 'A 406 response was returned when no format was requested.');
// Select only 'xml' as an accepted format. // Select only 'xml' as an accepted format.
$this->drupalPostForm($style_options, ['style_options[formats][xml]' => 'xml'], t('Apply')); $this->drupalPostForm($style_options, ['style_options[formats][xml]' => 'xml'], t('Apply'));
$this->drupalPostForm(NULL, [], t('Save')); $this->drupalPostForm(NULL, [], t('Save'));
...@@ -353,37 +383,27 @@ public function testResponseFormatConfiguration() { ...@@ -353,37 +383,27 @@ public function testResponseFormatConfiguration() {
$this->drupalPostForm($style_options, ['style_options[formats][json]' => 'json'], t('Apply')); $this->drupalPostForm($style_options, ['style_options[formats][json]' => 'json'], t('Apply'));
$this->drupalPostForm(NULL, [], t('Save')); $this->drupalPostForm(NULL, [], t('Save'));
// Should return a 200. // Should return a 406. Emulates a sample Firefox header.
// @todo This should be fixed when we have better content negotiation.
$this->drupalGet('test/serialize/field');
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when any format was requested.');
// Should return a 200. Emulates a sample Firefox header.
$this->drupalGet('test/serialize/field', [], ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']); $this->drupalGet('test/serialize/field', [], ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']);
$this->assertHeader('content-type', 'application/json'); $this->assertHeader('content-type', 'text/html; charset=UTF-8');
$this->assertResponse(200, 'A 200 response was returned when a browser accept header was requested.'); $this->assertResponse(406, 'A 406 response was returned when a browser accept header was requested.');
// Should return a 406.
$this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
$this->assertHeader('content-type', 'text/html; charset=UTF-8');
$this->assertResponse(406, 'A 406 response was returned when HTML was requested.');
// Should return a 200. // Should return a 200.
$this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]); $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
$this->assertHeader('content-type', 'application/json'); $this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when JSON was requested.'); $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['Content-Type'], ['application/json'], 'The header Content-type is correct.');
// Should return a 200. // Should return a 200.
$this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]); $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
$this->assertHeader('content-type', 'text/xml; charset=UTF-8'); $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
$this->assertResponse(200, 'A 200 response was returned when XML was requested'); $this->assertResponse(200, 'A 200 response was returned when XML was requested');
$headers = $this->drupalGetHeaders();
$this->assertSame(['text/xml; charset=UTF-8'], $headers['Content-Type']);
// Should return a 406.
$this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
// We want to show the first format by default, see
// \Drupal\rest\Plugin\views\style\Serializer::render.
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
// Now configure now format, so all of them should be allowed. // Now configure no format, so both serialization formats should be allowed.
$this->drupalPostForm($style_options, ['style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'], t('Apply')); $this->drupalPostForm($style_options, ['style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'], t('Apply'));
// Should return a 200. // Should return a 200.
...@@ -394,12 +414,11 @@ public function testResponseFormatConfiguration() { ...@@ -394,12 +414,11 @@ public function testResponseFormatConfiguration() {
$this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]); $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
$this->assertHeader('content-type', 'text/xml; charset=UTF-8'); $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
$this->assertResponse(200, 'A 200 response was returned when XML was requested'); $this->assertResponse(200, 'A 200 response was returned when XML was requested');
// Should return a 200.
// Should return a 406 for HTML still.
$this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]); $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
// We want to show the first format by default, see $this->assertHeader('content-type', 'text/html; charset=UTF-8');
// \Drupal\rest\Plugin\views\style\Serializer::render. $this->assertResponse(406, 'A 406 response was returned when HTML was requested.');
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
} }
/** /**
...@@ -598,7 +617,7 @@ public function testSerializerViewsUI() { ...@@ -598,7 +617,7 @@ public function testSerializerViewsUI() {
// Check if we receive the expected result. // Check if we receive the expected result.
$result = $this->xpath('//div[@id="views-live-preview"]/pre'); $result = $this->xpath('//div[@id="views-live-preview"]/pre');
$json_preview = $result[0]->getText(); $json_preview = $result[0]->getText();
$this->assertSame($json_preview, $this->drupalGet('test/serialize/field'), 'The expected JSON preview output was found.'); $this->assertSame($json_preview, $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]), 'The expected JSON preview output was found.');
} }
/** /**
......
...@@ -84,6 +84,8 @@ protected function setUp() { ...@@ -84,6 +84,8 @@ protected function setUp() {
->method('getSortedProviders') ->method('getSortedProviders')
->will($this->returnValue(['basic_auth' => 'data', 'cookie' => 'data'])); ->will($this->returnValue(['basic_auth' => 'data', 'cookie' => 'data']));
$container->setParameter('serializer.format_providers', ['json']);
\Drupal::setContainer($container); \Drupal::setContainer($container);
$this->restExport = RestExport::create($container, [], "test_routes", []); $this->restExport = RestExport::create($container, [], "test_routes", []);
......
...@@ -817,7 +817,7 @@ protected function drupalGetJSON($path, array $options = [], array $headers = [] ...@@ -817,7 +817,7 @@ protected function drupalGetJSON($path, array $options = [], array $headers = []
* The result of the request. * The result of the request.
*/ */
protected function drupalGetWithFormat($path, $format, array $options = [], array $headers = []) { protected function drupalGetWithFormat($path, $format, array $options = [], array $headers = []) {
$options += ['query' => ['_format' => $format]]; $options = array_merge_recursive(['query' => ['_format' => $format]], $options);
return $this->drupalGet($path, $options, $headers); return $this->drupalGet($path, $options, $headers);
} }
......
...@@ -84,6 +84,18 @@ public static function create(ContainerInterface $container, array $configuratio ...@@ -84,6 +84,18 @@ public static function create(ContainerInterface $container, array $configuratio
); );
} }
/**
* {@inheritdoc}
*/
protected function getRoute($view_id, $display_id) {
$route = parent::getRoute($view_id, $display_id);
// Explicitly set HTML as the format for Page displays.
$route->setRequirement('_format', 'html');
return $route;
}
/** /**
* Sets the current page views render array. * Sets the current page views render array.
* *
......
...@@ -163,8 +163,9 @@ public function testViewsWizardAndListing() { ...@@ -163,8 +163,9 @@ public function testViewsWizardAndListing() {
$this->drupalPostForm('admin/structure/views/add', $view4, t('Save and edit')); $this->drupalPostForm('admin/structure/views/add', $view4, t('Save and edit'));
$this->assertRaw(t('The view %view has been saved.', ['%view' => $view4['label']])); $this->assertRaw(t('The view %view has been saved.', ['%view' => $view4['label']]));
// Check that the REST export path works. // Check that the REST export path works. JSON will work, as all core
$this->drupalGet($view4['rest_export[path]']); // formats will be allowed. JSON and XML by default.
$this->drupalGet($view4['rest_export[path]'], ['query' => ['_format' => 'json']]);
$this->assertResponse(200); $this->assertResponse(200);
$data = Json::decode($this->getSession()->getPage()->getContent()); $data = Json::decode($this->getSession()->getPage()->getContent());
$this->assertEqual(count($data), 1, 'Only the node of type page is exported.'); $this->assertEqual(count($data), 1, 'Only the node of type page is exported.');
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
use Drupal\views\Views; use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
/** /**
* Form controller for the Views edit form. * Form controller for the Views edit form.
...@@ -417,15 +418,25 @@ public function getDisplayDetails($view, $display) { ...@@ -417,15 +418,25 @@ public function getDisplayDetails($view, $display) {
// path. // path.
elseif ($view->status() && $view->getExecutable()->displayHandlers->get($display['id'])->hasPath()) { elseif ($view->status() && $view->getExecutable()->displayHandlers->get($display['id'])->hasPath()) {
$path = $view->getExecutable()->displayHandlers->get($display['id'])->getPath(); $path = $view->getExecutable()->displayHandlers->get($display['id'])->getPath();
if ($path && (strpos($path, '%') === FALSE)) { if ($path && (strpos($path, '%') === FALSE)) {
if (!parse_url($path, PHP_URL_SCHEME)) { // Wrap this in a try/catch as trying to generate links to some
// @todo Views should expect and store a leading /. See: // routes may throw a NotAcceptableHttpException if they do not
// https://www.drupal.org/node/2423913 // respond to HTML, such as RESTExports.
$url = Url::fromUserInput('/' . ltrim($path, '/')); try {
if (!parse_url($path, PHP_URL_SCHEME)) {
// @todo Views should expect and store a leading /. See:
// https://www.drupal.org/node/2423913
$url = Url::fromUserInput('/' . ltrim($path, '/'));
}
else {
$url = Url::fromUri("base:$path");
}
} }
else { catch (NotAcceptableHttpException $e) {
$url = Url::fromUri("base:$path"); $url = '/' . $path;
} }
$build['top']['actions']['path'] = [ $build['top']['actions']['path'] = [
'#type' => 'link', '#type' => 'link',
'#title' => $this->t('View @display_title', ['@display_title' => $display_title]), '#title' => $this->t('View @display_title', ['@display_title' => $display_title]),
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
/** /**
* Defines a class to build a listing of view entities. * Defines a class to build a listing of view entities.
...@@ -255,9 +256,17 @@ protected function getDisplaysList(EntityInterface $view) { ...@@ -255,9 +256,17 @@ protected function getDisplaysList(EntityInterface $view) {
if ($display->hasPath()) { if ($display->hasPath()) {
$path = $display->getPath(); $path = $display->getPath();
if ($view->status() && strpos($path, '%') === FALSE) { if ($view->status() && strpos($path, '%') === FALSE) {
// @todo Views should expect and store a leading /. See: // Wrap this in a try/catch as trying to generate links to some
// https://www.drupal.org/node/2423913 // routes may throw a NotAcceptableHttpException if they do not
$rendered_path = \Drupal::l('/' . $path, Url::fromUserInput('/' . $path)); // respond to HTML, such as RESTExports.
try {
// @todo Views should expect and store a leading /. See:
// https://www.drupal.org/node/2423913
$rendered_path = \Drupal::l('/' . $path, Url::fromUserInput('/' . $path));
}
catch (NotAcceptableHttpException $e) {
$rendered_path = '/' . $path;
}
} }
else { else {
$rendered_path = '/' . $path; $rendered_path = '/' . $path;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment