Skip to content

Commit 7a47ee0

Browse files
amitaibuMateu Aguiló Bosch
authored and
Mateu Aguiló Bosch
committed
Allow rendeting public field with Field API formatter.
1 parent 2e4349b commit 7a47ee0

6 files changed

+201
-87
lines changed

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ language: php
22

33
php:
44
- 5.3
5-
- 5.5
65

76
mysql:
87
database: restful

plugins/restful/RestfulEntityBase.php

+93-31
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ abstract class RestfulEntityBase extends \RestfulDataProviderEFQ implements \Res
3030
* content. This can be used for example on a text field with filtered text
3131
* input format where we would need to do $wrapper->body->value->value().
3232
* Defaults to FALSE.
33+
* - "formatter": Used for rendering the value of a configurable field using
34+
* Drupal field API's formatter. The value is the $display value that is
35+
* passed to field_view_field().
3336
* - "wrapper_method": The wrapper's method name to perform on the field.
3437
* This can be used for example to get the entity label, by setting the
3538
* value to "label". Defaults to "value".
@@ -302,49 +305,29 @@ public function viewEntity($entity_id) {
302305
else {
303306
// Exposing an entity field.
304307
$property = $info['property'];
305-
306308
$sub_wrapper = $info['wrapper_method_on_entity'] ? $wrapper : $wrapper->{$property};
307309

308310
// Check user has access to the property.
309311
if ($property && !$this->checkPropertyAccess('view', $public_field_name, $sub_wrapper, $wrapper)) {
310312
continue;
311313
}
312314

313-
$method = $info['wrapper_method'] ? $info['wrapper_method'] : NULL;
314-
$resource = $info['resource'] ? $info['resource'] : NULL;
315-
316-
if ($sub_wrapper instanceof EntityListWrapper) {
317-
// Multiple value.
318-
foreach ($sub_wrapper as $item_wrapper) {
319-
if ($info['sub_property'] && $item_wrapper->value()) {
320-
$item_wrapper = $item_wrapper->{$info['sub_property']};
321-
}
322-
323-
if ($resource) {
324-
if ($value_from_resource = $this->getValueFromResource($item_wrapper, $property, $resource, $public_field_name, $wrapper->getIdentifier())) {
325-
$value[] = $value_from_resource;
326-
}
315+
if (empty($info['formatter'])) {
316+
if ($sub_wrapper instanceof EntityListWrapper) {
317+
// Multiple values.
318+
foreach ($sub_wrapper as $item_wrapper) {
319+
$value[] = $this->getValueFromProperty($wrapper, $item_wrapper, $info, $public_field_name);
327320
}
328-
else {
329-
// Wrapper method.
330-
$value[] = $item_wrapper->{$method}();
331-
}
332-
}
333-
}
334-
else {
335-
// Single value.
336-
if ($info['sub_property'] && $sub_wrapper->value()) {
337-
$sub_wrapper = $sub_wrapper->{$info['sub_property']};
338-
}
339-
340-
if ($resource) {
341-
$value = $this->getValueFromResource($sub_wrapper, $property, $resource, $public_field_name, $wrapper->getIdentifier());
342321
}
343322
else {
344-
// Wrapper method.
345-
$value = $sub_wrapper->{$method}();
323+
// Single value.
324+
$value = $this->getValueFromProperty($wrapper, $sub_wrapper, $info, $public_field_name);
346325
}
347326
}
327+
else {
328+
// Get value from field formatter.
329+
$value = $this->getValueFromFieldFormatter($wrapper, $sub_wrapper, $info);
330+
}
348331
}
349332

350333
if ($value && $info['process_callbacks']) {
@@ -363,6 +346,84 @@ public function viewEntity($entity_id) {
363346
return $values;
364347
}
365348

349+
/**
350+
* Get value from a property.
351+
*
352+
* @param EntityMetadataWrapper $wrapper
353+
* The wrapped entity.
354+
* @param EntityMetadataWrapper $sub_wrapper
355+
* The wrapped property.
356+
* @param array $info
357+
* The public field info array.
358+
* @param $public_field_name
359+
* The field name.
360+
*
361+
* @return mixed
362+
* A single or multiple values.
363+
*/
364+
protected function getValueFromProperty(\EntityMetadataWrapper $wrapper, \EntityMetadataWrapper $sub_wrapper, array $info, $public_field_name) {
365+
$property = $info['property'];
366+
$method = $info['wrapper_method'];
367+
$resource = $info['resource'] ?: NULL;
368+
369+
if ($info['sub_property'] && $sub_wrapper->value()) {
370+
$sub_wrapper = $sub_wrapper->{$info['sub_property']};
371+
}
372+
373+
if ($resource) {
374+
$value = $this->getValueFromResource($sub_wrapper, $property, $resource, $public_field_name, $wrapper->getIdentifier());
375+
}
376+
else {
377+
// Wrapper method.
378+
$value = $sub_wrapper->{$method}();
379+
}
380+
381+
return $value;
382+
}
383+
384+
/**
385+
* Get value from a field rendered by Drupal field API's formatter.
386+
*
387+
* @param EntityMetadataWrapper $wrapper
388+
* The wrapped entity.
389+
* @param EntityMetadataWrapper $sub_wrapper
390+
* The wrapped property.
391+
* @param array $info
392+
* The public field info array.
393+
*
394+
* @return mixed
395+
* A single or multiple values.
396+
*/
397+
protected function getValueFromFieldFormatter(\EntityMetadataWrapper $wrapper, \EntityMetadataWrapper $sub_wrapper, array $info) {
398+
$property = $info['property'];
399+
400+
if (!field_info_field($property)) {
401+
// Property is not a field.
402+
throw new \RestfulServerConfigurationException(format_string('@property is not a configurable field, so it cannot be processed using field API formatter', array('@property' => $property)));
403+
}
404+
405+
// Get values from the formatter.
406+
$output = field_view_field($this->getEntityType(), $wrapper->value(), $property, $info['formatter']);
407+
408+
// Unset the theme, as we just want to get the value from the formatter,
409+
// without the wrapping HTML.
410+
unset($output['#theme']);
411+
412+
413+
if ($sub_wrapper instanceof EntityListWrapper) {
414+
// Multiple values.
415+
foreach (element_children($output) as $delta) {
416+
$value[] = drupal_render($output[$delta]);
417+
}
418+
}
419+
else {
420+
// Single value.
421+
$value = drupal_render($output);
422+
}
423+
424+
return $value;
425+
}
426+
366427
/**
367428
* Get the "target_type" property from an field or property reference.
368429
*
@@ -1174,6 +1235,7 @@ public function getPublicFields() {
11741235
'sub_property' => FALSE,
11751236
'wrapper_method' => 'value',
11761237
'wrapper_method_on_entity' => FALSE,
1238+
'formatter' => FALSE,
11771239
);
11781240

11791241
if ($field = field_info_field($info['property'])) {

restful.info

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,6 @@ files[] = tests/RestfulUpdateEntityTestCase.test
8181
files[] = tests/RestfulSubResourcesCreateEntityTestCase.test
8282
files[] = tests/RestfulUpdateEntityCurlTestCase.test
8383
files[] = tests/RestfulUserLoginCookieTestCase.test
84-
files[] = tests/RestfulViewEntityTestCase.test
8584
files[] = tests/RestfulViewEntityMultiLingualTestCase.test
85+
files[] = tests/RestfulViewEntityTestCase.test
86+
files[] = tests/RestfulViewModeAndFormatterTestCase.test

tests/RestfulViewEntityTestCase.test

+2-54
Original file line numberDiff line numberDiff line change
@@ -266,25 +266,6 @@ class RestfulViewEntityTestCase extends RestfulCurlBaseTestCase {
266266
$this->assertEqual(count(array_filter(array_values($result[0]['images'][0]['styles']))) == 3, 'The image styles are populated.');
267267
}
268268

269-
/**
270-
* Test the view mode integration.
271-
*/
272-
public function testViewModes() {
273-
$handler = restful_get_restful_handler('articles', 1, 7);
274-
$nodes[] = $this->createNodeWithTags();
275-
$nodes[] = $this->createNodeWithTags();
276-
// Make sure to get more than one node to increase coverage on
277-
// \RestfulEntityViewMode.
278-
$results = $handler->get($nodes[0]->nid . ',' . $nodes[1]->nid);
279-
280-
// Make sure that all the fields were mapped.
281-
$this->assertNotNull($results[0]['body']);
282-
$this->assertFalse(empty($results[0]['body']), 'Body is not empty.');
283-
$this->assertNotNull($results[0]['tags']);
284-
$this->assertFalse(empty($results[0]['tags']), 'Tags are not empty.');
285-
$this->assertNotNull($results[0]['image']);
286-
}
287-
288269
/**
289270
* Test the generation of the Vary headers.
290271
*/
@@ -343,7 +324,7 @@ class RestfulViewEntityTestCase extends RestfulCurlBaseTestCase {
343324
* Test the Simple JSON formatter.
344325
*/
345326
public function testSimpleJson() {
346-
$node = $this->createNodeWithTags();
327+
$node = restful_test_create_node_with_tags();
347328
$handler = restful_get_restful_handler('articles', 1, 5);
348329
// Use the simple JSON formatter.
349330
$handler->setPluginKey('formatter', 'json');
@@ -369,7 +350,7 @@ class RestfulViewEntityTestCase extends RestfulCurlBaseTestCase {
369350

370351
// Assert the HATEOAS.
371352
// Create another node for pagination.
372-
$this->createNodeWithTags();
353+
restful_test_create_node_with_tags();
373354
$handler->setRange(1);
374355
$response = $handler->get();
375356
$result = $formatter->format($response);
@@ -380,37 +361,4 @@ class RestfulViewEntityTestCase extends RestfulCurlBaseTestCase {
380361
$this->assertEqual(count($decoded_json['data']), 1, 'The correct number of items was listed.');
381362
$this->assertNotNull($decoded_json['next'], '"Next" property added.');
382363
}
383-
384-
/**
385-
* Helper function that creates a node with taxonomy terms.
386-
*
387-
* @return object
388-
* The created node.
389-
*
390-
* @throws Exception
391-
* @throws FieldException
392-
*/
393-
protected function createNodeWithTags() {
394-
$bundle = 'article';
395-
// Create a node with some tags in it as embeds.
396-
$node = $this->drupalCreateNode(array('type' => $bundle));
397-
398-
$vocabulary = taxonomy_vocabulary_machine_name_load('tags');
399-
400-
// Create a random number of tags for the created node.
401-
for ($index = 0; $index < mt_rand(1, 10); $index++) {
402-
$term = (object) array(
403-
'vid' => $vocabulary->vid,
404-
'name' => $this->randomString(),
405-
);
406-
taxonomy_term_save($term);
407-
$terms[] = $term;
408-
$node->field_tags[LANGUAGE_NONE][$index]['tid'] = $term->tid;
409-
}
410-
node_save($node);
411-
412-
return $node;
413-
414-
}
415-
416364
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/**
4+
* @file
5+
* Contains RestfulViewModeFormatterTestCase
6+
*/
7+
8+
class RestfulViewModeAndFormatterTestCase extends DrupalWebTestCase {
9+
10+
public static function getInfo() {
11+
return array(
12+
'name' => 'View mode and formatter',
13+
'description' => 'Test the integration with entity view mode and field API formatters.',
14+
'group' => 'Restful',
15+
);
16+
}
17+
18+
function setUp() {
19+
parent::setUp('restful_example', 'restful_test');
20+
}
21+
22+
/**
23+
* Test the view mode integration.
24+
*/
25+
public function testViewModeIntegration() {
26+
$handler = restful_get_restful_handler('articles', 1, 7);
27+
$nodes[] = restful_test_create_node_with_tags();
28+
$nodes[] = restful_test_create_node_with_tags();
29+
// Make sure to get more than one node to increase coverage on
30+
// \RestfulEntityViewMode.
31+
$result = $handler->get($nodes[0]->nid . ',' . $nodes[1]->nid);
32+
33+
// Make sure that all the fields were mapped.
34+
$this->assertNotNull($result[0]['body'], 'Body field is populated.');
35+
$this->assertTrue($result[0]['tags'], 'Tags field is populated.');
36+
$this->assertNotNull($result[0]['image'], 'Image field is not NULL.');
37+
}
38+
39+
/**
40+
* Test the field API formatter integration.
41+
*/
42+
public function testFormatterIntegration() {
43+
$handler = restful_get_restful_handler('articles', 1, 5);
44+
45+
// Create node.
46+
$text = 'Some body with long text';
47+
$settings = array(
48+
'type' => 'article',
49+
'body' => array(
50+
LANGUAGE_NONE => array(
51+
array('value' => $text),
52+
)
53+
),
54+
);
55+
$node = $this->drupalCreateNode($settings);
56+
57+
// Field with no formatter.
58+
$result = $handler->get($node->nid);
59+
$this->assertEqual(trim(strip_tags($result[0]['body'])), $text, 'Raw value passed without a formatter.');
60+
61+
// Add formatter settings.
62+
$public_fields = $handler->getPublicFields();
63+
$display = array(
64+
'type' => 'text_summary_or_trimmed',
65+
'settings' => array(
66+
'trim_length' => 10,
67+
),
68+
);
69+
$public_fields['body']['formatter'] = $display;
70+
$handler->setPublicFields($public_fields);
71+
72+
$result = $handler->get($node->nid);
73+
// Core's trim formatter also inclues the opening <p> tag in the calculation
74+
// of number of chars.
75+
$this->assertEqual($result[0]['body'], '<p>Some bo</p>', 'Value trimmed by formatter.');
76+
}
77+
}

tests/modules/restful_test/restful_test.module

+27
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,33 @@ function restful_test_create_vocabulary_and_terms($machine_name = 'test_vocab',
390390
return $vid;
391391
}
392392

393+
/**
394+
* Helper function; Create a node with taxonomy terms.
395+
*
396+
* @return object
397+
* The saved node.
398+
*/
399+
function restful_test_create_node_with_tags() {
400+
$values = array('type' => 'article');
401+
$node = entity_create('node', $values);
402+
403+
$vocabulary = taxonomy_vocabulary_machine_name_load('tags');
404+
405+
// Create a random number of tags for the created node.
406+
for ($index = 0; $index < mt_rand(1, 10); $index++) {
407+
$term = (object) array(
408+
'vid' => $vocabulary->vid,
409+
'name' => 'term ' . $vocabulary->vid . '::' . $index,
410+
);
411+
taxonomy_term_save($term);
412+
$terms[] = $term;
413+
$node->field_tags[LANGUAGE_NONE][$index]['tid'] = $term->tid;
414+
}
415+
node_save($node);
416+
417+
return $node;
418+
}
419+
393420
/**
394421
* Instead of implementing hook_schema use this helper function.
395422
*

0 commit comments

Comments
 (0)