You are here

Drupal Services: Creating a node from an external form

Scenario

Solving the problem of systems talking to other systems has so many applications in our world of endless information.  For our tutorial, though, let's imagine that we have a small form, either on an app or external site, which users can use to create profiles.  We want that user submitted data to be saved on our separate Drupal site in a content type called 'bio', which has very similar (though not identical) fields.  Let's get it all setup!

Modules to enable

  • Services
  • REST Server (part of the Services package)
  • Custom module we're about to write

Set up the Services module

After enabling the Services and REST Server modules from above, go to /admin/structure/services and add a new endpoint:

 

Once you've added the end-point, you can further edit it, specifying which formats you want to enable for the request and response formats (under the 'Server') tab, as well as which resources to enable (under the 'Resources' tab).  Just to make sure everything is working, let's try some sample configurations and make sure we can get a single node or list of nodes in a couple of formats.

I've gone back to the newly created 'profile' endpoint and enabled the following formats under the 'Server' tab.  I'm pointing to the ones I'm most likely to be using though.  Since we'll use an html form, our request will come in as form-data.  I decided to enable xml and json as response formats, if I want to get a node via this api - we'll test both formats shortly.

First, let's move on to the 'Resources' tab, to enable us to both create and retrieve (view) nodes using this service.  Without enabling any resources, this would be useless!:

 


 

Test the module setup

Before we move onto CREATING Drupal nodes using this API, let's just make sure the Services module is working well by testing whether it will return existing nodes to us.

Test this by going to the following URL on your site, once all the setup from the previous step has been completed:

<your domain name>/<your endpoint name>/<the resource you're trying to retrieve>/<resource id (if you want a single one versus a list)>.<desired format>

Here are some examples:

  • www.example.com/profile/node/104.json     - Give me the node with nid 104 in a json format
  • www.example.com/profile/node                    - Give me a list of all your nodes (default format)
  • www.example.com/profile/node.json            - Give me a list of all your nodes (json format)
  • www.exampe.com/user/10.xml                      - Give me the user with uid 10 in xml format

Try several out, and make sure you're getting the result you expect!  Make sure that you only get back what you have permissions for, and what you have enabled and that the formats are working as you'd expect.  For example, if anonymous users do not have the permission to 'View user profiles', the last example should work (also, we did not enable that resource above), etc.

 

Creating a custom module to write/save a Drupal node based on a submitted external form

The external form

This can be any old form, but for the purposes of this exercise, let's imagine that our form is something like this:

<form action="http://example.com/your_endpoint/your_resource" method="post" enctype="multipart/form-data">
<table width="50%">
<tr>
  <td>Your name:</td><td><input name="name" type="text" /></td>
</tr>
<tr>
  <td>Job title:</td><td><input name="job_title" type="text" /></td>
</tr>
<tr>
  <td>Age:</td><td><input name="age" type="text" /></td>
</tr>
<tr>
  <td>Hometown:</td><td><input name="hometown" type="text" /></td>
</tr>
<tr>
  <td>Hometown state:</td><td><input name="state" type="text" /></td>
</tr>
<tr>
  <td>Bio/message:</td><td><textarea name="message"></textarea></td>
</tr>
<tr>
  <td colspan="2">&nbsp;</td>
</tr>
<tr>
  <td colspan="2"><input type="submit" value="Save">&nbsp;
  <input type="reset" value="Clear"></td>
</tr>
</table>
</form>

The custom module

Now let's create a custom module called bd_profile, which will just create a new custom resource, mapping the fields from the above form to fields defined in our bio node content type.

First, here is what my bio content type looks like:

Now here is my implementation of hook_services_resources() in my bd_profile.module file:

function bd_profile_services_resources() {
  return array(
    'bd_profile' => array(                            // My new resource
      'create' => array(
        'callback' => '_bd_profile_create_node',
        'access callback' => '_bd_profile_create_access',
        'args' => array(
          array(
            'name' => 'node',
            'optional' => FALSE,
            'source' => 'data',                         // Setting the source to 'data' in your args means that any data in the POST will be passed to the callback function
            'description' => 'The node data to create',
            'type' => 'array',
          ),
        ),
      ),
    ),
  );
}

