Created
November 3, 2012 00:53
-
-
Save soe/4005311 to your computer and use it in GitHub Desktop.
form.inc old
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// $Id$ | |
/** | |
* @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 | |
* @{ | |
* Functions to enable the processing and display of HTML forms. | |
* | |
* 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 primary function used with forms is drupal_get_form(), which is | |
* used for forms presented interactively to a user. Forms can also be built and | |
* submitted programmatically without any user input using the | |
* drupal_form_submit() function. | |
* | |
* drupal_get_form() handles retrieving, processing, and displaying a rendered | |
* HTML form for modules automatically. | |
* | |
* Here is an example of how to use drupal_get_form() and a form builder | |
* function: | |
* @code | |
* $form = drupal_get_form('my_module_example_form'); | |
* ... | |
* function my_module_example_form($form, &$form_state) { | |
* $form['submit'] = array( | |
* '#type' => 'submit', | |
* '#value' => t('Submit'), | |
* ); | |
* return $form; | |
* } | |
* function my_module_example_form_validate($form, &$form_state) { | |
* // Validation logic. | |
* } | |
* function my_module_example_form_submit($form, &$form_state) { | |
* // Submission logic. | |
* } | |
* @endcode | |
* | |
* Or with any number of additional arguments: | |
* @code | |
* $extra = "extra"; | |
* $form = drupal_get_form('my_module_example_form', $extra); | |
* ... | |
* function my_module_example_form($form, &$form_state, $extra) { | |
* $form['submit'] = array( | |
* '#type' => 'submit', | |
* '#value' => $extra, | |
* ); | |
* return $form; | |
* } | |
* @endcode | |
* | |
* The $form argument to form-related functions is a structured array containing | |
* the elements and properties of the form. For information on the array | |
* components and format, and more detailed explanations of the Form API | |
* workflow, see the | |
* @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html Form API reference @endlink | |
* and the | |
* @link http://drupal.org/node/37775 Form API section of the handbook. @endlink | |
* In addition, there is a set of Form API tutorials in | |
* @link form_example_tutorial.inc the Form Example Tutorial @endlink which | |
* provide basics all the way up through multistep forms. | |
* | |
* In the form builder, validation, submission, and other form functions, | |
* $form_state is the primary influence on the processing of the form and is | |
* passed by reference to most functions, so they use it to communicate with | |
* the form system and each other. | |
* | |
* The $form_state keys are: | |
* - 'values': An associative array of values submitted to the form. The | |
* validation functions and submit functions use this array for nearly all | |
* their decision making. (Note that | |
* @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7#tree #tree @endlink | |
* determines whether the values are a flat array or an array whose structure | |
* parallels the $form array.) | |
* - 'rebuild': If the submit function sets $form_state['rebuild'] to TRUE, | |
* submission is not completed and instead the form is rebuilt using any | |
* information that the submit function has made available to the form builder | |
* function via $form_state. This is commonly used for wizard-style | |
* multi-step forms, add-more buttons, and the like. For further information | |
* see drupal_build_form(). | |
* - 'redirect': a URL that will be used to redirect the form on submission. | |
* See drupal_redirect_form() for complete information. | |
* - 'storage': $form_state['storage'] is not a special key, and no specific | |
* support is provided for it in the Form API, but by tradition it was | |
* the location where application-specific data was stored for communication | |
* between the submit, validation, and form builder functions, especially | |
* in a multi-step-style form. Form implementations may use any key(s) within | |
* $form_state (other than the keys listed here and other reserved ones used | |
* by Form API internals) for this kind of storage. The recommended way to | |
* ensure that the chosen key doesn't conflict with ones used by the Form API | |
* or other modules is to use the module name as the key name or a prefix for | |
* the key name. For example, the Node module uses $form_state['node'] in node | |
* editing forms to store information about the node being edited, and this | |
* information stays available across successive clicks of the "Preview" | |
* button as well as when the "Save" button is finally clicked. | |
* - 'temporary': Since values for all non-reserved keys in $form_state persist | |
* throughout a multistep form sequence, the Form API provides the 'temporary' | |
* key for modules to use for communicating information across form-related | |
* functions during a single page request only. There is no use-case for this | |
* functionality in core. | |
* - 'triggering_element': (read-only) The form element that triggered | |
* submission. This is the same as the deprecated | |
* $form_state['clicked_button']. It is the element that caused submission, | |
* which may or may not be a button (in the case of AJAX forms.) This is | |
* often used to distinguish between various buttons in a submit handler, | |
* and is also used in AJAX handlers. | |
* - 'cache': The typical form workflow involves two page requests. During the | |
* first page request, a form is built and returned for the user to fill in. | |
* Then the user fills the form in and submits it, triggering a second page | |
* request in which the form must be built and processed. By default, $form | |
* and $form_state are built from scratch during each of these page requests. | |
* In some special use-cases, it is necessary or desired to persist the $form | |
* and $form_state variables from the initial page request to the one that | |
* processes the submission. A form builder function can set 'cache' to TRUE | |
* to do this. One example where this is needed is to handle AJAX submissions, | |
* so ajax_process_form() sets this for all forms that include an element with | |
* a #ajax property. (In AJAX, the handler has no way to build the form | |
* itself, so must rely on the cached version created on each page load, so | |
* it's a classic example of this use case.) Note that the persistence of | |
* $form and $form_state across successive submissions of a multi-step form | |
* happens automatically regardless of the value for 'cache'. | |
* - 'input': The array of values as they were submitted by the user. These are | |
* raw and unvalidated, so should not be used without a thorough understanding | |
* of security implications. In almost all cases, code should use the data in | |
* the 'values' array exclusively. The most common use of this key is for | |
* multi-step forms that need to clear some of the user input when setting | |
* 'rebuild'. | |
*/ | |
/** | |
* Wrapper for drupal_build_form() for use when $form_state is not needed. | |
* | |
* @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 ... | |
* Any additional arguments are passed on to the functions called by | |
* 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. | |
* | |
* @return | |
* The form array. | |
* | |
* @see drupal_build_form() | |
*/ | |
function drupal_get_form($form_id) { | |
$form_state = array(); | |
$args = func_get_args(); | |
// Remove $form_id from the arguments. | |
array_shift($args); | |
$form_state['build_info']['args'] = $args; | |
return drupal_build_form($form_id, $form_state); | |
} | |
/** | |
* Build and process a form based on a form id. | |
* | |
* 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 | |
* and submission if there is proper input. | |
* | |
* @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 | |
* reference so that the caller can use it to examine what in the form changed | |
* 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. | |
* The following parameters may be set in $form_state to affect how the form | |
* is rendered: | |
* - 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. | |
* - files: An optional array defining include files that need to be loaded | |
* for building the form. Each array entry may be the path to a file or | |
* another array containing values for the parameters 'type', 'module' and | |
* 'name' as needed by module_load_include(). The files listed here are | |
* automatically loaded by form_get_cache(). Defaults to the current menu | |
* router item's 'file' definition, if existent. | |
* - 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. | |
* Normally, $form_state['rebuild'] is set by a submit handler, since it is | |
* usually logic within a submit handler that determines whether a form is | |
* done or requires another step. However, a validation handler may already | |
* set $form_state['rebuild'] to cause the form processing to bypass submit | |
* handlers and rebuild the form instead, even if there are no validation | |
* errors. | |
* - 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. | |
* - 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. | |
* - 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. | |
* Further $form_state properties controlling the redirection behavior after | |
* form submission may be found in drupal_redirect_form(). | |
* | |
* @return | |
* The rendered form or NULL, depending upon the $form_state flags that were set. | |
* | |
* @see drupal_redirect_form() | |
*/ | |
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; | |
} | |
if (isset($_SESSION['batch_form_state'])) { | |
// We've been redirected here after a batch processing. The form has | |
// already been processed, but needs to be rebuilt. See _batch_finished(). | |
$form_state = $_SESSION['batch_form_state']; | |
unset($_SESSION['batch_form_state']); | |
return drupal_rebuild_form($form_id, $form_state); | |
} | |
// If the incoming input contains a form_build_id, we'll check the 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. | |
$check_cache = isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id']); | |
if ($check_cache) { | |
$form = form_get_cache($form_state['input']['form_build_id'], $form_state); | |
} | |
// If the previous bit of code didn't result in a populated $form object, we | |
// are hitting the form for the first time and we need to build it from | |
// scratch. | |
if (!isset($form)) { | |
// If we attempted to serve the form from cache, uncacheable $form_state | |
// keys need to be removed after retrieving and preparing the form, except | |
// any that were already set prior to retrieving the form. | |
if ($check_cache) { | |
$form_state_before_retrieval = $form_state; | |
} | |
$form = drupal_retrieve_form($form_id, $form_state); | |
drupal_prepare_form($form_id, $form, $form_state); | |
// form_set_cache() removes uncacheable $form_state keys defined in | |
// form_state_keys_no_cache() in order for multi-step forms to work | |
// properly. This means that form processing logic for single-step forms | |
// using $form_state['cache'] may depend on data stored in those keys | |
// during drupal_retrieve_form()/drupal_prepare_form(), but form | |
// processing should not depend on whether the form is cached or not, so | |
// $form_state is adjusted to match what it would be after a | |
// form_set_cache()/form_get_cache() sequence. These exceptions are | |
// allowed to survive here: | |
// - always_process: Does not make sense in conjunction with form caching | |
// in the first place, since passing form_build_id as a GET parameter is | |
// not desired. | |
// - temporary: Any assigned data is expected to survives within the same | |
// page request. | |
if ($check_cache) { | |
$uncacheable_keys = array_flip(array_diff(form_state_keys_no_cache(), array('always_process', 'temporary'))); | |
$form_state = array_diff_key($form_state, $uncacheable_keys); | |
$form_state += $form_state_before_retrieval; | |
} | |
} | |
// Now that we have a constructed form, process it. This is where: | |
// - Element #process functions get called to further refine $form. | |
// - User input, if any, gets incorporated in the #value property of the | |
// corresponding elements and into $form_state['values']. | |
// - Validation and submission handlers are called. | |
// - If this submission is part of a multistep workflow, the form is rebuilt | |
// to contain the information of the next step. | |
// - If necessary, the form and form state are cached or re-cached, so that | |
// appropriate information persists to the next page request. | |
// All of the handlers in the pipeline receive $form_state by reference and | |
// can use it to know or update information about the state of the form. | |
drupal_process_form($form_id, $form, $form_state); | |
// If this was a successful submission of a single-step form or the last step | |
// of a multi-step form, then drupal_process_form() issued a redirect to | |
// another page, or back to this page, but as a new request. Therefore, if | |
// we're here, it means that this is either a form being viewed initially | |
// before any user input, or there was a validation error requiring the form | |
// to be re-displayed, or we're in a multi-step workflow and need to display | |
// the form's next step. In any case, we have what we need in $form, and can | |
// return it for rendering. | |
return $form; | |
} | |
/** | |
* Retrieve default values for the $form_state array. | |
*/ | |
function form_state_defaults() { | |
return array( | |
'rebuild' => FALSE, | |
'rebuild_info' => array(), | |
'redirect' => NULL, | |
'build_info' => array('args' => array()), | |
'temporary' => array(), | |
'submitted' => FALSE, | |
'executed' => FALSE, | |
'programmed' => FALSE, | |
'cache'=> FALSE, | |
'method' => 'post', | |
'groups' => array(), | |
'buttons' => array(), | |
); | |
} | |
/** | |
* Constructs a new $form from the information in $form_state. | |
* | |
* This is the key function for making multi-step forms advance from step to | |
* step. It is called by drupal_process_form() when all user input processing, | |
* including calling validation and submission handlers, for the request is | |
* finished. If a validate or submit handler set $form_state['rebuild'] to TRUE, | |
* and if other conditions don't preempt a rebuild from happening, then this | |
* function is called to generate a new $form, the next step in the form | |
* workflow, to be returned for rendering. | |
* | |
* AJAX form submissions are almost always multi-step workflows, so that is one | |
* common use-case during which form rebuilding occurs. See ajax_form_callback() | |
* for more information about creating AJAX-enabled forms. | |
* | |
* @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 | |
* A keyed array containing the current state of the form. | |
* @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. The | |
* only properties copied from $old_form are the ones which both exist in | |
* $old_form and for which $form_state['rebuild_info']['copy'][PROPERTY] is | |
* TRUE. If $old_form is not passed, the entire $form is rebuilt freshly. | |
* 'rebuild_info' needs to be a separate top-level property next to | |
* 'build_info', since the contained data must not be cached. | |
* | |
* @return | |
* The newly built form. | |
* | |
* @see drupal_process_form() | |
* @see ajax_form_callback() | |
*/ | |
function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) { | |
$form = drupal_retrieve_form($form_id, $form_state); | |
// 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. | |
// @see drupal_prepare_form() | |
if (isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id'])) { | |
$form['#build_id'] = $old_form['#build_id']; | |
} | |
else { | |
$form['#build_id'] = 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . 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']) && !empty($form_state['rebuild_info']['copy']['#action'])) { | |
$form['#action'] = $old_form['#action']; | |
} | |
drupal_prepare_form($form_id, $form, $form_state); | |
// Caching is normally done in drupal_process_form(), but what needs to be | |
// cached is the $form structure before it passes through form_builder(), | |
// so we need to do it here. | |
// @todo For Drupal 8, find a way to avoid this code duplication. | |
if (empty($form_state['no_cache'])) { | |
form_set_cache($form['#build_id'], $form, $form_state); | |
} | |
// Clear out all group associations as these might be different when | |
// re-rendering the form. | |
$form_state['groups'] = array(); | |
// Return a fully built form that is ready for rendering. | |
return form_builder($form_id, $form, $form_state); | |
} | |
/** | |
* 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)) { | |
if ($cached = cache_get('form_state_' . $form_build_id, 'cache_form')) { | |
// Re-populate $form_state for subsequent rebuilds. | |
$form_state = $cached->data + $form_state; | |
// If the original form is contained in include files, load the files. | |
// See drupal_build_form(). | |
$form_state['build_info'] += array('files' => array()); | |
foreach ($form_state['build_info']['files'] as $file) { | |
if (is_array($file)) { | |
$file += array('type' => 'inc', 'name' => $file['module']); | |
module_load_include($file['type'], $file['module'], $file['name']); | |
} | |
elseif (file_exists($file)) { | |
require_once DRUPAL_ROOT . '/' . $file; | |
} | |
} | |
} | |
return $form; | |
} | |
} | |
} | |
/** | |
* Store a form in the cache. | |
*/ | |
function form_set_cache($form_build_id, $form, $form_state) { | |
// 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. | |
if ($data = array_diff_key($form_state, array_flip(form_state_keys_no_cache()))) { | |
cache_set('form_state_' . $form_build_id, $data, 'cache_form', REQUEST_TIME + $expire); | |
} | |
} | |
/** | |
* 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', | |
'rebuild_info', | |
'redirect', | |
'no_redirect', | |
'temporary', | |
// Internal properties defined by form processing. | |
'buttons', | |
'triggering_element', | |
'clicked_button', | |
'complete form', | |
'groups', | |
'input', | |
'method', | |
'submit_handlers', | |
'submitted', | |
'executed', | |
'validate_handlers', | |
'values', | |
); | |
} | |
/** | |
* 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 | |
* 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 | |
* 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 | |
* 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. Arguments that need to be passed by reference | |
* should not be included here, but rather placed directly in the $form_state | |
* build info array so that the reference can be preserved. For example, a | |
* form builder function with the following signature: | |
* @code | |
* function mymodule_form($form, &$form_state, &$object) { | |
* } | |
* @endcode | |
* would be called via drupal_form_submit() as follows: | |
* @code | |
* $form_state['values'] = $my_form_values; | |
* $form_state['build_info']['args'] = array(&$object); | |
* drupal_form_submit('mymodule_form', $form_state); | |
* @endcode | |
* For example: | |
* @code | |
* // register a new user | |
* $form_state = array(); | |
* $form_state['values']['name'] = 'robo-user'; | |
* $form_state['values']['mail'] = '[email protected]'; | |
* $form_state['values']['pass']['pass1'] = 'password'; | |
* $form_state['values']['pass']['pass2'] = 'password'; | |
* $form_state['values']['op'] = t('Create new account'); | |
* drupal_form_submit('user_register_form', $form_state); | |
* @endcode | |
*/ | |
function drupal_form_submit($form_id, &$form_state) { | |
if (!isset($form_state['build_info']['args'])) { | |
$args = func_get_args(); | |
array_shift($args); | |
array_shift($args); | |
$form_state['build_info']['args'] = $args; | |
} | |
// Merge in default values. | |
$form_state += form_state_defaults(); | |
$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(); | |
drupal_prepare_form($form_id, $form, $form_state); | |
drupal_process_form($form_id, $form, $form_state); | |
} | |
/** | |
* 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 | |
* different $form_id values to the proper form constructor function. | |
* @param $form_state | |
* 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. | |
*/ | |
function drupal_retrieve_form($form_id, &$form_state) { | |
$forms = &drupal_static(__FUNCTION__); | |
// Record the filepath of the include file containing the original form, so | |
// the form builder callbacks can be loaded when the form is being rebuilt | |
// from cache on a different path (such as 'system/ajax'). See | |
// form_get_cache(). | |
// $menu_get_item() is not available at installation time. | |
if (!isset($form_state['build_info']['files']['menu']) && !defined('MAINTENANCE_MODE')) { | |
$item = menu_get_item(); | |
if (!empty($item['include_file'])) { | |
$form_state['build_info']['files']['menu'] = $item['include_file']; | |
} | |
} | |
// We save two copies of the incoming arguments: one for modules to use | |
// when mapping form ids to constructor functions, and another to pass to | |
// the constructor function itself. | |
$args = $form_state['build_info']['args']; | |
// 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)) { | |
// In cases where many form_ids need to share a central constructor function, | |
// such as the node editing form, modules can implement hook_forms(). It | |
// maps one or more form_ids to the correct constructor functions. | |
// | |
// 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])) { | |
$forms = module_invoke_all('forms', $form_id, $args); | |
} | |
$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']; | |
$form_state['build_info']['base_form_id'] = $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']; | |
} | |
} | |
$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 | |
// 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; | |
} | |
// 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); | |
$form['#form_id'] = $form_id; | |
return $form; | |
} | |
/** | |
* Processes a form submission. | |
* | |
* This function is the heart of form API. The form gets built, validated and in | |
* appropriate cases, submitted and rebuilt. | |
* | |
* @param $form_id | |
* The unique string identifying the current form. | |
* @param $form | |
* An associative array containing the structure of the form. | |
* @param $form_state | |
* A keyed array containing the current state of the form. This | |
* includes the current persistent storage data for the form, and | |
* any data passed along by earlier steps when displaying a | |
* multi-step form. Additional information, like the sanitized $_POST | |
* data, is also accumulated here. | |
*/ | |
function drupal_process_form($form_id, &$form, &$form_state) { | |
$form_state['values'] = array(); | |
// 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']); | |
} | |
} | |
// form_builder() finishes building the form by calling element #process | |
// functions and mapping user input, if any, to #value properties, and also | |
// storing the values in $form_state['values']. We need to retain the | |
// unprocessed $form in case it needs to be cached. | |
$unprocessed_form = $form; | |
$form = form_builder($form_id, $form, $form_state); | |
// Only process the input if we have a correct form submission. | |
if ($form_state['process_input']) { | |
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. | |
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', 0) && !empty($form_state['values']['form_build_id'])) { | |
cache_clear_all('form_' . $form_state['values']['form_build_id'], 'cache_form'); | |
cache_clear_all('form_state_' . $form_state['values']['form_build_id'], 'cache_form'); | |
} | |
// If batches were set in the submit handlers, we process them now, | |
// possibly ending execution. We make sure we do not react to the batch | |
// that is already being processed (if a batch operation performs a | |
// drupal_form_submit). | |
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'))); | |
} | |
$batch['progressive'] = !$form_state['programmed']; | |
batch_process(); | |
// 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. | |
} | |
// Set a flag to indicate the the form has been processed and executed. | |
$form_state['executed'] = TRUE; | |
// Redirect the form based on values in $form_state. | |
drupal_redirect_form($form_state); | |
} | |
// Don't rebuild or cache form submissions invoked via drupal_form_submit(). | |
if (!empty($form_state['programmed'])) { | |
return; | |
} | |
// If $form_state['rebuild'] has been set and input has been processed | |
// without validation errors, we are in a multi-step workflow that is not | |
// yet complete. A new $form needs to be constructed based on the changes | |
// made to $form_state during this request. Normally, a submit handler sets | |
// $form_state['rebuild'] if a fully executed form requires another step. | |
// However, for forms that have not been fully executed (e.g., AJAX | |
// submissions triggered by non-buttons), there is no submit handler to set | |
// $form_state['rebuild']. It would not make sense to redisplay the | |
// identical form without an error for the user to correct, so we also | |
// rebuild error-free non-executed forms, regardless of | |
// $form_state['rebuild']. | |
// @todo D8: Simplify this logic; considering AJAX and non-HTML front-ends, | |
// along with element-level #submit properties, it makes no sense to have | |
// divergent form execution based on whether the triggering element has | |
// #executes_submit_callback set to TRUE. | |
if (($form_state['rebuild'] || !$form_state['executed']) && !form_get_errors()) { | |
// Form building functions (e.g., _form_builder_handle_input_element()) | |
// may use $form_state['rebuild'] to determine if they are running in the | |
// context of a rebuild, so ensure it is set. | |
$form_state['rebuild'] = TRUE; | |
$form = drupal_rebuild_form($form_id, $form_state, $form); | |
} | |
} | |
// After processing the form, the form builder or a #process callback may | |
// have set $form_state['cache'] to indicate that the form and form state | |
// shall be cached. But the form may only be cached if the 'no_cache' property | |
// is not set to TRUE. Only cache $form as it was prior to form_builder(), | |
// because form_builder() must run for each request to accomodate new user | |
// input. Rebuilt forms are not cached here, because drupal_rebuild_form() | |
// already takes care of that. | |
if (!$form_state['rebuild'] && $form_state['cache'] && empty($form_state['no_cache'])) { | |
form_set_cache($form['#build_id'], $unprocessed_form, $form_state); | |
} | |
} | |
/** | |
* 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. | |
* @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. | |
*/ | |
function drupal_prepare_form($form_id, &$form, &$form_state) { | |
global $user; | |
$form['#type'] = 'form'; | |
$form_state['programmed'] = isset($form_state['programmed']) ? $form_state['programmed'] : FALSE; | |
// 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'; | |
} | |
// Generate a new #build_id for this form, if none has been set already. The | |
// form_build_id is used as key to cache a particular build of the form. For | |
// multi-step forms, this allows the user to go back to an earlier build, make | |
// changes, and re-submit. | |
// @see drupal_build_form() | |
// @see drupal_rebuild_form() | |
if (!isset($form['#build_id'])) { | |
$form['#build_id'] = 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . mt_rand()); | |
} | |
$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. | |
// 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) { | |
unset($form['#token']); | |
} | |
// Otherwise, generate a public token based on the form id. | |
else { | |
$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']), | |
); | |
} | |
} | |
if (isset($form_id)) { | |
$form['form_id'] = array( | |
'#type' => 'hidden', | |
'#value' => $form_id, | |
'#id' => drupal_html_id("edit-$form_id"), | |
); | |
} | |
if (!isset($form['#id'])) { | |
$form['#id'] = drupal_html_id($form_id); | |
} | |
$form += element_info('form'); | |
$form += array('#tree' => FALSE, '#parents' => array()); | |
if (!isset($form['#validate'])) { | |
// Check for a handler specific to $form_id. | |
if (function_exists($form_id . '_validate')) { | |
$form['#validate'][] = $form_id . '_validate'; | |
} | |
// Otherwise check whether this is a shared form and whether there is a | |
// handler for the shared $form_id. | |
elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_validate')) { | |
$form['#validate'][] = $form_state['build_info']['base_form_id'] . '_validate'; | |
} | |
} | |
if (!isset($form['#submit'])) { | |
// Check for a handler specific to $form_id. | |
if (function_exists($form_id . '_submit')) { | |
$form['#submit'][] = $form_id . '_submit'; | |
} | |
// Otherwise check whether this is a shared form and whether there is a | |
// handler for the shared $form_id. | |
elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_submit')) { | |
$form['#submit'][] = $form_state['build_info']['base_form_id'] . '_submit'; | |
} | |
} | |
// If no #theme has been set, automatically apply theme suggestions. | |
// theme_form() itself is in #theme_wrappers and not #theme. Therefore, the | |
// #theme function only has to care for rendering the inner form elements, | |
// not the form itself. | |
if (!isset($form['#theme'])) { | |
$form['#theme'] = array($form_id); | |
if (isset($form_state['build_info']['base_form_id'])) { | |
$form['#theme'][] = $form_state['build_info']['base_form_id']; | |
} | |
} | |
// Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and | |
// hook_form_FORM_ID_alter() implementations. | |
$hooks = array('form'); | |
if (isset($form_state['build_info']['base_form_id'])) { | |
$hooks[] = 'form_' . $form_state['build_info']['base_form_id']; | |
} | |
$hooks[] = 'form_' . $form_id; | |
drupal_alter($hooks, $form, $form_state, $form_id); | |
} | |
/** | |
* Validates user-submitted form data from the $form_state using | |
* 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. | |
* @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; | |
* 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. | |
*/ | |
function drupal_validate_form($form_id, &$form, &$form_state) { | |
$validated_forms = &drupal_static(__FUNCTION__, array()); | |
if (isset($validated_forms[$form_id]) && empty($form_state['must_validate'])) { | |
return; | |
} | |
// If the session token was set by drupal_prepare_form(), ensure that it | |
// matches the current user's session. | |
if (isset($form['#token'])) { | |
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('This form is outdated. Reload the page and try again. Contact the site administrator if the problem persists.')); | |
} | |
} | |
_form_validate($form, $form_state, $form_id); | |
$validated_forms[$form_id] = TRUE; | |
// If validation errors are limited then remove any non validated form values, | |
// so that only values that passed validation are left for submit callbacks. | |
if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) { | |
$values = array(); | |
foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) { | |
// If the section exists within $form_state['values'], even if the value | |
// is NULL, copy it to $values. | |
$section_exists = NULL; | |
$value = drupal_array_get_nested_value($form_state['values'], $section, $section_exists); | |
if ($section_exists) { | |
drupal_array_set_nested_value($values, $section, $value); | |
} | |
} | |
// For convenience we always make the value of the pressed button available. | |
if (isset($form_state['triggering_element']['#button_type'])) { | |
$values[$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value']; | |
drupal_array_set_nested_value($values, $form_state['triggering_element']['#parents'], $form_state['triggering_element']['#value']); | |
} | |
$form_state['values'] = $values; | |
} | |
} | |
/** | |
* 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(). | |
* - If $form_state['rebuild'] is TRUE, the form needs to be rebuilt without | |
* redirection. | |
* | |
* @param $form_state | |
* A keyed array containing the current state of the form. | |
* | |
* @see drupal_process_form() | |
* @see drupal_build_form() | |
*/ | |
function drupal_redirect_form($form_state) { | |
// Skip redirection for form submissions invoked via drupal_form_submit(). | |
if (!empty($form_state['programmed'])) { | |
return; | |
} | |
// Skip redirection if rebuild is activated. | |
if (!empty($form_state['rebuild'])) { | |
return; | |
} | |
// Skip redirection if it was explicitly disallowed. | |
if (!empty($form_state['no_redirect'])) { | |
return; | |
} | |
// 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 { | |
// 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'; | |
$function($form_state['redirect']); | |
} | |
} | |
drupal_goto($_GET['q']); | |
} | |
} | |
/** | |
* 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. | |
* @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; | |
* 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) { | |
// Also used in the installer, pre-database setup. | |
$t = get_t(); | |
// Recurse through all children. | |
foreach (element_children($elements) as $key) { | |
if (isset($elements[$key]) && $elements[$key]) { | |
_form_validate($elements[$key], $form_state); | |
} | |
} | |
// Validate the current input. | |
if (!isset($elements['#validated']) || !$elements['#validated']) { | |
// 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']) { | |
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'])))); | |
} | |
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($elements['#value']) : $elements['#value']; | |
foreach ($value as $v) { | |
if (!isset($options[$v])) { | |
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); | |
} | |
} | |
} | |
// Non-multiple select fields always have a value in HTML. If the user | |
// does not change the form, it will be the value of the first option. | |
// Because of this, form validation for the field will almost always | |
// pass, even if the user did not select anything. To work around this | |
// browser behavior, required select fields without a #default_value get | |
// an additional, first empty option. In case the submitted value is | |
// identical to the empty option's value, we reset the element's value | |
// to NULL to trigger the regular #required handling below. | |
// @see form_process_select() | |
elseif ($elements['#type'] == 'select' && !$elements['#multiple'] && $elements['#required'] && !isset($elements['#default_value']) && $elements['#value'] === $elements['#empty_value']) { | |
$elements['#value'] = NULL; | |
form_set_value($elements, NULL, $form_state); | |
} | |
elseif (!isset($options[$elements['#value']])) { | |
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); | |
} | |
} | |
} | |
// 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 | |
// #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']); | |
} | |
// 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. | |
else { | |
drupal_static_reset('form_set_error:limit_validation_errors'); | |
} | |
// Make sure a value is passed when the field is required. | |
if (isset($elements['#needs_validation']) && $elements['#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. | |
// An unchecked checkbox has a #value of integer 0, different than string | |
// '0', which could be a valid value. | |
$is_empty_multiple = (!count($elements['#value'])); | |
$is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); | |
$is_empty_value = ($elements['#value'] === 0); | |
if ($is_empty_multiple || $is_empty_string || $is_empty_value) { | |
// Although discouraged, a #title is not mandatory for form elements. In | |
// case there is no #title, we cannot set a form error message. | |
// Instead of setting no #title, form constructors are encouraged to set | |
// #title_display to 'invisible' to improve accessibility. | |
if (isset($elements['#title'])) { | |
form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); | |
} | |
else { | |
form_error($elements); | |
} | |
} | |
} | |
// Call user-defined form level validators. | |
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']); | |
} | |
} | |
$elements['#validated'] = TRUE; | |
} | |
// Done validating this element, so turn off error suppression. | |
// _form_validate() turns it on again when starting on the next element, if | |
// it's still appropriate to do so. | |
drupal_static_reset('form_set_error:limit_validation_errors'); | |
} | |
/** | |
* A helper function used to execute custom validation and submission | |
* handlers for a given form. Button-specific handlers are checked | |
* first. If none exist, the function falls back to form-level handlers. | |
* | |
* @param $type | |
* The type of handler to execute. 'validate' or 'submit' are the | |
* defaults used by Form API. | |
* @param $form | |
* An associative array containing the structure of the form. | |
* @param $form_state | |
* A keyed array containing the current state of the form. If the user | |
* submitted the form by clicking a button with custom handler functions | |
* defined, those handlers will be stored here. | |
*/ | |
function form_execute_handlers($type, &$form, &$form_state) { | |
$return = FALSE; | |
// If there was a button pressed, use its handlers. | |
if (isset($form_state[$type . '_handlers'])) { | |
$handlers = $form_state[$type . '_handlers']; | |
} | |
// Otherwise, check for a form-level handler. | |
elseif (isset($form['#' . $type])) { | |
$handlers = $form['#' . $type]; | |
} | |
else { | |
$handlers = array(); | |
} | |
foreach ($handlers as $function) { | |
// Check if a previous _submit handler has set a batch, but make sure we | |
// do not react to a batch that is already being processed (for instance | |
// if a batch operation performs a drupal_form_submit()). | |
if ($type == 'submit' && ($batch =& batch_get()) && !isset($batch['id'])) { | |
// Some previous submit handler has set a batch. To ensure correct | |
// execution order, store the call in a special 'control' batch set. | |
// See _batch_next_set(). | |
$batch['sets'][] = array('form_submit' => $function); | |
$batch['has_form_submits'] = TRUE; | |
} | |
else { | |
$function($form, $form_state); | |
} | |
$return = TRUE; | |
} | |
return $return; | |
} | |
/** | |
* Files an error against a form element. | |
* | |
* When a validation error is detected, the validator calls form_set_error() to | |
* indicate which element needs to be changed and provide an error message. This | |
* causes the Form API to not execute the form submit handlers, and instead to | |
* re-display the form to the user with the corresponding elements rendered with | |
* an 'error' CSS class (shown as red by default). | |
* | |
* The standard form_set_error() behavior can be changed if a button provides | |
* the #limit_validation_errors property. Multistep forms not wanting to | |
* validate the whole form can set #limit_validation_errors on buttons to | |
* limit validation errors to only certain elements. For example, pressing the | |
* "Previous" button in a multistep form should not fire validation errors just | |
* because the current step has invalid values. If #limit_validation_errors is | |
* set on a clicked button, the button must also define a #submit property | |
* (may be set to an empty array). Any #submit handlers will be executed even if | |
* there is invalid input, so extreme care should be taken with respect to any | |
* actions taken by them. This is typically not a problem with buttons like | |
* "Previous" or "Add more" that do not invoke persistent storage of the | |
* submitted form values. Do not use the #limit_validation_errors property on | |
* buttons that trigger saving of form values to the database. | |
* | |
* The #limit_validation_errors property is a list of "sections" within | |
* $form_state['values'] that must contain valid values. Each "section" is an | |
* array with the ordered set of keys needed to reach that part of | |
* $form_state['values'] (i.e., the #parents property of the element). | |
* | |
* Example 1: Allow the "Previous" button to function, regardless of whether any | |
* user input is valid. | |
* | |
* @code | |
* $form['actions']['previous'] = array( | |
* '#type' => 'submit', | |
* '#value' => t('Previous'), | |
* '#limit_validation_errors' => array(), // No validation. | |
* '#submit' => array('some_submit_function'), // #submit required. | |
* ); | |
* @endcode | |
* | |
* Example 2: Require some, but not all, user input to be valid to process the | |
* submission of a "Previous" button. | |
* | |
* @code | |
* $form['actions']['previous'] = array( | |
* '#type' => 'submit', | |
* '#value' => t('Previous'), | |
* '#limit_validation_errors' => array( | |
* array('step1'), // Validate $form_state['values']['step1']. | |
* array('foo', 'bar'), // Validate $form_state['values']['foo']['bar']. | |
* ), | |
* '#submit' => array('some_submit_function'), // #submit required. | |
* ); | |
* @endcode | |
* | |
* This will require $form_state['values']['step1'] and everything within it | |
* (for example, $form_state['values']['step1']['choice']) to be valid, so | |
* calls to form_set_error('step1', $message) or | |
* form_set_error('step1][choice', $message) will prevent the submit handlers | |
* from running, and result in the error message being displayed to the user. | |
* However, calls to form_set_error('step2', $message) and | |
* form_set_error('step2][groupX][choiceY', $message) will be suppressed, | |
* resulting in the message not being displayed to the user, and the submit | |
* handlers will run despite $form_state['values']['step2'] and | |
* $form_state['values']['step2']['groupX']['choiceY'] containing invalid | |
* values. Errors for an invalid $form_state['values']['foo'] will be | |
* suppressed, but errors flagging invalid values for | |
* $form_state['values']['foo']['bar'] and everything within it will be | |
* flagged and submission prevented. | |
* | |
* Partial form validation is implemented by suppressing errors rather than by | |
* skipping the input processing and validation steps entirely, because some | |
* forms have button-level submit handlers that call Drupal API functions that | |
* assume that certain data exists within $form_state['values'], and while not | |
* doing anything with that data that requires it to be valid, PHP errors | |
* would be triggered if the input processing and validation steps were fully | |
* skipped. | |
* @see http://drupal.org/node/370537 | |
* @see http://drupal.org/node/763376 | |
* | |
* @param $name | |
* The name of the form element. If the #parents property of your form | |
* element is array('foo', 'bar', 'baz') then you may set an error on 'foo' | |
* or 'foo][bar][baz'. Setting an error on 'foo' sets an error for every | |
* element where the #parents array starts with 'foo'. | |
* @param $message | |
* The error message to present to the user. | |
* @param $limit_validation_errors | |
* Internal use only. The #limit_validation_errors property of the clicked | |
* button, if it exists. | |
* | |
* @return | |
* Return value is for internal use only. To get a list of errors, use | |
* form_get_errors() or form_get_error(). | |
*/ | |
function form_set_error($name = NULL, $message = '', $limit_validation_errors = NULL) { | |
$form = &drupal_static(__FUNCTION__, array()); | |
$sections = &drupal_static(__FUNCTION__ . ':limit_validation_errors'); | |
if (isset($limit_validation_errors)) { | |
$sections = $limit_validation_errors; | |
} | |
if (isset($name) && !isset($form[$name])) { | |
$record = TRUE; | |
if (isset($sections)) { | |
// #limit_validation_errors is an array of "sections" within which user | |
// input must be valid. If the element is within one of these sections, | |
// the error must be recorded. Otherwise, it can be suppressed. | |
// #limit_validation_errors can be an empty array, in which case all | |
// errors are suppressed. For example, a "Previous" button might want its | |
// submit action to be triggered even if none of the submitted values are | |
// valid. | |
$record = FALSE; | |
foreach ($sections as $section) { | |
// Exploding by '][' reconstructs the element's #parents. If the | |
// reconstructed #parents begin with the same keys as the specified | |
// section, then the element's values are within the part of | |
// $form_state['values'] that the clicked button requires to be valid, | |
// so errors for this element must be recorded. | |
if (array_slice(explode('][', $name), 0, count($section)) === $section) { | |
$record = TRUE; | |
break; | |
} | |
} | |
} | |
if ($record) { | |
$form[$name] = $message; | |
if ($message) { | |
drupal_set_message($message, 'error'); | |
} | |
} | |
} | |
return $form; | |
} | |
/** | |
* Clear all errors against all form elements made by form_set_error(). | |
*/ | |
function form_clear_error() { | |
drupal_static_reset('form_set_error'); | |
} | |
/** | |
* Return an associative array of all errors. | |
*/ | |
function form_get_errors() { | |
$form = form_set_error(); | |
if (!empty($form)) { | |
return $form; | |
} | |
} | |
/** | |
* Returns the error message filed against the given form element. | |
* | |
* Form errors higher up in the form structure override deeper errors as well as | |
* errors on the element itself. | |
*/ | |
function form_get_error($element) { | |
$form = form_set_error(); | |
$parents = array(); | |
foreach ($element['#parents'] as $parent) { | |
$parents[] = $parent; | |
$key = implode('][', $parents); | |
if (isset($form[$key])) { | |
return $form[$key]; | |
} | |
} | |
} | |
/** | |
* Flag an element as having an error. | |
*/ | |
function form_error(&$element, $message = '') { | |
form_set_error(implode('][', $element['#parents']), $message); | |
} | |
/** | |
* Walk through the structured form array, adding any required properties to | |
* each element and mapping the incoming input data to the proper elements. | |
* Also, execute any #process handlers attached to a specific element. | |
* | |
* This is one of the three primary functions that recursively iterates a form | |
* array. This one does it for completing the form building process. The other | |
* two are _form_validate() (invoked via drupal_validate_form() and used to | |
* invoke validation logic for each element) and drupal_render() (for rendering | |
* each element). Each of these three pipelines provides ample opportunity for | |
* modules to customize what happens. For example, during this function's life | |
* cycle, the following functions get called for each element: | |
* - $element['#value_callback']: A function that implements how user input is | |
* mapped to an element's #value property. This defaults to a function named | |
* 'form_type_TYPE_value' where TYPE is $element['#type']. | |
* - $element['#process']: An array of functions called after user input has | |
* been mapped to the element's #value property. These functions can be used | |
* to dynamically add child elements: for example, for the 'date' element | |
* type, one of the functions in this array is form_process_date(), which adds | |
* the individual 'year', 'month', 'day', etc. child elements. These functions | |
* can also be used to set additional properties or implement special logic | |
* other than adding child elements: for example, for the 'fieldset' element | |
* type, one of the functions in this array is form_process_fieldset(), which | |
* adds the attributes and JavaScript needed to make the fieldset collapsible | |
* if the #collapsible property is set. The #process functions are called in | |
* preorder traversal, meaning they are called for the parent element first, | |
* then for the child elements. | |
* - $element['#after_build']: An array of functions called after form_builder() | |
* is done with its processing of the element. These are called in postorder | |
* traversal, meaning they are called for the child elements first, then for | |
* the parent element. | |
* There are similar properties containing callback functions invoked by | |
* _form_validate() and drupal_render(), appropriate for those operations. | |
* | |
* Developers are strongly encouraged to integrate the functionality needed by | |
* their form or module within one of these three pipelines, using the | |
* appropriate callback property, rather than implementing their own recursive | |
* traversal of a form array. This facilitates proper integration between | |
* multiple modules. For example, module developers are familiar with the | |
* relative order in which hook_form_alter() implementations and #process | |
* functions run. A custom traversal function that affects the building of a | |
* form is likely to not integrate with hook_form_alter() and #process in the | |
* expected way. Also, deep recursion within PHP is both slow and memory | |
* intensive, so it is best to minimize how often it's done. | |
* | |
* As stated above, each element's #process functions are executed after its | |
* #value has been set. This enables those functions to execute conditional | |
* logic based on the current value. However, all of form_builder() runs before | |
* drupal_validate_form() is called, so during #process function execution, the | |
* element's #value has not yet been validated, so any code that requires | |
* validated values must reside within a submit handler. | |
* | |
* As a security measure, user input is used for an element's #value only if the | |
* element exists within $form, is not disabled (as per the #disabled property), | |
* and can be accessed (as per the #access property, except that forms submitted | |
* using drupal_form_submit() bypass #access restrictions). When user input is | |
* ignored due to #disabled and #access restrictions, the element's default | |
* value is used. | |
* | |
* Because of the preorder traversal, where #process functions of an element run | |
* before user input for its child elements is processed, and because of the | |
* Form API security of user input processing with respect to #access and | |
* #disabled described above, this generally means that #process functions | |
* should not use an element's (unvalidated) #value to affect the #disabled or | |
* #access of child elements. Use-cases where a developer may be tempted to | |
* implement such conditional logic usually fall into one of two categories: | |
* - Where user input from the current submission must affect the structure of a | |
* form, including properties like #access and #disabled that affect how the | |
* next submission needs to be processed, a multi-step workflow is needed. | |
* This is most commonly implemented with a submit handler setting persistent | |
* data within $form_state based on *validated* values in | |
* $form_state['values'] and setting $form_state['rebuild']. The form building | |
* functions must then be implmented to use the $form_state data to rebuild | |
* the form with the structure appropriate for the new state. | |
* - Where user input must affect the rendering of the form without affecting | |
* its structure, the necessary conditional rendering logic should reside | |
* within functions that run during the rendering phase (#pre_render, #theme, | |
* #theme_wrappers, and #post_render). | |
* | |
* @param $form_id | |
* A unique string identifying the form for validation, submission, | |
* theming, and hook_form_alter functions. | |
* @param $element | |
* An associative array containing the structure of the current element. | |
* @param $form_state | |
* A keyed array containing the current state of the form. In this | |
* context, it is used to accumulate information about which button | |
* was clicked when the form was submitted, as well as the sanitized | |
* $_POST data. | |
*/ | |
function form_builder($form_id, $element, &$form_state) { | |
// Initialize as unprocessed. | |
$element['#processed'] = FALSE; | |
// Use element defaults. | |
if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = element_info($element['#type']))) { | |
// Overlay $info onto $element, retaining preexisting keys in $element. | |
$element += $info; | |
$element['#defaults_loaded'] = TRUE; | |
} | |
// Assign basic defaults common for all form elements. | |
$element += array( | |
'#required' => FALSE, | |
'#attributes' => array(), | |
'#title_display' => 'before', | |
); | |
// Special handling if we're on the top level form element. | |
if (isset($element['#type']) && $element['#type'] == 'form') { | |
if (!empty($element['#https']) && variable_get('https', FALSE) && | |
!url_is_external($element['#action'])) { | |
global $base_root; | |
// Not an external URL so ensure that it is secure. | |
$element['#action'] = str_replace('http://', 'https://', $base_root) . $element['#action']; | |
} | |
// Store a complete copy of the form in form_state prior to building the form. | |
$form_state['complete form'] = $element; | |
// Set a flag if we have a correct form submission. This is always TRUE for | |
// programmed forms coming from drupal_form_submit(), or if the form_id coming | |
// from the POST data is set and matches the current form_id. | |
if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) { | |
$form_state['process_input'] = TRUE; | |
} | |
else { | |
$form_state['process_input'] = FALSE; | |
} | |
} | |
if (!isset($element['#id'])) { | |
$element['#id'] = drupal_html_id('edit-' . implode('-', $element['#parents'])); | |
} | |
// Handle input elements. | |
if (!empty($element['#input'])) { | |
_form_builder_handle_input_element($form_id, $element, $form_state); | |
} | |
// Allow for elements to expand to multiple elements, e.g., radios, | |
// checkboxes and files. | |
if (isset($element['#process']) && !$element['#processed']) { | |
foreach ($element['#process'] as $process) { | |
$element = $process($element, $form_state, $form_state['complete form']); | |
} | |
$element['#processed'] = TRUE; | |
} | |
// We start off assuming all form elements are in the correct order. | |
$element['#sorted'] = TRUE; | |
// Recurse through all child elements. | |
$count = 0; | |
foreach (element_children($element) as $key) { | |
// Prior to checking properties of child elements, their default properties | |
// need to be loaded. | |
if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = element_info($element[$key]['#type']))) { | |
$element[$key] += $info; | |
$element[$key]['#defaults_loaded'] = TRUE; | |
} | |
// Don't squash an existing tree value. | |
if (!isset($element[$key]['#tree'])) { | |
$element[$key]['#tree'] = $element['#tree']; | |
} | |
// Deny access to child elements if parent is denied. | |
if (isset($element['#access']) && !$element['#access']) { | |
$element[$key]['#access'] = FALSE; | |
} | |
// Make child elements inherit their parent's #disabled and #allow_focus | |
// values unless they specify their own. | |
foreach (array('#disabled', '#allow_focus') as $property) { | |
if (isset($element[$property]) && !isset($element[$key][$property])) { | |
$element[$key][$property] = $element[$property]; | |
} | |
} | |
// Don't squash existing parents value. | |
if (!isset($element[$key]['#parents'])) { | |
// Check to see if a tree of child elements is present. If so, | |
// continue down the tree if required. | |
$element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key); | |
} | |
// Ensure #array_parents follows the actual form structure. | |
$array_parents = isset($element['#array_parents']) ? $element['#array_parents'] : array(); | |
$array_parents[] = $key; | |
$element[$key]['#array_parents'] = $array_parents; | |
// Assign a decimal placeholder weight to preserve original array order. | |
if (!isset($element[$key]['#weight'])) { | |
$element[$key]['#weight'] = $count/1000; | |
} | |
else { | |
// If one of the child elements has a weight then we will need to sort | |
// later. | |
unset($element['#sorted']); | |
} | |
$element[$key] = form_builder($form_id, $element[$key], $form_state); | |
$count++; | |
} | |
// The #after_build flag allows any piece of a form to be altered | |
// after normal input parsing has been completed. | |
if (isset($element['#after_build']) && !isset($element['#after_build_done'])) { | |
foreach ($element['#after_build'] as $function) { | |
$element = $function($element, $form_state); | |
} | |
$element['#after_build_done'] = TRUE; | |
} | |
// If there is a file element, we need to flip a flag so later the | |
// form encoding can be set. | |
if (isset($element['#type']) && $element['#type'] == 'file') { | |
$form_state['has_file_element'] = TRUE; | |
} | |
// Final tasks for the form element after form_builder() has run for all other | |
// elements. | |
if (isset($element['#type']) && $element['#type'] == 'form') { | |
// If there is a file element, we set the form encoding. | |
if (isset($form_state['has_file_element'])) { | |
$element['#attributes']['enctype'] = 'multipart/form-data'; | |
} | |
// If a form contains a single textfield, and the ENTER key is pressed | |
// within it, Internet Explorer submits the form with no POST data | |
// identifying any submit button. Other browsers submit POST data as though | |
// the user clicked the first button. Therefore, to be as consistent as we | |
// can be across browsers, if no 'triggering_element' has been identified | |
// yet, default it to the first button. | |
if (!$form_state['programmed'] && !isset($form_state['triggering_element']) && !empty($form_state['buttons'])) { | |
$form_state['triggering_element'] = $form_state['buttons'][0]; | |
} | |
// If the triggering element specifies "button-level" validation and submit | |
// handlers to run instead of the default form-level ones, then add those to | |
// the form state. | |
foreach (array('validate', 'submit') as $type) { | |
if (isset($form_state['triggering_element']['#' . $type])) { | |
$form_state[$type . '_handlers'] = $form_state['triggering_element']['#' . $type]; | |
} | |
} | |
// If the triggering element executes submit handlers, then set the form | |
// state key that's needed for those handlers to run. | |
if (!empty($form_state['triggering_element']['#executes_submit_callback'])) { | |
$form_state['submitted'] = TRUE; | |
} | |
// Special processing if the triggering element is a button. | |
if (isset($form_state['triggering_element']['#button_type'])) { | |
// Because there are several ways in which the triggering element could | |
// have been determined (including from input variables set by JavaScript | |
// or fallback behavior implemented for IE), and because buttons often | |
// have their #name property not derived from their #parents property, we | |
// can't assume that input processing that's happened up until here has | |
// resulted in $form_state['values'][BUTTON_NAME] being set. But it's | |
// common for forms to have several buttons named 'op' and switch on | |
// $form_state['values']['op'] during submit handler execution. | |
$form_state['values'][$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value']; | |
// @todo Legacy support. Remove in Drupal 8. | |
$form_state['clicked_button'] = $form_state['triggering_element']; | |
} | |
// Update the copy of the complete form for usage in validation handlers. | |
$form_state['complete form'] = $element; | |
} | |
return $element; | |
} | |
/** | |
* Populate the #value and #name properties of input elements so they | |
* can be processed and rendered. | |
*/ | |
function _form_builder_handle_input_element($form_id, &$element, &$form_state) { | |
if (!isset($element['#name'])) { | |
$name = array_shift($element['#parents']); | |
$element['#name'] = $name; | |
if ($element['#type'] == 'file') { | |
// To make it easier to handle $_FILES in file.inc, we place all | |
// file fields in the 'files' array. Also, we do not support | |
// nested file names. | |
$element['#name'] = 'files[' . $element['#name'] . ']'; | |
} | |
elseif (count($element['#parents'])) { | |
$element['#name'] .= '[' . implode('][', $element['#parents']) . ']'; | |
} | |
array_unshift($element['#parents'], $name); | |
} | |
// Setting #disabled to TRUE results in user input being ignored, regardless | |
// of how the element is themed or whether JavaScript is used to change the | |
// control's attributes. However, it's good UI to let the user know that input | |
// is not wanted for the control. HTML supports two attributes for this: | |
// http://www.w3.org/TR/html401/interact/forms.html#h-17.12. If a form wants | |
// to start a control off with one of these attributes for UI purposes only, | |
// but still allow input to be processed if it's sumitted, it can set the | |
// desired attribute in #attributes directly rather than using #disabled. | |
// However, developers should think carefully about the accessibility | |
// implications of doing so: if the form expects input to be enterable under | |
// some condition triggered by JavaScript, how would someone who has | |
// JavaScript disabled trigger that condition? Instead, developers should | |
// consider whether a multi-step form would be more appropriate (#disabled can | |
// be changed from step to step). If one still decides to use JavaScript to | |
// affect when a control is enabled, then it is best for accessibility for the | |
// control to be enabled in the HTML, and disabled by JavaScript on document | |
// ready. | |
if (!empty($element['#disabled'])) { | |
if (!empty($element['#allow_focus'])) { | |
$element['#attributes']['readonly'] = 'readonly'; | |
} | |
else { | |
$element['#attributes']['disabled'] = 'disabled'; | |
} | |
} | |
// With JavaScript or other easy hacking, input can be submitted even for | |
// elements with #access=FALSE or #disabled=TRUE. For security, these must | |
// not be processed. Forms that set #disabled=TRUE on an element do not | |
// expect input for the element, and even forms submitted with | |
// drupal_form_submit() must not be able to get around this. Forms that set | |
// #access=FALSE on an element usually allow access for some users, so forms | |
// submitted with drupal_form_submit() may bypass access restriction and be | |
// treated as high-privelege users instead. | |
$process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))); | |
// Set the element's #value property. | |
if (!isset($element['#value']) && !array_key_exists('#value', $element)) { | |
$value_callback = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value'; | |
if ($process_input) { | |
// Get the input for the current element. NULL values in the input need to | |
// be explicitly distinguished from missing input. (see below) | |
$input_exists = NULL; | |
$input = drupal_array_get_nested_value($form_state['input'], $element['#parents'], $input_exists); | |
// For browser-submitted forms, the submitted values do not contain values | |
// for certain elements (empty multiple select, unchecked checkbox). | |
// During initial form processing, we add explicit NULL values for such | |
// elements in $form_state['input']. When rebuilding the form, we can | |
// distinguish elements having NULL input from elements that were not part | |
// of the initially submitted form and can therefore use default values | |
// for the latter, if required. Programmatically submitted forms can | |
// submit explicit NULL values when calling drupal_form_submit(), so we do | |
// not modify $form_state['input'] for them. | |
if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) { | |
// Add the necessary parent keys to $form_state['input'] and sets the | |
// element's input value to NULL. | |
drupal_array_set_nested_value($form_state['input'], $element['#parents'], NULL); | |
$input_exists = TRUE; | |
} | |
// If we have input for the current element, assign it to the #value | |
// property, optionally filtered through $value_callback. | |
if ($input_exists) { | |
if (function_exists($value_callback)) { | |
$element['#value'] = $value_callback($element, $input, $form_state); | |
} | |
if (!isset($element['#value']) && isset($input)) { | |
$element['#value'] = $input; | |
} | |
} | |
// Mark all posted values for validation. | |
if (isset($element['#value']) || (!empty($element['#required']))) { | |
$element['#needs_validation'] = TRUE; | |
} | |
} | |
// Load defaults. | |
if (!isset($element['#value'])) { | |
// Call #type_value without a second argument to request default_value handling. | |
if (function_exists($value_callback)) { | |
$element['#value'] = $value_callback($element, FALSE, $form_state); | |
} | |
// Final catch. If we haven't set a value yet, use the explicit default value. | |
// Avoid image buttons (which come with garbage value), so we only get value | |
// for the button actually clicked. | |
if (!isset($element['#value']) && empty($element['#has_garbage_value'])) { | |
$element['#value'] = isset($element['#default_value']) ? $element['#default_value'] : ''; | |
} | |
} | |
} | |
// Determine which element (if any) triggered the submission of the form and | |
// keep track of all the clickable buttons in the form for | |
// form_state_values_clean(). Enforce the same input processing restrictions | |
// as above. | |
if ($process_input) { | |
// Detect if the element triggered the submission via AJAX. | |
if (_form_element_triggered_scripted_submission($element, $form_state)) { | |
$form_state['triggering_element'] = $element; | |
} | |
// If the form was submitted by the browser rather than via AJAX, then it | |
// can only have been triggered by a button, and we need to determine which | |
// button within the constraints of how browsers provide this information. | |
if (isset($element['#button_type'])) { | |
// All buttons in the form need to be tracked for | |
// form_state_values_clean() and for the form_builder() code that handles | |
// a form submission containing no button information in $_POST. | |
$form_state['buttons'][] = $element; | |
if (_form_button_was_clicked($element, $form_state)) { | |
$form_state['triggering_element'] = $element; | |
} | |
} | |
} | |
// Set the element's value in $form_state['values'], but only, if its key | |
// does not exist yet (a #value_callback may have already populated it). | |
if (!drupal_array_nested_key_exists($form_state['values'], $element['#parents'])) { | |
form_set_value($element, $element['#value'], $form_state); | |
} | |
} | |
/** | |
* Helper function to handle the convoluted logic of button click detection. | |
* | |
* This detects button or non-button controls that trigger a form submission via | |
* AJAX or some other scriptable environment. These environments can set the | |
* special input key '_triggering_element_name' to identify the triggering | |
* element. If the name alone doesn't identify the element uniquely, the input | |
* key '_triggering_element_value' may also be set to require a match on element | |
* value. An example where this is needed is if there are several buttons all | |
* named 'op', and only differing in their value. | |
*/ | |
function _form_element_triggered_scripted_submission($element, &$form_state) { | |
if (!empty($form_state['input']['_triggering_element_name']) && $element['#name'] == $form_state['input']['_triggering_element_name']) { | |
if (empty($form_state['input']['_triggering_element_value']) || $form_state['input']['_triggering_element_value'] == $element['#value']) { | |
return TRUE; | |
} | |
} | |
return FALSE; | |
} | |
/** | |
* Helper function to handle the convoluted logic of button click detection. | |
* | |
* This detects button controls that trigger a form submission by being clicked | |
* and having the click processed by the browser rather than being captured by | |
* JavaScript. Essentially, it detects if the button's name and value are part | |
* of the POST data, but with extra code to deal with the convoluted way in | |
* which browsers submit data for image button clicks. | |
* | |
* This does not detect button clicks processed by AJAX (that is done in | |
* _form_element_triggered_scripted_submission()) and it does not detect form | |
* submissions from Internet Explorer in response to an ENTER key pressed in a | |
* textfield (form_builder() has extra code for that). | |
* | |
* Because this function contains only part of the logic needed to determine | |
* $form_state['triggering_element'], it should not be called from anywhere | |
* other than within the Form API. Form validation and submit handlers needing | |
* to know which button was clicked should get that information from | |
* $form_state['triggering_element']. | |
*/ | |
function _form_button_was_clicked($element, &$form_state) { | |
// First detect normal 'vanilla' button clicks. Traditionally, all | |
// standard buttons on a form share the same name (usually 'op'), | |
// and the specific return value is used to determine which was | |
// clicked. This ONLY works as long as $form['#name'] puts the | |
// value at the top level of the tree of $_POST data. | |
if (isset($form_state['input'][$element['#name']]) && $form_state['input'][$element['#name']] == $element['#value']) { | |
return TRUE; | |
} | |
// When image buttons are clicked, browsers do NOT pass the form element | |
// value in $_POST. Instead they pass an integer representing the | |
// coordinates of the click on the button image. This means that image | |
// buttons MUST have unique $form['#name'] values, but the details of | |
// their $_POST data should be ignored. | |
elseif (!empty($element['#has_garbage_value']) && isset($element['#value']) && $element['#value'] !== '') { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
* Removes internal Form API elements and buttons from submitted form values. | |
* | |
* This function can be used when a module wants to store all submitted form | |
* values, for example, by serializing them into a single database column. In | |
* such cases, all internal Form API values and all form button elements should | |
* not be contained, and this function allows to remove them before the module | |
* proceeds to storage. Next to button elements, the following internal values | |
* are removed: | |
* - form_id | |
* - form_token | |
* - form_build_id | |
* - op | |
* | |
* @param &$form_state | |
* A keyed array containing the current state of the form, including | |
* submitted form values; altered by reference. | |
*/ | |
function form_state_values_clean(&$form_state) { | |
// Remove internal Form API values. | |
unset($form_state['values']['form_id'], $form_state['values']['form_token'], $form_state['values']['form_build_id'], $form_state['values']['op']); | |
// Remove button values. | |
// form_builder() collects all button elements in a form. We remove the button | |
// value separately for each button element. | |
foreach ($form_state['buttons'] as $button) { | |
// Remove this button's value from the submitted form values by finding | |
// the value corresponding to this button. | |
// We iterate over the #parents of this button and move a reference to | |
// each parent in $form_state['values']. For example, if #parents is: | |
// array('foo', 'bar', 'baz') | |
// then the corresponding $form_state['values'] part will look like this: | |
// array( | |
// 'foo' => array( | |
// 'bar' => array( | |
// 'baz' => 'button_value', | |
// ), | |
// ), | |
// ) | |
// We start by (re)moving 'baz' to $last_parent, so we are able unset it | |
// at the end of the iteration. Initially, $values will contain a | |
// reference to $form_state['values'], but in the iteration we move the | |
// reference to $form_state['values']['foo'], and finally to | |
// $form_state['values']['foo']['bar'], which is the level where we can | |
// unset 'baz' (that is stored in $last_parent). | |
$parents = $button['#parents']; | |
$values = &$form_state['values']; | |
$last_parent = array_pop($parents); | |
foreach ($parents as $parent) { | |
$values = &$values[$parent]; | |
} | |
unset($values[$last_parent]); | |
} | |
} | |
/** | |
* Helper function to determine the value for an image button form element. | |
* | |
* @param $form | |
* The form element whose value is being populated. | |
* @param $input | |
* The incoming input to populate the form element. If this is FALSE, | |
* the element's default value should be returned. | |
* @param $form_state | |
* A keyed array containing the current state of the form. | |
* @return | |
* The data that will appear in the $form_state['values'] collection | |
* for this element. Return nothing to use the default. | |
*/ | |
function form_type_image_button_value($form, $input, $form_state) { | |
if ($input !== FALSE) { | |
if (!empty($input)) { | |
// If we're dealing with Mozilla or Opera, we're lucky. It will | |
// return a proper value, and we can get on with things. | |
return $form['#return_value']; | |
} | |
else { | |
// Unfortunately, in IE we never get back a proper value for THIS | |
// form element. Instead, we get back two split values: one for the | |
// X and one for the Y coordinates on which the user clicked the | |
// button. We'll find this element in the #post data, and search | |
// in the same spot for its name, with '_x'. | |
$input = $form_state['input']; | |
foreach (explode('[', $form['#name']) as $element_name) { | |
// chop off the ] that may exist. | |
if (substr($element_name, -1) == ']') { | |
$element_name = substr($element_name, 0, -1); | |
} | |
if (!isset($input[$element_name])) { | |
if (isset($input[$element_name . '_x'])) { | |
return $form['#return_value']; | |
} | |
return NULL; | |
} | |
$input = $input[$element_name]; | |
} | |
return $form['#return_value']; | |
} | |
} | |
} | |
/** | |
* Helper function to determine the value for a checkbox form element. | |
* | |
* @param $form | |
* The form element whose value is being populated. | |
* @param $input | |
* The incoming input to populate the form element. If this is FALSE, | |
* the element's default value should be returned. | |
* @return | |
* The data that will appear in the $element_state['values'] collection | |
* for this element. Return nothing to use the default. | |
*/ | |
function form_type_checkbox_value($element, $input = FALSE) { | |
if ($input !== FALSE) { | |
// Successful (checked) checkboxes are present with a value (possibly '0'). | |
// http://www.w3.org/TR/html401/interact/forms.html#successful-controls | |
// For an unchecked checkbox, we return integer 0, so we can explicitly | |
// test for a value different than string '0'. | |
return isset($input) ? $element['#return_value'] : 0; | |
} | |
} | |
/** | |
* Helper function to determine the value for a checkboxes form element. | |
* | |
* @param $element | |
* The form element whose value is being populated. | |
* @param $input | |
* The incoming input to populate the form element. If this is FALSE, | |
* the element's default value should be returned. | |
* @return | |
* The data that will appear in the $element_state['values'] collection | |
* for this element. Return nothing to use the default. | |
*/ | |
function form_type_checkboxes_value($element, $input = FALSE) { | |
if ($input === FALSE) { | |
$value = array(); | |
$element += array('#default_value' => array()); | |
foreach ($element['#default_value'] as $key) { | |
$value[$key] = $key; | |
} | |
return $value; | |
} | |
elseif (is_array($input)) { | |
// Programmatic form submissions use NULL to indicate that a checkbox | |
// should be unchecked; see drupal_form_submit(). We therefore remove all | |
// NULL elements from the array before constructing the return value, to | |
// simulate the behavior of web browsers (which do not send unchecked | |
// checkboxes to the server at all). This will not affect non-programmatic | |
// form submissions, since a checkbox can never legitimately be NULL. | |
foreach ($input as $key => $value) { | |
if (!isset($value)) { | |
unset($input[$key]); | |
} | |
} | |
return drupal_map_assoc($input); | |
} | |
else { | |
return array(); | |
} | |
} | |
/** | |
* Helper function to determine the value for a password_confirm form | |
* element. | |
* | |
* @param $element | |
* The form element whose value is being populated. | |
* @param $input | |
* The incoming input to populate the form element. If this is FALSE, | |
* the element's default value should be returned. | |
* @return | |
* The data that will appear in the $element_state['values'] collection | |
* for this element. Return nothing to use the default. | |
*/ | |
function form_type_password_confirm_value($element, $input = FALSE) { | |
if ($input === FALSE) { | |
$element += array('#default_value' => array()); | |
return $element['#default_value'] + array('pass1' => '', 'pass2' => ''); | |
} | |
} | |
/** | |
* Helper function to determine the value for a select form element. | |
* | |
* @param $element | |
* The form element whose value is being populated. | |
* @param $input | |
* The incoming input to populate the form element. If this is FALSE, | |
* the element's default value should be returned. | |
* @return | |
* The data that will appear in the $element_state['values'] collection | |
* for this element. Return nothing to use the default. | |
*/ | |
function form_type_select_value($element, $input = FALSE) { | |
if ($input !== FALSE) { | |
if (isset($element['#multiple']) && $element['#multiple']) { | |
// If an enabled multi-select submits NULL, it means all items are | |
// unselected. A disabled multi-select always submits NULL, and the | |
// default value should be used. | |
if (empty($element['#disabled'])) { | |
return (is_array($input)) ? drupal_map_assoc($input) : array(); | |
} | |
else { | |
return (isset($element['#default_value']) && is_array($element['#default_value'])) ? $element['#default_value'] : array(); | |
} | |
} | |
// Non-multiple select elements may have an empty option preprended to them | |
// (see form_process_select()). When this occurs, usually #empty_value is | |
// an empty string, but some forms set #empty_value to integer 0 or some | |
// other non-string constant. PHP receives all submitted form input as | |
// strings, but if the empty option is selected, set the value to match the | |
// empty value exactly. | |
elseif (isset($element['#empty_value']) && $input === (string) $element['#empty_value']) { | |
return $element['#empty_value']; | |
} | |
else { | |
return $input; | |
} | |
} | |
} | |
/** | |
* Helper function to determine the value for a textfield form element. | |
* | |
* @param $element | |
* The form element whose value is being populated. | |
* @param $input | |
* The incoming input to populate the form element. If this is FALSE, | |
* the element's default value should be returned. | |
* @return | |
* The data that will appear in the $element_state['values'] collection | |
* for this element. Return nothing to use the default. | |
*/ | |
function form_type_textfield_value($element, $input = FALSE) { | |
if ($input !== FALSE && $input !== NULL) { | |
// Equate $input to the form value to ensure it's marked for | |
// validation. | |
return str_replace(array("\r", "\n"), '', $input); | |
} | |
} | |
/** | |
* Helper function to determine the value for form's token value. | |
* | |
* @param $element | |
* The form element whose value is being populated. | |
* @param $input | |
* The incoming input to populate the form element. If this is FALSE, | |
* the element's default value should be returned. | |
* @return | |
* The data that will appear in the $element_state['values'] collection | |
* for this element. Return nothing to use the default. | |
*/ | |
function form_type_token_value($element, $input = FALSE) { | |
if ($input !== FALSE) { | |
return (string) $input; | |
} | |
} | |
/** | |
* Change submitted form values during form validation. | |
* | |
* Use this function to change the submitted value of a form element in a form | |
* validation function, so that the changed value persists in $form_state | |
* through to the submission handlers. | |
* | |
* Note that form validation functions are specified in the '#validate' | |
* component of the form array (the value of $form['#validate'] is an array of | |
* validation function names). If the form does not originate in your module, | |
* you can implement hook_form_FORM_ID_alter() to add a validation function | |
* to $form['#validate']. | |
* | |
* @param $element | |
* The form element that should have its value updated; in most cases you can | |
* just pass in the element from the $form array, although the only component | |
* that is actually used is '#parents'. If constructing yourself, set | |
* $element['#parents'] to be an array giving the path through the form | |
* array's keys to the element whose value you want to update. For instance, | |
* if you want to update the value of $form['elem1']['elem2'], which should be | |
* stored in $form_state['values']['elem1']['elem2'], you would set | |
* $element['#parents'] = array('elem1','elem2'). | |
* @param $value | |
* The new value for the form element. | |
* @param $form_state | |
* Form state array where the value change should be recorded. | |
*/ | |
function form_set_value($element, $value, &$form_state) { | |
drupal_array_set_nested_value($form_state['values'], $element['#parents'], $value); | |
} | |
/** | |
* Allows PHP array processing of multiple select options with the same value. | |
* | |
* Used for form select elements which need to validate HTML option groups | |
* and multiple options which may return the same value. Associative PHP arrays | |
* cannot handle these structures, since they share a common key. | |
* | |
* @param $array | |
* The form options array to process. | |
* | |
* @return | |
* An array with all hierarchical elements flattened to a single array. | |
*/ | |
function form_options_flatten($array) { | |
// Always reset static var when first entering the recursion. | |
drupal_static_reset('_form_options_flatten'); | |
return _form_options_flatten($array); | |
} | |
/** | |
* Helper function for form_options_flatten(). | |
* | |
* Iterates over arrays which may share common values and produces a flat | |
* array that has removed duplicate keys. Also handles cases where objects | |
* are passed as array values. | |
*/ | |
function _form_options_flatten($array) { | |
$return = &drupal_static(__FUNCTION__); | |
foreach ($array as $key => $value) { | |
if (is_object($value)) { | |
_form_options_flatten($value->option); | |
} | |
elseif (is_array($value)) { | |
_form_options_flatten($value); | |
} | |
else { | |
$return[$key] = 1; | |
} | |
} | |
return $return; | |
} | |
/** | |
* Processes a select list form element. | |
* | |
* This process callback is mandatory for select fields, since all user agents | |
* automatically preselect the first available option of single (non-multiple) | |
* select lists. | |
* | |
* @param $element | |
* The form element to process. Properties used: | |
* - #multiple: (optional) Indicates whether one or more options can be | |
* selected. Defaults to FALSE. | |
* - #default_value: Must be NULL or not set in case there is no value for the | |
* element yet, in which case a first default option is inserted by default. | |
* Whether this first option is a valid option depends on whether the field | |
* is #required or not. | |
* - #required: (optional) Whether the user needs to select an option (TRUE) | |
* or not (FALSE). Defaults to FALSE. | |
* - #empty_option: (optional) The label to show for the first default option. | |
* By default, the label is automatically set to "- Please select -" for a | |
* required field and "- None -" for an optional field. | |
* - #empty_value: (optional) The value for the first default option, which is | |
* used to determine whether the user submitted a value or not. | |
* - If #required is TRUE, this defaults to '' (an empty string). | |
* - If #required is not TRUE and this value isn't set, then no extra option | |
* is added to the select control, leaving the control in a slightly | |
* illogical state, because there's no way for the user to select nothing, | |
* since all user agents automatically preselect the first available | |
* option. But people are used to this being the behavior of select | |
* controls. | |
* @todo Address the above issue in Drupal 8. | |
* - If #required is not TRUE and this value is set (most commonly to an | |
* empty string), then an extra option (see #empty_option above) | |
* representing a "non-selection" is added with this as its value. | |
* | |
* @see _form_validate() | |
*/ | |
function form_process_select($element) { | |
// #multiple select fields need a special #name. | |
if ($element['#multiple']) { | |
$element['#attributes']['multiple'] = 'multiple'; | |
$element['#attributes']['name'] = $element['#name'] . '[]'; | |
} | |
// A non-#multiple select needs special handling to prevent user agents from | |
// preselecting the first option without intention. #multiple select lists do | |
// not get an empty option, as it would not make sense, user interface-wise. | |
else { | |
$required = $element['#required']; | |
// If the element is required and there is no #default_value, then add an | |
// empty option that will fail validation, so that the user is required to | |
// make a choice. Also, if there's a value for #empty_value or | |
// #empty_option, then add an option that represents emptiness. | |
if (($required && !isset($element['#default_value'])) || isset($element['#empty_value']) || isset($element['#empty_option'])) { | |
$element += array( | |
'#empty_value' => '', | |
'#empty_option' => $required ? t('- Select - ') : t('- None -'), | |
); | |
// The empty option is prepended to #options and purposively not merged | |
// to prevent another option in #options mistakenly using the same value | |
// as #empty_value. | |
$empty_option = array($element['#empty_value'] => $element['#empty_option']); | |
$element['#options'] = $empty_option + $element['#options']; | |
} | |
} | |
return $element; | |
} | |
/** | |
* Returns HTML for a select form element. | |
* | |
* It is possible to group options together; to do this, change the format of | |
* $options to an associative array in which the keys are group labels, and the | |
* values are associative arrays in the normal $options format. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #value, #options, #description, #extra, | |
* #multiple, #required, #name, #attributes, #size. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_select($variables) { | |
$element = $variables['element']; | |
element_set_attributes($element, array('id', 'name', 'size')); | |
_form_set_class($element, array('form-select')); | |
return '<select' . drupal_attributes($element['#attributes']) . '>' . form_select_options($element) . '</select>'; | |
} | |
/** | |
* Converts a select form element's options array into an HTML. | |
* | |
* @param $element | |
* An associative array containing the properties of the element. | |
* @param $choices | |
* Mixed: Either an associative array of items to list as choices, or an | |
* object with an 'option' member that is an associative array. This | |
* parameter is only used internally and should not be passed. | |
* @return | |
* An HTML string of options for the select form element. | |
*/ | |
function form_select_options($element, $choices = NULL) { | |
if (!isset($choices)) { | |
$choices = $element['#options']; | |
} | |
// array_key_exists() accommodates the rare event where $element['#value'] is NULL. | |
// isset() fails in this situation. | |
$value_valid = isset($element['#value']) || array_key_exists('#value', $element); | |
$value_is_array = $value_valid && is_array($element['#value']); | |
$options = ''; | |
foreach ($choices as $key => $choice) { | |
if (is_array($choice)) { | |
$options .= '<optgroup label="' . $key . '">'; | |
$options .= form_select_options($element, $choice); | |
$options .= '</optgroup>'; | |
} | |
elseif (is_object($choice)) { | |
$options .= form_select_options($element, $choice->option); | |
} | |
else { | |
$key = (string) $key; | |
if ($value_valid && (!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) { | |
$selected = ' selected="selected"'; | |
} | |
else { | |
$selected = ''; | |
} | |
$options .= '<option value="' . check_plain($key) . '"' . $selected . '>' . check_plain($choice) . '</option>'; | |
} | |
} | |
return $options; | |
} | |
/** | |
* Traverses a select element's #option array looking for any values | |
* that hold the given key. Returns an array of indexes that match. | |
* | |
* This function is useful if you need to modify the options that are | |
* already in a form element; for example, to remove choices which are | |
* not valid because of additional filters imposed by another module. | |
* One example might be altering the choices in a taxonomy selector. | |
* To correctly handle the case of a multiple hierarchy taxonomy, | |
* #options arrays can now hold an array of objects, instead of a | |
* direct mapping of keys to labels, so that multiple choices in the | |
* selector can have the same key (and label). This makes it difficult | |
* to manipulate directly, which is why this helper function exists. | |
* | |
* This function does not support optgroups (when the elements of the | |
* #options array are themselves arrays), and will return FALSE if | |
* arrays are found. The caller must either flatten/restore or | |
* manually do their manipulations in this case, since returning the | |
* index is not sufficient, and supporting this would make the | |
* "helper" too complicated and cumbersome to be of any help. | |
* | |
* As usual with functions that can return array() or FALSE, do not | |
* forget to use === and !== if needed. | |
* | |
* @param $element | |
* The select element to search. | |
* @param $key | |
* The key to look for. | |
* @return | |
* An array of indexes that match the given $key. Array will be | |
* empty if no elements were found. FALSE if optgroups were found. | |
*/ | |
function form_get_options($element, $key) { | |
$keys = array(); | |
foreach ($element['#options'] as $index => $choice) { | |
if (is_array($choice)) { | |
return FALSE; | |
} | |
elseif (is_object($choice)) { | |
if (isset($choice->option[$key])) { | |
$keys[] = $index; | |
} | |
} | |
elseif ($index == $key) { | |
$keys[] = $index; | |
} | |
} | |
return $keys; | |
} | |
/** | |
* Returns HTML for a fieldset form element and its children. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #attributes, #children, #collapsed, #collapsible, | |
* #description, #id, #title, #value. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_fieldset($variables) { | |
$element = $variables['element']; | |
element_set_attributes($element, array('id')); | |
_form_set_class($element, array('form-wrapper')); | |
$output = '<fieldset' . drupal_attributes($element['#attributes']) . '>'; | |
if (!empty($element['#title'])) { | |
// Always wrap fieldset legends in a SPAN for CSS positioning. | |
$output .= '<legend><span class="fieldset-legend">' . $element['#title'] . '</span></legend>'; | |
} | |
$output .= '<div class="fieldset-wrapper">'; | |
if (!empty($element['#description'])) { | |
$output .= '<div class="fieldset-description">' . $element['#description'] . '</div>'; | |
} | |
$output .= $element['#children']; | |
if (isset($element['#value'])) { | |
$output .= $element['#value']; | |
} | |
$output .= '</div>'; | |
$output .= "</fieldset>\n"; | |
return $output; | |
} | |
/** | |
* Returns HTML for a radio button form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #required, #return_value, #value, #attributes, #title, | |
* #description | |
* | |
* @ingroup themeable | |
*/ | |
function theme_radio($variables) { | |
$element = $variables['element']; | |
$element['#attributes']['type'] = 'radio'; | |
element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); | |
if (isset($element['#return_value']) && check_plain($element['#value']) == $element['#return_value']) { | |
$element['#attributes']['checked'] = 'checked'; | |
} | |
_form_set_class($element, array('form-radio')); | |
return '<input' . drupal_attributes($element['#attributes']) . ' />'; | |
} | |
/** | |
* Returns HTML for a set of radio button form elements. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #value, #options, #description, #required, | |
* #attributes, #children. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_radios($variables) { | |
$element = $variables['element']; | |
$attributes = array(); | |
if (isset($element['#id'])) { | |
$attributes['id'] = $element['#id']; | |
} | |
$attributes['class'] = 'form-radios'; | |
if (!empty($element['#attributes']['class'])) { | |
$attributes['class'] .= ' ' . implode(' ', $element['#attributes']['class']); | |
} | |
return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>'; | |
} | |
/** | |
* Expand a password_confirm field into two text boxes. | |
*/ | |
function form_process_password_confirm($element) { | |
$element['pass1'] = array( | |
'#type' => 'password', | |
'#title' => t('Password'), | |
'#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'], | |
'#required' => $element['#required'], | |
'#attributes' => array('class' => array('password-field')), | |
); | |
$element['pass2'] = array( | |
'#type' => 'password', | |
'#title' => t('Confirm password'), | |
'#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'], | |
'#required' => $element['#required'], | |
'#attributes' => array('class' => array('password-confirm')), | |
); | |
$element['#element_validate'] = array('password_confirm_validate'); | |
$element['#tree'] = TRUE; | |
if (isset($element['#size'])) { | |
$element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size']; | |
} | |
return $element; | |
} | |
/** | |
* Validate password_confirm element. | |
*/ | |
function password_confirm_validate($element, &$element_state) { | |
$pass1 = trim($element['pass1']['#value']); | |
$pass2 = trim($element['pass2']['#value']); | |
if (!empty($pass1) || !empty($pass2)) { | |
if (strcmp($pass1, $pass2)) { | |
form_error($element, t('The specified passwords do not match.')); | |
} | |
} | |
elseif ($element['#required'] && !empty($element_state['input'])) { | |
form_error($element, t('Password field is required.')); | |
} | |
// Password field must be converted from a two-element array into a single | |
// string regardless of validation results. | |
form_set_value($element['pass1'], NULL, $element_state); | |
form_set_value($element['pass2'], NULL, $element_state); | |
form_set_value($element, $pass1, $element_state); | |
return $element; | |
} | |
/** | |
* Returns HTML for a date selection form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #value, #options, #description, #required, | |
* #attributes. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_date($variables) { | |
$element = $variables['element']; | |
return '<div class="container-inline">' . drupal_render_children($element) . '</div>'; | |
} | |
/** | |
* Roll out a single date element. | |
*/ | |
function form_process_date($element) { | |
// Default to current date | |
if (empty($element['#value'])) { | |
$element['#value'] = array( | |
'day' => format_date(REQUEST_TIME, 'custom', 'j'), | |
'month' => format_date(REQUEST_TIME, 'custom', 'n'), | |
'year' => format_date(REQUEST_TIME, 'custom', 'Y'), | |
); | |
} | |
$element['#tree'] = TRUE; | |
// Determine the order of day, month, year in the site's chosen date format. | |
$format = variable_get('date_format_short', 'm/d/Y - H:i'); | |
$sort = array(); | |
$sort['day'] = max(strpos($format, 'd'), strpos($format, 'j')); | |
$sort['month'] = max(strpos($format, 'm'), strpos($format, 'M')); | |
$sort['year'] = strpos($format, 'Y'); | |
asort($sort); | |
$order = array_keys($sort); | |
// Output multi-selector for date. | |
foreach ($order as $type) { | |
switch ($type) { | |
case 'day': | |
$options = drupal_map_assoc(range(1, 31)); | |
break; | |
case 'month': | |
$options = drupal_map_assoc(range(1, 12), 'map_month'); | |
break; | |
case 'year': | |
$options = drupal_map_assoc(range(1900, 2050)); | |
break; | |
} | |
$element[$type] = array( | |
'#type' => 'select', | |
'#value' => $element['#value'][$type], | |
'#attributes' => $element['#attributes'], | |
'#options' => $options, | |
); | |
} | |
return $element; | |
} | |
/** | |
* Validates the date type to stop dates like February 30, 2006. | |
*/ | |
function date_validate($form) { | |
if (!checkdate($form['#value']['month'], $form['#value']['day'], $form['#value']['year'])) { | |
form_error($form, t('The specified date is invalid.')); | |
} | |
} | |
/** | |
* Helper function for usage with drupal_map_assoc to display month names. | |
*/ | |
function map_month($month) { | |
$months = &drupal_static(__FUNCTION__, array( | |
1 => 'Jan', | |
2 => 'Feb', | |
3 => 'Mar', | |
4 => 'Apr', | |
5 => 'May', | |
6 => 'Jun', | |
7 => 'Jul', | |
8 => 'Aug', | |
9 => 'Sep', | |
10 => 'Oct', | |
11 => 'Nov', | |
12 => 'Dec', | |
)); | |
return t($months[$month]); | |
} | |
/** | |
* If no default value is set for weight select boxes, use 0. | |
*/ | |
function weight_value(&$form) { | |
if (isset($form['#default_value'])) { | |
$form['#value'] = $form['#default_value']; | |
} | |
else { | |
$form['#value'] = 0; | |
} | |
} | |
/** | |
* Roll out a single radios element to a list of radios, | |
* using the options array as index. | |
*/ | |
function form_process_radios($element) { | |
if (count($element['#options']) > 0) { | |
foreach ($element['#options'] as $key => $choice) { | |
if (!isset($element[$key])) { | |
// Generate the parents as the autogenerator does, so we will have a | |
// unique id for each radio button. | |
$parents_for_id = array_merge($element['#parents'], array($key)); | |
$element[$key] = array( | |
'#type' => 'radio', | |
'#title' => $choice, | |
'#return_value' => check_plain($key), | |
'#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL, | |
'#attributes' => $element['#attributes'], | |
'#parents' => $element['#parents'], | |
'#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), | |
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, | |
); | |
} | |
} | |
} | |
return $element; | |
} | |
/** | |
* Returns HTML for a checkbox form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #value, #return_value, #description, #required, | |
* #attributes. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_checkbox($variables) { | |
$element = $variables['element']; | |
$t = get_t(); | |
$element['#attributes']['type'] = 'checkbox'; | |
element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); | |
// Unchecked checkbox has #value of integer 0. | |
if (isset($element['#return_value']) && isset($element['#value']) && $element['#value'] !== 0 && $element['#value'] == $element['#return_value']) { | |
$element['#attributes']['checked'] = 'checked'; | |
} | |
_form_set_class($element, array('form-checkbox')); | |
return '<input' . drupal_attributes($element['#attributes']) . ' />'; | |
} | |
/** | |
* Returns HTML for a set of checkbox form elements. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #children, #attributes. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_checkboxes($variables) { | |
$element = $variables['element']; | |
$attributes = array(); | |
if (isset($element['#id'])) { | |
$attributes['id'] = $element['#id']; | |
} | |
$attributes['class'][] = 'form-checkboxes'; | |
if (!empty($element['#attributes']['class'])) { | |
$attributes['class'] = array_merge($attributes['class'], $element['#attributes']['class']); | |
} | |
return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>'; | |
} | |
/** | |
* Add form_element theming to an element if title or description is set. | |
* | |
* This is used as a pre render function for checkboxes and radios. | |
*/ | |
function form_pre_render_conditional_form_element($element) { | |
// Set the element's title attribute to show #title as a tooltip, if needed. | |
if (isset($element['#title']) && $element['#title_display'] == 'attribute') { | |
$element['#attributes']['title'] = $element['#title']; | |
if (!empty($element['#required'])) { | |
// Append an indication that this field is required. | |
$element['#attributes']['title'] .= ' (' . $t('Required') . ')'; | |
} | |
} | |
if (isset($element['#title']) || isset($element['#description'])) { | |
$element['#theme_wrappers'][] = 'form_element'; | |
} | |
return $element; | |
} | |
function form_process_checkboxes($element) { | |
$value = is_array($element['#value']) ? $element['#value'] : array(); | |
$element['#tree'] = TRUE; | |
if (count($element['#options']) > 0) { | |
if (!isset($element['#default_value']) || $element['#default_value'] == 0) { | |
$element['#default_value'] = array(); | |
} | |
foreach ($element['#options'] as $key => $choice) { | |
if (!isset($element[$key])) { | |
$element[$key] = array( | |
'#type' => 'checkbox', | |
'#processed' => TRUE, | |
'#title' => $choice, | |
'#return_value' => $key, | |
'#default_value' => isset($value[$key]) ? $key : NULL, | |
'#attributes' => $element['#attributes'], | |
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, | |
); | |
} | |
} | |
} | |
return $element; | |
} | |
/** | |
* Processes a form actions container element. | |
* | |
* @param $element | |
* An associative array containing the properties and children of the | |
* form actions container. | |
* @param $form_state | |
* The $form_state array for the form this element belongs to. | |
* | |
* @return | |
* The processed element. | |
*/ | |
function form_process_actions($element, &$form_state) { | |
$element['#attributes']['class'][] = 'form-actions'; | |
return $element; | |
} | |
/** | |
* Processes a container element. | |
* | |
* @param $element | |
* An associative array containing the properties and children of the | |
* container. | |
* @param $form_state | |
* The $form_state array for the form this element belongs to. | |
* @return | |
* The processed element. | |
*/ | |
function form_process_container($element, &$form_state) { | |
// Generate the ID of the element if it's not explicitly given. | |
if (!isset($element['#id'])) { | |
$element['#id'] = drupal_html_id(implode('-', $element['#parents']) . '-wrapper'); | |
} | |
return $element; | |
} | |
/** | |
* Returns HTML for a container for grouped form items. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #id, #attributes, #children. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_container($variables) { | |
$element = $variables['element']; | |
if (!isset($element['#attributes']['id'])) { | |
$element['#attributes']['id'] = $element['#id']; | |
} | |
// Force the 'form-wrapper' class. | |
$element['#attributes']['class'][] = 'form-wrapper'; | |
return '<div' . drupal_attributes($element['#attributes']) . '>' . $element['#children'] . '</div>'; | |
} | |
/** | |
* Returns HTML for a table with radio buttons or checkboxes. | |
* | |
* An example of per-row options: | |
* @code | |
* $options = array(); | |
* $options[0]['title'] = "A red row" | |
* $options[0]['#attributes'] = array ('class' => array('red-row')); | |
* $options[1]['title'] = "A blue row" | |
* $options[1]['#attributes'] = array ('class' => array('blue-row')); | |
* | |
* $form['myselector'] = array ( | |
* '#type' => 'tableselect', | |
* '#title' => 'My Selector' | |
* '#options' => $options, | |
* ); | |
* @endcode | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties and children of | |
* the tableselect element. Properties used: #header, #options, #empty, | |
* and #js_select. The #options property is an array of selection options; | |
* each array element of #options is an array of properties. These | |
* properties can include #attributes, which is added to the | |
* table row's HTML attributes; see theme_table(). | |
* | |
* @ingroup themeable | |
*/ | |
function theme_tableselect($variables) { | |
$element = $variables['element']; | |
$rows = array(); | |
$header = $element['#header']; | |
if (!empty($element['#options'])) { | |
// Generate a table row for each selectable item in #options. | |
foreach (element_children($element) as $key) { | |
$row = array(); | |
$row['data'] = array(); | |
if (isset($element['#options'][$key]['#attributes'])) { | |
$row += $element['#options'][$key]['#attributes']; | |
} | |
// Render the checkbox / radio element. | |
$row['data'][] = drupal_render($element[$key]); | |
// As theme_table only maps header and row columns by order, create the | |
// correct order by iterating over the header fields. | |
foreach ($element['#header'] as $fieldname => $title) { | |
$row['data'][] = $element['#options'][$key][$fieldname]; | |
} | |
$rows[] = $row; | |
} | |
// Add an empty header or a "Select all" checkbox to provide room for the | |
// checkboxes/radios in the first table column. | |
if ($element['#js_select']) { | |
// Add a "Select all" checkbox. | |
drupal_add_js('misc/tableselect.js'); | |
array_unshift($header, array('class' => array('select-all'))); | |
} | |
else { | |
// Add an empty header when radio buttons are displayed or a "Select all" | |
// checkbox is not desired. | |
array_unshift($header, ''); | |
} | |
} | |
return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => $element['#empty'], 'attributes' => $element['#attributes'])); | |
} | |
/** | |
* Create the correct amount of checkbox or radio elements to populate the table. | |
* | |
* @param $element | |
* An associative array containing the properties and children of the | |
* tableselect element. | |
* @return | |
* The processed element. | |
*/ | |
function form_process_tableselect($element) { | |
if ($element['#multiple']) { | |
$value = is_array($element['#value']) ? $element['#value'] : array(); | |
} | |
else { | |
// Advanced selection behaviour make no sense for radios. | |
$element['#js_select'] = FALSE; | |
} | |
$element['#tree'] = TRUE; | |
if (count($element['#options']) > 0) { | |
if (!isset($element['#default_value']) || $element['#default_value'] === 0) { | |
$element['#default_value'] = array(); | |
} | |
// Create a checkbox or radio for each item in #options in such a way that | |
// the value of the tableselect element behaves as if it had been of type | |
// checkboxes or radios. | |
foreach ($element['#options'] as $key => $choice) { | |
// Do not overwrite manually created children. | |
if (!isset($element[$key])) { | |
if ($element['#multiple']) { | |
$element[$key] = array( | |
'#type' => 'checkbox', | |
'#title' => '', | |
'#return_value' => $key, | |
'#default_value' => isset($value[$key]) ? $key : NULL, | |
'#attributes' => $element['#attributes'], | |
); | |
} | |
else { | |
// Generate the parents as the autogenerator does, so we will have a | |
// unique id for each radio button. | |
$parents_for_id = array_merge($element['#parents'], array($key)); | |
$element[$key] = array( | |
'#type' => 'radio', | |
'#title' => '', | |
'#return_value' => $key, | |
'#default_value' => ($element['#default_value'] == $key) ? $key : NULL, | |
'#attributes' => $element['#attributes'], | |
'#parents' => $element['#parents'], | |
'#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), | |
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, | |
); | |
} | |
if (isset($element['#options'][$key]['#weight'])) { | |
$element[$key]['#weight'] = $element['#options'][$key]['#weight']; | |
} | |
} | |
} | |
} | |
else { | |
$element['#value'] = array(); | |
} | |
return $element; | |
} | |
/** | |
* Adds fieldsets to the specified group or adds group members to this | |
* fieldset. | |
* | |
* @param &$element | |
* An associative array containing the properties and children of the | |
* fieldset. Note that $element must be taken by reference here, so processed | |
* child elements are taken over into $form_state. | |
* @param $form_state | |
* The $form_state array for the form this fieldset belongs to. | |
* @return | |
* The processed element. | |
*/ | |
function form_process_fieldset(&$element, &$form_state) { | |
$parents = implode('][', $element['#parents']); | |
// Each fieldset forms a new group. The #type 'vertical_tabs' basically only | |
// injects a new fieldset. | |
$form_state['groups'][$parents]['#group_exists'] = TRUE; | |
$element['#groups'] = &$form_state['groups']; | |
// Process vertical tabs group member fieldsets. | |
if (isset($element['#group'])) { | |
// Add this fieldset to the defined group (by reference). | |
$group = $element['#group']; | |
$form_state['groups'][$group][] = &$element; | |
} | |
// Contains form element summary functionalities. | |
$element['#attached']['js']['misc/form.js'] = array('group' => JS_LIBRARY, 'weight' => 1); | |
// The .form-wrapper class is required for #states to treat fieldsets like | |
// containers. | |
if (!isset($element['#attributes']['class'])) { | |
$element['#attributes']['class'] = array(); | |
} | |
// Collapsible fieldsets | |
if (!empty($element['#collapsible'])) { | |
$element['#attached']['js'][] = 'misc/collapse.js'; | |
$element['#attributes']['class'][] = 'collapsible'; | |
if (!empty($element['#collapsed'])) { | |
$element['#attributes']['class'][] = 'collapsed'; | |
} | |
} | |
return $element; | |
} | |
/** | |
* Adds members of this group as actual elements for rendering. | |
* | |
* @param $element | |
* An associative array containing the properties and children of the | |
* fieldset. | |
* | |
* @return | |
* The modified element with all group members. | |
*/ | |
function form_pre_render_fieldset($element) { | |
// Fieldsets may be rendered outside of a Form API context. | |
if (!isset($element['#parents']) || !isset($element['#groups'])) { | |
return $element; | |
} | |
// Inject group member elements belonging to this group. | |
$parents = implode('][', $element['#parents']); | |
$children = element_children($element['#groups'][$parents]); | |
if (!empty($children)) { | |
foreach ($children as $key) { | |
// Break references and indicate that the element should be rendered as | |
// group member. | |
$child = (array) $element['#groups'][$parents][$key]; | |
$child['#group_fieldset'] = TRUE; | |
// Inject the element as new child element. | |
$element[] = $child; | |
$sort = TRUE; | |
} | |
// Re-sort the element's children if we injected group member elements. | |
if (isset($sort)) { | |
$element['#sorted'] = FALSE; | |
} | |
} | |
if (isset($element['#group'])) { | |
$group = $element['#group']; | |
// If this element belongs to a group, but the group-holding element does | |
// not exist, we need to render it (at its original location). | |
if (!isset($element['#groups'][$group]['#group_exists'])) { | |
// Intentionally empty to clarify the flow; we simply return $element. | |
} | |
// If we injected this element into the group, then we want to render it. | |
elseif (!empty($element['#group_fieldset'])) { | |
// Intentionally empty to clarify the flow; we simply return $element. | |
} | |
// Otherwise, this element belongs to a group and the group exists, so we do | |
// not render it. | |
elseif (element_children($element['#groups'][$group])) { | |
$element['#printed'] = TRUE; | |
} | |
} | |
return $element; | |
} | |
/** | |
* Creates a group formatted as vertical tabs. | |
* | |
* @param $element | |
* An associative array containing the properties and children of the | |
* fieldset. | |
* @param $form_state | |
* The $form_state array for the form this vertical tab widget belongs to. | |
* @return | |
* The processed element. | |
*/ | |
function form_process_vertical_tabs($element, &$form_state) { | |
// Inject a new fieldset as child, so that form_process_fieldset() processes | |
// this fieldset like any other fieldset. | |
$element['group'] = array( | |
'#type' => 'fieldset', | |
'#theme_wrappers' => array(), | |
'#parents' => $element['#parents'], | |
); | |
// The JavaScript stores the currently selected tab in this hidden | |
// field so that the active tab can be restored the next time the | |
// form is rendered, e.g. on preview pages or when form validation | |
// fails. | |
$name = implode('__', $element['#parents']); | |
if (isset($form_state['values'][$name . '__active_tab'])) { | |
$element['#default_tab'] = $form_state['values'][$name . '__active_tab']; | |
} | |
$element[$name . '__active_tab'] = array( | |
'#type' => 'hidden', | |
'#default_value' => $element['#default_tab'], | |
'#attributes' => array('class' => array('vertical-tabs-active-tab')), | |
); | |
return $element; | |
} | |
/** | |
* Returns HTML for an element's children fieldsets as vertical tabs. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties and children of the | |
* fieldset. Properties used: #children. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_vertical_tabs($variables) { | |
$element = $variables['element']; | |
// Add required JavaScript and Stylesheet. | |
drupal_add_library('system', 'vertical-tabs'); | |
$output = '<h2 class="element-invisible">' . t('Vertical Tabs') . '</h2>'; | |
$output .= '<div class="vertical-tabs-panes">' . $element['#children'] . '</div>'; | |
return $output; | |
} | |
/** | |
* Returns HTML for a submit button form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #attributes, #button_type, #name, #value. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_submit($variables) { | |
return theme('button', $variables['element']); | |
} | |
/** | |
* Returns HTML for a button form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #attributes, #button_type, #name, #value. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_button($variables) { | |
$element = $variables['element']; | |
$element['#attributes']['type'] = 'submit'; | |
element_set_attributes($element, array('id', 'name', 'value')); | |
$element['#attributes']['class'][] = 'form-' . $element['#button_type']; | |
if (!empty($element['#attributes']['disabled'])) { | |
$element['#attributes']['class'][] = 'form-button-disabled'; | |
} | |
return '<input' . drupal_attributes($element['#attributes']) . ' />'; | |
} | |
/** | |
* Returns HTML for an image button form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #attributes, #button_type, #name, #value, #title, #src. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_image_button($variables) { | |
$element = $variables['element']; | |
$element['#attributes']['type'] = 'image'; | |
element_set_attributes($element, array('id', 'name', 'value')); | |
$element['#attributes']['src'] = file_create_url($element['#src']); | |
if (!empty($element['#title'])) { | |
$element['#attributes']['alt'] = $element['#title']; | |
$element['#attributes']['title'] = $element['#title']; | |
} | |
$element['#attributes']['class'][] = 'form-' . $element['#button_type']; | |
if (!empty($element['#attributes']['disabled'])) { | |
$element['#attributes']['class'][] = 'form-button-disabled'; | |
} | |
return '<input' . drupal_attributes($element['#attributes']) . ' />'; | |
} | |
/** | |
* Returns HTML for a hidden form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #name, #value, #attributes. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_hidden($variables) { | |
$element = $variables['element']; | |
$element['#attributes']['type'] = 'hidden'; | |
element_set_attributes($element, array('name', 'value')); | |
return '<input' . drupal_attributes($element['#attributes']) . " />\n"; | |
} | |
/** | |
* Returns HTML for a textfield form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #value, #description, #size, #maxlength, | |
* #required, #attributes, #autocomplete_path. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_textfield($variables) { | |
$element = $variables['element']; | |
$element['#attributes']['type'] = 'text'; | |
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength')); | |
_form_set_class($element, array('form-text')); | |
$extra = ''; | |
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) { | |
drupal_add_js('misc/autocomplete.js'); | |
$element['#attributes']['class'][] = 'form-autocomplete'; | |
$attributes = array(); | |
$attributes['type'] = 'hidden'; | |
$attributes['id'] = $element['#attributes']['id'] . '-autocomplete'; | |
$attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE)); | |
$attributes['disabled'] = 'disabled'; | |
$attributes['class'][] = 'autocomplete'; | |
$extra = '<input' . drupal_attributes($attributes) . ' />'; | |
} | |
$output = '<input' . drupal_attributes($element['#attributes']) . ' />'; | |
return $output . $extra; | |
} | |
/** | |
* Returns HTML for a form. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #action, #method, #attributes, #children | |
* | |
* @ingroup themeable | |
*/ | |
function theme_form($variables) { | |
$element = $variables['element']; | |
if (isset($element['#action'])) { | |
$element['#attributes']['action'] = drupal_strip_dangerous_protocols($element['#action']); | |
} | |
element_set_attributes($element, array('method', 'id')); | |
if (empty($element['#attributes']['accept-charset'])) { | |
$element['#attributes']['accept-charset'] = "UTF-8"; | |
} | |
// Anonymous DIV to satisfy XHTML compliance. | |
return '<form' . drupal_attributes($element['#attributes']) . '><div>' . $element['#children'] . '</div></form>'; | |
} | |
/** | |
* Returns HTML for a textarea form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #value, #description, #rows, #cols, #required, | |
* #attributes | |
* | |
* @ingroup themeable | |
*/ | |
function theme_textarea($variables) { | |
$element = $variables['element']; | |
element_set_attributes($element, array('id', 'name', 'cols', 'rows')); | |
_form_set_class($element, array('form-textarea')); | |
$wrapper_attributes = array( | |
'class' => array('form-textarea-wrapper'), | |
); | |
// Add resizable behavior. | |
if (!empty($element['#resizable'])) { | |
drupal_add_js('misc/textarea.js'); | |
$wrapper_attributes['class'][] = 'resizable'; | |
} | |
$output = '<div' . drupal_attributes($wrapper_attributes) . '>'; | |
$output .= '<textarea' . drupal_attributes($element['#attributes']) . '>' . check_plain($element['#value']) . '</textarea>'; | |
$output .= '</div>'; | |
return $output; | |
} | |
/** | |
* Returns HTML for a password form element. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #value, #description, #size, #maxlength, | |
* #required, #attributes. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_password($variables) { | |
$element = $variables['element']; | |
$element['#attributes']['type'] = 'password'; | |
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength')); | |
_form_set_class($element, array('form-text')); | |
return '<input' . drupal_attributes($element['#attributes']) . ' />'; | |
} | |
/** | |
* Expand weight elements into selects. | |
*/ | |
function form_process_weight($element) { | |
for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) { | |
$weights[$n] = $n; | |
} | |
$element['#options'] = $weights; | |
$element['#type'] = 'select'; | |
$element['#is_weight'] = TRUE; | |
$element += element_info('select'); | |
return $element; | |
} | |
/** | |
* Returns HTML for a file upload form element. | |
* | |
* For assistance with handling the uploaded file correctly, see the API | |
* provided by file.inc. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #name, #size, #description, #required, | |
* #attributes. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_file($variables) { | |
$element = $variables['element']; | |
$element['#attributes']['type'] = 'file'; | |
element_set_attributes($element, array('id', 'name', 'size')); | |
_form_set_class($element, array('form-file')); | |
return '<input' . drupal_attributes($element['#attributes']) . ' />'; | |
} | |
/** | |
* Returns HTML for a form element. | |
* | |
* Each form element is wrapped in a DIV container having the following CSS | |
* classes: | |
* - form-item: Generic for all form elements. | |
* - form-type-#type: The internal element #type. | |
* - form-item-#name: The internal form element #name (usually derived from the | |
* $form structure and set via form_builder()). | |
* - form-disabled: Only set if the form element is #disabled. | |
* | |
* In addition to the element itself, the DIV contains a label for the element | |
* based on the optional #title_display property, and an optional #description. | |
* | |
* The optional #title_display property can have these values: | |
* - before: The label is output before the element. This is the default. | |
* The label includes the #title and the required marker, if #required. | |
* - after: The label is output after the element. For example, this is used | |
* for radio and checkbox #type elements as set in system_element_info(). | |
* If the #title is empty but the field is #required, the label will | |
* contain only the required marker. | |
* - invisible: Labels are critical for screen readers to enable them to | |
* properly navigate through forms but can be visually distracting. This | |
* property hides the label for everyone except screen readers. | |
* - attribute: Set the title attribute on the element to create a tooltip | |
* but output no label element. This is supported only for checkboxes | |
* and radios in form_pre_render_conditional_form_element(). It is used | |
* where a visual label is not needed, such as a table of checkboxes where | |
* the row and column provide the context. The tooltip will include the | |
* title and required marker. | |
* | |
* If the #title property is not set, then the label and any required marker | |
* will not be output, regardless of the #title_display or #required values. | |
* This can be useful in cases such as the password_confirm element, which | |
* creates children elements that have their own labels and required markers, | |
* but the parent element should have neither. Use this carefully because a | |
* field without an associated label can cause accessibility challenges. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #title, #title_display, #description, #id, #required, | |
* #children, #type, #name. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_form_element($variables) { | |
$element = &$variables['element']; | |
// This is also used in the installer, pre-database setup. | |
$t = get_t(); | |
// This function is invoked as theme wrapper, but the rendered form element | |
// may not necessarily have been processed by form_builder(). | |
$element += array( | |
'#title_display' => 'before', | |
); | |
// Add element #id for #type 'item'. | |
if (isset($element['#markup']) && !empty($element['#id'])) { | |
$attributes['id'] = $element['#id']; | |
} | |
// Add element's #type and #name as class to aid with JS/CSS selectors. | |
$attributes['class'] = array('form-item'); | |
if (!empty($element['#type'])) { | |
$attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-'); | |
} | |
if (!empty($element['#name'])) { | |
$attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); | |
} | |
// Add a class for disabled elements to facilitate cross-browser styling. | |
if (!empty($element['#attributes']['disabled'])) { | |
$attributes['class'][] = 'form-disabled'; | |
} | |
$output = '<div' . drupal_attributes($attributes) . '>' . "\n"; | |
// If #title is not set, we don't display any label or required marker. | |
if (!isset($element['#title'])) { | |
$element['#title_display'] = 'none'; | |
} | |
$prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ' : ''; | |
$suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>' : ''; | |
switch ($element['#title_display']) { | |
case 'before': | |
case 'invisible': | |
$output .= ' ' . theme('form_element_label', $variables); | |
$output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; | |
break; | |
case 'after': | |
$output .= ' ' . $prefix . $element['#children'] . $suffix; | |
$output .= ' ' . theme('form_element_label', $variables) . "\n"; | |
break; | |
case 'none': | |
case 'attribute': | |
// Output no label and no required marker, only the children. | |
$output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; | |
break; | |
} | |
if (!empty($element['#description'])) { | |
$output .= '<div class="description">' . $element['#description'] . "</div>\n"; | |
} | |
$output .= "</div>\n"; | |
return $output; | |
} | |
/** | |
* Returns HTML for a marker for required form elements. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_form_required_marker($variables) { | |
// This is also used in the installer, pre-database setup. | |
$t = get_t(); | |
$attributes = array( | |
'class' => 'form-required', | |
'title' => $t('This field is required.'), | |
); | |
return '<span' . drupal_attributes($attributes) . '>*</span>'; | |
} | |
/** | |
* Returns HTML for a form element label and required marker. | |
* | |
* Form element labels include the #title and a #required marker. The label is | |
* associated with the element itself by the element #id. Labels may appear | |
* before or after elements, depending on theme_form_element() and #title_display. | |
* | |
* This function will not be called for elements with no labels, depending on | |
* #title_display. For elements that have an empty #title and are not required, | |
* this function will output no label (''). For required elements that have an | |
* empty #title, this will output the required marker alone within the label. | |
* The label will use the #id to associate the marker with the field that is | |
* required. That is especially important for screenreader users to know | |
* which field is required. | |
* | |
* @param $variables | |
* An associative array containing: | |
* - element: An associative array containing the properties of the element. | |
* Properties used: #required, #title, #id, #value, #description. | |
* | |
* @ingroup themeable | |
*/ | |
function theme_form_element_label($variables) { | |
$element = $variables['element']; | |
// This is also used in the installer, pre-database setup. | |
$t = get_t(); | |
// If title and required marker are both empty, output no label. | |
if (empty($element['#title']) && empty($element['#required'])) { | |
return ''; | |
} | |
// If the element is required, a required marker is appended to the label. | |
$required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : ''; | |
$title = filter_xss_admin($element['#title']); | |
$attributes = array(); | |
// Style the label as class option to display inline with the element. | |
if ($element['#title_display'] == 'after') { | |
$attributes['class'] = 'option'; | |
} | |
// Show label only to screen readers to avoid disruption in visual flows. | |
elseif ($element['#title_display'] == 'invisible') { | |
$attributes['class'] = 'element-invisible'; | |
} | |
if (!empty($element['#id'])) { | |
$attributes['for'] = $element['#id']; | |
} | |
// The leading whitespace helps visually separate fields from inline labels. | |
return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n"; | |
} | |
/** | |
* Sets a form element's class attribute. | |
* | |
* Adds 'required' and 'error' classes as needed. | |
* | |
* @param &$element | |
* The form element. | |
* @param $name | |
* Array of new class names to be added. | |
*/ | |
function _form_set_class(&$element, $class = array()) { | |
if (!empty($class)) { | |
if (!isset($element['#attributes']['class'])) { | |
$element['#attributes']['class'] = array(); | |
} | |
$element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class); | |
} | |
// This function is invoked from form element theme functions, but the | |
// rendered form element may not necessarily have been processed by | |
// form_builder(). | |
if (!empty($element['#required'])) { | |
$element['#attributes']['class'][] = 'required'; | |
} | |
if (isset($element['#parents']) && form_get_error($element)) { | |
$element['#attributes']['class'][] = 'error'; | |
} | |
} | |
/** | |
* @} End of "defgroup form_api". | |
*/ | |
/** | |
* @defgroup batch Batch operations | |
* @{ | |
* Functions allowing forms processing to be spread out over several page | |
* requests, thus ensuring that the processing does not get interrupted | |
* because of a PHP timeout, while allowing the user to receive feedback | |
* on the progress of the ongoing operations. | |
* | |
* The API is primarily designed to integrate nicely with the Form API | |
* workflow, but can also be used by non-Form API scripts (like update.php) | |
* or even simple page callbacks (which should probably be used sparingly). | |
* | |
* Example: | |
* @code | |
* $batch = array( | |
* 'title' => t('Exporting'), | |
* 'operations' => array( | |
* array('my_function_1', array($account->uid, 'story')), | |
* array('my_function_2', array()), | |
* ), | |
* 'finished' => 'my_finished_callback', | |
* 'file' => 'path_to_file_containing_myfunctions', | |
* ); | |
* batch_set($batch); | |
* // only needed if not inside a form _submit handler : | |
* batch_process(); | |
* @endcode | |
* | |
* Note: if the batch 'title', 'init_message', 'progress_message', or | |
* 'error_message' could contain any user input, it is the responsibility of | |
* the code calling batch_set() to sanitize them first with a function like | |
* check_plain() or filter_xss(). | |
* | |
* Sample batch operations: | |
* @code | |
* // Simple and artificial: load a node of a given type for a given user | |
* function my_function_1($uid, $type, &$context) { | |
* // The $context array gathers batch context information about the execution (read), | |
* // as well as 'return values' for the current operation (write) | |
* // The following keys are provided : | |
* // 'results' (read / write): The array of results gathered so far by | |
* // the batch processing, for the current operation to append its own. | |
* // 'message' (write): A text message displayed in the progress page. | |
* // The following keys allow for multi-step operations : | |
* // 'sandbox' (read / write): An array that can be freely used to | |
* // store persistent data between iterations. It is recommended to | |
* // use this instead of $_SESSION, which is unsafe if the user | |
* // continues browsing in a separate window while the batch is processing. | |
* // 'finished' (write): A float number between 0 and 1 informing | |
* // the processing engine of the completion level for the operation. | |
* // 1 (or no value explicitly set) means the operation is finished | |
* // and the batch processing can continue to the next operation. | |
* | |
* $node = node_load(array('uid' => $uid, 'type' => $type)); | |
* $context['results'][] = $node->nid . ' : ' . $node->title; | |
* $context['message'] = $node->title; | |
* } | |
* | |
* // More advanced example: multi-step operation - load all nodes, five by five | |
* function my_function_2(&$context) { | |
* if (empty($context['sandbox'])) { | |
* $context['sandbox']['progress'] = 0; | |
* $context['sandbox']['current_node'] = 0; | |
* $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); | |
* } | |
* $limit = 5; | |
* $result = db_select('node') | |
* ->fields('node', array('nid')) | |
* ->condition('nid', $context['sandbox']['current_node'], '>') | |
* ->orderBy('nid') | |
* ->range(0, $limit) | |
* ->execute(); | |
* foreach ($result as $row) { | |
* $node = node_load($row->nid, NULL, TRUE); | |
* $context['results'][] = $node->nid . ' : ' . $node->title; | |
* $context['sandbox']['progress']++; | |
* $context['sandbox']['current_node'] = $node->nid; | |
* $context['message'] = $node->title; | |
* } | |
* if ($context['sandbox']['progress'] != $context['sandbox']['max']) { | |
* $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; | |
* } | |
* } | |
* @endcode | |
* | |
* Sample 'finished' callback: | |
* @code | |
* function batch_test_finished($success, $results, $operations) { | |
* if ($success) { | |
* $message = format_plural(count($results), 'One post processed.', '@count posts processed.'); | |
* } | |
* else { | |
* $message = t('Finished with an error.'); | |
* } | |
* drupal_set_message($message); | |
* // Providing data for the redirected page is done through $_SESSION. | |
* foreach ($results as $result) { | |
* $items[] = t('Loaded node %title.', array('%title' => $result)); | |
* } | |
* $_SESSION['my_batch_results'] = $items; | |
* } | |
* @endcode | |
*/ | |
/** | |
* Opens a new batch. | |
* | |
* @param $batch | |
* An array defining the batch. The following keys can be used -- only | |
* 'operations' is required, and batch_init() provides default values for | |
* the messages. | |
* - 'operations': Array of function calls to be performed. | |
* Example: | |
* @code | |
* array( | |
* array('my_function_1', array($arg1)), | |
* array('my_function_2', array($arg2_1, $arg2_2)), | |
* ) | |
* @endcode | |
* - 'title': Title for the progress page. Only safe strings should be passed. | |
* Defaults to t('Processing'). | |
* - 'init_message': Message displayed while the processing is initialized. | |
* Defaults to t('Initializing.'). | |
* - 'progress_message': Message displayed while processing the batch. | |
* Available placeholders are @current, @remaining, @total, @percentage, | |
* @estimate and @elapsed. Defaults to t('Completed @current of @total.'). | |
* - 'error_message': Message displayed if an error occurred while processing | |
* the batch. Defaults to t('An error has occurred.'). | |
* - 'finished': Name of a function to be executed after the batch has | |
* completed. This should be used to perform any result massaging that | |
* may be needed, and possibly save data in $_SESSION for display after | |
* final page redirection. | |
* - 'file': Path to the file containing the definitions of the | |
* 'operations' and 'finished' functions, for instance if they don't | |
* reside in the main .module file. The path should be relative to | |
* base_path(), and thus should be built using drupal_get_path(). | |
* - 'css': Array of paths to CSS files to be used on the progress page. | |
* - 'url_options': options passed to url() when constructing redirect | |
* URLs for the batch. | |
* | |
* Operations are added as new batch sets. Batch sets are used to ensure | |
* clean code independence, ensuring that several batches submitted by | |
* different parts of the code (core / contrib modules) can be processed | |
* correctly while not interfering or having to cope with each other. Each | |
* batch set gets to specify his own UI messages, operates on its own set | |
* of operations and results, and triggers its own 'finished' callback. | |
* Batch sets are processed sequentially, with the progress bar starting | |
* fresh for every new set. | |
*/ | |
function batch_set($batch_definition) { | |
if ($batch_definition) { | |
$batch =& batch_get(); | |
// Initialize the batch if needed. | |
if (empty($batch)) { | |
$batch = array( | |
'sets' => array(), | |
'has_form_submits' => FALSE, | |
); | |
} | |
// Base and default properties for the batch set. | |
// Use get_t() to allow batches at install time. | |
$t = get_t(); | |
$init = array( | |
'sandbox' => array(), | |
'results' => array(), | |
'success' => FALSE, | |
'start' => 0, | |
'elapsed' => 0, | |
); | |
$defaults = array( | |
'title' => $t('Processing'), | |
'init_message' => $t('Initializing.'), | |
'progress_message' => $t('Completed @current of @total.'), | |
'error_message' => $t('An error has occurred.'), | |
'css' => array(), | |
); | |
$batch_set = $init + $batch_definition + $defaults; | |
// Tweak init_message to avoid the bottom of the page flickering down after | |
// init phase. | |
$batch_set['init_message'] .= '<br/> '; | |
// The non-concurrent workflow of batch execution allows us to save | |
// numberOfItems() queries by handling our own counter. | |
$batch_set['total'] = count($batch_set['operations']); | |
$batch_set['count'] = $batch_set['total']; | |
// Add the set to the batch. | |
if (empty($batch['id'])) { | |
// The batch is not running yet. Simply add the new set. | |
$batch['sets'][] = $batch_set; | |
} | |
else { | |
// The set is being added while the batch is running. Insert the new set | |
// right after the current one to ensure execution order, and store its | |
// operations in a queue. | |
$index = $batch['current_set'] + 1; | |
$slice1 = array_slice($batch['sets'], 0, $index); | |
$slice2 = array_slice($batch['sets'], $index); | |
$batch['sets'] = array_merge($slice1, array($batch_set), $slice2); | |
_batch_populate_queue($batch, $index); | |
} | |
} | |
} | |
/** | |
* Processes the batch. | |
* | |
* Unless the batch has been marked with 'progressive' = FALSE, the function | |
* issues a drupal_goto and thus ends page execution. | |
* | |
* This function is generally not needed in form submit handlers; | |
* Form API takes care of batches that were set during form submission. | |
* | |
* @param $redirect | |
* (optional) Path to redirect to when the batch has finished processing. | |
* @param $url | |
* (optional - should only be used for separate scripts like update.php) | |
* URL of the batch processing page. | |
* @param $redirect_callback | |
* (optional) Specify a function to be called to redirect to the progressive | |
* processing page. By default drupal_goto() will be used to redirect to a | |
* page which will do the progressive page. Specifying another function will | |
* allow the progressive processing to be processed differently. | |
*/ | |
function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') { | |
$batch =& batch_get(); | |
drupal_theme_initialize(); | |
if (isset($batch)) { | |
// Add process information | |
$process_info = array( | |
'current_set' => 0, | |
'progressive' => TRUE, | |
'url' => $url, | |
'url_options' => array(), | |
'source_url' => $_GET['q'], | |
'redirect' => $redirect, | |
'theme' => $GLOBALS['theme_key'], | |
'redirect_callback' => $redirect_callback, | |
); | |
$batch += $process_info; | |
// The batch is now completely built. Allow other modules to make changes | |
// to the batch so that it is easier to reuse batch processes in other | |
// environments. | |
drupal_alter('batch', $batch); | |
// Assign an arbitrary id: don't rely on a serial column in the 'batch' | |
// table, since non-progressive batches skip database storage completely. | |
$batch['id'] = db_next_id(); | |
// Move operations to a job queue. Non-progressive batches will use a | |
// memory-based queue. | |
foreach ($batch['sets'] as $key => $batch_set) { | |
_batch_populate_queue($batch, $key); | |
} | |
// Initiate processing. | |
if ($batch['progressive']) { | |
// Now that we have a batch id, we can generate the redirection link in | |
// the generic error message. | |
$t = get_t(); | |
$batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished'))))); | |
// Clear the way for the drupal_goto() redirection to the batch processing | |
// page, by saving and unsetting the 'destination', if there is any. | |
if (isset($_GET['destination'])) { | |
$batch['destination'] = $_GET['destination']; | |
unset($_GET['destination']); | |
} | |
// Store the batch. | |
db_insert('batch') | |
->fields(array( | |
'bid' => $batch['id'], | |
'timestamp' => REQUEST_TIME, | |
'token' => drupal_get_token($batch['id']), | |
'batch' => serialize($batch), | |
)) | |
->execute(); | |
// Set the batch number in the session to guarantee that it will stay alive. | |
$_SESSION['batches'][$batch['id']] = TRUE; | |
// Redirect for processing. | |
$function = $batch['redirect_callback']; | |
if (function_exists($function)) { | |
$function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id']))); | |
} | |
} | |
else { | |
// Non-progressive execution: bypass the whole progressbar workflow | |
// and execute the batch in one pass. | |
require_once DRUPAL_ROOT . '/includes/batch.inc'; | |
_batch_process(); | |
} | |
} | |
} | |
/** | |
* Retrieves the current batch. | |
*/ | |
function &batch_get() { | |
// Not drupal_static(), because Batch API operates at a lower level than most | |
// use-cases for resetting static variables, and we specifically do not want a | |
// global drupal_static_reset() resetting the batch information. Functions | |
// that are part of the Batch API and need to reset the batch information may | |
// call batch_get() and manipulate the result by reference. Functions that are | |
// not part of the Batch API can also do this, but shouldn't. | |
static $batch = array(); | |
return $batch; | |
} | |
/** | |
* Populates a job queue with the operations of a batch set. | |
* | |
* Depending on whether the batch is progressive or not, the BatchQueue or | |
* BatchMemoryQueue handler classes will be used. | |
* | |
* @param $batch | |
* The batch array. | |
* @param $set_id | |
* The id of the set to process. | |
* @return | |
* The name and class of the queue are added by reference to the batch set. | |
*/ | |
function _batch_populate_queue(&$batch, $set_id) { | |
$batch_set = &$batch['sets'][$set_id]; | |
if (isset($batch_set['operations'])) { | |
$batch_set += array( | |
'queue' => array( | |
'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id, | |
'class' => $batch['progressive'] ? 'BatchQueue' : 'BatchMemoryQueue', | |
), | |
); | |
$queue = _batch_queue($batch_set); | |
$queue->createQueue(); | |
foreach ($batch_set['operations'] as $operation) { | |
$queue->createItem($operation); | |
} | |
unset($batch_set['operations']); | |
} | |
} | |
/** | |
* Returns a queue object for a batch set. | |
* | |
* @param $batch_set | |
* The batch set. | |
* @return | |
* The queue object. | |
*/ | |
function _batch_queue($batch_set) { | |
static $queues; | |
// The class autoloader is not available when running update.php, so make | |
// sure the files are manually included. | |
if (!isset($queues)) { | |
$queues = array(); | |
require_once DRUPAL_ROOT . '/modules/system/system.queue.inc'; | |
require_once DRUPAL_ROOT . '/includes/batch.queue.inc'; | |
} | |
if (isset($batch_set['queue'])) { | |
$name = $batch_set['queue']['name']; | |
$class = $batch_set['queue']['class']; | |
if (!isset($queues[$class][$name])) { | |
$queues[$class][$name] = new $class($name); | |
} | |
return $queues[$class][$name]; | |
} | |
} | |
/** | |
* @} End of "defgroup batch". | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment