SproutOnline.com has a variety of games and mobile apps that submit user-created data via POST to a URL and then email data to a gmail account. Not the most elegant way to do it but it's worked for years and they never had a problem. Last week something broke and we couldn't (quickly) find the cause. Instead of spending more time troubleshooting, I decided to create a resource in Services 3.x that would save each POST as a node.

Each game and mobile app posts data the same way; you can think of it in terms of an HTML form posting data exactly like this:

 

I don't have source code for any of the flash games or mobile apps submitting data so I need to create a services resource that converts data to a Drupal node. We already have a Services endpoint and a number of resources available for some of our newer flash games and mobile apps. This needs to work with the current endpoint so the first step is to implement hook_services_resources() in a custom module like this:

function sprout_send_to_sprout_services_resources() {
return array(
's2s' => array(
'create' => array(
'callback' => '_sprout_send_to_sprout_create_node',
'access callback' => '_sprout_send_to_sprout_access',
'args' => array(
array(
'name' => 'node',
'optional' => FALSE,
'source' => 'data',
'description' => 'The node data to create',
'type' => 'array',
),
),
),
),
);
}

Some important things to note:

  • Setting the source to 'data' in your args means that any data in the POST will be passed to the callback function
  • The access callback simply returns TRUE. Interestingly it needs to call a function, you cannot simply return TRUE as you would when using hook_menu

Now I go to the services admin page and enable my resource. You'll note that I disable authentication.

Next I created a content type and fields to which I'll map s2s submissions, very simple and par-for-the-course. I added a Flag to the content type as well, producers will be able to use this to signify if a submission has been used for on air promos.

Now it's time to create the callback function that creates the node, adds data to fields, and then adds the image to the node. See my comments inline.

function _sprout_send_to_sprout_create_node($arg) {
// Minimally there needs to be something submitted
if($arg) {
// Create the node
$node = new stdClass();
$node->type = 'sprout_send_to_sprout';
$node->title = 'TITLE';
$node->language = LANGUAGE_NONE;
$node->uid = 0;
node_object_prepare($node);
// Create a map of predefined POST args to Drupal fields
$map = array(
'submitType' => 'field_s2s_submittype',
'firstName' => 'field_s2s_firstname',
'age' => 'field_s2s_age',
'hometown' => 'field_s2s_hometown',
'state' => 'field_s2s_state',
);
// 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];
$newarr[$field] = $value;
}
// What undefined (ie. unknown) args have been passed?
$arr2 = array_diff_key($arg, $map);
// Associate unknown arg values with the Drupal general field
foreach($arr2 as $key => $value) {
if(isset($newarr['field_s2s_general'])) {
$newarr['field_s2s_general'] .= $key . " | " . $value . "\n";
} else {
$newarr['field_s2s_general'] = $key . " | " . $value . "\n";
}
}
// Save all arg values as Drupal fields
foreach($newarr AS $key => $value) {
$node->{$key}[$node->language][0]['value'] = $value;
}
// If there's a file, save it to a Drupal field
if($_FILES['image']['name']) {
$file_saved = file_save_data(fopen($_FILES['image']['tmp_name'], 'r'), file_default_scheme() . "://" . $_FILES['image']['name']);
file_usage_add($file_saved, 'sprout_send_to_sprout', 'files', $file_saved->fid);
$node->field_s2s_image[$node->language][0] = (array)$file_saved;
}
// 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 first thing you may notice is that $args, which contains data from $_POST, does not contain $_FILES data. The second thing you may notice is that I had to use file_save_data() instead of file_save_upload(); this is because the image is not (yet) a Drupal object.

Posts from our legacy games and apps are now being saved in Drupal as nodes, so let's use views to quickly build an admin screen for producers to view, search, and filter submissions:

Done!

Comments

Add new comment

The content of this field is kept private and will not be shown publicly.