background image
HomeRecent PostsDrupalSearchTagsRSSContactAboutAccount
Eric.London's picture

In this snippet, I'll show you how you can submit a webform programmatically using drupal_execute(). The first thing you'll need to do is figure out what the $form_state data looks like when the webform is submitted, so you can recreate that structure and pass it into drupal_execute().

One way to accomplish this is to add a validation/submit handler to the form using hook_form_alter() and then output the contents of the submitted data (using krumo, print_r, etc).

The following code will prepend a validation handler to the webform $form, so we can dump the submitted data to the screen:

<?php
function MYMODULE_form_alter(&$form, $form_state, $form_id) {
 
 
// define node id of webform
 
$webform_node_id = 146;
 
 
// check for form id of webform
 
if ($form_id == 'webform_client_form_' . $webform_node_id) {
 
   
// prepend a validation callback to the form
   
array_unshift($form['#validate'],
     
'_MYMODULE_form_alter_webform_' . $webform_node_id . '_validate');
 
  }
 
}
?>

And here is the validation handler which will dump the submitted data to the screen. NOTE: krumo() is available from the devel module; you could use print_r() as well:

<?php
function _MYMODULE_form_alter_webform_146_validate($form, &$form_state) {
 
// debug
 
krumo($form_state);
  die;
}
?>

Now, if I populate the webform with some data:

And submit the form, I will get the following debug output:

You'll need to mirror the structure of the submitted data when creating your $form_state variable, which will be passed into drupal_execute().

In the following function, I show how you can submit a webform programmatically. There is even additional code in there which loads a user's previously submitted data, if you care.

<?php
function MYMODULE_some_function() {

 
// define webform node id
 
$webform_node_id = 146;

 
// define user submitting the webform
 
$user_id = 2;

 
// load webform node
 
if ($node = node_load($webform_node_id)) {

   
// load module include file, per loading previously submitted webform data
   
module_load_include('inc', 'webform', 'webform_report');

   
// get submissions for user
   
$submissions = webform_get_submissions($node->nid, NULL, $user_id);

   
// get submission data ($submission) and submission id ($sid)
   
if (FALSE !== ($sid = key($submissions))) {
     
$submission = $submissions[$sid];
    }
    else {
     
$submission = NULL;
     
$sid = NULL;
    }

   
// create array of $form_state data
    // NOTE: be sure to use the debug output as a guide to make this array match!
   
$form_state = array(
     
'submitted' => true,
     
'values' => array(
       
'submission' => $submission,
       
'submitted' => array(
         
'test_field_1' => 'abc',
         
'test_field_2' => '123',
         
'test_field_3' => 'def',
        ),
       
'details' => array(
         
'email_subject' => $node->webform['email_subject'],
         
'email_from_name' => $node->webform['email_from_name'],
         
'email_from_address' => $node->webform['email_from_address'],
         
'nid' => $webform_node_id,
         
'sid' => $sid,
        ),
       
'op' => t('Submit'),
       
'submit' => t('Submit'),
       
'form_id' => 'webform_client_form_'. $webform_node_id,
      ),
    );

   
// Saves the webform data submited prior to login.
   
drupal_execute('webform_client_form_'. $webform_node_id, $form_state, $node, $submission, TRUE, FALSE);
   
  }

}
?>

This code snippet shows you how to refresh a view using AJAX and form controls, and allows users to save their search parameters as nodes. This tutorial consists of 2 major parts: 1) regenerating a view using arguments; and 2) saving these arguments in a node which can be reloaded at a later point.

1) Create a multiple select taxonomy vocabulary and add some terms to it. For my example, I called the vocabulary "Eric's Vocab 1" and assigned 5 terms to it: Term 1, Term 2, Term 3, Term 4, Term 5. It will be used to pass term IDs as arguments to the view.

2) Create a view that has a "Taxonomy: Term ID" argument. Make sure you choose "Display all values" for the action to take if argument is not present. Add a validator for Taxonomy term. Choose "Term IDs separated by , or +" for argument type. Enable allow multiple terms per argument and allow multiple arguments to work together. Add view filters and fields as necessary. For my example, I created a view called "eric_ajax_reload", added filter for published nodes, and added the node title and taxonomy terms as fields.

3) To test my view and argument, I added a bunch of content that had various taxonomy terms assigned to each one. I used the Live Preview functionality at the bottom of the view-edit screen to ensure my view was working with arguments.

4) Create a menu callback to embed the view into a page and bypass the theme layer:

<?php
// define menu item
function ericview_menu() {
 
$items[] = array();

 
$items['ericview/page/view'] = array(
   
'page callback' => 'ericview_callback_page_view',
   
'access arguments' => array('access content'),
   
'title' => t("Eric's Title"),
   
'type' => MENU_CALLBACK,
  );
   
  return
$items;
}

// define menu callback
function ericview_callback_page_view() {
 
$page_content = "";
 
$page_contents .= _ericview_view_create();
  print
$page_contents
 
exit();
}

// define function to create view html
function _ericview_view_create() {

 
// TODO: validate $_GET args
   
  // create view arguments
 
if (is_array($_GET['vocab1'])) {
   
$selectArgs = implode('+', $_GET['vocab1']);
  }

 
$html = "";
 
$viewName = 'eric_ajax_reload';
 
$display_id = 'default';
 
$html .= views_embed_view($viewName, $display_id, $selectArgs);
  return
$html;
}
?>

Now, if you flush your menu cache, you should be able to get to this new page. At this point, modifying the vocab1 query string value and reloading the page should update your view. If not, make sure your argument is working properly using the Live Preview.

5) Create another page callback that will consist of 2 columns: 1 column that has a form used to update the view, and 1 column that shows the view.

<?php
// define menu item
function ericview_menu() {
 
$items[] = array();
 
// ...code...
 
$items['ericview/page'] = array(
   
'page callback' => 'ericview_callback_page',
   
'access arguments' => array('access content'),
   
'title' => t("Eric's Title"),
   
'type' => MENU_CALLBACK,
  );
 
// ...code... 
 
return $items;
}

// define menu callback
function ericview_callback_page() {
   
 
// include css
 
drupal_add_css(drupal_get_path('module','ericview') . '/ericview.css');

 
// include javascript
 
drupal_add_js(drupal_get_path('module','ericview') . '/ericview.js');
   
 
$page_contents = "";
 
$page_contents .= "<div id='eric_column_left'>"
   
. drupal_get_form('_ericview_form') . "</div>";
 
$page_contents .= "<div id='eric_column_right'>"
   
. _ericview_view_create() . "</div>";
 
$page_contents .= "<div class='clearBoth'></div>";
   
  return
$page_contents;
   
}

// create a form that uses the taxonomy vocab as a multiple select element
function _ericview_form() {

 
$form = array();
   
 
$vocabName = "Eric's Vocab 1";
   
 
// TODO: use a function instead :)
 
$sql = "
    select td.tid, td.name
    from {vocabulary} v
    join {term_data} td on td.vid = v.vid
    where v.name = '%s'"
;
 
$resource = db_query($sql, db_escape_string($vocabName));
 
$results = array();
  while (
$row = db_fetch_array($resource)) $results[$row['tid']] = $row['name'];
   
 
$form['vocab1'] = array(
   
'#title' => $vocabName,
   
'#type' => 'select',
   
'#options' => $results,
   
'#multiple' => true,
   
'#attributes' => array(
     
'onChange' => 'ericview_select_change(this);',
    ),
   
'#default_value' => is_array($_GET['vocab1']) ? $_GET['vocab1'] : array(),
  );
   
 
$form['save'] = array(
   
'#type' => 'submit',
   
'#value' => 'Save',
  );
   
  return
$form;
  
}
?>

Here is the jQuery I added to get the selected options from the select element, make the AJAX call to regenerate the view, and update the contents of the right column

<?php
function ericview_select_change(whichThis) {
  var
vocab1Options = new Array();
  $(
'option:selected', whichThis).each(function(i){
   
vocab1Options[i] = $(this).val();
  });
   
 
// define args
  // note: the following will not work
  // var args = { 'vocab1': vocab1Options };
  // generates: ?vocab1=2&vocab1=3&vocab1=4
  // not:       ?vocab1[]=2&vocab1[]=3&vocab1[]=4
 
var args = { };

 
// create get path       
 
var getPath = '/ericview/page/view'   

 
// add query string arguments to get path
 
getArgs = "";
  for (var
i=1; i<=vocab1Options.length; i++) {
    if (
getArgs.length==0) getArgs += "?";
   
getArgs += 'vocab1[]=' + vocab1Options[i-1];
    if (
i != vocab1Options.length) getArgs += "&";
  }
 
getPath = getPath + getArgs;
   
 
// make ajax call using get
 
$.get(getPath, args,
    function(
result){
      if (
result) $('#eric_column_right').html(result);
    }
  );
   
}
?>

And, here is some CSS to create a simple 2 column layout:

.clearBoth {
  clear: both;
}

#eric_column_left {
  float: left;
  width: 25%;
}

#eric_column_right {
  float: left;
  width: 75%;
}

After flushing the menu cache, and going to the new page, you should see a 2 column layout: the left column containing a select element with the taxonomy terms and the right column containing the view. When selecting options in the taxonomy select element, they options should be passed to view as arguments, and the view should be reloaded:

The second part of this tutorial is adding the functionality to save the search as a node. There should be a Save button already on the form that does not do anything. We'll need to add a submit handler to save the selected options into a node. So, first you'll need to create a new node type. For my example, I created a node called "saved_search" with a single textarea field called "field_search_parameters". Here's the code I added to make the submit button function:

