Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
project
drupal
Commits
73f39734
Commit
73f39734
authored
Feb 27, 2013
by
Dries
Browse files
Issue
#1850734
by klausi: Make serialization formats configurable.
parent
00dfa022
Changes
11
Hide whitespace changes
Inline
Side-by-side
core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php
View file @
73f39734
...
...
@@ -54,15 +54,30 @@ public function __construct(ResourcePluginManager $manager, ConfigFactory $confi
public
function
dynamicRoutes
(
RouteBuildEvent
$event
)
{
$collection
=
$event
->
getRouteCollection
();
$enabled_resources
=
$this
->
config
->
get
(
'rest.settings'
)
->
load
()
->
get
(
'resources'
);
$resources
=
$this
->
config
->
get
(
'rest.settings'
)
->
load
()
->
get
(
'resources'
);
if
(
$resources
&&
$enabled
=
array_intersect_key
(
$this
->
manager
->
getDefinitions
(),
$resources
))
{
foreach
(
$enabled
as
$key
=>
$resource
)
{
$plugin
=
$this
->
manager
->
getInstance
(
array
(
'id'
=>
$key
));
// Iterate over all enabled resource plugins.
foreach
(
$enabled_resources
as
$id
=>
$enabled_methods
)
{
$plugin
=
$this
->
manager
->
getInstance
(
array
(
'id'
=>
$id
));
foreach
(
$plugin
->
routes
()
as
$name
=>
$route
)
{
foreach
(
$plugin
->
routes
()
as
$name
=>
$route
)
{
$method
=
$route
->
getRequirement
(
'_method'
);
// Only expose routes where the method is enabled in the configuration.
if
(
$method
&&
isset
(
$enabled_methods
[
$method
]))
{
$route
->
setRequirement
(
'_access_rest_csrf'
,
'TRUE'
);
$collection
->
add
(
"rest.
$name
"
,
$route
);
// If the array of configured format restrictions is empty for a
// method always add the route.
if
(
empty
(
$enabled_methods
[
$method
]))
{
$collection
->
add
(
"rest.
$name
"
,
$route
);
continue
;
}
// If there is no format requirement or if it matches the
// configuration also add the route.
$format_requirement
=
$route
->
getRequirement
(
'_format'
);
if
(
!
$format_requirement
||
isset
(
$enabled_methods
[
$method
][
$format_requirement
]))
{
$collection
->
add
(
"rest.
$name
"
,
$route
);
}
}
}
}
...
...
core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
View file @
73f39734
...
...
@@ -26,15 +26,11 @@ abstract class ResourceBase extends PluginBase implements ResourceInterface {
public
function
permissions
()
{
$permissions
=
array
();
$definition
=
$this
->
getDefinition
();
foreach
(
$this
->
request
Methods
()
as
$method
)
{
foreach
(
$this
->
available
Methods
()
as
$method
)
{
$lowered_method
=
strtolower
(
$method
);
// Only expose permissions where the HTTP request method exists on the
// plugin.
if
(
method_exists
(
$this
,
$lowered_method
))
{
$permissions
[
"restful
$lowered_method
$this->plugin_id
"
]
=
array
(
'title'
=>
t
(
'Access @method on %label resource'
,
array
(
'@method'
=>
$method
,
'%label'
=>
$definition
[
'label'
])),
);
}
$permissions
[
"restful
$lowered_method
$this->plugin_id
"
]
=
array
(
'title'
=>
t
(
'Access @method on %label resource'
,
array
(
'@method'
=>
$method
,
'%label'
=>
$definition
[
'label'
])),
);
}
return
$permissions
;
}
...
...
@@ -47,38 +43,44 @@ public function routes() {
$path_prefix
=
strtr
(
$this
->
plugin_id
,
':'
,
'/'
);
$route_name
=
strtr
(
$this
->
plugin_id
,
':'
,
'.'
);
$methods
=
$this
->
request
Methods
();
$methods
=
$this
->
available
Methods
();
foreach
(
$methods
as
$method
)
{
$lower_method
=
strtolower
(
$method
);
// Only expose routes where the HTTP request method exists on the plugin.
if
(
method_exists
(
$this
,
$lower_method
))
{
$route
=
new
Route
(
"/
$path_prefix
/
{
id
}
"
,
array
(
'_controller'
=>
'Drupal\rest\RequestHandler::handle'
,
// Pass the resource plugin ID along as default property.
'_plugin'
=>
$this
->
plugin_id
,
),
array
(
// The HTTP method is a requirement for this route.
'_method'
=>
$method
,
'_permission'
=>
"restful
$lower_method
$this->plugin_id
"
,
));
$route
=
new
Route
(
"/
$path_prefix
/
{
id
}
"
,
array
(
'_controller'
=>
'Drupal\rest\RequestHandler::handle'
,
// Pass the resource plugin ID along as default property.
'_plugin'
=>
$this
->
plugin_id
,
),
array
(
// The HTTP method is a requirement for this route.
'_method'
=>
$method
,
'_permission'
=>
"restful
$lower_method
$this->plugin_id
"
,
));
switch
(
$method
)
{
case
'POST'
:
// POST routes do not require an ID in the URL path.
$route
->
setPattern
(
"/
$path_prefix
"
);
$route
->
addDefaults
(
array
(
'id'
=>
NULL
));
break
;
switch
(
$method
)
{
case
'POST'
:
// POST routes do not require an ID in the URL path.
$route
->
setPattern
(
"/
$path_prefix
"
);
$route
->
addDefaults
(
array
(
'id'
=>
NULL
));
$collection
->
add
(
"
$route_name
.
$method
"
,
$route
);
break
;
case
'GET'
:
case
'HEAD'
:
// Restrict GET and HEAD requests to the media type specified in the
// HTTP Accept headers.
// @todo Replace hard coded format here with available formats.
$route
->
addRequirements
(
array
(
'_format'
=>
'drupal_jsonld'
));
break
;
}
case
'GET'
:
case
'HEAD'
:
// Restrict GET and HEAD requests to the media type specified in the
// HTTP Accept headers.
$formats
=
drupal_container
()
->
getParameter
(
'serializer.formats'
);
foreach
(
$formats
as
$format_name
=>
$label
)
{
// Expose one route per available format.
//$format_route = new Route($route->getPattern(), $route->getDefaults(), $route->getRequirements());
$format_route
=
clone
$route
;
$format_route
->
addRequirements
(
array
(
'_format'
=>
$format_name
));
$collection
->
add
(
"
$route_name
.
$method
.
$format_name
"
,
$format_route
);
}
break
;
$collection
->
add
(
"
$route_name
.
$method
"
,
$route
);
default
:
$collection
->
add
(
"
$route_name
.
$method
"
,
$route
);
break
;
}
}
...
...
@@ -95,7 +97,7 @@ public function routes() {
* The list of allowed HTTP request method strings.
*/
protected
function
requestMethods
()
{
return
drupal_map_assoc
(
array
(
return
array
(
'HEAD'
,
'GET'
,
'POST'
,
...
...
@@ -105,6 +107,21 @@ protected function requestMethods() {
'OPTIONS'
,
'CONNECT'
,
'PATCH'
,
));
);
}
/**
* Implements ResourceInterface::availableMethods().
*/
public
function
availableMethods
()
{
$methods
=
$this
->
requestMethods
();
$available
=
array
();
foreach
(
$methods
as
$method
)
{
// Only expose methods where the HTTP request method exists on the plugin.
if
(
method_exists
(
$this
,
strtolower
(
$method
)))
{
$available
[]
=
$method
;
}
}
return
$available
;
}
}
core/modules/rest/lib/Drupal/rest/Plugin/ResourceInterface.php
View file @
73f39734
...
...
@@ -36,4 +36,12 @@ public function routes();
*/
public
function
permissions
();
/**
* Returns the available HTTP request methods on this plugin.
*
* @return array
* The list of supported methods. Example: array('GET', 'POST', 'PATCH').
*/
public
function
availableMethods
();
}
core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
View file @
73f39734
...
...
@@ -50,12 +50,7 @@ public function get($id = NULL) {
$record
=
db_query
(
"SELECT * FROM
{
watchdog
}
WHERE wid = :wid"
,
array
(
':wid'
=>
$id
))
->
fetchObject
();
if
(
!
empty
(
$record
))
{
// Serialization is done here, so we indicate with NULL that there is no
// subsequent serialization necessary.
$response
=
new
ResourceResponse
(
NULL
,
200
,
array
(
'Content-Type'
=>
'application/vnd.drupal.ld+json'
));
// @todo remove hard coded format here.
$response
->
setContent
(
drupal_json_encode
(
$record
));
return
$response
;
return
new
ResourceResponse
((
array
)
$record
);
}
}
throw
new
NotFoundHttpException
(
t
(
'Log entry with ID @id was not found'
,
array
(
'@id'
=>
$id
)));
...
...
core/modules/rest/lib/Drupal/rest/RequestHandler.php
View file @
73f39734
...
...
@@ -12,6 +12,7 @@
use
Symfony\Component\HttpFoundation\Request
;
use
Symfony\Component\HttpFoundation\Response
;
use
Symfony\Component\HttpKernel\Exception\HttpException
;
use
Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException
;
use
Symfony\Component\Serializer\Exception\UnexpectedValueException
;
/**
...
...
@@ -43,17 +44,28 @@ public function handle(Request $request, $id = NULL) {
$received
=
$request
->
getContent
();
$unserialized
=
NULL
;
if
(
!
empty
(
$received
))
{
$definition
=
$resource
->
getDefinition
();
$class
=
$definition
[
'serialization_class'
];
try
{
// @todo Replace the format here with something we get from the HTTP
// Content-type header. See http://drupal.org/node/1850704
$unserialized
=
$serializer
->
deserialize
(
$received
,
$class
,
'drupal_jsonld'
);
$format
=
$request
->
getContentType
();
// Only allow serialization formats that are explicitly configured. If no
// formats are configured allow all and hope that the serializer knows the
// format. If the serializer cannot handle it an exception will be thrown
// that bubbles up to the client.
$config
=
$this
->
container
->
get
(
'config.factory'
)
->
get
(
'rest.settings'
)
->
get
(
'resources'
);
$enabled_formats
=
$config
[
$plugin
][
$request
->
getMethod
()];
if
(
empty
(
$enabled_formats
)
||
isset
(
$enabled_formats
[
$format
]))
{
$definition
=
$resource
->
getDefinition
();
$class
=
$definition
[
'serialization_class'
];
try
{
$unserialized
=
$serializer
->
deserialize
(
$received
,
$class
,
$format
);
}
catch
(
UnexpectedValueException
$e
)
{
$error
[
'error'
]
=
$e
->
getMessage
();
$content
=
$serializer
->
serialize
(
$error
,
$format
);
return
new
Response
(
$content
,
400
,
array
(
'Content-Type'
=>
$request
->
getMimeType
(
$format
)));
}
}
catch
(
UnexpectedValueException
$e
)
{
$error
[
'error'
]
=
$e
->
getMessage
();
$content
=
$serializer
->
serialize
(
$error
,
'drupal_jsonld'
);
return
new
Response
(
$content
,
400
,
array
(
'Content-Type'
=>
'application/vnd.drupal.ld+json'
));
else
{
throw
new
UnsupportedMediaTypeHttpException
();
}
}
...
...
@@ -63,21 +75,26 @@ public function handle(Request $request, $id = NULL) {
}
catch
(
HttpException
$e
)
{
$error
[
'error'
]
=
$e
->
getMessage
();
$content
=
$serializer
->
serialize
(
$error
,
'drupal_jsonld'
);
$format
=
$request
->
attributes
->
get
(
RouteObjectInterface
::
ROUTE_OBJECT
)
->
getRequirement
(
'_format'
)
?:
'drupal_jsonld'
;
$content
=
$serializer
->
serialize
(
$error
,
$format
);
// Add the default content type, but only if the headers from the
// exception have not specified it already.
$headers
=
$e
->
getHeaders
()
+
array
(
'Content-Type'
=>
'application/vnd.drupal.ld+json'
);
$headers
=
$e
->
getHeaders
()
+
array
(
'Content-Type'
=>
$request
->
getMimeType
(
$format
)
);
return
new
Response
(
$content
,
$e
->
getStatusCode
(),
$headers
);
}
// Serialize the outgoing data for the response, if available.
$data
=
$response
->
getResponseData
();
if
(
$data
!=
NULL
)
{
// @todo Replace the format here with something we get from the HTTP
// Accept headers. See http://drupal.org/node/1833440
$output
=
$serializer
->
serialize
(
$data
,
'drupal_jsonld'
);
// All REST routes are restricted to exactly one format, so instead of
// parsing it out of the Accept headers again we can simply retrieve the
// format requirement. If there is no format associated just pick Drupal
// JSON-LD.
$format
=
$request
->
attributes
->
get
(
RouteObjectInterface
::
ROUTE_OBJECT
)
->
getRequirement
(
'_format'
)
?:
'drupal_jsonld'
;
$output
=
$serializer
->
serialize
(
$data
,
$format
);
$response
->
setContent
(
$output
);
$response
->
headers
->
set
(
'Content-Type'
,
'application/vnd.drupal.ld+json'
);
$response
->
headers
->
set
(
'Content-Type'
,
$request
->
getMimeType
(
$format
)
);
}
return
$response
;
}
...
...
core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
View file @
73f39734
...
...
@@ -38,7 +38,7 @@ public function testCreate() {
// entity types here as well.
$entity_type
=
'entity_test'
;
$this
->
enableService
(
'entity:'
.
$entity_type
);
$this
->
enableService
(
'entity:'
.
$entity_type
,
'POST'
);
// Create a user account that has the required permissions to create
// resources via the web API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful post entity:'
.
$entity_type
));
...
...
core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
View file @
73f39734
...
...
@@ -36,7 +36,7 @@ public function testDelete() {
// Define the entity types we want to test.
$entity_types
=
array
(
'entity_test'
,
'node'
,
'user'
);
foreach
(
$entity_types
as
$entity_type
)
{
$this
->
enableService
(
'entity:'
.
$entity_type
);
$this
->
enableService
(
'entity:'
.
$entity_type
,
'DELETE'
);
// Create a user account that has the required permissions to delete
// resources via the web API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful delete entity:'
.
$entity_type
));
...
...
core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
View file @
73f39734
...
...
@@ -156,18 +156,24 @@ protected function entityValues($entity_type) {
* @param string|FALSE $resource_type
* The resource type that should get web API enabled or FALSE to disable all
* resource types.
* @param string $method
* The HTTP method to enable, e.g. GET, POST etc.
* @param string $format
* (Optional) The serialization format, e.g. jsonld.
*/
protected
function
enableService
(
$resource_type
)
{
protected
function
enableService
(
$resource_type
,
$method
=
'GET'
,
$format
=
NULL
)
{
// Enable web API for this entity type.
$config
=
config
(
'rest.settings'
);
$settings
=
array
();
if
(
$resource_type
)
{
$config
->
set
(
'resources'
,
array
(
$resource_type
=>
$resource_type
,
));
}
else
{
$config
->
set
(
'resources'
,
array
());
if
(
$format
)
{
$settings
[
$resource_type
][
$method
][
$format
]
=
'TRUE'
;
}
else
{
$settings
[
$resource_type
][
$method
]
=
array
();
}
}
$config
->
set
(
'resources'
,
$settings
);
$config
->
save
();
// Rebuild routing cache, so that the web API paths are available.
...
...
core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
View file @
73f39734
...
...
@@ -38,7 +38,7 @@ public function testRead() {
// Define the entity types we want to test.
$entity_types
=
array
(
'entity_test'
);
foreach
(
$entity_types
as
$entity_type
)
{
$this
->
enableService
(
'entity:'
.
$entity_type
);
$this
->
enableService
(
'entity:'
.
$entity_type
,
'GET'
);
// Create a user account that has the required permissions to delete
// resources via the web API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful get entity:'
.
$entity_type
));
...
...
@@ -57,12 +57,8 @@ public function testRead() {
$this
->
assertEqual
(
$data
[
'uuid'
][
LANGUAGE_DEFAULT
][
0
][
'value'
],
$entity
->
uuid
(),
'Entity UUID is correct'
);
// Try to read the entity with an unsupported mime format.
// Because the matcher checks mime type first, then method, this will hit
// zero viable routes on the method. If the mime matcher wasn't working,
// we would still find an existing GET route with the wrong format. That
// means this is a valid functional test for mime-matching.
$response
=
$this
->
httpRequest
(
'entity/'
.
$entity_type
.
'/'
.
$entity
->
id
(),
'GET'
,
NULL
,
'application/wrongformat'
);
$this
->
assertResponse
(
4
0
5
);
$this
->
assertResponse
(
4
1
5
);
// Try to read an entity that does not exist.
$response
=
$this
->
httpRequest
(
'entity/'
.
$entity_type
.
'/9999'
,
'GET'
,
NULL
,
'application/vnd.drupal.ld+json'
);
...
...
core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php
View file @
73f39734
...
...
@@ -38,7 +38,7 @@ public function testPatchUpdate() {
// entity types here as well.
$entity_type
=
'entity_test'
;
$this
->
enableService
(
'entity:'
.
$entity_type
);
$this
->
enableService
(
'entity:'
.
$entity_type
,
'PATCH'
);
// Create a user account that has the required permissions to create
// resources via the web API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful patch entity:'
.
$entity_type
));
...
...
@@ -103,7 +103,7 @@ public function testPutUpdate() {
// entity types here as well.
$entity_type
=
'entity_test'
;
$this
->
enableService
(
'entity:'
.
$entity_type
);
$this
->
enableService
(
'entity:'
.
$entity_type
,
'PUT'
);
// Create a user account that has the required permissions to create
// resources via the web API.
$account
=
$this
->
drupalCreateUser
(
array
(
'restful put entity:'
.
$entity_type
));
...
...
core/modules/rest/rest.admin.inc
View file @
73f39734
...
...
@@ -34,7 +34,10 @@ function rest_admin_form($form, &$form_state) {
}
asort
(
$entity_resources
);
asort
(
$other_resources
);
$enabled_resources
=
config
(
'rest.settings'
)
->
get
(
'resources'
)
?:
array
();
$config
=
config
(
'rest.settings'
)
->
get
(
'resources'
)
?:
array
();
// Strip out the nested method configuration, we are only interested in the
// plugin IDs of the resources.
$enabled_resources
=
drupal_map_assoc
(
array_keys
(
$config
));
// Render the output using table_select().
$header
=
array
(
...
...
@@ -79,9 +82,19 @@ function rest_admin_form($form, &$form_state) {
* Form submission handler for rest_admin_form().
*/
function
rest_admin_form_submit
(
$form
,
&
$form_state
)
{
$resources
=
array_filter
(
$form_state
[
'values'
][
'entity_resources'
]);
$
enabled_
resources
=
array_filter
(
$form_state
[
'values'
][
'entity_resources'
]);
if
(
!
empty
(
$form_state
[
'values'
][
'other_resources'
]))
{
$resources
+=
array_filter
(
$form_state
[
'values'
][
'other_resources'
]);
$enabled_resources
+=
array_filter
(
$form_state
[
'values'
][
'other_resources'
]);
}
$resources
=
array
();
$plugin_manager
=
drupal_container
()
->
get
(
'plugin.manager.rest'
);
// Enable all methods and all formats for each selected resource.
foreach
(
$enabled_resources
as
$resource
)
{
$plugin
=
$plugin_manager
->
getInstance
(
array
(
'id'
=>
$resource
));
$methods
=
$plugin
->
availableMethods
();
// An empty array means all formats are allowed for a method.
$resources
[
$resource
]
=
array_fill_keys
(
$methods
,
array
());
}
$config
=
config
(
'rest.settings'
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment