Image upload with headless / decoupled drupal?

We encourage users to post events happening in the community to the community events group on https://www.drupal.org.
lightguardjp's picture

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.

Comments

When I did this in 7 for the

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

lightguardjp's picture

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

Yeah, thought I'd kill the

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?

lightguardjp's picture

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

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

kimberlydb's picture

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

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: