background image

Content tagged with: form

Eric's picture

In this tutorial I'll show you how to upload an image using the Forms API, create a new node, and attach the image to the CCK (filefield/imagefield) field. I wrote this code to work with the modules I primarily use for image processing: cck, filefield, imageapi, imagecache, imagefield, mimedetect, and transliteration.

After I installed those modules, I created a new node type (admin/content/types/add) called "Image" and added a single imagefield field.

Image node fields

Next, I created a custom module with a hook_menu() implementation:

<?php
// NOTE: this variable is used through the code,
// so I thought it would be better to put it in a constant
define('IMAGE_UPLOAD_CONTAINER', 'image_upload');

/**
* Implements hook_menu()
*/
function helper_menu() {

 
// create a blank array of menu items
 
$items = array();
 
 
// define page callback for upload form
  // NOTE: you'll want to restrict permission better [see: access arguments]
 
$items['upload'] = array(
   
'title' => t('Upload'),
   
'description' => t('Upload'),
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('helper_page_callback_upload_form'),
   
'access arguments' => array('access content'),
   
'type' => MENU_CALLBACK,
  );
 
 
// return menu items
 
return $items;

}
?>

I defined the form function page callback:

<?php
/**
* Implements page callback for upload form
*/
function helper_page_callback_upload_form() {

 
// create an empty form array
 
$form = array();
 
 
// set the form encoding type
 
$form['#attributes']['enctype'] = "multipart/form-data";
 
 
// add a file upload file
 
$form[IMAGE_UPLOAD_CONTAINER] = array(
   
'#type' => 'file',
   
'#title' => t('Upload an image'),
  );
  
 
// add a submit button
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );
 
 
// return form array
 
return $form;

}
?>

This page callback function results in the following form:

Image node form

Then I added the form validation and submit handler functions:

<?php
/**
* Implements form validation handler
*/
function helper_page_callback_upload_form_validate($form, &$form_state) {

 
// if a file was uploaded, process it.
 
if (isset($_FILES['files']) && is_uploaded_file($_FILES['files']['tmp_name'][IMAGE_UPLOAD_CONTAINER])) {

   
// validate file extension
    // NOTE: you can ellaborate on this code and add additional validation
   
if ($_FILES['files']['type'][IMAGE_UPLOAD_CONTAINER] != 'image/jpeg') {
     
form_set_error(IMAGE_UPLOAD_CONTAINER, 'Invalid file extension.');
      return;
    }

   
// attempt to save the uploaded file
   
$file = file_save_upload(IMAGE_UPLOAD_CONTAINER, array(), file_directory_path());

   
// set error if file was not uploaded
   
if (!$file) {
     
form_set_error(IMAGE_UPLOAD_CONTAINER, 'Error uploading file.');
      return;
    }
      
   
// set files to form_state, to process when form is submitted
   
$form_state['storage'][IMAGE_UPLOAD_CONTAINER] = $file;
      
  }
  else {
   
// set error
   
form_set_error(IMAGE_UPLOAD_CONTAINER, 'Error uploading file.');
    return;  
  }

}

/**
* Implements form submit handler
*/
function helper_page_callback_upload_form_submit($form, &$form_state) {
 
 
// create new node object
 
$new_node = (object) array(
   
'type' => 'image',
   
'uid' => $GLOBALS['user']->uid,
   
'name' => $GLOBALS['user']->name,
   
'title' => t('YOUR NODE TITLE'),
   
'status' => 1,
   
'field_image' => array(
      (array)
$form_state['storage'][IMAGE_UPLOAD_CONTAINER],
    ),
  );
   
 
// save node
 
node_save($new_node);
 
 
// clear form storage, to allow form to submit
 
$form_state['storage'] = array();
 
 
// redirect user, set message, etc!

}
?>

After using the form to upload an image, the following node was created:

New image node

Eric'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);
   
  }

}
?>

Eric's picture

For some time now I've wanted to write a blog entry about using AHAH to create dynamically generated form elements. After a recent conversation at work regarding usability, I now had a real world example to create: how to use tiered taxonomy to dynamically generate a form. This code snippet will show you how to create a form that creates child select dropdowns based on the parent taxonomy term the user selects.

First I established a multi-tier taxonomy called "AHAH":

For this example I created a menu callback to display my initial form:

<?php
function helper_menu() {
 
 
$items = array();
 
 
$items['ahah-form'] = array(
   
'title' => 'AHAH Form',
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('_helper_callback_ahah_form'),
   
'type' => MENU_CALLBACK,
   
'access callback' => 'user_access',
   
'access arguments' => array('access content'),
  );
 
  return
$items;
 
}
?>

I then defined the page callback to show the initial form:

<?php
function _helper_callback_ahah_form() {

 
// define an array to contain form elements
 
$form = array();
 
 
// define the top level vid
 
$vid = 2;
 
 
// fetch a tree of taxonomy elements
 
$tree = taxonomy_get_tree($vid, 0, -1, 1);
 
 
// loop though taxonomy and collect elements
 
$options = array();
  foreach (
$tree as $key => $value) {
   
$options[$value->tid] = $value->name;
  }
 
 
// create the first select dropdown input
 
$form['select_1'] = array(
   
'#type' => 'select',
   
'#options' => $options,
   
'#title' => t('Select 1'),
   
'#size' => 5,
   
'#multiple' => false,
   
'#ahah' => array(
     
'event' => 'change',
     
'path' => 'ahah-form-callback',
     
'wrapper' => 'wrapper-1',
     
'method' => 'replace',
    ),
  );
 
 
// pass the top level vid in the form
 
$form['ahah_vid'] = array(
   
'#type' => 'hidden',
   
'#value' => $vid,
  );
 
 
// create an empty form element to contain the second taxonomy dropdown
 
$form['wrapper_1'] = array(
   
'#prefix' => '<div id="wrapper-1">',
   
'#suffix' => '</div>',
   
'#value' => '&nbsp;',
  );
 
 
// add a form submit button
 
$form['submit'] = array(
   
'#value' => 'Submit',
   
'#type' => 'submit'
 
);
 
  return
$form;
 
}
?>

The above form callback produces the following:

Next, I defined a callback to handle the AHAH page request:

<?php
// new menu item:
function helper_menu() {
 
 
// ...
 
 
$items['ahah-form-callback'] = array(
   
'title' => 'AHAH Form Callback',
   
'page callback' => '_helper_callback_ahah_form_callback',
   
'type' => MENU_CALLBACK,
   
'access callback' => 'user_access',
   
'access arguments' => array('access content'),
  );
 
 
// ...

 
return $items;
 
}

// and, here's the AHAH callback used to create the new form elements:
function _helper_callback_ahah_form_callback() {
 
 
// define a string variable to contain callback output
 
$output = "";
 
 
// pull the top level vid from the $_POST data
 
$vid = $_POST['ahah_vid'];
 
 
// pull the selected dropdown from the $_PODT data
 
$parentVid = $_POST['select_1'];
 
 
// loop through the taxonomy tree and fetch child taxonomies
 
$options = array();
 
$tree = taxonomy_get_tree($vid, $parentVid, -1, 1);  
  foreach (
$tree as $key => $value) {
   
$options[$value->tid] = $value->name;
  }
 
 
// define the second tier select dropdown element
 
$form['select_2'] = array(
   
'#type' => 'select',
   
'#options' => $options,
   
'#title' => t('Select 2'),
   
'#size' => 5,
   
'#multiple' => false,
  );
 
 
// rebuild form object and output new form elements
 
$output .= ahah_render($form, 'select_2');
 
 
// render form output as JSON
 
print drupal_to_js(array('data' => $output, 'status' => true));
 
 
// exit to avoid rendering the theme layer
 
exit();
 
}

// Lastly, here's a help function pulled from Nick Lewis's blog to alter the form
// see: http://www.nicklewis.org/node/967
// NOTE: based on poll module, see: poll_choice_js() function in poll.module
function ahah_render($fields, $name) {
 
$form_state = array('submitted' => FALSE);
 
$form_build_id = $_POST['form_build_id'];
 
// Add the new element to the stored form. Without adding the element to the
  // form, Drupal is not aware of this new elements existence and will not
  // process it. We retreive the cached form, add the element, and resave.
 
$form = form_get_cache($form_build_id, $form_state);
 
$form[$name] = $fields;
 
form_set_cache($form_build_id, $form, $form_state);
 
$form += array(
   
'#post' => $_POST,
   
'#programmed' => FALSE,
  );
 
// Rebuild the form.
 
$form = form_builder($_POST['form_id'], $form, $form_state);

 
// Render the new output.
 
$new_form = $form[$name];
  return
drupal_render($new_form);
}
?>

The above code allows the user to select an option from the top level tier of taxonomy and the AHAH callback will generate the a select dropdown of the child taxonomies as shown below:

On form submission, you'll see that the options the user selected as stored in $form_state['values']['select_1'] and $form_state['values']['select_2']

Eric's picture

In this blog entry, I'll show you how you can add some module code to allow users to select different themes for their profile page. I decided to use the standard user-profile.tpl.php as the base template. I copied this file into my theme folder and replicated it a few times. I named the files:

user-profile-version-1.tpl.php
user-profile-version-2.tpl.php

For my example, I simply added the text "Version 1" and "Version 2" to the top of these files to show it's working, but you could revise the layout, add CSS, etc.

Next, I created a module to contain all the following code. I defined a hook_perm() and hook_menu() function to add a menu local task (tab) to the user page.

<?php
function MYMODULE_perm() {
  return array(
   
'choose profile theme'
 
);
}

function
MYMODULE_menu() {
 
 
$items = array();
 
 
$items['user/%user/choose-profile-them'] = array(
   
'title' => 'Choose Profile Theme',
   
'page callback' => '_MYMODULE_callback_choose_profile_theme',
   
'page arguments' => array(1),
   
'access callback' => 'user_access',
   
'access arguments' => array('choose profile theme'),
   
'type' => MENU_LOCAL_TASK,
  );
 
  return
$items;
 
}
?>

user profile tab

Next, I defined the page callback to create a list of available templates for the user to choose from.

<?php
function _MYMODULE_callback_choose_profile_theme($user) {

 
// create an empty strong variable for page html
 
$html = "";

 
// define a list of available profile templates
 
$profileTemplates = array(
   
'user-profile-version-1' => 'Version 1',
   
'user-profile-version-2' => 'Version 2'
 
);
 
 
// loop through templates and ensure they exist
 
foreach ($profileTemplates as $template => $name) {
    if (!
file_exists(path_to_theme() . '/' . $template . '.tpl.php')) {
      unset(
$profileTemplates[$name]); 
    }
  }
 
  if (
count($profileTemplates)) {
   
$html .= drupal_get_form('_MYMODULE_callback_choose_profile_theme_form', $user, $profileTemplates);
  } else {
   
$html .= "No profile themes currently exist.";
  }
 
  return
$html;
 
}
?>

The following functions define the form array, and validation and submit handlers. When the form is submitted, the template option is saved to the user account variables using the user_save() function.

<?php
function _MYMODULE_callback_choose_profile_theme_form($form_state, $user, $profileTemplates) {

 
$form = array();
 
 
// add a select input element
 
$form['profileTemplate'] = array(
   
'#type' => 'select',
   
'#title' => t('Profile Template'),
   
'#options' => $profileTemplates,
   
'#default_value' => $GLOBALS['user']->profileTemplate
 
);
 
 
// add a submit button
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => t('Submit')
  );
 
 
// add hidden element for userID
 
$form['userID'] = array(
   
'#type' => 'hidden',
   
'#value' => $user->uid
 
);
 
  return
$form;
}

function
_MYMODULE_callback_choose_profile_theme_form_validate($form, &$form_state) {
 
// TODO: add your additional form validation here
 
  // ensure userID submitted matches current user
 
if ($form_state['values']['userID']!=$GLOBALS['user']->uid) {
   
form_set_error('', t('Error processing form.')); 
  }
 
}

function
_MYMODULE_callback_choose_profile_theme_form_submit($form, &$form_state) {

 
// load user object
 
$user = user_load($form_state['values']['userID']);
 
 
// save profile template to user variables
 
user_save($user, array('profileTemplate' => $form_state['values']['profileTemplate']));
 
 
// set a message
 
drupal_set_message('Your profile template has been set.');
 
}
?>

user profile form

Last, I defined the preprocess function to see if a template has been set in the user variables, and add the templates suggestion to change the user profile template.

<?php
function MYMODULE_preprocess_user_profile(&$variables) {

 
// ensure the template file exists, and is set in the user object
 
if (file_exists(path_to_theme() . '/' . $variables['user']->profileTemplate . '.tpl.php') && $variables['user']->profileTemplate) {

   
// set a templates suggestion
   
$variables['template_files'][] = $variables['user']->profileTemplate;
  }
 
}
?>

As you can see below, when I choose the "Version 2" template option, the file "user-profile-version-2.tpl.php" is being loaded, and the text "Version 2" is displayed at the top of the page.

user profile selected

Eric's picture

In this tutorial, I'll show you how you can expose your search form on another site using jQuery. At first, I thought about scraping the form's html using AJAX.. and quickly remembered you cannot easily do that. Which lead me to review the AJAX functionality included in jQuery. Bingo, one of my favorites: jQuery.getJSON. To summarize this code, I create a callback function to display the form's json-ified html which can then be easily embedded on another site.

First I defined the menu hook:

<?php
function MYMODULE_menu() {

 
$items = array();

 
// add a page callback for the url: "external-search.js"
 
$items['external-search.js'] = array(
   
'page callback' => '_MYMODULE_external_search',
   
'type' => MENU_CALLBACK,
   
'access arguments' => array('search content'),
  );
   
  return
$items;
   
}
?>

Then I created the callback function for the menu callback:

<?php
function _MYMODULE_external_search() {

 
// create a json string of the search form html
 
$json = drupal_to_js(drupal_get_form('search_form'));
   
 
// format the json as a callback function
  // see: http://docs.jquery.com/Ajax/jQuery.getJSON for more information
 
if ($_GET['jsoncallback']) {
   
$json = $_GET['jsoncallback'] . "(" . $json . ");";
  }
   
 
// output the json
 
print $json;

 
// stop the script, so the theme layer is not applied
 
die;
}
?>

One problem though, the form submits locally. That can be fixed using a form_alter function:

<?php
function MYMODULE_form_alter(&$form, $form_state, $form_id) {
   
 
// check for external search form and set form action to be full path
 
if ($form_id == 'search_form' && arg(0)=='external-search.js') {
   
// change the form action to be the full path
   
$form['#action'] = 'http://' . $_SERVER['HTTP_HOST'] . $form['#action'];
  }
}
?>

Now, if you clear your cache and go to http://YOURSITE/external-search.js, you should see the JSON (and nothing else).

Lastly, you can embed the code on another site using a few lines of jQuery. You can even pull the jQuery from your site if the external site does not have jQuery included.

<!-- Include jQuery (as necessary) -->
<script type='text/javascript' src='http://YOURSITE/misc/jquery.js' ></script>

<!-- create a div container to contain the search form -->
<div id='embedded_search'></div>

<!-- add the jQuery to embed the form -->
<script type='text/javascript'>
$(document).ready(function(){
  // make the ajax request
  $.getJSON("http://YOURSITE/external-search.js?jsoncallback=?",
    function(data){
      // append the form to the container
      $('#embedded_search').append(data);           
    }
  );
});
</script>

Now people should be able to access your site's search form from another site!