Requesting a node that has multiple values in a field works. Creating a node with multi value field (that is not a reference) field doesn't work.

If this already works, then it's not documented how to send the values.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

lokapujya’s picture

Version: 7.x-2.1 » 7.x-2.x-dev
Assigned: Unassigned » lokapujya
Status: Active » Needs review
FileSize
2.56 KB

Possibly, this belongs to the enity API queue; I'm not sure yet. The modules are closely related.

Attaching a test to demonstrate the problem.

lokapujya’s picture

Title: Can this work with multi value fields? » Allow creating nodes with multi-value fields.

Status: Needs review » Needs work

The last submitted patch, 1: 2301237-1.patch, failed testing.

lokapujya’s picture

Project: RESTful Web Services » Entity API
Version: 7.x-2.x-dev » 7.x-1.x-dev
Component: Code » Core integration
Status: Needs work » Needs review
FileSize
978 bytes

If this passes, then entity API is probably off the hook. Here, I'm testing the part of entity API that is used, when making the REST call, to create the node with a multi-value field.

lokapujya’s picture

Project: Entity API »
Version: 7.x-1.x-dev »
Component: Core integration » Code

Looks like Entity API is ok.

I think I am sending the multi-value field correctly.
Creating the multi-value array:

<?php
$multivaluefield = array($this->randomName(8), $this->randomName(8), $this->randomName(8), $this->randomName(8));
?>

Setting the field:

<?php $this->field_name => $multivaluefield,
?>
lokapujya’s picture

FileSize
2.56 KB
868 bytes

Fixing the test. Previous patches changes were affecting the test that follows the new test.

lokapujya’s picture

Assigned: lokapujya » Unassigned
Status: Needs review » Closed (works as designed)

Copied the example from testResourceArray within the REST tests. Instead of id and resource, just used the value; it seems to be working.

lokapujya’s picture

Assigned: Unassigned » lokapujya
Category: Support request » Bug report
Issue summary: View changes
Status: Closed (works as designed) » Needs review
FileSize
2.85 KB
1.63 KB

Re-opening since the test still fails.
Here is the test-only patch.

lokapujya’s picture

FileSize
3.25 KB
405 bytes

Here is the fix.

lokapujya’s picture

Project: » RESTful Web Services
Version: » 7.x-2.x-dev
lokapujya’s picture

Status: Needs review » Needs work
lokapujya’s picture

Status: Needs work » Needs review

test me.

The last submitted patch, 8: 2301237-8.patch, failed testing.

nicxvan’s picture

This patch seems to work.

The format I had to use for the array was as follows:

'field_name' => array(
array( 'value1' ),
array( 'value2')
)

Works for text fields. I am going to test it on image fields now.

nicxvan’s picture

I was not able to get this working with images in conjunction with the following patch: https://www.drupal.org/node/1819594

Adding a single image works like this:
'field_image' =>
'data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg')

encode file just base64 encodes the image from the local file.

Attempted the following three structures with no success:

'field_image' =>
'data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg'),
'data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg')
ERROR: 406 Not Acceptable: Unknown data property 0.

'field_image' => array(
'data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg'),
'data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg')
)
ERROR: Warning: preg_split() expects parameter 2 to be string, array given in restws_image_support()

'field_image' => array(
array('data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg')),
array('data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg'))
)
ERROR: 406 Not Acceptable: Invalid data value given. Be sure it matches the required data type and format.

I think the third format is the closest to what we need. The second error is from the code in the patch in the attached issue.
The first I think is the most wrong.

nicxvan’s picture

Just a note, a single image on the multivalue image field works fine.

nicxvan’s picture

With both patches the following script will add an image to the restws site assuming you point encode_file at a valid file.
(you need to add the website and the username and password along with typical restws permissions and username structure.

// LOGIN AND GET CSRF TOKEN
/*
    * Server REST - user.login
*/
// Website
$website = '';  // This will change depending on the site we are updating
// REST Server URL
$login_url = $website.'/restws/session/token';  // Do not change this variable
// User data
$user = '';  //  This can change depending on the site we are updating
$pass = ''; // I will send the password by text

// Create Node Array
$node_info = array(
      'title'     => 'removing fields again', //* this is the title of the node
      'author'    => 1,  //* Required Drupal field always set to 1
      'type'      => 'article', //* Required internal Drupal field 
	  'body' => array(  // This is the short description
        'value' => 'hi there!', // what is shown
        'summary' => 'hi there',  // not used but internal and necessary, it's a summary
        'format' => 'filtered_html' // Restricted html set
      ),
      'status'    => 1, // This determine whether the item is published or not 0 = hide
	  'field_image' => 	  array('data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg'))
	  // Adds an image, you can encode the file as base 64 before uploading it
);


$json = json_encode($node_info);
	  $command = add_node($json);


function add_node($post_data){
	global $website;
	global $login_url;
	global $user;
	global $pass;
	
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $login_url);
	curl_setopt($curl, CURLOPT_USERPWD, "$user:$pass");
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($curl, CURLOPT_HEADER, 1);
	// Parse the header to get the token and cookie
	$result = curl_exec($curl);
	$token = substr($result, (strlen($result) - 43));
	preg_match('/^Set-Cookie:\s*([^;]*)/mi', $result, $m);
	$cookie = $m[1];

	curl_setopt($curl, CURLOPT_URL, "$website"."/node");
	curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-CSRF-Token: ' . $token));
	curl_setopt($curl, CURLOPT_POST, 1); // Do a regular HTTP POST
	curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
	curl_setopt($curl, CURLOPT_COOKIE, "$cookie");

	$response = curl_exec($curl); // execute and get response
	echo $response;
	
	curl_close ($curl);

	unset($curl);

return;
}


function encode_file ($file_location) {
$content = file_get_contents($file_location);

$encoded_file = base64_encode($content);

return $encoded_file;
}
nicxvan’s picture

This is the same as above except SHOULD do the multivalue image with the same caveats

// LOGIN AND GET CSRF TOKEN
/*
    * Server REST - user.login
*/
// Website
$website = '';  // This will change depending on the site we are updating
// REST Server URL
$login_url = $website.'/restws/session/token';  // Do not change this variable
// User data
$user = '';  //  This can change depending on the site we are updating
$pass = ''; // I will send the password by text

// Create Node Array
$node_info = array(
      'title'     => 'removing fields again', //* this is the title of the node
      'author'    => 1,  //* Required Drupal field always set to 1
      'type'      => 'article', //* Required internal Drupal field 
	  'body' => array(  // This is the short description
        'value' => 'hi there!', // what is shown
        'summary' => 'hi there',  // not used but internal and necessary, it's a summary
        'format' => 'filtered_html' // Restricted html set
      ),
      'status'    => 1, // This determine whether the item is published or not 0 = hide
	  'field_image' => array(
	  array('data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg')),
	  array('data:image/jpeg;base64,'.encode_file('../sites/default/files/pengoothumb.jpg'))
	  )
	  // Adds an image, you can encode the file as base 64 before uploading it
);


$json = json_encode($node_info);
	  $command = add_node($json);


function add_node($post_data){
	global $website;
	global $login_url;
	global $user;
	global $pass;
	
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $login_url);
	curl_setopt($curl, CURLOPT_USERPWD, "$user:$pass");
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($curl, CURLOPT_HEADER, 1);
	// Parse the header to get the token and cookie
	$result = curl_exec($curl);
	$token = substr($result, (strlen($result) - 43));
	preg_match('/^Set-Cookie:\s*([^;]*)/mi', $result, $m);
	$cookie = $m[1];

	curl_setopt($curl, CURLOPT_URL, "$website"."/node");
	curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-CSRF-Token: ' . $token));
	curl_setopt($curl, CURLOPT_POST, 1); // Do a regular HTTP POST
	curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
	curl_setopt($curl, CURLOPT_COOKIE, "$cookie");

	$response = curl_exec($curl); // execute and get response
	echo $response;
	
	curl_close ($curl);

	unset($curl);

return;
}


function encode_file ($file_location) {
$content = file_get_contents($file_location);

$encoded_file = base64_encode($content);

return $encoded_file;
}
lokapujya’s picture

Since this patch gets multi-value text fields working, I'm moving the multi-value images to a new issue: #2320083: Allow creating nodes with multi-value image fields.

nicxvan’s picture

Multivalue images work with this: format is: 'field_image' => array(
'data:image/jpeg;base64,'.encode_file('../sites/default/files/penguinthumb.jpg'),
'data:image/jpeg;base64,'.encode_file('../sites/default/files/tulipthumb.jpg')
)

encode_file is a function that just base 64 encodes the images referenced. The function is copied below for clarity THIS IS NOT PART OF RESTWS!!!

function encode_file ($file_location) {
$content = file_get_contents($file_location);

$encoded_file = base64_encode($content);

return $encoded_file;
}

Only works with the following patches: https://www.drupal.org/node/1819594

lokapujya’s picture

Assigned: lokapujya » Unassigned

lokapujya queued 9: 2301237-9.patch for re-testing.

lokapujya queued 9: 2301237-9.patch for re-testing.

lokapujya queued 9: 2301237-9.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 9: 2301237-9.patch, failed testing.

lokapujya’s picture

The errors don't seem to have anything to do with this patch.

lokapujya’s picture

lokapujya’s picture

Status: Postponed » Needs review
lokapujya’s picture

FileSize
3.25 KB

Retest.

m.stenta’s picture

Status: Needs review » Needs work

I'd like to resurrect this issue, because we are experiencing something similar in farmOS 7.x-1.x. However, the patch here does not fix the issue we're having, and I think the real culprit might be somewhere different in the code.

The specific issue we are seeing is with regard to list_text fields. We have a "flags" field that is a set of checkboxes. When you GET an entity via the API, these flags look like this:

"flags":["priority", "needs_review"]

However, sending that same format in a POST or PUT request (to create/update entities, respectively), it fails. No error is displayed, and there is no response from the server. However, there is a TypeError and about 15 PHP Warnings in the "Recent log messages".

TypeError: Argument 2 passed to RestWSBaseFormat::getResourceReferenceValue() must be of the type array, string given, called in /var/www/html/profiles/farm/modules/contrib/restws/restws.formats.inc on line 278 in RestWSBaseFormat->getResourceReferenceValue() (line 311 of /var/www/html/profiles/farm/modules/contrib/restws/restws.formats.inc).

At quick glance, it seems that the "flags" field (which is of type list_text), is being parsed as a reference field, which it is not.

It is also curious that the patch for this issue is making a change to getResourceReferenceValue() as well. But why? Text fields are not reference fields, so why does making a change to that method fix it? It seems that there is a deeper issue here, where multi-value fields are always assumed to be reference fields.

Notably, we stumbled upon a form of JSON that DOES work to set the "flags" field for us:

"flags":[{"id": "priority"}, {"id": "needs_review"}]

The only reason that works is because restws is assuming that all arrays are arrays of references. So if you wrap your text in an object with an id, it works. But it shouldn't.

m.stenta’s picture

Here is the curl command I am running to create a Log entity in farmOS (note that I'm omitting the cookie and csrf token stuff for simplicity):

curl -X POST -H 'Content-Type: application/json' -d '{"name": "Test API flags", "type": "farm_activity", "flags": ["priority"]}' http://localhost/log

Tracing this with XDebug, I can see that:

1. restws_handle_request() takes the payload (the JSON string after the -d flag in the command above), and passes it to RestWSBaseFormat->createResource() which unserializes it using RestWSFormatJSON->unserialize()
2. RestWSFormatJSON->unserialize() simply runs the payload through drupal_json_decode(), and then passes the output of that as reference into RestWSBaseFormat->getPropertyValues().
3. RestWSBaseFormat->getPropertyValues() iterates through each item in the payload array, and only continues with it if the property's value is an array.
4. Then, it looks to see if the field property info array has a key called property info. If it does, it recursively runs getPropertyValues() again on the values. Otherwise, it passes it into RestWSBaseFormat->getResourceReferenceValue().

So, it appears that values inside arrays are ALWAYS passed into getResourceReferenceValue(), even when they are NOT reference fields. This seems wrong.

It seems that getPropertyValues() should be checking to see if the field is a reference field first, and if it isn't then it should NOT process it any further. As it's written right now, it seems to have an implicit assumption that multi-value fields are always reference fields.

m.stenta’s picture

It seems that getPropertyValues() should be checking to see if the field is a reference field first, and if it isn't then it should NOT process it any further. As it's written right now, it seems to have an implicit assumption that multi-value fields are always reference fields.

Just to test this theory, I added a return; at the very top of getPropertyValues() so that it would not do ANY processing of the values. Then I ran the curl command from my last comment, and it worked! It created the entity with the flags associated with it properly. Then, I added a multi-value textfield to the entity and tested with that as well, and that also worked!

So that seems to prove my hypothesis that getPropertyValues() is the source of this issue. It does not do anything to prevent non-reference fields from being treated as references.

m.stenta’s picture

Assuming the above is correct, and the issue has been pinpointed... the next question is: how do we fix it? How do test that a field is a reference field inside getPropertyValues() to ensure that it does not process non-reference fields?

m.stenta’s picture

Status: Needs work » Needs review
FileSize
3.65 KB
4.65 KB
5.79 KB

Attached are two patches and an interdiff.

The first patch demonstrates the problem, and the tests should fail. It is inspired by @lokapujya's patch, and also includes a test for a list_text field, in addition to a test of a text field.

The second patch fixes the issue by checking to see if the value that will be passed from getPropertyValues() to getResourceReferenceValue is an array. If it is not an array, then it skips it. This works because reference fields are always an array (converted from a JSON object like {"id": 123}), whereas non-references are just a flat value. Previously, these flat values were being passed into getResourceReferenceValue, which explicitly requires an array as its second argument (assuming it will be a reference value). This is what leads to the error described above:

TypeError: Argument 2 passed to RestWSBaseFormat::getResourceReferenceValue() must be of the type array, string given

So, this just adds a simple check to make sure that the value being passed into getResourceReferenceValue is actually an array. If it isn't then it skips it.

The last submitted patch, 34: 2301237-test-fail-34.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.