Commit 30ca0d1d authored by catch's avatar catch

Issue #2481453 by dawehner, pwolanin, rteijeiro, neclimdul, znerol: Implement...

Issue #2481453 by dawehner, pwolanin, rteijeiro, neclimdul, znerol: Implement query parameter based content negotiation as alternative to extensions
parent 5340b188
......@@ -560,10 +560,6 @@ services:
http_middleware.negotiation:
class: Drupal\Core\StackMiddleware\NegotiationMiddleware
arguments: ['@http_negotiation.format_negotiator']
calls:
- [registerFormat, ['drupal_ajax', ['application/vnd.drupal-ajax']]]
- [registerFormat, ['drupal_dialog', ['application/vnd.drupal-dialog']]]
- [registerFormat, ['drupal_modal', ['application/vnd.drupal-modal']]]
tags:
- { name: http_middleware, priority: 400 }
http_middleware.reverse_proxy:
......@@ -758,8 +754,8 @@ services:
password:
class: Drupal\Core\Password\PhpassHashedPassword
arguments: [16]
accept_header_matcher:
class: Drupal\Core\Routing\AcceptHeaderMatcher
request_format_route_filter:
class: Drupal\Core\Routing\RequestFormatRouteFilter
tags:
- { name: route_filter }
content_type_header_matcher:
......
......@@ -10,10 +10,7 @@
use Symfony\Component\HttpFoundation\Request;
/**
* This class is a central library for content type negotiation.
*
* @todo Replace this class with a real content negotiation library based on
* mod_negotiation. Development of that is a work in progress.
* Provides content negotation based upon query parameters.
*/
class ContentNegotiation {
......@@ -36,21 +33,8 @@ public function getContentType(Request $request) {
return 'iframeupload';
}
// Check all formats, if priority format is found return it.
$first_found_format = FALSE;
foreach ($request->getAcceptableContentTypes() as $mime_type) {
$format = $request->getFormat($mime_type);
if ($format === 'html') {
return $format;
}
if (!is_null($format) && !$first_found_format) {
$first_found_format = $format;
}
}
// No HTML found, return first found.
if ($first_found_format) {
return $first_found_format;
if ($request->query->has('_format')) {
return $request->query->get('_format');
}
if ($request->isXmlHttpRequest()) {
......
......@@ -198,13 +198,7 @@ public function onException(GetResponseForExceptionEvent $event) {
* The format as which to treat the exception.
*/
protected function getFormat(Request $request) {
// @todo We are trying to switch to a more robust content negotiation
// library in https://www.drupal.org/node/1505080 that will make
// $request->getRequestFormat() reliable as a better alternative
// to this code. We therefore use this style for now on the expectation
// that it will get replaced with better code later. This approach makes
// that change easier when we get to it.
$format = \Drupal::service('http_negotiation.format_negotiator')->getContentType($request);
$format = $request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT, $request->getRequestFormat());
// These are all JSON errors for our purposes. Any special handling for
// them can/should happen in earlier listeners if desired.
......
......@@ -86,11 +86,12 @@ public function onException(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
// Make the exception available for example when rendering a block.
$event->getRequest()->attributes->set('exception', $exception);
$request = $event->getRequest();
$request->attributes->set('exception', $exception);
$handled_formats = $this->getHandledFormats();
$format = $event->getRequest()->getRequestFormat();
$format = $request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT, $request->getRequestFormat());
if ($exception instanceof HttpExceptionInterface && (empty($handled_formats) || in_array($format, $handled_formats))) {
$method = 'on' . $exception->getStatusCode();
......
......@@ -10,6 +10,7 @@
use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface as BaseRouteFilterInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouteCollection;
......@@ -95,7 +96,9 @@ public function filter(RouteCollection $collection, Request $request) {
if (isset($filter_ids)) {
foreach ($filter_ids as $filter_id) {
$collection = $this->container->get($filter_id)->filter($collection, $request);
if ($filter = $this->container->get($filter_id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
$collection = $filter->filter($collection, $request);
}
}
}
return $collection;
......
<?php
/**
* @file
* Contains \Drupal\Core\Routing\RequestFormatRouteFilter.
*/
namespace Drupal\Core\Routing;
use Drupal\Component\Utility\SafeMarkup;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides a route filter, which filters by the request format.
*/
class RequestFormatRouteFilter implements RouteFilterInterface {
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
return $route->hasRequirement('_format');
}
/**
* {@inheritdoc}
*/
public function filter(RouteCollection $collection, Request $request) {
$format = $request->getRequestFormat('html');
/** @var \Symfony\Component\Routing\Route $route */
foreach ($collection as $name => $route) {
// If the route has no _format specification, we move it to the end. If it
// does, then no match means the route is removed entirely.
if ($supported_formats = array_filter(explode('|', $route->getRequirement('_format')))) {
if (!in_array($format, $supported_formats)) {
$collection->remove($name);
}
}
else {
$collection->add($name, $route);
}
}
if (count($collection)) {
return $collection;
}
// We do not throw a
// \Symfony\Component\Routing\Exception\ResourceNotFoundException here
// because we don't want to return a 404 status code, but rather a 406.
throw new NotAcceptableHttpException(SafeMarkup::format('No route found for the specified format @format.', ['@format' => $format]));
}
}
......@@ -38,7 +38,7 @@ class NegotiationMiddleware implements HttpKernelInterface {
*
* @var array
*/
protected $formats;
protected $formats = [];
/**
* Constructs a new NegotiationMiddleware.
......@@ -57,10 +57,12 @@ public function __construct(HttpKernelInterface $app, ContentNegotiation $negoti
* {@inheritdoc}
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) {
// Register available mime types.
foreach ($this->formats as $format => $mime_type) {
$request->setFormat($format, $mime_type);
}
// Determine the request format using the negotiator.
$request->setRequestFormat($this->negotiator->getContentType($request));
return $this->app->handle($request, $type, $catch);
}
......
......@@ -83,11 +83,18 @@
var pb = this;
// When doing a post request, you need non-null data. Otherwise a
// HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
var uri = this.uri;
if (uri.indexOf('?') === -1) {
uri += '?';
}
else {
uri += '&';
}
uri += '_format=json';
$.ajax({
type: this.method,
url: this.uri,
url: uri,
data: '',
dataType: 'json',
success: function (progress) {
// Display errors.
if (progress.status === 0) {
......
......@@ -285,7 +285,7 @@ public function testBlockContextualLinks() {
// Get server-rendered contextual links.
// @see \Drupal\contextual\Tests\ContextualDynamicContextTest:renderContextualLinks()
$post = array('ids[0]' => $id, 'ids[1]' => $cached_id);
$response = $this->drupalPost('contextual/render', 'application/json', $post, array('query' => array('destination' => 'test-page')));
$response = $this->drupalPostWithFormat('contextual/render', 'json', $post, array('query' => array('destination' => 'test-page')));
$this->assertResponse(200);
$json = Json::decode($response);
$this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="entityviewedit-form"><a href="' . base_path() . 'admin/structure/views/view/test_view_block/edit/block_1">Edit view</a></li></ul>');
......
......@@ -55,7 +55,7 @@ protected function setUp() {
* Test comment row.
*/
public function testCommentRestExport() {
$this->drupalGet(sprintf('node/%d/comments', $this->nodeUserCommented->id()), [], ['Accept' => 'application/hal+json']);
$this->drupalGetWithFormat(sprintf('node/%d/comments', $this->nodeUserCommented->id()), 'hal_json');
$this->assertResponse(200);
$contents = Json::decode($this->getRawContent());
$this->assertEqual($contents[0]['subject'], 'How much wood would a woodchuck chuck');
......
......@@ -386,6 +386,7 @@ display:
uses_fields: false
formats:
json: json
hal_json: hal_json
row:
type: data_field
options:
......
......@@ -904,7 +904,7 @@ protected function renderContextualLinks($ids, $current_path) {
for ($i = 0; $i < count($ids); $i++) {
$post['ids[' . $i . ']'] = $ids[$i];
}
return $this->drupalPost('contextual/render', 'application/json', $post, array('query' => array('destination' => $current_path)));
return $this->drupalPostWithFormat('contextual/render', 'json', $post, array('query' => array('destination' => $current_path)));
}
/**
......
......@@ -181,6 +181,6 @@ protected function renderContextualLinks($ids, $current_path) {
for ($i = 0; $i < count($ids); $i++) {
$post['ids[' . $i . ']'] = $ids[$i];
}
return $this->drupalPost('contextual/render', 'application/json', $post, array('query' => array('destination' => $current_path)));
return $this->drupalPostWithFormat('contextual/render', 'json', $post, array('query' => array('destination' => $current_path)));
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\dblog\Tests\Rest;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\rest\Tests\RESTTestBase;
/**
......@@ -45,7 +46,7 @@ public function testWatchdog() {
$account = $this->drupalCreateUser(array('restful get dblog'));
$this->drupalLogin($account);
$response = $this->httpRequest("dblog/$id", 'GET', NULL, $this->defaultMimeType);
$response = $this->httpRequest(Url::fromRoute('rest.dblog.GET.' . $this->defaultFormat, ['id' => $id, '_format' => $this->defaultFormat]), 'GET');
$this->assertResponse(200);
$this->assertHeader('content-type', $this->defaultMimeType);
$log = Json::decode($response);
......@@ -54,7 +55,7 @@ public function testWatchdog() {
$this->assertEqual($log['message'], 'Test message', 'Log message text is correct.');
// Request an unknown log entry.
$response = $this->httpRequest("dblog/9999", 'GET', NULL, $this->defaultMimeType);
$response = $this->httpRequest(Url::fromRoute('rest.dblog.GET.' . $this->defaultFormat, ['id' => 9999, '_format' => $this->defaultFormat]), 'GET');
$this->assertResponse(404);
$decoded = Json::decode($response);
$this->assertEqual($decoded['error'], 'Log entry with ID 9999 was not found', 'Response message is correct.');
......
......@@ -409,7 +409,7 @@ function testSwitchingSecurity() {
'value' => self::$sampleContent,
'original_format_id' => $case['format'],
);
$response = $this->drupalPost('editor/filter_xss/' . $format, 'application/json', $post);
$response = $this->drupalPostWithFormat('editor/filter_xss/' . $format, 'json', $post);
$this->assertResponse(200);
$json = Json::decode($response);
$this->assertIdentical($json, $expected_filtered_value, 'The value was correctly filtered for XSS attack vectors.');
......
......@@ -8,6 +8,7 @@
namespace Drupal\editor\Tests;
use Drupal\Component\Serialization\Json;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\simpletest\WebTestBase;
/**
......@@ -88,7 +89,7 @@ public function testUsersWithoutPermission() {
$this->assertRaw('<p>Do you also love Drupal?</p><figure class="caption caption-img"><img src="druplicon.png" /><figcaption>Druplicon</figcaption></figure>');
// Retrieving the untransformed text should result in an empty 403 response.
$response = $this->drupalPost('editor/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', array());
$response = $this->drupalPost('editor/' . 'node/1/body/en/full', '', array(), array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax')));
$this->assertResponse(403);
$this->assertIdentical('{}', $response);
}
......
......@@ -12,7 +12,7 @@
/**
* Encodes HAL data in JSON.
*
* Simply respond to application/hal+json requests using the JSON encoder.
* Simply respond to hal_json format requests using the JSON encoder.
*/
class JsonEncoder extends SymfonyJsonEncoder {
......
......@@ -19,7 +19,7 @@ class HalServiceProvider implements ServiceModifierInterface {
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
if ($container->has('http_middleware.negotiation')) {
if ($container->has('http_middleware.negotiation') && is_a($container->getDefinition('http_middleware.negotiation')->getClass(), '\Drupal\Core\StackMiddleware\NegotiationMiddleware', TRUE)) {
$container->getDefinition('http_middleware.negotiation')->addMethodCall('registerFormat', ['hal_json', ['application/hal+json']]);
}
}
......
......@@ -8,6 +8,7 @@
namespace Drupal\hal\Normalizer;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\rest\LinkManager\LinkManagerInterface;
......@@ -195,14 +196,19 @@ public function denormalize($data, $class, $format = NULL, array $context = arra
/**
* Constructs the entity URI.
*
* @param $entity
* @param \Drupal\Core\Entity\EntityInterface
* The entity.
*
* @return string
* The entity URI.
*/
protected function getEntityUri($entity) {
return $entity->url('canonical', array('absolute' => TRUE));
protected function getEntityUri(EntityInterface $entity) {
// Some entity types don't provide a canonical link template, at least call
// out to ->url().
if ($entity->isNew() || !$entity->hasLinkTemplate('canonical')) {
return $entity->url('canonical', []);
}
$url = $entity->urlInfo('canonical', ['absolute' => TRUE]);
return $url->setRouteParameter('_format', 'hal_json')->toString();
}
/**
......
......@@ -7,6 +7,7 @@
namespace Drupal\hal\Tests;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
/**
......@@ -169,14 +170,15 @@ public function testNormalize() {
/**
* Constructs the entity URI.
*
* @param $entity
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*
* @return string
* The entity URI.
*/
protected function getEntityUri($entity) {
return $entity->url('canonical', array('absolute' => TRUE));
protected function getEntityUri(EntityInterface $entity) {
$url = $entity->urlInfo('canonical', ['absolute' => TRUE]);
return $url->setRouteParameter('_format', 'hal_json')->toString();
}
}
......@@ -79,15 +79,16 @@ function testPageCacheTags() {
}
/**
* Tests support for different cache items with different Accept headers.
* Tests support for different cache items with different request formats
* specified via a query parameter.
*/
function testAcceptHeaderRequests() {
function testQueryParameterFormatRequests() {
$config = $this->config('system.performance');
$config->set('cache.page.max_age', 300);
$config->save();
$accept_header_cache_url = Url::fromRoute('system_test.page_cache_accept_header');
$json_accept_header = array('Accept: application/json');
$accept_header_cache_url_with_json = Url::fromRoute('system_test.page_cache_accept_header', ['_format' => 'json']);
$this->drupalGet($accept_header_cache_url);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'HTML page was not yet cached.');
......@@ -95,9 +96,9 @@ function testAcceptHeaderRequests() {
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'HTML page was cached.');
$this->assertRaw('<p>oh hai this is html.</p>', 'The correct HTML response was returned.');
$this->drupalGet($accept_header_cache_url, array(), $json_accept_header);
$this->drupalGet($accept_header_cache_url_with_json);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Json response was not yet cached.');
$this->drupalGet($accept_header_cache_url, array(), $json_accept_header);
$this->drupalGet($accept_header_cache_url_with_json);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Json response was cached.');
$this->assertRaw('{"content":"oh hai this is json"}', 'The correct Json response was returned.');
......@@ -105,8 +106,8 @@ function testAcceptHeaderRequests() {
\Drupal::service('module_installer')->install(['node', 'rest', 'hal']);
$this->drupalCreateContentType(['type' => 'article']);
$node = $this->drupalCreateNode(['type' => 'article']);
$node_uri = 'node/' . $node->id();
$hal_json_accept_header = ['Accept: application/hal+json'];
$node_uri = $node->urlInfo();
$node_url_with_hal_json_format = $node->urlInfo('canonical')->setRouteParameter('_format', 'hal_json');
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load('anonymous');
$role->grantPermission('restful get entity:node');
......@@ -121,20 +122,20 @@ function testAcceptHeaderRequests() {
// Now request a HAL page, we expect that the first request is a cache miss
// and it serves HTML.
$this->drupalGet($node_uri, [], $hal_json_accept_header);
$this->drupalGet($node_url_with_hal_json_format);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
$this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/hal+json');
$this->drupalGet($node_uri, [], $hal_json_accept_header);
$this->drupalGet($node_url_with_hal_json_format);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
$this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/hal+json');
// Clear the page cache. After that request a HAL request, followed by an
// ordinary HTML one.
\Drupal::cache('render')->deleteAll();
$this->drupalGet($node_uri, [], $hal_json_accept_header);
$this->drupalGet($node_url_with_hal_json_format);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
$this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/hal+json');
$this->drupalGet($node_uri, [], $hal_json_accept_header);
$this->drupalGet($node_url_with_hal_json_format);
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
$this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/hal+json');
......
......@@ -181,7 +181,7 @@ public function testAutocompleteQuickEdit() {
// Save the entity.
$post = array('nocssjs' => 'true');
$response = $this->drupalPost('quickedit/entity/node/' . $this->node->id(), 'application/json', $post);
$response = $this->drupalPostWithFormat('quickedit/entity/node/' . $this->node->id(), 'json', $post);
$this->assertResponse(200);
// The full node display should now link to all entities, with the new
......
......@@ -12,7 +12,8 @@
use Drupal\block_content\Entity\BlockContent;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\WebTestBase;
......@@ -106,7 +107,7 @@ public function testUserWithoutPermission() {
// Retrieving the metadata should result in an empty 403 response.
$post = array('fields[0]' => 'node/1/body/en/full');
$response = $this->drupalPost('quickedit/metadata', 'application/json', $post);
$response = $this->drupalPostWithFormat(Url::fromRoute('quickedit.metadata'), 'json', $post);
$this->assertIdentical('{"message":""}', $response);
$this->assertResponse(403);
......@@ -114,11 +115,11 @@ public function testUserWithoutPermission() {
// was empty as above, but we need to make sure that malicious users aren't
// able to use any of the other endpoints either.
$post = array('editors[0]' => 'form') + $this->getAjaxPageStatePostData();
$response = $this->drupalPost('quickedit/attachments', 'application/vnd.drupal-ajax', $post);
$response = $this->drupalPost('quickedit/attachments', '', $post, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']]);
$this->assertIdentical('{}', $response);
$this->assertResponse(403);
$post = array('nocssjs' => 'true') + $this->getAjaxPageStatePostData();
$response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', $post);
$response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', '', $post, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']]);
$this->assertIdentical('{}', $response);
$this->assertResponse(403);
$edit = array();
......@@ -129,11 +130,11 @@ public function testUserWithoutPermission() {
$edit['body[0][value]'] = '<p>Malicious content.</p>';
$edit['body[0][format]'] = 'filtered_html';
$edit['op'] = t('Save');
$response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', $edit);
$response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', '', $edit, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']]);
$this->assertIdentical('{}', $response);
$this->assertResponse(403);
$post = array('nocssjs' => 'true');
$response = $this->drupalPost('quickedit/entity/' . 'node/1', 'application/json', $post);
$response = $this->drupalPostWithFormat('quickedit/entity/' . 'node/1', 'json', $post);
$this->assertIdentical('{"message":""}', $response);
$this->assertResponse(403);
}
......@@ -166,7 +167,7 @@ public function testUserWithPermission() {
// Retrieving the metadata should result in a 200 JSON response.
$htmlPageDrupalSettings = $this->drupalSettings;
$post = array('fields[0]' => 'node/1/body/en/full');
$response = $this->drupalPost('quickedit/metadata', 'application/json', $post);
$response = $this->drupalPostWithFormat('quickedit/metadata', 'json', $post);
$this->assertResponse(200);
$expected = array(
'node/1/body/en/full' => array(
......@@ -239,7 +240,7 @@ public function testUserWithPermission() {
// Save the entity by moving the PrivateTempStore values to entity storage.
$post = array('nocssjs' => 'true');
$response = $this->drupalPost('quickedit/entity/' . 'node/1', 'application/json', $post);
$response = $this->drupalPostWithFormat('quickedit/entity/' . 'node/1', 'json', $post);
$this->assertResponse(200);
$ajax_commands = Json::decode($response);
$this->assertIdentical(1, count($ajax_commands), 'The entity submission HTTP request results in one AJAX command.');
......@@ -293,7 +294,7 @@ public function testUserWithPermission() {
// Save the entity.
$post = array('nocssjs' => 'true');
$response = $this->drupalPost('quickedit/entity/' . 'node/1', 'application/json', $post);
$response = $this->drupalPostWithFormat('quickedit/entity/' . 'node/1', 'json', $post);
$this->assertResponse(200);
$ajax_commands = Json::decode($response);
$this->assertIdentical(1, count($ajax_commands));
......@@ -325,7 +326,7 @@ public function testTitleBaseField() {
// Retrieving the metadata should result in a 200 JSON response.
$htmlPageDrupalSettings = $this->drupalSettings;
$post = array('fields[0]' => 'node/1/title/en/full');
$response = $this->drupalPost('quickedit/metadata', 'application/json', $post);
$response = $this->drupalPostWithFormat('quickedit/metadata', 'json', $post);
$this->assertResponse(200);
$expected = array(
'node/1/title/en/full' => array(
......@@ -383,7 +384,7 @@ public function testTitleBaseField() {
// Save the entity by moving the PrivateTempStore values to entity storage.
$post = array('nocssjs' => 'true');
$response = $this->drupalPost('quickedit/entity/' . 'node/1', 'application/json', $post);
$response = $this->drupalPostWithFormat('quickedit/entity/' . 'node/1', 'json', $post);
$this->assertResponse(200);
$ajax_commands = Json::decode($response);
$this->assertIdentical(1, count($ajax_commands), 'The entity submission HTTP request results in one AJAX command.');
......
......@@ -266,10 +266,13 @@ public function collectRoutes(RouteCollection $collection) {
$route->setMethods(['GET']);
// Format as a string using pipes as a delimiter.
$requirements['_format'] = implode('|', $style_plugin->getFormats());
// Add the new requirements to the route.
$route->addRequirements($requirements);
if ($formats = $style_plugin->getFormats()) {
// Allow a REST Export View to be returned with an HTML-only accept
// format. That allows browsers or other non-compliant systems to access
// the view, as it is unlikely to have a conflicting HTML representation
// anyway.
$route->setRequirement('_format', implode('|', $formats + ['html']));
}
}
}
......
......@@ -146,11 +146,7 @@ public function render() {
* An array of formats.
*/
public function getFormats() {
if (!empty($this->options['formats'])) {
return $this->options['formats'];
}
return $this->formats;
return $this->options['formats'];
}
}
......@@ -38,7 +38,7 @@ public function testRead() {
$entity->save();
// Try to read the resource as an anonymous user, which should not work.
$this->httpRequest($entity->urlInfo(), 'GET', NULL, $this->defaultMimeType);
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$this->assertResponse('401', 'HTTP response code is 401 when the request is not authenticated and the user is anonymous.');
$this->assertRaw(json_encode(['message' => 'A fatal error occurred: No authentication credentials provided.']));
......@@ -55,7 +55,7 @@ public function testRead() {
// Try to read the resource with session cookie authentication, which is
// not enabled and should not work.
$this->httpRequest($entity->urlInfo(), 'GET', NULL, $this->defaultMimeType);
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$this->assertResponse('403', 'HTTP response code is 403 when the request was authenticated by the wrong authentication provider.');
// Ensure that cURL settings/headers aren't carried over to next request.
......@@ -63,7 +63,7 @@ public function testRead() {
// Now read it with the Basic authentication which is enabled and should
// work.
$this->basicAuthGet($entity->urlInfo(), $account->getUsername(), $account->pass_raw, $this->defaultMimeType);
$this->basicAuthGet($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), $account->getUsername(), $account->pass_raw);
$this->assertResponse('200', 'HTTP response code is 200 for successfully authenticated requests.');
$this->curlClose();
}
......
......@@ -51,14 +51,14 @@ public function testNodes() {
$node = $this->entityCreate('node');
$node->save();
$this->httpRequest($node->urlInfo(), 'GET', NULL, $this->defaultMimeType);
$this->httpRequest($node->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$this->assertResponse(200);
$this->assertHeader('Content-type', $this->defaultMimeType);
// Also check that JSON works and the routing system selects the correct
// REST route.
$this->enableService('entity:node', 'GET', 'json');
$this->httpRequest($node->urlInfo(), 'GET', NULL, 'application/json');
$this->httpRequest($node->urlInfo()->setRouteParameter('_format', 'json'), 'GET');
$this->assertResponse(200);
$this->assertHeader('Content-type', 'application/json');
......
......@@ -35,14 +35,14 @@ public function testConfigChangePageCache() {
$entity = $this->entityCreate('entity_test');
$entity->save();
// Read it over the REST API.
$this->httpRequest($entity->urlInfo(), 'GET', NULL, $this->defaultMimeType);
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'MISS');
$this->assertCacheTag('config:rest.settings');
$this->assertCacheTag('entity_test:1');
// Read it again, should be page-cached now.
$this->httpRequest($entity->urlInfo(), 'GET', NULL, $this->defaultMimeType);
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'HIT');
$this->assertCacheTag('config:rest.settings');
......@@ -51,7 +51,7 @@ public function testConfigChangePageCache() {
// Trigger a config save which should clear the page cache, so we should get
// a cache miss now for the same request.
$this->config('rest.settings')->save();
$this->httpRequest($entity->urlInfo(), 'GET', NULL, $this->defaultMimeType);
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'MISS');
$this->assertCacheTag('config:rest.settings');
......
......@@ -8,6 +8,7 @@
namespace Drupal\rest\Tests;
use Drupal\Component\Serialization\Json;