Once you've gone this far, you can go ahead and enable your new custom module, and you should be able to see your brand new resource for your existing endpoint!  As you can see, we only defined the 'create' operation for it, so that's all that's displayed.  Let's go ahead and enable it:

Finally, in my .module file, I have to implement the two internal callback functions specified above: _bd_profile_create_access() and _bd_profile_create_node().  Here they are:

The access callback simply returns TRUE in my case, so ANYONE would be able to submit a profile for my system to save.  Obviously, this is rather permissive, but you can implement your own any way you want.

/**
 * Access callback
 */
function _bd_profile_create_access() {
    return TRUE;
}

Here is the callback function which creates the new node

/**
 * Callback function that creates the node
 */
function _bd_profile_create_node($arg) {
  // Minimally there needs to be something submitted
  if($arg) {
    // Create the node
    $node = new stdClass();
    $node->type = 'bio';
    $node->title = $arg['name'];
    $node->language = LANGUAGE_NONE;
    $node->uid = 0;
    node_object_prepare($node);
 
    // Create a map of predefined POST args to Drupal fields
    $map = array(
      'job_title' => 'field_title',
      'message' => 'body',
    );
 
    // Array to store both mapped and unmapped fields
    $node_fields = array();
 
    // What predefined args have been passed?
    $arr1 = array_intersect_key($arg, $map);
    // Build an array associating Drupal fieldnames to arg values
    foreach($arr1 as $key => $value) {
      $field = $map[$key];                    // Get the drupal field that matches the form field
      $node_fields[$field] = $value;
    }
 
    // What undefined (ie. unknown) args have been passed?
    $arr2 = array_diff_key($arg, $map);
    // Associate unknown arg values with the 'general info' field on our bio/profile pages
    foreach($arr2 as $key => $value) {
      if(isset($node_fields['field_general_info'])) {
        $node_fields['field_general_info'] .= $key . " | " . $value . "\n";
      } else {
        $node_fields['field_general_info'] = $key . " | " . $value . "\n";
      }
    }
 
    // Save all arg values as Drupal fields
    foreach($node_fields AS $key => $value) {
      $node->{$key}[$node->language][0]['value'] = $value;
    }
 
    // Save the node
    $node = node_submit($node);
    node_save($node);
  } else {
    // Error if no args were passed
    return services_error(t('No data submitted!'), 406, t('No data submitted!'));
  }
}

 

The above code does something interesting - it maps three of the form fields we use - the title of the node becomes the person's name, and then the job title and bio are mapped.  However, if any additional fields are submitted in the form, they are all concatenated and put in a 'General info' field.  I saw this approach from the following R.J.Townsend blog post, and loved it - it could have many applications when you are not sure if the data you're expecting may change.

Test the node creation

We're done!  Go ahead and go to your html form that you want to be communicating data to your Drupal site, fill it out, and make sure the node gets saved as the appropriate content type on the other end.  Good luck!  Let me know if you have any comments or suggestions.

Share

Comments

can you explain service for update node?
i'm nubitol on drupal.

thank's

I follow your instructions. My final result is
"This XML file does not appear to have any style information associated with it. The document tree is shown below.
<result>Node type is required</result>"

Please, reply me back.
Thanks!

Hmm, it sounds like it's confused about the node type.  Did you include the type of node you need to create in your custom module (what was 'bio' in my case)?

Yes, I included the 'bio' node type in the custom module. I also created the 'bio' content type. I was testing on localhost. "This XML file does not appear to have any style information associated with it. The document tree is shown below.
<result>Node type is required</result>" occurs when I fill and save the external form.

I am also facing the same error. Did you get any solution for the same?

How do you manage image files as in your form?

i try all this steps. but i got same error(node type). How can i fix this problem. can you tell me.

your_endpoint/your_resource

in the above example what should i write in place of "your_resouce".

what should i write in place of "your_resource"..i tried as sitename/my end point/node/content,sitename/my end point/node/bio,sitename/my end point/content/bio..none of them is working...i am getting response as "XML file does not appear to have any style information associated."

Same problem here, have you find a solution?

Its really unclear what you're doing to handle image submission here which is the tricky part and the image is ommitted from the form code..
Do you have a solution to this problem?

How to display json result in a drupal node?

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.