<?php
// define a submit handler that serializes the selected form data and creates a node
function _ericview_form_submit($form, &$form_state) {

 
// define a list of values to save
  // TODO: make this more dynamic?
 
$saveValues = array('vocab1');
   
 
$savedParams = array();
  foreach (
$form_state['values'] as $k => $v) {
    if (
in_array($k, $saveValues)) {
     
$savedParams[$k] = $v;
    }
  }
   
 
// create saved search node
 
$node = (object) NULL;
 
$node->type = 'saved_search';
 
$nodeData = array();
 
$nodeData['values'] = array(
   
'title' => 'Saved Search: ' . date('r'),
   
'field_search_parameters' => array(
      array(
       
'value' => serialize($savedParams)
      )
    ),
   
'name' => $GLOBALS['user']->name,
   
'op' => t('Save'),
  );
   
 
// include node file, necessary for node generation
 
module_load_include('inc', 'node', 'node.pages');
   
 
// create node using drupal_execute
 
drupal_execute('saved_search_node_form', $nodeData, $node);
   
}
?>

Now when the user clicks on the save button, a node will be created that contains serialized search form data in a field. If you view one of these newly created nodes, you get some unusable serialized data on the screen:

Instead, you can add a nodeapi hook to redirect the user back to the menu callback:

<?php
function ericview_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  if (
$node->type == 'saved_search' && $op=='view') {
       
   
// unserialize data
   
$data = unserialize($node->field_search_parameters[0]['value']);
       
   
// create query string       
   
$queryString = array();
    foreach (
$data as $k => $v) {
      if (
is_array($v)) {
        foreach (
$v as $v2) {
         
$queryString[] = $k."[]=".$v2;
        }
      } else {
       
$queryString[] = "$k=$v";
      }
    }
   
$queryString = implode("&", $queryString);
       
   
// redirect user
   
drupal_goto('ericview/page', $queryString);
       
  }
}
?>

For my example, I unserialize the data in the node's field and create a query string based on the data. That way, the $_GET parameters will be passed into the form and the view, acting as a saved search.

Here are some tips on how to create a node programmatically in a Drupal module. The safest way to create a node is to use the drupal_execute() function which simulates the submission of a form [see API documentation]. This function accepts a variable number of arguments: 1) $form_id; 2) $form_state; and 3) any additional arguments. When creating a new node, the 3rd argument should be a $node object.

The $form_id should be formatted: NODETYPE_node_form. To confirm this, you could use hook_form_alter to show the form IDs for the current page (NOTE: you'll have to be on node/add/NODETYPE to display the node form ID).

<?php
function MYMODULE_form_alter(&$form, $form_state, $form_id) {
  echo
$form_id . "<BR>";
}
?>

For the second argument, you'll have to create an array that contains $form_state values (the data submitted from the node/add/NODETYPE form). Here is a basic $form_state array:

<?php
$form_state
= array();
$form_state['values'] = array(
 
'title' => t('MYTITLE'),
 
'body' => t('MYBODY'),
 
'name' => $GLOBALS['user']->name,
 
'op' => t('Save'),
);
?>

If you are creating a more complex node type, you'll want to include more information in in the $form_state['values'] array. I find it helpful to view the submitted node form data using the following code:

<?php
function MYMODULE_form_alter(&$form, $form_state, $form_id) {
 
// ensure we are modifying the right node type
 
if ($form_id == 'NODETYPE_node_form') {
   
// add an additional validation handler
   
$form['#validate'][] = '_MYMODULE_node_form_validate';
  }
}

function
_MYMODULE_node_form_validate($form, &$form_state) {
 
// display the submitted data and then die
 
echo "<pre>";
 
print_r($form_state['values']);
  echo
"</pre>";
  die;
}
?>

Now, if you create a test node (node/add/NODETYPE), you'll see the data that's being submitted from the form. NOTE: be sure to remove these two functions when you are familiar with the structure of the $form_state variable.

The 3rd argument will be a (mostly) empty $node object containing the node type:

<?php
$node
= new StdClass();
$node->type = 'NODETYPE';
?>

Here's a code snippet that shows all the pieces together:

<?php
// create $form_state
$form_state = array();
$form_state['values'] = array(
 
'title' => t('MYTITLE'),
 
'body' => t('MYBODY'),
 
'name' => $GLOBALS['user']->name,
 
'op' => t('Save'),
);

// create $node
$node = (object) NULL;
$node->type = 'NODETYPE';

// include node file, necessary for node generation
module_load_include('inc', 'node', 'node.pages');

// create node using drupal_execute
drupal_execute('NODETYPE_node_form', $form_state, $node);
?>

Syndicate content