Newer
Older
* @defgroup forms Form builder functions
* @{
* Functions that build an abstract representation of a HTML form.
*
* All modules should declare their form builder functions to be in this
* group and each builder function should reference its validate and submit
* functions using \@see. Conversely, validate and submit functions should
* reference the form builder function using \@see. For examples, of this see
* system_modules_uninstall() or user_pass(), the latter of which has the
* following in its doxygen documentation:
*
* \@ingroup forms
* \@see user_pass_validate().
* \@see user_pass_submit().
*
* @} End of "defgroup forms".
*/
/**
* @defgroup form_api Form generation

Dries Buytaert
committed
* Functions to enable the processing and display of HTML forms.

Dries Buytaert
committed
* Drupal uses these functions to achieve consistency in its form processing and
* presentation, while simplifying code and reducing the amount of HTML that
* must be explicitly generated by modules.
*
* The drupal_get_form() function handles retrieving and processing an HTML

Dries Buytaert
committed
* form for modules automatically. For example:

Dries Buytaert
committed
*
* // Display the user registration form.
* $output = drupal_get_form('user_register_form');

Dries Buytaert
committed
*
* Forms can also be built and submitted programmatically without any user input

Dries Buytaert
committed
* using the drupal_form_submit() function.

Dries Buytaert
committed
*
* For information on the format of the structured arrays used to define forms,

Dries Buytaert
committed
* and more detailed explanations of the Form API workflow, see the
* @link http://api.drupal.org/api/file/developer/topics/forms_api_reference.html reference @endlink
* and the @link http://api.drupal.org/api/file/developer/topics/forms_api.html quickstart guide. @endlink

Dries Buytaert
committed
* Wrapper for drupal_build_form() for use when $form_state is not needed.

Dries Buytaert
committed
* The unique string identifying the desired form. If a function with that
* name exists, it is called to build the form array. Modules that need to
* generate the same form (or very similar forms) using different $form_ids
* can implement hook_forms(), which maps different $form_id values to the
* proper form constructor function. Examples may be found in node_forms(),
* search_forms(), and user_forms().

Dries Buytaert
committed
* @param ...
* Any additional arguments are passed on to the functions called by

Dries Buytaert
committed
* drupal_get_form(), including the unique form constructor function. For
* example, the node_edit form requires that a node object is passed in here
* when it is called.

Dries Buytaert
committed
*

Dries Buytaert
committed
* @return

Dries Buytaert
committed
* The form array.

Dries Buytaert
committed
*
* @see drupal_build_form()

Dries Buytaert
committed
*/
function drupal_get_form($form_id) {

Dries Buytaert
committed
$form_state = array();

Dries Buytaert
committed
$args = func_get_args();

Dries Buytaert
committed
// Remove $form_id from the arguments.
array_shift($args);

Angie Byron
committed
$form_state['build_info']['args'] = $args;

Dries Buytaert
committed
return drupal_build_form($form_id, $form_state);
}
/**

Dries Buytaert
committed
* Build and process a form based on a form id.

Dries Buytaert
committed
*
* The form may also be retrieved from the cache if the form was built in a
* previous page-load. The form is then passed on for processing, validation

Dries Buytaert
committed
* and submission if there is proper input.

Dries Buytaert
committed
*
* @param $form_id
* The unique string identifying the desired form. If a function with that
* name exists, it is called to build the form array. Modules that need to
* generate the same form (or very similar forms) using different $form_ids
* can implement hook_forms(), which maps different $form_id values to the
* proper form constructor function. Examples may be found in node_forms(),
* search_forms(), and user_forms().
* @param &$form_state
* An array which stores information about the form. This is passed as a

Angie Byron
committed
* reference so that the caller can use it to examine what in the form changed

Dries Buytaert
committed
* when the form submission process is complete. Furthermore, it may be used
* to store information related to the processed data in the form, which will
* persist across page requests when the 'cache' or 'rebuild' flag is set.

Dries Buytaert
committed
* The following parameters may be set in $form_state to affect how the form
* is rendered:

Angie Byron
committed
* - build_info: A keyed array of build information that is necessary to
* rebuild the form from cache when the original context may no longer be
* available:
* - args: An array of arguments to pass to the form builder.
* - file: An optional include file that contains the form and is
* automatically loaded by form_get_cache(). Defaults to the current menu
* router item's 'file' definition, if existent.

Dries Buytaert
committed
* - rebuild: Normally, after the entire form processing is completed and
* submit handlers ran, a form is considered to be done and
* drupal_redirect_form() will redirect the user to a new page using a GET
* request (so a browser refresh does not re-submit the form). However, if
* 'rebuild' has been set to TRUE, then a new copy of the form is
* immediately built and sent to the browser; instead of a redirect. This is
* used for multi-step forms, such as wizards and confirmation forms. Also,
* if a form validation handler has set 'rebuild' to TRUE and a validation
* error occurred, then the form is rebuilt prior to being returned,
* enabling form elements to be altered, as appropriate to the particular
* validation error.

Dries Buytaert
committed
* - input: An array of input that corresponds to $_POST or $_GET, depending
* on the 'method' chosen (see below).
* - method: The HTTP form method to use for finding the input for this form.
* May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method
* forms do not use form ids so are always considered to be submitted, which
* can have unexpected effects. The 'get' method should only be used on
* forms that do not change data, as that is exclusively the domain of post.
* - no_redirect: If set to TRUE the form will NOT perform a drupal_goto(),
* even if 'redirect' is set.
* - cache: If set to TRUE the original, unprocessed form structure will be
* cached, which allows to rebuild the entire form from cache.
* - no_cache: If set to TRUE the form will NOT be cached, even if 'cache' is
* set.

Dries Buytaert
committed
* - always_process: If TRUE and the method is GET, a form_id is not
* necessary. This should only be used on RESTful GET forms that do NOT
* write data, as this could lead to security issues. It is useful so that
* searches do not need to have a form_id in their query arguments to
* trigger the search.
* - must_validate: Ordinarily, a form is only validated once but there are
* times when a form is resubmitted internally and should be validated
* again. Setting this to TRUE will force that to happen. This is most
* likely to occur during AHAH or AJAX operations.

Dries Buytaert
committed
* - temporary: An array holding temporary data accessible during the current
* page request only. It may be used to temporary save any data that doesn't
* need to or shouldn't be cached during the whole form workflow, e.g. data
* that needs to be accessed during the current form build process only.
* - wrapper_callback: Modules that wish to pre-populate certain forms with
* common elements, such as back/next/save buttons in multi-step form
* wizards, may define a form builder function name that returns a form
* structure, which is passed on to the actual form builder function.
* Such implementations may either define the 'wrapper_callback' via
* hook_forms() or have to invoke drupal_build_form() (instead of
* drupal_get_form()) on their own in a custom menu callback to prepare
* $form_state accordingly.

Dries Buytaert
committed
* Further $form_state properties controlling the redirection behavior after
* form submission may be found in drupal_redirect_form().
*

Dries Buytaert
committed
* @return
* The rendered form or NULL, depending upon the $form_state flags that were set.

Dries Buytaert
committed
*
* @see drupal_redirect_form()

Dries Buytaert
committed
*/
function drupal_build_form($form_id, &$form_state) {
// Ensure some defaults; if already set they will not be overridden.
$form_state += form_state_defaults();
if (!isset($form_state['input'])) {
$form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST;
}

Dries Buytaert
committed
if (isset($_SESSION['batch_form_state'])) {
// We've been redirected here after a batch processing : the form has
// already been processed, so we grab the post-process $form_state value
// and move on to form display. See _batch_finished() function.
$form_state = $_SESSION['batch_form_state'];
unset($_SESSION['batch_form_state']);
}
else {

Dries Buytaert
committed
// If the incoming input contains a form_build_id, we'll check the

Dries Buytaert
committed
// cache for a copy of the form in question. If it's there, we don't
// have to rebuild the form to proceed. In addition, if there is stored
// form_state data from a previous step, we'll retrieve it so it can
// be passed on to the form processing code.

Dries Buytaert
committed
if (isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id'])) {
$form_build_id = $form_state['input']['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);
}

Dries Buytaert
committed
// If the previous bit of code didn't result in a populated $form
// object, we're hitting the form for the first time and we need
// to build it from scratch.
if (!isset($form)) {
// Record the filepath of the include file containing the original form,
// so the form builder callbacks can be loaded when the form is being

Dries Buytaert
committed
// rebuilt from cache on a different path (such as 'system/ajax'). See
// form_get_cache().

Dries Buytaert
committed
// $menu_get_item() is not available at installation time.

Angie Byron
committed
if (!isset($form_state['build_info']['file']) && !defined('MAINTENANCE_MODE')) {
$item = menu_get_item();

Dries Buytaert
committed
if (!empty($item['include_file'])) {
$form_state['build_info']['file'] = $item['include_file'];
}
}

Angie Byron
committed
$form = drupal_retrieve_form($form_id, $form_state);
$form_build_id = 'form-' . md5(uniqid(mt_rand(), TRUE));
$form['#build_id'] = $form_build_id;

Dries Buytaert
committed
// Fix the form method, if it is 'get' in $form_state, but not in $form.
if ($form_state['method'] == 'get' && !isset($form['#method'])) {
$form['#method'] = 'get';
}

Dries Buytaert
committed
drupal_prepare_form($form_id, $form, $form_state);
// Store a copy of the unprocessed form to cache in case
// $form_state['cache'] is set.

Gábor Hojtsy
committed
$original_form = $form;
}

Dries Buytaert
committed

Dries Buytaert
committed
// Now that we know we have a form, we'll process it (validating,
// submitting, and handling the results returned by its submission
// handlers. Submit handlers accumulate data in the form_state by
// altering the $form_state variable, which is passed into them by
// reference.
drupal_process_form($form_id, $form, $form_state);
}
// Most simple, single-step forms will be finished by this point --
// drupal_process_form() usually redirects to another page (or to
// a 'fresh' copy of the form) once processing is complete. If one
// of the form's handlers has set $form_state['redirect'] to FALSE,
// the form will simply be re-rendered with the values still in its
// fields.
//
// If $form_state['rebuild'] has been set and input has been processed, we

Dries Buytaert
committed
// know that we're in a multi-part process of some sort and the form's
// workflow is not complete. We need to construct a fresh copy of the form,
// passing in the latest $form_state in addition to any other variables passed

Dries Buytaert
committed
// into drupal_get_form().
if ($form_state['rebuild'] && $form_state['process_input'] && !form_get_errors()) {

Dries Buytaert
committed
$form = drupal_rebuild_form($form_id, $form_state);

Dries Buytaert
committed
}
// After processing the form, the form builder or a #process callback may
// have set $form_state['cache'] to indicate that the original form and the
// $form_state shall be cached. But the form may only be cached if the
// special 'no_cache' property is not set to TRUE and we are not rebuilding.
elseif (isset($form_build_id) && $form_state['cache'] && empty($form_state['no_cache'])) {
// Cache the original, unprocessed form upon initial build of the form.
if (isset($original_form)) {
form_set_cache($form_build_id, $original_form, $form_state);
}
// After processing a cached form, only update the cached form state.
else {
form_set_cache($form_build_id, NULL, $form_state);
}
}

Dries Buytaert
committed
// Don't override #theme if someone already set it.
if (!isset($form['#theme'])) {

Dries Buytaert
committed
drupal_theme_initialize();

Dries Buytaert
committed
$registry = theme_get_registry();
if (isset($registry[$form_id])) {
$form['#theme'] = $form_id;
}
}

Dries Buytaert
committed

Dries Buytaert
committed
return $form;

Dries Buytaert
committed
}

Dries Buytaert
committed

Dries Buytaert
committed
/**
* Retrieve default values for the $form_state array.
*/
function form_state_defaults() {
return array(
'rebuild' => FALSE,
'redirect' => NULL,

Dries Buytaert
committed
'build_info' => array('args' => array()),
'temporary' => array(),

Dries Buytaert
committed
'submitted' => FALSE,
'programmed' => FALSE,
'cache'=> FALSE,
'method' => 'post',
'groups' => array(),
'buttons' => array(),

Dries Buytaert
committed
);
}

Gábor Hojtsy
committed
/**

Dries Buytaert
committed
* Retrieves a form, caches it and processes it again.

Gábor Hojtsy
committed
*
* If your AJAX callback simulates the pressing of a button, then your AJAX
* callback will need to do the same as what drupal_get_form() would do when the

Gábor Hojtsy
committed
* button is pressed: get the form from the cache, run drupal_process_form over
* it and then if it needs rebuild, run drupal_rebuild_form() over it. Then send

Gábor Hojtsy
committed
* back a part of the returned form.
* $form_state['triggering_element']['#array_parents'] will help you to find
* which part.
* @see ajax_form_callback() for an example.

Gábor Hojtsy
committed
*
* @param $form_id
* The unique string identifying the desired form. If a function
* with that name exists, it is called to build the form array.
* Modules that need to generate the same form (or very similar forms)
* using different $form_ids can implement hook_forms(), which maps
* different $form_id values to the proper form constructor function. Examples
* may be found in node_forms(), search_forms(), and user_forms().
* @param $form_state

Dries Buytaert
committed
* A keyed array containing the current state of the form.

Dries Buytaert
committed
* @param $old_form
* (optional) A previously built $form. Used to retain the #build_id and
* #action properties in AJAX callbacks and similar partial form rebuilds.
* Should not be passed for regular rebuilds, for which the entire $form
* should be rebuilt freshly.
*

Gábor Hojtsy
committed
* @return
* The newly built form.
*/

Dries Buytaert
committed
function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {

Dries Buytaert
committed
// AJAX and other contexts may call drupal_rebuild_form() even when
// $form_state['rebuild'] isn't set, but _form_builder_handle_input_element()
// needs to distinguish a rebuild from an initial build in order to process
// user input correctly. Form constructors and form processing functions may
// also need to handle a rebuild differently than an initial build.
$form_state['rebuild'] = TRUE;

Dries Buytaert
committed
$form = drupal_retrieve_form($form_id, $form_state);

Gábor Hojtsy
committed

Dries Buytaert
committed
// If only parts of the form will be returned to the browser (e.g. AJAX or
// RIA clients), re-use the old #build_id to not require client-side code to
// manually update the hidden 'build_id' input element.
// Otherwise, a new #build_id is generated, to not clobber the previous
// build's data in the form cache; also allowing the user to go back to an
// earlier build, make changes, and re-submit.
$form['#build_id'] = isset($old_form['#build_id']) ? $old_form['#build_id'] : 'form-' . md5(mt_rand());
// #action defaults to request_uri(), but in case of AJAX and other partial
// rebuilds, the form is submitted to an alternate URL, and the original
// #action needs to be retained.
if (isset($old_form['#action'])) {
$form['#action'] = $old_form['#action'];

Gábor Hojtsy
committed
}

Dries Buytaert
committed

Gábor Hojtsy
committed
drupal_prepare_form($form_id, $form, $form_state);
if (empty($form_state['no_cache'])) {

Dries Buytaert
committed
// We cache the form structure and the form state so it can be retrieved
// later for validation.

Dries Buytaert
committed
form_set_cache($form['#build_id'], $form, $form_state);

Dries Buytaert
committed
}

Gábor Hojtsy
committed

Dries Buytaert
committed
// Clear out all group associations as these might be different when
// re-rendering the form.
$form_state['groups'] = array();

Dries Buytaert
committed
// Do not call drupal_process_form(), since it would prevent the rebuilt form
// to submit.
$form = form_builder($form_id, $form, $form_state);

Gábor Hojtsy
committed
return $form;
}
/**
* Fetch a form from cache.
*/
function form_get_cache($form_build_id, &$form_state) {
if ($cached = cache_get('form_' . $form_build_id, 'cache_form')) {
$form = $cached->data;
global $user;
if ((isset($form['#cache_token']) && drupal_valid_token($form['#cache_token'])) || (!isset($form['#cache_token']) && !$user->uid)) {

Angie Byron
committed
if ($cached = cache_get('form_state_' . $form_build_id, 'cache_form')) {
// Re-populate $form_state for subsequent rebuilds.

Dries Buytaert
committed
$form_state = $cached->data + $form_state;

Angie Byron
committed
// If the original form is contained in an include file, load the file.

Dries Buytaert
committed
// See drupal_build_form().

Angie Byron
committed
if (!empty($form_state['build_info']['file']) && file_exists($form_state['build_info']['file'])) {
require_once DRUPAL_ROOT . '/' . $form_state['build_info']['file'];
}
}
}
}
/**

Angie Byron
committed
* Store a form in the cache.
*/
function form_set_cache($form_build_id, $form, $form_state) {

Dries Buytaert
committed
// 6 hours cache life time for forms should be plenty.
$expire = 21600;
// Cache form structure.
if (isset($form)) {
if ($GLOBALS['user']->uid) {
$form['#cache_token'] = drupal_get_token();
}
cache_set('form_' . $form_build_id, $form, 'cache_form', REQUEST_TIME + $expire);
// Cache form state.

Dries Buytaert
committed
if ($data = array_diff_key($form_state, array_flip(form_state_keys_no_cache()))) {

Angie Byron
committed
cache_set('form_state_' . $form_build_id, $data, 'cache_form', REQUEST_TIME + $expire);
}
}

Dries Buytaert
committed
/**
* Returns an array of $form_state keys that shouldn't be cached.
*/
function form_state_keys_no_cache() {
return array(
// Public properties defined by form constructors and form handlers.
'always_process',
'must_validate',
'rebuild',
'redirect',
'no_redirect',
'temporary',
// Internal properties defined by form processing.
'buttons',
'triggering_element',

Dries Buytaert
committed
'clicked_button',
'complete form',
'groups',
'input',
'method',
'submit_handlers',
'submitted',
'validate_handlers',
'values',
);
}
/**

Dries Buytaert
committed
* Retrieves, populates, and processes a form.
*
* This function allows you to supply values for form elements and submit a
* form for processing. Compare to drupal_get_form(), which also builds and
* processes a form, but does not allow you to supply values.
*
* There is no return value, but you can check to see if there are errors
* by calling form_get_errors().
*
* @param $form_id
* The unique string identifying the desired form. If a function
* with that name exists, it is called to build the form array.
* Modules that need to generate the same form (or very similar forms)
* using different $form_ids can implement hook_forms(), which maps

Dries Buytaert
committed
* different $form_id values to the proper form constructor function. Examples
* may be found in node_forms(), search_forms(), and user_forms().

Dries Buytaert
committed
* @param $form_state

Dries Buytaert
committed
* A keyed array containing the current state of the form. Most important is
* the $form_state['values'] collection, a tree of data used to simulate the
* incoming $_POST information from a user's form submission. If a key is not
* filled in $form_state['values'], then the default value of the respective
* element is used. To submit an unchecked checkbox or other control that
* browsers submit by not having a $_POST entry, include the key, but set the
* value to NULL.
* @param ...
* Any additional arguments are passed on to the functions called by

Dries Buytaert
committed
* drupal_form_submit(), including the unique form constructor function.
* For example, the node_edit form requires that a node object be passed
* in here when it is called.

Dries Buytaert
committed
* $form_state = array();
* $form_state['values']['name'] = 'robo-user';
* $form_state['values']['mail'] = 'robouser@example.com';

Dries Buytaert
committed
* $form_state['values']['pass']['pass1'] = 'password';
* $form_state['values']['pass']['pass2'] = 'password';

Gábor Hojtsy
committed
* $form_state['values']['op'] = t('Create new account');
* drupal_form_submit('user_register_form', $form_state);

Dries Buytaert
committed
* $form_state = array();

Gábor Hojtsy
committed
* module_load_include('inc', 'node', 'node.pages');
* $node = array('type' => 'story');

Dries Buytaert
committed
* $form_state['values']['title'] = 'My node';
* $form_state['values']['body'] = 'This is the body text!';
* $form_state['values']['name'] = 'robo-user';

Gábor Hojtsy
committed
* $form_state['values']['op'] = t('Save');

Dries Buytaert
committed
* drupal_form_submit('story_node_form', $form_state, (object)$node);
*/

Dries Buytaert
committed
function drupal_form_submit($form_id, &$form_state) {

Angie Byron
committed
if (!isset($form_state['build_info']['args'])) {

Dries Buytaert
committed
$args = func_get_args();
array_shift($args);
array_shift($args);

Angie Byron
committed
$form_state['build_info']['args'] = $args;

Dries Buytaert
committed
}

Dries Buytaert
committed
// Merge in default values.
$form_state += form_state_defaults();

Dries Buytaert
committed
$form = drupal_retrieve_form($form_id, $form_state);
$form_state['input'] = $form_state['values'];
$form_state['programmed'] = TRUE;
// Programmed forms are always submitted.
$form_state['submitted'] = TRUE;
// Reset form validation.
$form_state['must_validate'] = TRUE;
form_clear_error();

Dries Buytaert
committed
drupal_prepare_form($form_id, $form, $form_state);
drupal_process_form($form_id, $form, $form_state);
}

Dries Buytaert
committed
/**
* Retrieves the structured array that defines a given form.
*
* @param $form_id
* The unique string identifying the desired form. If a function
* with that name exists, it is called to build the form array.
* Modules that need to generate the same form (or very similar forms)
* using different $form_ids can implement hook_forms(), which maps

Dries Buytaert
committed
* different $form_id values to the proper form constructor function.

Dries Buytaert
committed
* @param $form_state

Dries Buytaert
committed
* A keyed array containing the current state of the form, including the
* additional arguments to drupal_get_form() or drupal_form_submit() in the
* 'args' component of the array.

Dries Buytaert
committed
*/

Dries Buytaert
committed
function drupal_retrieve_form($form_id, &$form_state) {

Dries Buytaert
committed
$forms = &drupal_static(__FUNCTION__);

Dries Buytaert
committed

Dries Buytaert
committed
// We save two copies of the incoming arguments: one for modules to use

Dries Buytaert
committed
// when mapping form ids to constructor functions, and another to pass to

Dries Buytaert
committed
// the constructor function itself.

Angie Byron
committed
$args = $form_state['build_info']['args'];

Dries Buytaert
committed
// We first check to see if there's a function named after the $form_id.
// If there is, we simply pass the arguments on to it to get the form.
if (!function_exists($form_id)) {

Dries Buytaert
committed
// In cases where many form_ids need to share a central constructor function,

Dries Buytaert
committed
// such as the node editing form, modules can implement hook_forms(). It

Dries Buytaert
committed
// maps one or more form_ids to the correct constructor functions.

Dries Buytaert
committed
//
// We cache the results of that hook to save time, but that only works
// for modules that know all their form_ids in advance. (A module that
// adds a small 'rate this comment' form to each comment in a list
// would need a unique form_id for each one, for example.)
//
// So, we call the hook if $forms isn't yet populated, OR if it doesn't
// yet have an entry for the requested form_id.
if (!isset($forms) || !isset($forms[$form_id])) {

Dries Buytaert
committed
$forms = module_invoke_all('forms', $form_id, $args);

Dries Buytaert
committed
}
$form_definition = $forms[$form_id];
if (isset($form_definition['callback arguments'])) {
$args = array_merge($form_definition['callback arguments'], $args);
}
if (isset($form_definition['callback'])) {
$callback = $form_definition['callback'];
}
// In case $form_state['wrapper_callback'] is not defined already, we also
// allow hook_forms() to define one.
if (!isset($form_state['wrapper_callback']) && isset($form_definition['wrapper_callback'])) {
$form_state['wrapper_callback'] = $form_definition['wrapper_callback'];
}

Dries Buytaert
committed
}

Dries Buytaert
committed
$form = array();
// We need to pass $form_state by reference in order for forms to modify it,
// since call_user_func_array() requires that referenced variables are passed
// explicitly.
$args = array_merge(array($form, &$form_state), $args);
// When the passed $form_state (not using drupal_get_form()) defines a
// 'wrapper_callback', then it requests to invoke a separate (wrapping) form
// builder function to pre-populate the $form array with form elements, which
// the actual form builder function ($callback) expects. This allows for
// pre-populating a form with common elements for certain forms, such as

Dries Buytaert
committed
// back/next/save buttons in multi-step form wizards. See drupal_build_form().
if (isset($form_state['wrapper_callback']) && function_exists($form_state['wrapper_callback'])) {
$form = call_user_func_array($form_state['wrapper_callback'], $args);
// Put the prepopulated $form into $args.
$args[0] = $form;
}

Dries Buytaert
committed

Dries Buytaert
committed
// If $callback was returned by a hook_forms() implementation, call it.
// Otherwise, call the function named after the form id.
$form = call_user_func_array(isset($callback) ? $callback : $form_id, $args);

Dries Buytaert
committed
$form['#form_id'] = $form_id;
return $form;

Dries Buytaert
committed
}
/**

Dries Buytaert
committed
* Processes a form submission.
*

Dries Buytaert
committed
* This function is the heart of form API. The form gets built, validated and in
* appropriate cases, submitted.
*
* @param $form_id
* The unique string identifying the current form.
* @param $form
* An associative array containing the structure of the form.

Dries Buytaert
committed
* @param $form_state
* A keyed array containing the current state of the form. This
* includes the current persistent storage data for the form, and

Dries Buytaert
committed
* any data passed along by earlier steps when displaying a
* multi-step form. Additional information, like the sanitized $_POST
* data, is also accumulated here.

Dries Buytaert
committed
function drupal_process_form($form_id, &$form, &$form_state) {
$form_state['values'] = array();

Dries Buytaert
committed
// With $_GET, these forms are always submitted if requested.
if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) {
if (!isset($form_state['input']['form_build_id'])) {
$form_state['input']['form_build_id'] = $form['#build_id'];
}
if (!isset($form_state['input']['form_id'])) {
$form_state['input']['form_id'] = $form_id;
}
if (!isset($form_state['input']['form_token']) && isset($form['#token'])) {
$form_state['input']['form_token'] = drupal_get_token($form['#token']);
}
}

Dries Buytaert
committed
$form = form_builder($form_id, $form, $form_state);
// Only process the input if we have a correct form submission.
if ($form_state['process_input']) {

Dries Buytaert
committed
drupal_validate_form($form_id, $form, $form_state);
// drupal_html_id() maintains a cache of element IDs it has seen,
// so it can prevent duplicates. We want to be sure we reset that
// cache when a form is processed, so scenarios that result in
// the form being built behind the scenes and again for the
// browser don't increment all the element IDs needlessly.
drupal_static_reset('drupal_html_id');
if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) {
// Execute form submit handlers.

Dries Buytaert
committed
form_execute_handlers('submit', $form, $form_state);
// We'll clear out the cached copies of the form and its stored data
// here, as we've finished with them. The in-memory copies are still
// here, though.
if (variable_get('cache', CACHE_DISABLED) == CACHE_DISABLED && !empty($form_state['values']['form_build_id'])) {
cache_clear_all('form_' . $form_state['values']['form_build_id'], 'cache_form');
cache_clear_all('storage_' . $form_state['values']['form_build_id'], 'cache_form');

Dries Buytaert
committed
}
// If batches were set in the submit handlers, we process them now,

Gábor Hojtsy
committed
// possibly ending execution. We make sure we do not react to the batch
// that is already being processed (if a batch operation performs a

Dries Buytaert
committed
// drupal_form_submit).

Gábor Hojtsy
committed
if ($batch =& batch_get() && !isset($batch['current_set'])) {
// Store $form_state information in the batch definition.
// We need the full $form_state when either:
// - Some submit handlers were saved to be called during batch
// processing. See form_execute_handlers().
// - The form is multistep.
// In other cases, we only need the information expected by
// drupal_redirect_form().
if ($batch['has_form_submits'] || !empty($form_state['rebuild'])) {
$batch['form_state'] = $form_state;
}
else {
$batch['form_state'] = array_intersect_key($form_state, array_flip(array('programmed', 'rebuild', 'storage', 'no_redirect', 'redirect')));
}

Dries Buytaert
committed
$batch['progressive'] = !$form_state['programmed'];
batch_process();

Dries Buytaert
committed
// Execution continues only for programmatic forms.
// For 'regular' forms, we get redirected to the batch processing
// page. Form redirection will be handled in _batch_finished(),
// after the batch is processed.
}

Dries Buytaert
committed
// Set a flag to indicate the the form has been processed and executed.
$form_state['executed'] = TRUE;

Dries Buytaert
committed
// Redirect the form based on values in $form_state.
drupal_redirect_form($form_state);

Dries Buytaert
committed
}
}
}
/**
* Prepares a structured form array by adding required elements,
* executing any hook_form_alter functions, and optionally inserting
* a validation token to prevent tampering.
*
* @param $form_id
* A unique string identifying the form for validation, submission,
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.

Dries Buytaert
committed
* @param $form_state
* A keyed array containing the current state of the form. Passed
* in here so that hook_form_alter() calls can use it, as well.

Dries Buytaert
committed
*/

Dries Buytaert
committed
function drupal_prepare_form($form_id, &$form, &$form_state) {
global $user;

Dries Buytaert
committed
$form['#type'] = 'form';

Dries Buytaert
committed
$form_state['programmed'] = isset($form_state['programmed']) ? $form_state['programmed'] : FALSE;

Dries Buytaert
committed
if (isset($form['#build_id'])) {
$form['form_build_id'] = array(
'#type' => 'hidden',
'#value' => $form['#build_id'],
'#id' => $form['#build_id'],
'#name' => 'form_build_id',
);
}
// Add a token, based on either #token or form_id, to any form displayed to
// authenticated users. This ensures that any submitted form was actually
// requested previously by the user and protects against cross site request
// forgeries.

Dries Buytaert
committed
// This does not apply to programmatically submitted forms. Furthermore, since
// tokens are session-bound and forms displayed to anonymous users are very
// likely cached, we cannot assign a token for them.
// During installation, there is no $user yet.
if (!empty($user->uid) && !$form_state['programmed']) {
// Form constructors may explicitly set #token to FALSE when cross site
// request forgery is irrelevant to the form, such as search forms.
if (isset($form['#token']) && $form['#token'] === FALSE) {

Gerhard Killesreiter
committed
unset($form['#token']);

Dries Buytaert
committed
// Otherwise, generate a public token based on the form id.

Gerhard Killesreiter
committed
else {

Dries Buytaert
committed
$form['#token'] = $form_id;
$form['form_token'] = array(
'#id' => drupal_html_id('edit-' . $form_id . '-form-token'),
'#type' => 'token',
'#default_value' => drupal_get_token($form['#token']),
);

Gerhard Killesreiter
committed
}
if (isset($form_id)) {

Dries Buytaert
committed
$form['form_id'] = array(
'#type' => 'hidden',
'#value' => $form_id,
'#id' => drupal_html_id("edit-$form_id"),

Dries Buytaert
committed
);
if (!isset($form['#id'])) {
$form['#id'] = drupal_html_id($form_id);
$form += element_info('form');

Dries Buytaert
committed
$form += array('#tree' => FALSE, '#parents' => array());
if (function_exists($form_id . '_validate')) {
$form['#validate'] = array($form_id . '_validate');

Dries Buytaert
committed
if (!isset($form['#submit'])) {
if (function_exists($form_id . '_submit')) {
// We set submit here so that it can be altered.
$form['#submit'] = array($form_id . '_submit');
// Invoke hook_form_FORM_ID_alter() implementations.
drupal_alter('form_' . $form_id, $form, $form_state);
// Invoke hook_form_alter() implementations.
drupal_alter('form', $form, $form_state, $form_id);

Dries Buytaert
committed
/**

Dries Buytaert
committed
* Validates user-submitted form data from the $form_state using

Dries Buytaert
committed
* the validate functions defined in a structured form array.
*
* @param $form_id
* A unique string identifying the form for validation, submission,
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form, which is passed
* by reference. Form validation handlers are able to alter the form structure
* (like #process and #after_build callbacks during form building) in case of
* a validation error. If a validation handler alters the form structure, it
* is responsible for validating the values of changed form elements in
* $form_state['values'] to prevent form submit handlers from receiving
* unvalidated values.

Dries Buytaert
committed
* @param $form_state
* A keyed array containing the current state of the form. The current
* user-submitted data is stored in $form_state['values'], though
* form validation functions are passed an explicit copy of the
* values for the sake of simplicity. Validation handlers can also
* $form_state to pass information on to submit handlers. For example:
* $form_state['data_for_submission'] = $data;

Dries Buytaert
committed
* This technique is useful when validation requires file parsing,
* web service requests, or other expensive requests that should
* not be repeated in the submission step.

Dries Buytaert
committed
*/
function drupal_validate_form($form_id, &$form, &$form_state) {

Dries Buytaert
committed
$validated_forms = &drupal_static(__FUNCTION__, array());

Dries Buytaert
committed
if (isset($validated_forms[$form_id]) && empty($form_state['must_validate'])) {

Dries Buytaert
committed
// If the session token was set by drupal_prepare_form(), ensure that it
// matches the current user's session.

Dries Buytaert
committed
if (isset($form['#token'])) {

Dries Buytaert
committed
if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) {
// Setting this error will cause the form to fail validation.
form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));

Dries Buytaert
committed
_form_validate($form, $form_state, $form_id);
$validated_forms[$form_id] = TRUE;

Dries Buytaert
committed
/**

Dries Buytaert
committed
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
* Redirects the user to a URL after a form has been processed.
*
* After a form was executed, the data in $form_state controls whether the form
* is redirected. By default, we redirect to a new destination page. The path of
* the destination page can be set in $form_state['redirect']. If that is not
* set, the user is redirected to the current page to display a fresh,
* unpopulated copy of the form.
*
* There are several triggers that may prevent a redirection though:
* - If $form_state['redirect'] is FALSE, a form builder function or form
* validation/submit handler does not want a user to be redirected, which
* means that drupal_goto() is not invoked. For most forms, the redirection
* logic will be the same regardless of whether $form_state['redirect'] is
* undefined or FALSE. However, in case it was not defined and the current
* request contains a 'destination' query string, drupal_goto() will redirect
* to that given destination instead. Only setting $form_state['redirect'] to
* FALSE will prevent any redirection.
* - If $form_state['no_redirect'] is TRUE, then the callback that originally
* built the form explicitly disallows any redirection, regardless of the
* redirection value in $form_state['redirect']. For example, ajax_get_form()
* defines $form_state['no_redirect'] when building a form in an AJAX
* callback to prevent any redirection. $form_state['no_redirect'] should NOT
* be altered by form builder functions or form validation/submit handlers.
* - If $form_state['programmed'] is TRUE, the form submission was usually
* invoked via drupal_form_submit(), so any redirection would break the script
* that invoked drupal_form_submit().

Dries Buytaert
committed
* - If $form_state['rebuild'] is TRUE, the form needs to be rebuilt without

Dries Buytaert
committed
* redirection.

Dries Buytaert
committed
*

Dries Buytaert
committed
* @param $form_state
* A keyed array containing the current state of the form.
*
* @see drupal_process_form()
* @see drupal_build_form()

Dries Buytaert
committed
*/

Dries Buytaert
committed
function drupal_redirect_form($form_state) {
// Skip redirection for form submissions invoked via drupal_form_submit().
if (!empty($form_state['programmed'])) {
return;

Dries Buytaert
committed
}

Dries Buytaert
committed
// Skip redirection if rebuild is activated.
if (!empty($form_state['rebuild'])) {

Dries Buytaert
committed
return;
}
// Skip redirection if it was explicitly disallowed.
if (!empty($form_state['no_redirect'])) {
return;

Dries Buytaert
committed
}

Dries Buytaert
committed
// Only invoke drupal_goto() if redirect value was not set to FALSE.
if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) {
if (isset($form_state['redirect'])) {
if (is_array($form_state['redirect'])) {
call_user_func_array('drupal_goto', $form_state['redirect']);
}
else {

Dries Buytaert
committed
// This function can be called from the installer, which guarantees
// that $redirect will always be a string, so catch that case here
// and use the appropriate redirect function.
$function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto';

Dries Buytaert
committed
$function($form_state['redirect']);
}
}
drupal_goto($_GET['q']);
}

Dries Buytaert
committed
}
/**
* Performs validation on form elements. First ensures required fields are
* completed, #maxlength is not exceeded, and selected options were in the
* list of options given to the user. Then calls user-defined validators.
*
* @param $elements
* An associative array containing the structure of the form.

Dries Buytaert
committed
* @param $form_state
* A keyed array containing the current state of the form. The current
* user-submitted data is stored in $form_state['values'], though
* form validation functions are passed an explicit copy of the
* values for the sake of simplicity. Validation handlers can also
* $form_state to pass information on to submit handlers. For example:
* $form_state['data_for_submission'] = $data;

Dries Buytaert
committed
* This technique is useful when validation requires file parsing,
* web service requests, or other expensive requests that should
* not be repeated in the submission step.
* @param $form_id
* A unique string identifying the form for validation, submission,
* theming, and hook_form_alter functions.
*/
function _form_validate(&$elements, &$form_state, $form_id = NULL) {

Gábor Hojtsy
committed
// Also used in the installer, pre-database setup.
$t = get_t();

Dries Buytaert
committed

Dries Buytaert
committed
// Recurse through all children.
foreach (element_children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key]) {

Dries Buytaert
committed
_form_validate($elements[$key], $form_state);

Dries Buytaert
committed
}
}

Angie Byron
committed

Dries Buytaert
committed
// Validate the current input.
if (!isset($elements['#validated']) || !$elements['#validated']) {

Angie Byron
committed
// The following errors are always shown.
if (isset($elements['#needs_validation'])) {
// Verify that the value is not longer than #maxlength.
if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {

Gábor Hojtsy
committed
form_error($elements, $t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => drupal_strlen($elements['#value']))));
}

Gábor Hojtsy
committed
if (isset($elements['#options']) && isset($elements['#value'])) {
if ($elements['#type'] == 'select') {
$options = form_options_flatten($elements['#options']);
}
else {
$options = $elements['#options'];
}
if (is_array($elements['#value'])) {
$value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value'];
foreach ($value as $v) {
if (!isset($options[$v])) {

Gábor Hojtsy
committed
form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.'));
watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
elseif (!isset($options[$elements['#value']])) {

Gábor Hojtsy
committed
form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.'));
watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);

Angie Byron
committed
// While this element is being validated, it may be desired that some calls
// to form_set_error() be suppressed and not result in a form error, so
// that a button that implements low-risk functionality (such as "Previous"
// or "Add more") that doesn't require all user input to be valid can still
// have its submit handlers triggered. The triggering element's

Angie Byron
committed
// #limit_validation_errors property contains the information for which
// errors are needed, and all other errors are to be suppressed. The
// #limit_validation_errors property is ignored if submit handlers will run,
// but the element doesn't have a #submit property, because it's too large a
// security risk to have any invalid user input when executing form-level
// submit handlers.
if (isset($form_state['triggering_element']['#limit_validation_errors']) && ($form_state['triggering_element']['#limit_validation_errors'] !== FALSE) && !($form_state['submitted'] && !isset($form_state['triggering_element']['#submit']))) {
form_set_error(NULL, '', $form_state['triggering_element']['#limit_validation_errors']);

Angie Byron
committed
}
// If submit handlers won't run (due to the submission having been triggered
// by an element whose #executes_submit_callback property isn't TRUE), then
// it's safe to suppress all validation errors, and we do so by default,
// which is particularly useful during an AJAX submission triggered by a
// non-button. An element can override this default by setting the
// #limit_validation_errors property. For button element types,
// #limit_validation_errors defaults to FALSE (via system_element_info()),
// so that full validation is their default behavior.
elseif (isset($form_state['triggering_element']) && !isset($form_state['triggering_element']['#limit_validation_errors']) && !$form_state['submitted']) {
form_set_error(NULL, '', array());
}
// As an extra security measure, explicitly turn off error suppression if
// one of the above conditions wasn't met. Since this is also done at the
// end of this function, doing it here is only to handle the rare edge case
// where a validate handler invokes form processing of another form.

Angie Byron
committed
else {
drupal_static_reset('form_set_error:limit_validation_errors');
}

Angie Byron
committed
// Make sure a value is passed when the field is required.
// A simple call to empty() will not cut it here as some fields, like
// checkboxes, can return a valid value of '0'. Instead, check the
// length if it's a string, and the item count if it's an array.

Dries Buytaert
committed
// An unchecked checkbox has a #value of integer 0, different than string

Angie Byron
committed
// '0', which could be a valid value.
if (isset($elements['#needs_validation']) && $elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0) || $elements['#value'] === 0)) {
form_error($elements, $t('!name field is required.', array('!name' => $elements['#title'])));
}
// Call user-defined form level validators.

Dries Buytaert
committed
if (isset($form_id)) {
form_execute_handlers('validate', $elements, $form_state);
}
// Call any element-specific validators. These must act on the element
// #value data.
elseif (isset($elements['#element_validate'])) {
foreach ($elements['#element_validate'] as $function) {
$function($elements, $form_state, $form_state['complete form']);