Image upload with headless / decoupled drupal?

Events happening in the community are now at Drupal community events on www.drupal.org.
Posted by lightguardjp on November 23, 2015 at 6:14pm

I'm trying to figure out the best way to handle images in a headless drupal 8 install. Using the /entity rest endpoint is not working during serialization as it seems like I need to have a URI for the existing file on the drupal install. I'd like to be able to do everything in one step, if that's possible.

Categories: , , ,

Comments

When I did this in 7 for the

Posted by stevepurkiss (not verified) on November 23, 2015 at 7:05pm
stevepurkiss's picture

When I did this in 7 for the back-end of http://www.nickjr.co.uk/create/#!/colour I had to create a custom call due to the way Drupal works uploading images first then providing that URI for the node. I just copied the code from the original functions (apart from the random filename generator which I copied from elsewhere) - I've cleaned out the project-specific stuff (it links with the line-art etc.) & pasted below so you can see how it did it, again it's not for 8 & not tried it but AFAIK you'll probably need to do similar - hope it helps!

/**
* POST a new image

@param int $uid
* Integer specifying the user id
* @param object $image
* A base64 encoded image
* @param string $title
* String specifying the title of the image

@return array
* An associative array containing the nid of the node created
*/
function _mysite_api_image_create($uid, $title = NULL, $image) {

// Ensure the user account exists
$account = user_load($uid);
if (empty($account)) {
return services_error(t('Error during POST/image: There is no user with uid @uid.', array('@uid' => $uid)), 404);
}

// Adds backwards compatability with regression fixed in #1083242
// $image['image'] can be base64 encoded file so we check whether it is
// file array or file data.
$image = _services_arg_value($image, 'image');

// Create a random filename
srand((double) microtime() * 1000000);
$filename = '';
while (strlen($filename) != 20) {
$type = rand(1, 3);
if ($type == 1) {
$filename = $filename . chr(rand(48, 57));
}
if ($type == 2) {
$filename = $filename . chr(rand(65, 90));
}
if ($type == 3) {
$filename = $filename . chr(rand(97, 122));
}
}

// Title
if ((isset($title) && ($title != ''))) {
$title = check_plain($title);
}
else {
$title = $filename;
}

// If the file data or filename is empty then bail.
if (!isset($image['image'])) {
return services_error(t("Error during POST/image: Missing data the file upload can not be completed"), 500);
}

// As requested image sent in base64 so need to extract file type
// Image data such as file type could be sent along as other fields in file array
$imgdata = base64_decode($image['image']);
$f = finfo_open();
$mime_type = finfo_buffer($f, $imgdata, FILEINFO_MIME_TYPE);
$extensions = array(
'image/gif' => '.gif',
'image/jpeg' => '.jpg',
'image/png' => '.png',
);

$image['filename'] = $filename . $extensions[$mime_type];

// Get the directory name for the location of the file:
$image['filepath'] = file_default_scheme() . '://field/image/' . $uid . "/" . $image['filename'];

$dir = drupal_dirname($image['filepath']);

// Build the destination folder tree if it doesn't already exists.
if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY)) {
return services_error(t("Error during POST/image: Could not create destination directory for file."), 500);
}

// Rename potentially executable files, to help prevent exploits.
if (preg_match('/.(php|pl|py|cgi|asp|js)$/i', $image['filename']) && (drupal_substr($image['filename'], -4) != '.txt')) {
$image['filepath'] .= '.txt';
$image['filename'] .= '.txt';
}

// Write the file
if (!$image_saved = file_save_data(base64_decode($image['image']), $image['filepath'])) {
return services_error(t("Error during POST/image: Could not write file to destination"), 500);
}

if (isset($image['status']) && $image['status'] == 0) {
// Save as temporary file.
$image_saved->status = 0;
file_save($image_saved);
}
else {
// Required to be able to reference this file.
file_usage_add($image_saved, 'services', 'files', $image_saved->fid);
}

// Create a new node for this image
$node = new stdClass();
$node->title = $title;
$node->language = LANGUAGE_NONE;
$node->type = "image";
node_object_prepare($node);
$node->name = $account->name;
$node->status = 1; //(1 or 0): published or not
$node->promote = 0; //(1 or 0): promoted to front page
$node->comment = 0; // 0 = comments disabled, 1 = read only, 2 = read/write
$node->field_image[$node->language][]['fid'] = $image_saved->fid;

$node = node_submit($node); // Prepare node for saving

try {
node_save($node);
}
catch (Exception $e) {
return services_error(t("Error during POST/image: Error saving node."), 500);
}

return array("nid" => $node->nid);
}

Thanks

Posted by lightguardjp on November 23, 2015 at 7:01pm

I figured I'd need to something like that :(

Yeah, thought I'd kill the

Posted by stevepurkiss (not verified) on November 23, 2015 at 7:09pm
stevepurkiss's picture

Yeah, thought I'd kill the dream quick :D

/should/ be easier in 8, and perhaps something which could come back as a useful addition/module, I'd be up for reviewing/tidying up/helping out, although it might be something that turns out instance-specific & example solution like I posted above may be the furthest we can get otherwise I would've thought someone would have done similar already - maybe they do but just move on!

Do post back results if you can, thanks!

Could I hook this into the existing chain?

Posted by lightguardjp on November 23, 2015 at 7:23pm

Any ideas if I could hook this into the existing entity chain? Can a module override a base type?

So I found a D7 module which

Posted by stevepurkiss (not verified) on November 23, 2015 at 10:03pm
stevepurkiss's picture

So I found a D7 module which does this - came out in March this year so about a year after I did the code above ;) Interesting to see how it approaches the issue, don't see any 8 version but seems like a good codebase to start with:

RESTful web services support for files and images:
https://www.drupal.org/project/restws_file

I ran into this with my

Posted by kimberlydb on November 25, 2015 at 8:01pm

I ran into this with my application running AngularJS front-end and Drupal 8 back-end.

You do have to create the file before you can set your node field value to it. So basically I do a POST to create the file and then update the node image field.

I was researching to see how other people had solved this, I mostly found unanswered issues and a open thread with several in-progress patches attached.

https://www.drupal.org/node/1927648

This was actually the place to start. So if you haven’t, I’d go ahead and start there.

On the Angular side of things, I had a simple filereader directive that provided the base64 encoding of the file for you to then pass to a service call. I made minor modifications to this to better suit my needs, but ultimately used this already existing plugin. From there, I simply needed to make my service calls to create the file (a POST to entity/file).
eg

var file = {
"_links": { "type": {"href": serviceUrl + "rest/type/file/file"}},
"data": [{"value": $scope.formData.logo.file,}],
"uri" : [{"value": "public://" + $scope.formData.logo.name}],
"filemime": [{"value": $scope.formData.logo.type}],
"filename" : [{"value": $scope.formData.logo.name}],
"filesize" : [{"value": $scope.formData.logo.size}],
"status": [{"value": 1}],
"validators" : [{}]
};

File.create({}, file).$promise.then(function (response) {
....
});

Here's some code from Gizra

Posted by stevepurkiss (not verified) on November 26, 2015 at 11:12am
stevepurkiss's picture

Here's some code from Gizra which whizzed past my twitter timeline yesterday - they've done a whole load of headless Drupal!

https://gizra.github.io/elm-hedley/#!/articles

https://github.com/Gizra/elm-hedley/blob/master/src/js/elm-interop.js

Headless Drupal

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds:

AltStyle によって変換されたページ (->オリジナル) /