I'm trying to use drupal_execute() to save a custom node. I see my form's validate function get called (and it returns successfully - no call to form_set_error) . However, the form's submit and insert hooks never are called. Is there something special I need to do with using drupal_execute() for adding new nodes?

I've successfully used it in the past to handle adding taxonomy terms but the node insert/update forms appear to be different.

Comments

bjvetter’s picture

I added a mymodule_form_submit method to my module and it gets called. However, all of the form values are missing except for the one named "body".

Peering through the stack traces in drupal, the values were originally passed into drupal_execute() and then drupal_process_form() then handed off to drupal_prepare_form() which then shoved them in form[#post]. After drupal_prepare_form() calls form_builder(), the data in #post is moved from form[#post] and is now inserted in each field definition in the form. Each form item has a #post with the complete list of form values. (this is done explicitly around line 780 in form.inc).

My form_submit function is called from drupal_submit_form(). drupal_submit_form() is not using the #post data in the form to create the arguments passed to my form_submit function. Instead, it relies on the global $form_values. But $form_values is mostly empty (the exception being the name of the form and my body field). I don't see anything trying to place my values (in the various #post elements) into the $form_values array.

So is there some special format for the values I pass in that perhaps I'm not meeting?

And of course, from the documentation, it didn't even sound like I needed to implement the module_form_submit function, just module_insert() unless I wanted to process the node before it was inserted/updated. Perhaps the form value issues are getting in the way.

bjvetter’s picture

Oh, by the way. If I submit it manually through my add form, it does work. It only fails to work when I submit using drupal_execute().

bjvetter’s picture

I was able to get the form to submit. For anyone else who may run into this and can't find the instructions (I never did, but it might be somewhere)...

I first had to change the name of the form I was using. Instead of using the form-id "<module>_form", you must use the "node" form: "<module>_node_form" (replace <module> with your module name). (btw, since my module name and node type are the same, it is possible that it should be <node type>_node_form - I just don't know).

When using the *_node_form, my module's insert/update/... functions were called as were the underlying node functions.

Unfortunately, this didn't solve my problems with the values in the forms being placed correctly. In the end, I had to fully populate the node object as well as the form_values array (each with the same values) to get the values passed to my <module>_insert or <module>_form_submit functions. The few examples I found with drupal_execute() on drupal.org didn't do this. It may be peculiar to creating new nodes or I may still be doing something incorrectly. In any case, I'm now happily populating nodes programmatically.

pielgrzym’s picture

Thanks for publishing the solution. I also had the same problem and passing *my_module_name*_node_form to the drupal_execute did work! It should be mentioned in the api docs! :)

--
Pielgrzym

meecect’s picture

I followed your directions and am able to programmatically insert nodes, but every time I do so, it immediately takes me to the node view screen. I am trying to cycle through a list of content and generate a node for each one, so that behavior is getting in the way, as my loop stops processing after the first node is created.

Did you ever come across that issue?

Also, I agree the documentation is poor when it comes to needing hook_submit. I don't even have a hook_submit. I do have a hook_insert, and it gets called when I do the drupal_execute with nodetype_node_form.

What is the difference between insert and submit? Could that be my problem?

mooffie’s picture

it immediately takes me to the node view screen.

drupal_execute() doesn't redirect. I guess you have a module that explicitly call drupal_goto() in its hook_nodeapi. Maybe you have 'nodegoto' installed? Disable all modules you can and try again.

meecect’s picture

I think the problem was that the hook_view was failing in these cases, so drupal_execute doesn't redirect, but hook_view does get called and I can see it in the stack. But because hook_view was failoing, it would error out right there. I fixed my code so that hook_view doesn't fail for these auto generated nodes, and now they all add correctly.

etaroza’s picture

As I understand, calling _node_form with drupal_execute() actually submits the form defined within node's hook_form() implementation, i.e. node_form(). And the latter form action is on the path node/XXX/...., that's after submitting you get to the node/XXX page.

Hence you need some code that redirects you from node/XXX to some page after submit. Most likely you'd want to redirect to $_GET['q'], i.e. current page.

Hope this makes sense.

Evaldas
---
www.linkedin.com/in/taroza
evaldas-taroza.lt

Optaros - www.optaros.com

caligari’s picture

Yes, you must use <node-type>_node_form in modules where module name and node type are not the same.

More info: FormAPI: better handling of programmatic submission.

aaustin’s picture

I was also frustrated that most examples of using drupal_execute (including the one in the awesome Pro Drupal Development book) do not address submitting nodes.

This is what I have found works:

  $form_id = '{your_node_name}_node_form';
  $node = array('type' => '{your_node_name}');
  $form_values = array(
    'title' => 'New node',
    'body' => 'This node was submitted programmatically.',
    'taxonomy' => array('tags' => array('1' => ('cool, awesome, programmatically')))
  );
  drupal_execute($form_id, $field_values, $node);

I put the taxonomy line in there because that caused me a bit of confusion as well. I kept thinking that drupal_execute should handle 'taxonomy[tags][1]' => 'cool, awesome, programmatically' But if the form field looks like an array, it needs to be an array for drupal_execute.

I also found that you need to tack that extra $node array argument on to drupal_retrieve_form() as well.

NaX’s picture

Thanks for that you saved me a lot of time.

I also added the author info, other wise anonymous is the owner of the node.

  global $user;
  $form_id = '{your_node_name}_node_form';
  $node = array('type' => '{your_node_name}');
  $form_values = array(
    'title' => 'New node',
    'body' => 'This node was submitted programmatically.',
    'taxonomy' => array('tags' => array('1' => ('cool, awesome, programmatically'))),
    'name' => $user->name,
  );
  drupal_execute($form_id, $field_values, $node);
johnhanley’s picture

I am creating a module that programmatically creates a custom node using drupal_execute() via hook_cron().

The node is successfully created, but any subsequent code in hook_cron() does not execute. Instead the following error occurs in the log:

Cron run exceeded the time limit and was aborted.

The module does not include or require hook_validate or hook_submit and adding them in (to perhaps satisfy some strange dependency) does not make a difference.

I have confirmed drupal_execute() is in fact the culprit. Somewhere along the way a critical error occurs and the function never returns.

Has anyone else experience anything like this with drupal_execute()?

All comments/suggestions appreciated.

johnhanley’s picture

I suspected the redirect property might have something to do with the previously described problem, but setting it to FALSE didn't help.

  $form['#redirect'] = FALSE;
johnhanley’s picture

I managed to get drupal_execute() to behave (and not kill cron) by passing the same $form_values array in both the second and third arguments (as bjvetter alluded to earlier in this thread):

drupal_execute($form_id, $form_values, $form_values);
aaustin’s picture

I have had this problem when I try to surpass my php settings for allowable time and memory usage.

So I have had to play around with the number of nodes getting imported at a time, as well as my php.ini settings.

johnhanley’s picture

For my purposes the particular node is only created programmatically via cron and never manually. With this in mind the corresponding node form only contains hidden elements.

johnhanley’s picture

Nax, I'm wondering if you were able to successfully define a user name in the above example. All my nodes created programmatically via cron are assigned to "anonymous" even when passing a valid user name in $field_values.

mooffie’s picture

That's because drupal_execute() is just like submitting a form. Cron runs as anonymous (unless you override the global $user).

Now,

Surf your site as anonymous. Create a node. On the node editing form open the "Authoring information" fieldset and assign this node to somebody else. You'll discover that you can't. There's no such fieldset. That's because anonymous don't have the 'administer nodes' permission.

johnhanley’s picture

mooffie, what you say appears to be true and it's a real bummer. It's too bad the author name can't be defined programmatically no matter what.

For my purposes nodes created via cron as anonymous is extremely limiting. I need to assign them the same author as the parent (super) node.

mooffie’s picture

[...] and it's a real bummer. It's too bad the [...]

It's not a bummer. People have misconceptions about drupal_execute(). It's not a "Data API". Drupal, unfortunately, lacks one. Yes, it's certainly a real problem. In this regard, ol' node_save() is better.

For my purposes nodes created via cron as anonymous is extremely limiting.

In the other thread you posted to it was suggested to do $_GLOBAL['user'] = user_load(...); prior to calling drupal_execute().

johnhanley’s picture

I ended up rethinking my whole approach and devised a method that made nodes created anonymously a non-factor.

Nevertheless, thanks for sharing your insight.

evoltech’s picture

In the other thread you posted to it was suggested to do $_GLOBAL['user'] = user_load(...); prior to calling drupal_execute().

on drupal 5.9 to implement this method I used the following code to switch the user that my hook_cron() function ran as:

globals $user;
$user = user_load(array(uid => 1));

This thread helped me a lot. Thanks everyone!

hackbloc.org : exploit code not people

zilverdistel’s picture

For non freetagging vocabularies, this worked for me:

  $terms = explode(',', $categories);
  $tids = array();
  foreach ($terms as $term) {
    $tid = _some_function_to_get_tid_of_term($term);
    if ($tid) {
      $tids[$tid] = $tid;
    }
  }
  if (!empty($tids)) {
    $values['taxonomy'] = array($vid => $tids);
  }
ragaskar’s picture

to clarify for other users

'taxonomy[tags][1]' is a string, so it submits as such. Drupal is looking, however, for the value of $form_values['taxonomy']['tags'][1], which we can clearly see is not equal to $form_values['taxonomy'[tags][1]'].

additionally, the default $node values for node_form are

$node=array('uid' => $user->uid, 'name' => $user->name, 'type' => $node_type);

at a minimum, one should provide these values to ensure that the ownership is properly assigned.

Thanks for your comment -- saved me from looking through the core modules myself!

akhodakovskiy’s picture

What about situation when I need to execute form which contains upload fields? For example, add Image node...
How upload field should be added to the $form_values array?

Have such example, but it doesn't work for me

drupal_execute("image2_node_form",
  array('title'=>'Image 1',
    'field_image'=> array(array('filepath'=>'/home/brad/Desktop/frozen_branches.jpg',
      'filename'=>'frozen_branches.jpg',
      'filemime'=>'image/jpeg'))
  ),
  array('type'=>'image2')
);

Aleksey Khodakovskiy

NaX’s picture

I have not tried this yet, but I think its a little more complex than just adding it to the form array.

If you look at both the image_prepare() function from the image.module and the _upload_prepare() function from the upload.module. They both use file_check_upload() function found the file.inc include.

Form what I can tell the file_check_upload() function processes the $_FILES array. So I think you need to look into modifying the $_FILES array to mimic a real file upload and the file needs to be on the server in a temporary location.

I wonder if there might be a better way using FAPI.

akhodakovskiy’s picture

I found the solution for image node. We just need to create empty array for images and Drupal will do uploading for you.
I think upload form field should have same name as in the real node form:

$node = array('type' => 'image'); 
$values = array(
  'title' => $_FILES['files']['name']['image'],
  'body' => '',
  'new_file' => 1,
  'uid' => $user->uid,
  'name' => $user->name,
  'images' => array(
        '_original' => '',
        'thumbnail' => '',
        'preview' => '')
);
$redirect_url = drupal_execute('image_node_form', $values, $node);

Aleksey Khodakovskiy

isaac77’s picture

Deadmonk, your post was very helpful! Here's what worked for a cck filefield (as opposed to imagefield):

  • so far, I don't seem to need to set 'new_file' => 1, though i might be missing something
  • as you noted, i just created an empty array for the file field: $values['field_foo_upload'] = array();
    note that i had to append _upload to the field name.
  • in the custom form this is being used with, i named the upload field the same thing as the upload field in the "real" node create/edit form for my content type. in other words, the standard edit form for my content type showed that the file upload field is named files[field_foo_upload]. That is where the $values['field_foo_upload'] in $values['field_foo_upload'] = array(); comes from.
scb’s picture

Does anyone know how to create a node with a cck filefield using drupal_execute (with no real form submitting, just pulling the data from elsewhere)
I've tried this, but something's not working: ($v holds the data for the node)

                $values["field_foo_upload"] = array();
                $values["field_foo"] = array(
                    array(
                          'description' => $v['original_filename'],
                          'delete' => 0,
                          'list' => 1,
                          'filename' => $v['filename'],
                          'filepath' => $v['filepath'],
                          'filemime' => $v['filemime'],
                          'filesize' => $v['filesize'],
                          'fid' => 'upload',
                          'previous_filepath' => $v['filepath'],
                   ));

I've tried the same approach for imagefield, and it works perfectly... this way:

$values["field_bar"] = array(
                array(
                  'fid' => 'upload',
                  'title' => $v['title'],
                  'filename' => $v['filename'],
                  'filepath' => $v['filepath'],
                  'filesize' => $v['filesize'],
                ),
              );

but no luck for the filefield... has anyone done this? I've also tried with the standard upload.module attachment, also no luck...

Thanks!

isaac77’s picture

I have a custom form with several file upload fields. For each uploaded file, I would like to create a separate node with the file attached using filefield module.

My code loops through the file fields on the form. For each one, the $_FILES array is modified so that the relevant info (temp path, filesize, etc) is stored in $_FILES['files']['field_myfile_upload']. drupal_execute is then run to create the new node with attached file. The process is repeated for each file field. This works fine for the first file, but I cannot get it to work for additional files. Anyone have ideas?

Following is a simplified code sample... Would really, really appreciate help on this. Thanks!


// set title, body, etc for $new_node
$new_node['title'] = 'new node with file attached';  // etc.

// set up $_FILES array . each time the code loops through, a different file's path, name, size, etc are used
$_FILES['files']['name']['field_myfile_upload'] = [set value here...];
$_FILES['files']['type']['field_myfile_upload'] = [set value here...];
$_FILES['files']['tmp_name']['field_myfile_upload'] = [set value here...];
$_FILES['files']['error']['field_myfile_upload'] = [set value here...];
$_FILES['files']['size']['field_myfile_upload'] = [set value here...];

$new_node['field_myfile_upload'] = array();  // provide empty array

drupal_execute('mynodetype_node_form', $new_node, $node);

matbintang’s picture

I can't seem to fake the uploaded file well enough in drupal6.
my drupal_execute script is failing in file.inc:

 if (isset($_FILES['files']) && $_FILES['files']['name'][$source] && is_uploaded_file($_FILES['files']['tmp_name'][$source]))

and here:

if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath))

Any ideas on how I can fix this? Apart from bypassing the check of cause.

Here is my script:

module_load_include('inc', 'node', 'node.pages');  // new for Drupal 6

$filepath='/tmp/images/HPIM4391.jpg';
unset($_FILES);
$_FILES['files']['name']['image'] = basename($filepath);
$_FILES['files']['name']['upload'] = 0;
$_FILES['files']['type']['image'] = 'image/jpeg';
$_FILES['files']['type']['upload'] = 0;
$_FILES['files']['tmp_name']['image'] = $filepath;
$_FILES['files']['tmp_name']['upload'] = 0;
$_FILES['files']['error']['image'] = 0;
$_FILES['files']['error']['upload'] = 4;
$_FILES['files']['size']['image'] = filesize($filepath);
$_FILES['files']['size']['upload'] = 0;

   $form_state = array();
   $nodeTmp = array('type' => image); // a variable holding the content type
   $form_state['values']['type'] = 'image';
   $form_state['values']['status'] = 1;
   $form_state['values']['title'] = 'my test node';   // the node's title
   $form_state['values']['body'] = 'just my test node'; // the body, not required
   $form_state['values']['status'] = 1; //publish all imported nodes
   $form_state['values']['promote'] = 1; //promote all imported nodes
   $form_state['values']['sticky'] = 0; //remove sticky from imported nodes
   $form_state['values']['image'] = array();
   $form_state['values']['name'] = 'webmaster'; 

   $form_state['values']['op'] = t('Save');  // this seems to be a required value


drupal_execute('image_node_form', $form_state, (object) $nodeTmp);
NaX’s picture

For Drupal 6 this post helped me a lot
http://drupal.org/node/293663

ryan_courtnage’s picture

Great thead - very useful!

isaac77’s picture

thanks to all who contributed to this thread! the following might be slightly redundant, but in case it helps someone else:

for 'regular' taxonomies (i.e. taxonomies that do not use free-tagging), setting something like this before calling drupal_execute seems to work:
$values['taxonomy'][1]= array(5,7,8);
1 is the vocabulary id and 5, 7, and 8 are the term ids of the taxonomy terms you want to assign to the newly created node.

i think the following should work for free-tagging taxonomies (though i haven't tested this):
$values['taxonomy'][tags][1]= 'foo, bar';

sinasalek’s picture

If you need to create a translation node, you may get into trouble because drupal_execute() behavior is weired. it took me some time to figure out how it works in this case.

Here is the solution :

$sourceNodeNid=125;//nid of the node you want to translate it
$language='fa';
$form_id = '{your_node_name}_node_form';
$node = array('type' => '{your_node_name}');
$_GET['language']=$language;
$_GET['translation']=$sourceNodeNid;
$form_values = array(
    'title' => 'New node',
    'translation'=>$language,
    'translation_nid'=>$sourceNodeNid,
    'body' => 'This node was submitted programmatically.',
    'taxonomy' => array('tags' => array('1' => ('cool, awesome, programmatically')))
 );
 drupal_execute($form_id, $field_values, $node);

You might wonder why i set $_GET['language'],$_GET['translation']. that's because drupal still use this parameters instead of the hidden fields ('translation_nid','language') which already defined in form.

sina.salek.ws
Feel freedom with open source softwares

sina.salek.ws, Software Manager & Lead developer
Feel freedom with open source softwares

aaustin’s picture

If you're trying to do this with Drupal 6:

http://drupal.org/node/293663

light-blue’s picture

If you need Drupal 5 to load and programmatically submit a CCK-based form which allows all modules to fire their hook_form_alter functions--so that you could take advantage of complex cck defaults (money, address, fullname, etc.)--then do this (note that my $key is your node type)

	global $user;
					
	//create a new node, so this form saves to a node
	$node = new StdClass();
	$node->type = $key;
	$node->uid = $user->uid;
	$node->name = $user->name;
	$node->title = 'awesome content created of type '.$key;
	$node->status=TRUE;
					
	//form args
	$args[]="{$key}_node_form";
	$args[]=$node;

	//from drupal_get_form
	$form = call_user_func_array('drupal_retrieve_form', $args); //returns the array, not HTML like drupal_get_form
	drupal_prepare_form($args[0], $form); //fires all hook_form_alter
					
	//save form into this node type
	drupal_execute($args[0],$args,$node); 

johnhanley’s picture

...because it ignores all hook_nodeapi actions except for 'presave'. IMO calling node_save directly circumvents the system and could lead to unexpected behavior and/or undesired results depending on what other modules you have enabled that depend on hook_nodeapi when creating or updating a node.

light-blue’s picture

My previous post just allows adding with defaults. Here is how to edit (Drupal 5.x):

  $node=node_load(YOUR_NID_NUMBER); 

  //form args
  unset($fargs);
  $fargs[]="YOURCCKNODETYPEHERE_node_form";
  $fargs[]=$node;
  
  $form = call_user_func_array('drupal_retrieve_form', $fargs);

 //cck modifications go here. #parameters[1] is your node.  I'm changing a filefield value.
  $form['#parameters'][1]->YOURFIELD[0]['delete']=1;

 //save -- pretend you submitted the form, REQUIRED FOR certain cck types like filefield and nodereference
  drupal_execute($fargs[0],$fargs,$res);
Summit’s picture

Hi,
How to do this in D6? I also have problems of node-saving trying to update openresort from D5 to d6.

function business_save($node) {
if ($node->type == 'business' && $node->ptype) {
$fields = business_fields();
/* Be very sure that we have a product entry to update! */
if (db_result(db_query('SELECT COUNT(nid) FROM {business} WHERE nid = %d', $node->nid))) {
foreach ($node as $key => $value) {
if (!is_numeric($key) && in_array($key, $fields)) {
if (($key == 'www' or $key == 'email') and strlen($value) > 0) {
$value = business_update_node($node, $value, $key);
}
$q[] = db_escape_string($key) . " = '$value'";
}
}

db_query("UPDATE {business} SET " . implode(', ', $q) . " WHERE nid = %d", $node->nid);

if ($node->ptype) {
module_invoke($node->ptype, 'businessapi', $node, 'update');
}
} else {
foreach ($node as $key => $value) {
if (in_array($key, $fields)) {
$k[] = db_escape_string($key);
//call to function to check if tags are present for a link
if (($key == 'www' or $key == 'email') and strlen($value) > 0) {
$value = business_update_node($node, $value, $key);
}
$v[] = $value;
$s[] = "'%s'";
}
}
db_query('INSERT INTO {business} (' . implode(', ', $k) . ') VALUES(' . implode(', ', $s) . ')', $v);
if ($node->ptype) {
module_invoke($node->ptype, 'businessapi', $node, 'insert');
}
}
_business_update_amemities($node);
}
}

Thanks a lot in advance for your reply!

autopoietic’s picture

Watch out for required file include:

http://www.drupaler.co.uk/blog/drupalexecute-gotcha-drupal-6x/61

ie for node_form:

  $form_id = '<node type>_node_form';
  $form_state['values'] = array();
  $form_state['values']['title'] = 'Programmatically saved node';
  $node = array('type' => '<node type>');

  // node module stores form processing functions in a
  // seperate include entitled node.pages.inc
  // so must load that before executing form
  module_load_include('inc', 'node', 'node.pages'); 
  
  drupal_execute($form_id, $form_state, $node);