2005-10-07 06:11:12 +00:00
< ? php
2005-11-01 09:58:01 +00:00
2005-10-07 06:11:12 +00:00
/**
2008-01-21 15:17:01 +00:00
* @ 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
2008-02-06 19:38:28 +00:00
* functions using \ @ see . Conversely , validate and submit functions should
2008-01-21 15:17:01 +00:00
* reference the form builder function using \ @ see . For examples , of this see
2008-02-06 19:38:28 +00:00
* system_modules_uninstall () or user_pass (), the latter of which has the
2008-01-21 15:17:01 +00:00
* following in its doxygen documentation :
*
* \ @ ingroup forms
* \ @ see user_pass_validate () .
* \ @ see user_pass_submit () .
*
2012-01-02 04:43:23 +00:00
* @ } End of " defgroup forms " .
2008-01-21 15:17:01 +00:00
*/
/**
* @ defgroup form_api Form generation
2005-10-07 06:11:12 +00:00
* @ {
2006-08-18 18:58:47 +00:00
* Functions to enable the processing and display of HTML forms .
2005-10-07 06:11:12 +00:00
*
2006-08-18 18:58:47 +00:00
* 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 .
*
2010-06-15 15:52:04 +00:00
* 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 .
2006-08-18 18:58:47 +00:00
*
2010-06-15 15:52:04 +00:00
* 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 :
2007-11-09 07:40:55 +00:00
* @ code
2010-06-15 15:52:04 +00:00
* $form = drupal_get_form ( 'my_module_example_form' );
* ...
* function my_module_example_form ( $form , & $form_state ) {
* $form [ 'submit' ] = array (
* '#type' => 'submit' ,
* '#value' => t ( 'Submit' ),
* );
2010-06-16 05:06:15 +00:00
* return $form ;
2010-06-15 15:52:04 +00:00
* }
* function my_module_example_form_validate ( $form , & $form_state ) {
* // Validation logic.
* }
* function my_module_example_form_submit ( $form , & $form_state ) {
* // Submission logic.
* }
2007-11-09 07:40:55 +00:00
* @ endcode
2006-08-18 18:58:47 +00:00
*
2010-06-15 15:52:04 +00:00
* 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 ,
* );
2010-06-16 05:06:15 +00:00
* return $form ;
2010-06-15 15:52:04 +00:00
* }
* @ endcode
2006-08-18 18:58:47 +00:00
*
2010-06-15 15:52:04 +00:00
* 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
2011-09-27 17:39:34 +00:00
* @ link http :// drupal . org / node / 37775 Form API documentation section . @ endlink
2010-06-15 15:52:04 +00:00
* 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 .
*
2011-09-27 17:39:34 +00:00
* See drupal_build_form () for documentation of $form_state keys .
2005-10-07 06:11:12 +00:00
*/
/**
2009-03-14 20:13:27 +00:00
* Wrapper for drupal_build_form () for use when $form_state is not needed .
2005-10-07 06:11:12 +00:00
*
* @ param $form_id
2009-03-14 20:13:27 +00:00
* 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 (),
2012-01-24 22:53:40 +00:00
* and search_forms () .
2006-08-18 18:58:47 +00:00
* @ param ...
2007-10-31 15:10:33 +00:00
* Any additional arguments are passed on to the functions called by
2009-03-14 20:13:27 +00:00
* drupal_get_form (), including the unique form constructor function . For
* example , the node_edit form requires that a node object is passed in here
2010-11-27 19:12:56 +00:00
* when it is called . These are available to implementations of
2010-10-20 00:53:27 +00:00
* hook_form_alter () and hook_form_FORM_ID_alter () as the array
* $form_state [ 'build_info' ][ 'args' ] .
2010-03-26 17:14:46 +00:00
*
2006-08-18 18:58:47 +00:00
* @ return
2009-05-12 08:37:45 +00:00
* The form array .
2009-03-14 20:13:27 +00:00
*
* @ see drupal_build_form ()
2006-08-18 18:58:47 +00:00
*/
function drupal_get_form ( $form_id ) {
2009-03-14 20:13:27 +00:00
$form_state = array ();
2007-05-14 13:43:38 +00:00
$args = func_get_args ();
2009-03-14 20:13:27 +00:00
// Remove $form_id from the arguments.
array_shift ( $args );
2009-11-04 04:56:54 +00:00
$form_state [ 'build_info' ][ 'args' ] = $args ;
2009-03-14 20:13:27 +00:00
return drupal_build_form ( $form_id , $form_state );
}
/**
2009-05-12 08:37:45 +00:00
* Build and process a form based on a form id .
2009-03-14 20:13:27 +00:00
*
* 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
2009-05-12 08:37:45 +00:00
* and submission if there is proper input .
2009-03-14 20:13:27 +00:00
*
* @ 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 (),
2012-01-24 22:53:40 +00:00
* and search_forms () .
2011-05-08 19:50:38 +00:00
* @ param $form_state
2009-03-14 20:13:27 +00:00
* An array which stores information about the form . This is passed as a
2009-11-04 04:56:54 +00:00
* reference so that the caller can use it to examine what in the form changed
2009-12-05 14:33:55 +00:00
* 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 .
2009-03-14 20:13:27 +00:00
* The following parameters may be set in $form_state to affect how the form
* is rendered :
2011-09-27 17:39:34 +00:00
* - build_info : Internal . An associative array of information stored by Form
* API that is necessary to build and rebuild the form from cache when the
* original context may no longer be available :
* - args : A list of arguments to pass to the form constructor .
2010-07-17 18:52:39 +00:00
* - 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
2010-11-27 19:12:56 +00:00
* automatically loaded by form_get_cache () . By default the current menu
2011-09-27 17:39:34 +00:00
* router item 's ' file ' definition is added , if any . Use
* form_load_include () to add include files from a form constructor .
2011-12-14 15:04:55 +00:00
* - base_form_id : Identification for a base form , as declared in a
* hook_forms () implementation .
2011-09-27 17:39:34 +00:00
* - rebuild_info : Internal . Similar to 'build_info' , but pertaining to
* drupal_rebuild_form () .
2009-11-21 14:06:46 +00:00
* - rebuild : Normally , after the entire form processing is completed and
2011-09-27 17:39:34 +00:00
* submit handlers have run , a form is considered to be done and
2009-11-21 14:06:46 +00:00
* 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
2011-09-27 17:39:34 +00:00
* immediately built and sent to the browser , instead of a redirect . This is
2010-09-27 00:53:56 +00:00
* 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 .
2011-09-27 17:39:34 +00:00
* - redirect : Used to redirect the form on submission . It may either be a
* string containing the destination URL , or an array of arguments
* compatible with drupal_goto () . See drupal_redirect_form () for complete
* information .
* - no_redirect : If set to TRUE the form will NOT perform a drupal_goto (),
* even if 'redirect' is set .
2009-03-14 20:13:27 +00:00
* - 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
2011-09-27 17:39:34 +00:00
* forms that do not change data , as that is exclusively the domain of
* 'post.'
2009-11-18 18:51:11 +00:00
* - cache : If set to TRUE the original , unprocessed form structure will be
2011-09-27 17:39:34 +00:00
* cached , which allows the entire form to be rebuilt from cache . A typical
* form workflow involves two page requests ; first , a form is built and
* rendered 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 . Often , 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 . 'cache' can be set
* to TRUE to do this . A prominent example is an Ajax - enabled form , in which
* ajax_process_form () enables form caching for all forms that include an
* element with the #ajax property. (The Ajax handler has no way to build
* the form itself , so must rely on the cached version . ) Note that the
* persistence of $form and $form_state happens automatically for
* ( multi - step ) forms having the 'rebuild' flag set , regardless of the value
* for 'cache' .
2009-11-18 18:51:11 +00:00
* - no_cache : If set to TRUE the form will NOT be cached , even if 'cache' is
* set .
2011-09-27 17:39:34 +00:00
* - 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 . )
* - 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' . The values correspond to $_POST or $_GET ,
* depending on the 'method' chosen .
2009-03-14 20:13:27 +00:00
* - 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 .
2011-09-27 17:39:34 +00:00
* - must_validate : Ordinarily , a form is only validated once , but there are
2009-03-14 20:13:27 +00:00
* times when a form is resubmitted internally and should be validated
* again . Setting this to TRUE will force that to happen . This is most
2011-09-27 17:39:34 +00:00
* likely to occur during Ajax operations .
* - programmed : If TRUE , the form was submitted programmatically , usually
* invoked via drupal_form_submit () . Defaults to FALSE .
* - process_input : Boolean flag . TRUE signifies correct form submission .
* This is always TRUE for programmed forms coming from drupal_form_submit ()
* ( see 'programmed' key ), or if the form_id coming from the $_POST data is
* set and matches the current form_id .
* - submitted : If TRUE , the form has been submitted . Defaults to FALSE .
* - executed : If TRUE , the form was submitted and has been processed and
* executed . Defaults to FALSE .
* - 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 key is
* often used to distinguish between various buttons in a submit handler ,
* and is also used in Ajax handlers .
* - has_file_element : Internal . If TRUE , there is a file element and Form API
* will set the appropriate 'enctype' HTML attribute on the form .
* - groups : Internal . An array containing references to fieldsets to render
* them within vertical tabs .
* - storage : $form_state [ 'storage' ] is not a special key , and no specific
* support is provided for it in the Form API . 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 .
* - buttons : A list containing copies of all submit and button elements in
* the form .
* - complete_form : A reference to the $form variable containing the complete
* form structure . #process, #after_build, #element_validate, and other
* handlers being invoked on a form element may use this reference to access
* other information in the form the element is contained in .
2009-12-05 14:33:55 +00:00
* - temporary : An array holding temporary data accessible during the current
2011-09-27 17:39:34 +00:00
* page request only . All $form_state properties that are not reserved keys
* ( see form_state_keys_no_cache ()) persist throughout a multistep form
* sequence . Form API provides this key for modules to communicate
* information across form - related functions during a single page request .
* It may be used to temporarily save data that does not need to or should
* not be cached during the whole form workflow ; e . g . , data that needs to be
* accessed during the current form build process only . There is no use - case
* for this functionality in Drupal core .
2009-09-18 00:12:48 +00:00
* - 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 .
2009-11-04 05:39:14 +00:00
* 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 .
2011-09-27 17:39:34 +00:00
* Information on how certain $form_state properties control redirection
* behavior after form submission may be found in drupal_redirect_form () .
2009-09-21 06:44:14 +00:00
*
2009-03-14 20:13:27 +00:00
* @ return
2011-07-08 00:15:30 +00:00
* The rendered form . This function may also perform a redirect and hence may
* not return at all , depending upon the $form_state flags that were set .
2009-09-21 06:44:14 +00:00
*
* @ see drupal_redirect_form ()
2009-03-14 20:13:27 +00:00
*/
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 ;
}
2007-05-14 13:43:38 +00:00
if ( isset ( $_SESSION [ 'batch_form_state' ])) {
2010-09-13 01:09:26 +00:00
// We've been redirected here after a batch processing. The form has
// already been processed, but needs to be rebuilt. See _batch_finished().
2007-05-14 13:43:38 +00:00
$form_state = $_SESSION [ 'batch_form_state' ];
unset ( $_SESSION [ 'batch_form_state' ]);
2010-09-13 01:09:26 +00:00
return drupal_rebuild_form ( $form_id , $form_state );
2006-08-25 08:15:24 +00:00
}
2010-07-29 00:46:09 +00:00
2010-09-13 01:09:26 +00:00
// 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 );
2007-05-14 13:43:38 +00:00
}
2010-09-13 01:09:26 +00:00
// 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 ;
2009-12-01 16:28:57 +00:00
}
2009-05-24 17:39:35 +00:00
2010-09-13 01:09:26 +00:00
$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 ;
2010-09-09 23:01:48 +00:00
}
2009-05-12 08:37:45 +00:00
}
2006-12-06 15:49:45 +00:00
2010-09-13 01:09:26 +00:00
// 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.
2009-05-12 08:37:45 +00:00
return $form ;
2007-05-14 13:43:38 +00:00
}
2006-12-06 15:49:45 +00:00
2009-03-14 20:13:27 +00:00
/**
* Retrieve default values for the $form_state array .
*/
function form_state_defaults () {
return array (
2009-07-06 13:31:47 +00:00
'rebuild' => FALSE ,
2010-09-13 01:09:26 +00:00
'rebuild_info' => array (),
2009-07-06 13:31:47 +00:00
'redirect' => NULL ,
2010-11-27 19:12:56 +00:00
// @todo 'args' is usually set, so no other default 'build_info' keys are
// appended via += form_state_defaults().
'build_info' => array (
'args' => array (),
'files' => array (),
),
2009-12-05 14:33:55 +00:00
'temporary' => array (),
2009-03-14 20:13:27 +00:00
'submitted' => FALSE ,
2010-09-27 00:53:56 +00:00
'executed' => FALSE ,
2009-03-14 20:13:27 +00:00
'programmed' => FALSE ,
2009-07-06 13:31:47 +00:00
'cache' => FALSE ,
'method' => 'post' ,
2009-04-11 22:19:46 +00:00
'groups' => array (),
2010-03-26 18:58:12 +00:00
'buttons' => array (),
2009-03-14 20:13:27 +00:00
);
}
2007-11-19 19:23:28 +00:00
/**
2010-09-13 01:09:26 +00:00
* 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 .
2007-11-19 19:23:28 +00:00
*
2011-02-19 00:09:11 +00:00
* Ajax form submissions are almost always multi - step workflows , so that is one
2010-09-13 01:09:26 +00:00
* common use - case during which form rebuilding occurs . See ajax_form_callback ()
2011-02-19 00:09:11 +00:00
* for more information about creating Ajax - enabled forms .
2007-11-19 19:23:28 +00:00
*
* @ 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
2012-01-24 22:53:40 +00:00
* may be found in node_forms () and search_forms () .
2007-11-19 19:23:28 +00:00
* @ param $form_state
2009-12-05 14:33:55 +00:00
* A keyed array containing the current state of the form .
2010-03-31 19:34:56 +00:00
* @ param $old_form
* ( optional ) A previously built $form . Used to retain the #build_id and
2011-02-19 00:09:11 +00:00
* #action properties in Ajax callbacks and similar partial form rebuilds. The
2010-09-13 01:09:26 +00:00
* 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 .
2010-03-31 19:34:56 +00:00
*
2007-11-19 19:23:28 +00:00
* @ return
* The newly built form .
2010-09-13 01:09:26 +00:00
*
* @ see drupal_process_form ()
* @ see ajax_form_callback ()
2007-11-19 19:23:28 +00:00
*/
2010-03-31 19:34:56 +00:00
function drupal_rebuild_form ( $form_id , & $form_state , $old_form = NULL ) {
2009-03-14 20:13:27 +00:00
$form = drupal_retrieve_form ( $form_id , $form_state );
2007-11-19 19:23:28 +00:00
2011-02-19 00:09:11 +00:00
// If only parts of the form will be returned to the browser (e.g., Ajax or
2010-03-31 19:34:56 +00:00
// 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.
2010-09-13 01:09:26 +00:00
// @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 ());
}
2010-03-31 19:34:56 +00:00
2011-02-19 00:09:11 +00:00
// #action defaults to request_uri(), but in case of Ajax and other partial
2010-03-31 19:34:56 +00:00
// rebuilds, the form is submitted to an alternate URL, and the original
// #action needs to be retained.
2010-09-13 01:09:26 +00:00
if ( isset ( $old_form [ '#action' ]) && ! empty ( $form_state [ 'rebuild_info' ][ 'copy' ][ '#action' ])) {
2010-03-31 19:34:56 +00:00
$form [ '#action' ] = $old_form [ '#action' ];
2007-11-19 19:23:28 +00:00
}
2010-03-31 19:34:56 +00:00
2007-11-19 19:23:28 +00:00
drupal_prepare_form ( $form_id , $form , $form_state );
2010-09-13 01:09:26 +00:00
// 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.
2009-11-18 18:51:11 +00:00
if ( empty ( $form_state [ 'no_cache' ])) {
2010-03-31 19:34:56 +00:00
form_set_cache ( $form [ '#build_id' ], $form , $form_state );
2009-03-14 20:13:27 +00:00
}
2007-11-19 19:23:28 +00:00
2009-12-01 03:07:34 +00:00
// Clear out all group associations as these might be different when
// re-rendering the form.
2009-04-11 22:19:46 +00:00
$form_state [ 'groups' ] = array ();
2010-09-13 01:09:26 +00:00
// Return a fully built form that is ready for rendering.
return form_builder ( $form_id , $form , $form_state );
2007-11-19 19:23:28 +00:00
}
2007-07-29 17:28:23 +00:00
/**
* Fetch a form from cache .
*/
function form_get_cache ( $form_build_id , & $form_state ) {
2011-09-11 16:14:18 +00:00
if ( $cached = cache ( 'form' ) -> get ( 'form_' . $form_build_id )) {
2007-07-29 17:28:23 +00:00
$form = $cached -> data ;
2009-08-27 04:40:12 +00:00
2008-10-11 04:06:29 +00:00
global $user ;
if (( isset ( $form [ '#cache_token' ]) && drupal_valid_token ( $form [ '#cache_token' ])) || ( ! isset ( $form [ '#cache_token' ]) && ! $user -> uid )) {
2011-09-11 16:14:18 +00:00
if ( $cached = cache ( 'form' ) -> get ( 'form_state_' . $form_build_id )) {
2009-11-04 04:56:54 +00:00
// Re-populate $form_state for subsequent rebuilds.
2009-12-05 14:33:55 +00:00
$form_state = $cached -> data + $form_state ;
2009-11-04 04:56:54 +00:00
2010-07-17 18:52:39 +00:00
// If the original form is contained in include files, load the files.
2010-11-27 19:12:56 +00:00
// @see form_load_include()
2010-07-17 18:52:39 +00:00
$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 ;
}
2009-11-04 04:56:54 +00:00
}
2008-10-11 04:06:29 +00:00
}
return $form ;
2007-07-29 17:28:23 +00:00
}
}
}
/**
2009-11-04 04:56:54 +00:00
* Store a form in the cache .
2007-07-29 17:28:23 +00:00
*/
function form_set_cache ( $form_build_id , $form , $form_state ) {
2008-05-05 21:28:49 +00:00
// 6 hours cache life time for forms should be plenty.
$expire = 21600 ;
2009-11-27 15:14:58 +00:00
// Cache form structure.
if ( isset ( $form )) {
if ( $GLOBALS [ 'user' ] -> uid ) {
$form [ '#cache_token' ] = drupal_get_token ();
}
2011-09-11 16:14:18 +00:00
cache ( 'form' ) -> set ( 'form_' . $form_build_id , $form , REQUEST_TIME + $expire );
2008-10-11 04:06:29 +00:00
}
2009-11-27 15:14:58 +00:00
// Cache form state.
2009-12-05 14:33:55 +00:00
if ( $data = array_diff_key ( $form_state , array_flip ( form_state_keys_no_cache ()))) {
2011-09-11 16:14:18 +00:00
cache ( 'form' ) -> set ( 'form_state_' . $form_build_id , $data , REQUEST_TIME + $expire );
2007-07-29 17:28:23 +00:00
}
}
2009-12-05 14:33:55 +00:00
/**
* 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' ,
2010-09-13 01:09:26 +00:00
'rebuild_info' ,
2009-12-05 14:33:55 +00:00
'redirect' ,
'no_redirect' ,
'temporary' ,
// Internal properties defined by form processing.
'buttons' ,
2010-03-26 18:58:12 +00:00
'triggering_element' ,
2009-12-05 14:33:55 +00:00
'clicked_button' ,
2011-08-01 03:41:25 +00:00
'complete_form' ,
2009-12-05 14:33:55 +00:00
'groups' ,
'input' ,
'method' ,
'submit_handlers' ,
'submitted' ,
2010-09-27 00:53:56 +00:00
'executed' ,
2009-12-05 14:33:55 +00:00
'validate_handlers' ,
'values' ,
);
}
2010-11-27 19:12:56 +00:00
/**
* Loads an include file and makes sure it is loaded whenever the form is processed .
*
* Example :
* @ code
* // Load node.admin.inc from Node module.
* form_load_include ( $form_state , 'inc' , 'node' , 'node.admin' );
* @ endcode
*
* Use this function instead of module_load_include () from inside a form
* constructor or any form processing logic as it ensures that the include file
* is loaded whenever the form is processed . In contrast to using
* module_load_include () directly , form_load_include () makes sure the include
* file is correctly loaded also if the form is cached .
*
* @ param $form_state
* The current state of the form .
* @ param $type
* The include file ' s type ( file extension ) .
* @ param $module
* The module to which the include file belongs .
* @ param $name
* ( optional ) The base file name ( without the $type extension ) . If omitted ,
* $module is used ; i . e . , resulting in " $module . $type " by default .
*
* @ return
* The filepath of the loaded include file , or FALSE if the include file was
* not found or has been loaded already .
*
* @ see module_load_include ()
*/
function form_load_include ( & $form_state , $type , $module , $name = NULL ) {
if ( ! isset ( $name )) {
$name = $module ;
}
if ( ! isset ( $form_state [ 'build_info' ][ 'files' ][ " $module : $name . $type " ])) {
// Only add successfully included files to the form state.
if ( $result = module_load_include ( $type , $module , $name )) {
$form_state [ 'build_info' ][ 'files' ][ " $module : $name . $type " ] = array (
'type' => $type ,
'module' => $module ,
'name' => $name ,
);
return $result ;
}
}
return FALSE ;
}
2006-08-31 14:59:28 +00:00
/**
2010-03-04 09:07:27 +00:00
* 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 () .
2006-08-31 14:59:28 +00:00
*
* @ 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
2007-05-14 13:43:38 +00:00
* different $form_id values to the proper form constructor function . Examples
2012-01-24 22:53:40 +00:00
* may be found in node_forms () and search_forms () .
2007-05-14 13:43:38 +00:00
* @ param $form_state
2009-12-01 03:07:34 +00:00
* 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 .
2006-08-31 14:59:28 +00:00
* @ param ...
2007-10-31 15:10:33 +00:00
* Any additional arguments are passed on to the functions called by
2009-04-29 07:18:04 +00:00
* drupal_form_submit (), including the unique form constructor function .
2007-10-31 15:10:33 +00:00
* For example , the node_edit form requires that a node object be passed
2010-04-23 07:50:28 +00:00
* 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
2006-11-26 22:35:20 +00:00
* For example :
2009-06-18 15:48:13 +00:00
* @ code
2006-11-26 22:35:20 +00:00
* // register a new user
2007-05-14 13:43:38 +00:00
* $form_state = array ();
* $form_state [ 'values' ][ 'name' ] = 'robo-user' ;
* $form_state [ 'values' ][ 'mail' ] = 'robouser@example.com' ;
2009-11-23 22:37:37 +00:00
* $form_state [ 'values' ][ 'pass' ][ 'pass1' ] = 'password' ;
* $form_state [ 'values' ][ 'pass' ][ 'pass2' ] = 'password' ;
2007-09-25 15:14:37 +00:00
* $form_state [ 'values' ][ 'op' ] = t ( 'Create new account' );
2009-10-10 16:48:39 +00:00
* drupal_form_submit ( 'user_register_form' , $form_state );
2009-06-18 15:48:13 +00:00
* @ endcode
2006-08-31 14:59:28 +00:00
*/
2009-04-29 07:18:04 +00:00
function drupal_form_submit ( $form_id , & $form_state ) {
2009-11-04 04:56:54 +00:00
if ( ! isset ( $form_state [ 'build_info' ][ 'args' ])) {
2009-03-14 20:13:27 +00:00
$args = func_get_args ();
array_shift ( $args );
array_shift ( $args );
2009-11-04 04:56:54 +00:00
$form_state [ 'build_info' ][ 'args' ] = $args ;
2009-03-14 20:13:27 +00:00
}
2009-12-05 14:33:55 +00:00
// Merge in default values.
$form_state += form_state_defaults ();
2009-01-26 14:08:44 +00:00
2010-12-21 21:05:22 +00:00
// Populate $form_state['input'] with the submitted values before retrieving
// the form, to be consistent with what drupal_build_form() does for
// non-programmatic submissions (form builder functions may expect it to be
// there).
2009-03-14 20:13:27 +00:00
$form_state [ 'input' ] = $form_state [ 'values' ];
2010-12-21 21:05:22 +00:00
2009-03-14 20:13:27 +00:00
$form_state [ 'programmed' ] = TRUE ;
2010-12-21 21:05:22 +00:00
$form = drupal_retrieve_form ( $form_id , $form_state );
2009-07-06 13:31:47 +00:00
// Programmed forms are always submitted.
$form_state [ 'submitted' ] = TRUE ;
2009-01-22 12:46:07 +00:00
2010-03-07 07:49:26 +00:00
// Reset form validation.
$form_state [ 'must_validate' ] = TRUE ;
form_clear_error ();
2007-05-14 13:43:38 +00:00
drupal_prepare_form ( $form_id , $form , $form_state );
drupal_process_form ( $form_id , $form , $form_state );
2006-08-31 14:59:28 +00:00
}
2006-08-18 18:58:47 +00:00
/**
* 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
2007-05-14 13:43:38 +00:00
* different $form_id values to the proper form constructor function .
2007-06-04 07:22:23 +00:00
* @ param $form_state
2010-03-06 12:43:45 +00:00
* 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 .
2006-08-18 18:58:47 +00:00
*/
2007-06-04 07:22:23 +00:00
function drupal_retrieve_form ( $form_id , & $form_state ) {
2009-06-02 13:47:26 +00:00
$forms = & drupal_static ( __FUNCTION__ );
2006-08-18 18:58:47 +00:00
2010-09-13 01:09:26 +00:00
// 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' ])) {
2010-11-27 19:12:56 +00:00
// Do not use form_load_include() here, as the file is already loaded.
// Anyway, form_get_cache() is able to handle filepaths too.
2010-09-13 01:09:26 +00:00
$form_state [ 'build_info' ][ 'files' ][ 'menu' ] = $item [ 'include_file' ];
}
}
2006-10-12 20:36:51 +00:00
// We save two copies of the incoming arguments: one for modules to use
2007-05-14 13:43:38 +00:00
// when mapping form ids to constructor functions, and another to pass to
2009-03-14 20:13:27 +00:00
// the constructor function itself.
2009-11-04 04:56:54 +00:00
$args = $form_state [ 'build_info' ][ 'args' ];
2006-10-12 20:36:51 +00:00
// 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.
2009-08-24 00:14:23 +00:00
if ( ! function_exists ( $form_id )) {
2007-05-14 13:43:38 +00:00
// In cases where many form_ids need to share a central constructor function,
2006-10-12 20:36:51 +00:00
// such as the node editing form, modules can implement hook_forms(). It
2007-05-14 13:43:38 +00:00
// maps one or more form_ids to the correct constructor functions.
2006-10-12 20:36:51 +00:00
//
// 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 ])) {
2007-05-14 13:43:38 +00:00
$forms = module_invoke_all ( 'forms' , $form_id , $args );
2006-08-18 18:58:47 +00:00
}
$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' ];
2010-09-09 23:01:48 +00:00
$form_state [ 'build_info' ][ 'base_form_id' ] = $callback ;
2006-08-18 18:58:47 +00:00
}
2009-11-04 05:39:14 +00:00
// 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' ];
}
2006-08-18 18:58:47 +00:00
}
2007-06-04 07:22:23 +00:00
2009-11-04 05:39:14 +00:00
$form = array ();
2011-10-21 03:58:07 +00:00
// Assign a default CSS class name based on $form_id.
// This happens here and not in drupal_prepare_form() in order to allow the
// form constructor function to override or remove the default class.
$form [ '#attributes' ][ 'class' ][] = drupal_html_class ( $form_id );
// Same for the base form ID, if any.
if ( isset ( $form_state [ 'build_info' ][ 'base_form_id' ])) {
$form [ '#attributes' ][ 'class' ][] = drupal_html_class ( $form_state [ 'build_info' ][ 'base_form_id' ]);
}
2009-11-04 05:39:14 +00:00
// 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 );
2009-09-18 00:12:48 +00:00
// 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
2010-03-26 17:14:46 +00:00
// back/next/save buttons in multi-step form wizards. See drupal_build_form().
2011-12-15 17:33:38 +00:00
if ( isset ( $form_state [ 'wrapper_callback' ])) {
2009-11-04 05:39:14 +00:00
$form = call_user_func_array ( $form_state [ 'wrapper_callback' ], $args );
// Put the prepopulated $form into $args.
$args [ 0 ] = $form ;
2009-09-18 00:12:48 +00:00
}
2007-06-04 07:22:23 +00:00
2006-10-12 20:36:51 +00:00
// If $callback was returned by a hook_forms() implementation, call it.
// Otherwise, call the function named after the form id.
2006-08-31 14:59:28 +00:00
$form = call_user_func_array ( isset ( $callback ) ? $callback : $form_id , $args );
2009-03-14 20:13:27 +00:00
$form [ '#form_id' ] = $form_id ;
2009-08-27 04:40:12 +00:00
2006-08-31 14:59:28 +00:00
return $form ;
2006-08-18 18:58:47 +00:00
}
/**
2009-09-21 06:44:14 +00:00
* Processes a form submission .
*
2006-08-18 18:58:47 +00:00
* This function is the heart of form API . The form gets built , validated and in
2010-09-13 01:09:26 +00:00
* appropriate cases , submitted and rebuilt .
2006-08-18 18:58:47 +00:00
*
* @ param $form_id
* The unique string identifying the current form .
2005-10-07 06:11:12 +00:00
* @ param $form
* An associative array containing the structure of the form .
2007-05-14 13:43:38 +00:00
* @ param $form_state
* A keyed array containing the current state of the form . This
2007-07-02 14:41:37 +00:00
* includes the current persistent storage data for the form , and
2007-05-14 13:43:38 +00:00
* any data passed along by earlier steps when displaying a
* multi - step form . Additional information , like the sanitized $_POST
* data , is also accumulated here .
2005-10-07 06:11:12 +00:00
*/
2007-05-14 13:43:38 +00:00
function drupal_process_form ( $form_id , & $form , & $form_state ) {
$form_state [ 'values' ] = array ();
2009-03-14 20:13:27 +00:00
// 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' ]);
}
}
2010-09-13 01:09:26 +00:00
// 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 ;
2007-05-14 13:43:38 +00:00
$form = form_builder ( $form_id , $form , $form_state );
2009-07-06 13:31:47 +00:00
// Only process the input if we have a correct form submission.
if ( $form_state [ 'process_input' ]) {
2007-05-14 13:43:38 +00:00
drupal_validate_form ( $form_id , $form , $form_state );
2009-10-05 01:18:26 +00:00
// drupal_html_id() maintains a cache of element IDs it has seen,
2007-10-24 13:35:26 +00:00
// so it can prevent duplicates. We want to be sure we reset that
2008-12-30 16:43:20 +00:00
// cache when a form is processed, so scenarios that result in
2007-10-24 13:35:26 +00:00
// the form being built behind the scenes and again for the
// browser don't increment all the element IDs needlessly.
2009-10-05 01:18:26 +00:00
drupal_static_reset ( 'drupal_html_id' );
2007-10-24 13:35:26 +00:00
2009-07-06 13:31:47 +00:00
if ( $form_state [ 'submitted' ] && ! form_get_errors () && ! $form_state [ 'rebuild' ]) {
// Execute form submit handlers.
2007-05-14 13:43:38 +00:00
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.
2010-05-12 08:26:15 +00:00
if ( ! variable_get ( 'cache' , 0 ) && ! empty ( $form_state [ 'values' ][ 'form_build_id' ])) {
2011-09-11 16:14:18 +00:00
cache ( 'form' ) -> delete ( 'form_' . $form_state [ 'values' ][ 'form_build_id' ]);
cache ( 'form' ) -> delete ( 'form_state_' . $form_state [ 'values' ][ 'form_build_id' ]);
2007-05-14 13:43:38 +00:00
}
// If batches were set in the submit handlers, we process them now,
2007-09-14 10:40:55 +00:00
// possibly ending execution. We make sure we do not react to the batch
// that is already being processed (if a batch operation performs a
2009-04-29 07:18:04 +00:00
// drupal_form_submit).
2007-09-14 10:40:55 +00:00
if ( $batch =& batch_get () && ! isset ( $batch [ 'current_set' ])) {
2010-01-08 06:36:34 +00:00
// 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().
2010-04-04 13:22:51 +00:00
if ( $batch [ 'has_form_submits' ] || ! empty ( $form_state [ 'rebuild' ])) {
2010-01-08 06:36:34 +00:00
$batch [ 'form_state' ] = $form_state ;
}
else {
$batch [ 'form_state' ] = array_intersect_key ( $form_state , array_flip ( array ( 'programmed' , 'rebuild' , 'storage' , 'no_redirect' , 'redirect' )));
}
2009-03-14 20:13:27 +00:00
$batch [ 'progressive' ] = ! $form_state [ 'programmed' ];
2007-05-04 09:41:37 +00:00
batch_process ();
2010-01-08 06:36:34 +00:00
2007-05-14 13:43:38 +00:00
// 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.
2007-05-04 09:41:37 +00:00
}
2007-05-14 13:43:38 +00:00
2009-07-06 13:31:47 +00:00
// Set a flag to indicate the the form has been processed and executed.
$form_state [ 'executed' ] = TRUE ;
2009-09-21 06:44:14 +00:00
// Redirect the form based on values in $form_state.
drupal_redirect_form ( $form_state );
2006-07-22 19:26:58 +00:00
}
2010-09-13 01:09:26 +00:00
// Don't rebuild or cache form submissions invoked via drupal_form_submit().
if ( ! empty ( $form_state [ 'programmed' ])) {
return ;
}
2010-09-27 00:53:56 +00:00
// 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.
2011-02-19 00:09:11 +00:00
// However, for forms that have not been fully executed (e.g., Ajax
2010-09-27 00:53:56 +00:00
// 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'].
2011-02-19 00:09:11 +00:00
// @todo D8: Simplify this logic; considering Ajax and non-HTML front-ends,
2010-09-27 00:53:56 +00:00
// 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 );
}
2010-09-13 01:09:26 +00:00
}
2010-09-27 00:53:56 +00:00
2010-09-13 01:09:26 +00:00
// 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(),
2011-09-24 20:44:06 +00:00
// because form_builder() must run for each request to accommodate new user
2010-09-27 00:53:56 +00:00
// 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' ])) {
2010-09-13 01:09:26 +00:00
form_set_cache ( $form [ '#build_id' ], $unprocessed_form , $form_state );
2006-07-22 19:26:58 +00:00
}
}
/**
* 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 .
2007-05-14 13:43:38 +00:00
* @ 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 .
2006-07-22 19:26:58 +00:00
*/
2007-05-14 13:43:38 +00:00
function drupal_prepare_form ( $form_id , & $form , & $form_state ) {
2006-10-31 08:06:18 +00:00
global $user ;
2005-10-11 19:44:35 +00:00
$form [ '#type' ] = 'form' ;
2009-03-14 20:13:27 +00:00
$form_state [ 'programmed' ] = isset ( $form_state [ 'programmed' ]) ? $form_state [ 'programmed' ] : FALSE ;
2006-08-18 18:58:47 +00:00
2010-09-13 01:09:26 +00:00
// 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' ;
2006-08-25 08:15:24 +00:00
}
2010-09-13 01:09:26 +00:00
// 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' ,
);
2007-01-23 19:17:55 +00:00
// 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.
2009-12-14 13:51:57 +00:00
// 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 ) {
2006-03-20 16:28:10 +00:00
unset ( $form [ '#token' ]);
2005-11-06 11:38:56 +00:00
}
2009-12-14 13:51:57 +00:00
// Otherwise, generate a public token based on the form id.
2006-03-20 16:28:10 +00:00
else {
2009-12-14 13:51:57 +00:00
$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' ]),
);
2006-03-20 16:28:10 +00:00
}
2005-10-07 06:11:12 +00:00
}
2006-10-31 08:06:18 +00:00
2006-02-27 14:46:49 +00:00
if ( isset ( $form_id )) {
2007-05-14 13:43:38 +00:00
$form [ 'form_id' ] = array (
'#type' => 'hidden' ,
'#value' => $form_id ,
2009-10-05 01:18:26 +00:00
'#id' => drupal_html_id ( " edit- $form_id " ),
2007-05-14 13:43:38 +00:00
);
2006-02-27 14:46:49 +00:00
}
2006-02-02 02:10:31 +00:00
if ( ! isset ( $form [ '#id' ])) {
2009-10-05 01:18:26 +00:00
$form [ '#id' ] = drupal_html_id ( $form_id );
2006-02-02 02:10:31 +00:00
}
2005-10-26 01:24:09 +00:00
2009-02-03 18:55:32 +00:00
$form += element_info ( 'form' );
2008-08-15 07:55:54 +00:00
$form += array ( '#tree' => FALSE , '#parents' => array ());
2005-10-07 06:11:12 +00:00
2005-11-22 21:31:15 +00:00
if ( ! isset ( $form [ '#validate' ])) {
2011-07-04 17:11:33 +00:00
// Ensure that modules can rely on #validate being set.
$form [ '#validate' ] = array ();
2010-09-09 23:01:48 +00:00
// Check for a handler specific to $form_id.
2009-08-24 00:14:23 +00:00
if ( function_exists ( $form_id . '_validate' )) {
2010-09-09 23:01:48 +00:00
$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' ;
2005-11-22 21:31:15 +00:00
}
}
2005-12-02 15:21:01 +00:00
if ( ! isset ( $form [ '#submit' ])) {
2011-07-04 17:11:33 +00:00
// Ensure that modules can rely on #submit being set.
$form [ '#submit' ] = array ();
2010-09-09 23:01:48 +00:00
// Check for a handler specific to $form_id.
2009-08-24 00:14:23 +00:00
if ( function_exists ( $form_id . '_submit' )) {
2010-09-09 23:01:48 +00:00
$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' ;
2005-11-22 21:31:15 +00:00
}
}
2010-09-14 21:42:05 +00:00
// If no #theme has been set, automatically apply theme suggestions.
2010-09-13 01:09:26 +00:00
// 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.
2010-09-14 21:42:05 +00:00
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' ];
2010-09-13 01:09:26 +00:00
}
}
2010-09-09 23:01:48 +00:00
// 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 );
2005-10-07 06:11:12 +00:00
}
2006-07-22 19:26:58 +00:00
/**
2007-05-14 13:43:38 +00:00
* Validates user - submitted form data from the $form_state using
2006-07-22 19:26:58 +00:00
* 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
2009-11-28 14:39:31 +00:00
* 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 .
2007-05-14 13:43:38 +00:00
* @ 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 :
2010-01-25 10:38:35 +00:00
* $form_state [ 'data_for_submission' ] = $data ;
2007-05-14 13:43:38 +00:00
* 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 .
2006-07-22 19:26:58 +00:00
*/
2009-11-28 14:39:31 +00:00
function drupal_validate_form ( $form_id , & $form , & $form_state ) {
2009-06-02 13:47:26 +00:00
$validated_forms = & drupal_static ( __FUNCTION__ , array ());
2006-04-07 13:00:37 +00:00
2009-03-14 20:13:27 +00:00
if ( isset ( $validated_forms [ $form_id ]) && empty ( $form_state [ 'must_validate' ])) {
2006-04-07 13:00:37 +00:00
return ;
}
2005-10-07 06:11:12 +00:00
2006-08-18 18:58:47 +00:00
// If the session token was set by drupal_prepare_form(), ensure that it
2007-01-23 19:17:55 +00:00
// matches the current user's session.
2005-10-11 19:44:35 +00:00
if ( isset ( $form [ '#token' ])) {
2007-05-14 13:43:38 +00:00
if ( ! drupal_valid_token ( $form_state [ 'values' ][ 'form_token' ], $form [ '#token' ])) {
2011-09-11 20:07:49 +00:00
$path = current_path ();
$query = drupal_get_query_parameters ();
$url = url ( $path , array ( 'query' => $query ));
2007-01-23 19:17:55 +00:00
// Setting this error will cause the form to fail validation.
2011-09-11 20:07:49 +00:00
form_set_error ( 'form_token' , t ( 'The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.' , array ( '@link' => $url )));
2005-10-07 06:11:12 +00:00
}
}
2007-05-14 13:43:38 +00:00
_form_validate ( $form , $form_state , $form_id );
2006-04-07 13:00:37 +00:00
$validated_forms [ $form_id ] = TRUE ;
2010-08-27 11:54:32 +00:00
// 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 ) {
2010-09-10 07:58:40 +00:00
// 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 ) {
2010-08-27 11:54:32 +00:00
drupal_array_set_nested_value ( $values , $section , $value );
}
}
2010-11-13 14:04:08 +00:00
// A button's #value does not require validation, so for convenience we
// allow the value of the clicked button to be retained in its normal
// $form_state['values'] locations, even if these locations are not included
// in #limit_validation_errors.
2010-08-27 11:54:32 +00:00
if ( isset ( $form_state [ 'triggering_element' ][ '#button_type' ])) {
2010-11-13 14:04:08 +00:00
$button_value = $form_state [ 'triggering_element' ][ '#value' ];
// Like all input controls, the button value may be in the location
// dictated by #parents. If it is, copy it to $values, but do not override
// what may already be in $values.
$parents = $form_state [ 'triggering_element' ][ '#parents' ];
if ( ! drupal_array_nested_key_exists ( $values , $parents ) && drupal_array_get_nested_value ( $form_state [ 'values' ], $parents ) === $button_value ) {
drupal_array_set_nested_value ( $values , $parents , $button_value );
}
// Additionally, form_builder() places the button value in
// $form_state['values'][BUTTON_NAME]. If it's still there, after
// validation handlers have run, copy it to $values, but do not override
// what may already be in $values.
$name = $form_state [ 'triggering_element' ][ '#name' ];
if ( ! isset ( $values [ $name ]) && isset ( $form_state [ 'values' ][ $name ]) && $form_state [ 'values' ][ $name ] === $button_value ) {
$values [ $name ] = $button_value ;
}
2010-08-27 11:54:32 +00:00
}
$form_state [ 'values' ] = $values ;
}
2005-10-07 06:11:12 +00:00
}
2006-07-22 19:26:58 +00:00
/**
2009-09-21 06:44:14 +00:00
* 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
2011-06-22 06:11:46 +00:00
* is redirected . By default , we redirect to a new destination page . The path
* of the destination page can be set in $form_state [ 'redirect' ], as either a
* string containing the destination or an array of arguments compatible with
* drupal_goto () . If that is not set , the user is redirected to the current
* page to display a fresh , unpopulated copy of the form .
*
* For example , to redirect to 'node' :
* @ code
* $form_state [ 'redirect' ] = 'node' ;
* @ endcode
* Or to redirect to 'node/123?foo=bar#baz' :
* @ code
* $form_state [ 'redirect' ] = array (
* 'node/123' ,
* array (
* 'query' => array (
* 'foo' => 'bar' ,
* ),
* 'fragment' => 'baz' ,
* ),
* );
* @ endcode
2009-09-21 06:44:14 +00:00
*
* 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 ()
2011-02-19 00:09:11 +00:00
* defines $form_state [ 'no_redirect' ] when building a form in an Ajax
2009-09-21 06:44:14 +00:00
* 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 () .
2009-11-21 14:06:46 +00:00
* - If $form_state [ 'rebuild' ] is TRUE , the form needs to be rebuilt without
2009-09-21 06:44:14 +00:00
* redirection .
2006-07-22 19:26:58 +00:00
*
2009-09-21 06:44:14 +00:00
* @ param $form_state
* A keyed array containing the current state of the form .
*
* @ see drupal_process_form ()
* @ see drupal_build_form ()
2006-07-22 19:26:58 +00:00
*/
2009-09-21 06:44:14 +00:00
function drupal_redirect_form ( $form_state ) {
// Skip redirection for form submissions invoked via drupal_form_submit().
if ( ! empty ( $form_state [ 'programmed' ])) {
return ;
2006-07-22 19:26:58 +00:00
}
2009-11-21 14:06:46 +00:00
// Skip redirection if rebuild is activated.
if ( ! empty ( $form_state [ 'rebuild' ])) {
2009-09-21 06:44:14 +00:00
return ;
}
// Skip redirection if it was explicitly disallowed.
if ( ! empty ( $form_state [ 'no_redirect' ])) {
return ;
2006-07-22 19:26:58 +00:00
}
2009-09-21 06:44:14 +00:00
// 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' ]);
2007-07-01 17:41:16 +00:00
}
else {
2009-07-28 12:13:47 +00:00
// 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' ;
2009-09-21 06:44:14 +00:00
$function ( $form_state [ 'redirect' ]);
2007-07-01 17:41:16 +00:00
}
}
drupal_goto ( $_GET [ 'q' ]);
}
2006-07-22 19:26:58 +00:00
}
2007-01-23 19:17:55 +00:00
/**
* 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 .
2007-05-14 13:43:38 +00:00
* @ 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 :
2010-01-25 10:38:35 +00:00
* $form_state [ 'data_for_submission' ] = $data ;
2007-05-14 13:43:38 +00:00
* 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 .
2007-01-23 19:17:55 +00:00
* @ param $form_id
* A unique string identifying the form for validation , submission ,
* theming , and hook_form_alter functions .
*/
2009-11-28 14:39:31 +00:00
function _form_validate ( & $elements , & $form_state , $form_id = NULL ) {
2007-11-11 16:14:45 +00:00
// Also used in the installer, pre-database setup.
$t = get_t ();
2007-11-13 14:04:08 +00:00
2006-05-16 10:11:19 +00:00
// Recurse through all children.
foreach ( element_children ( $elements ) as $key ) {
if ( isset ( $elements [ $key ]) && $elements [ $key ]) {
2007-05-14 13:43:38 +00:00
_form_validate ( $elements [ $key ], $form_state );
2006-05-16 10:11:19 +00:00
}
}
2010-01-02 23:30:53 +00:00
2007-12-31 08:54:37 +00:00
// Validate the current input.
2006-11-16 09:06:59 +00:00
if ( ! isset ( $elements [ '#validated' ]) || ! $elements [ '#validated' ]) {
2010-01-02 23:30:53 +00:00
// The following errors are always shown.
2006-03-06 14:46:51 +00:00
if ( isset ( $elements [ '#needs_validation' ])) {
2006-12-21 16:16:44 +00:00
// Verify that the value is not longer than #maxlength.
if ( isset ( $elements [ '#maxlength' ]) && drupal_strlen ( $elements [ '#value' ]) > $elements [ '#maxlength' ]) {
2007-11-11 16:14:45 +00:00
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' ]))));
2006-12-21 16:16:44 +00:00
}
2007-10-11 09:22:39 +00:00
if ( isset ( $elements [ '#options' ]) && isset ( $elements [ '#value' ])) {
2006-03-05 02:46:55 +00:00
if ( $elements [ '#type' ] == 'select' ) {
$options = form_options_flatten ( $elements [ '#options' ]);
}
else {
$options = $elements [ '#options' ];
}
if ( is_array ( $elements [ '#value' ])) {
2010-10-21 20:46:58 +00:00
$value = in_array ( $elements [ '#type' ], array ( 'checkboxes' , 'tableselect' )) ? array_keys ( $elements [ '#value' ]) : $elements [ '#value' ];
2006-03-05 02:46:55 +00:00
foreach ( $value as $v ) {
if ( ! isset ( $options [ $v ])) {
2007-11-11 16:14:45 +00:00
form_error ( $elements , $t ( 'An illegal choice has been detected. Please contact the site administrator.' ));
2011-07-04 16:58:33 +00:00
watchdog ( 'form' , 'Illegal choice %choice in !name element.' , array ( '%choice' => $v , '!name' => empty ( $elements [ '#title' ]) ? $elements [ '#parents' ][ 0 ] : $elements [ '#title' ]), WATCHDOG_ERROR );
2006-03-05 02:46:55 +00:00
}
2005-12-19 14:30:53 +00:00
}
}
2010-09-24 21:36:22 +00:00
// 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
2010-10-04 18:00:46 +00:00
// 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.
2010-09-24 21:36:22 +00:00
// @see form_process_select()
2010-10-04 18:00:46 +00:00
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 );
2010-09-24 21:36:22 +00:00
}
2006-03-05 02:46:55 +00:00
elseif ( ! isset ( $options [ $elements [ '#value' ]])) {
2007-11-11 16:14:45 +00:00
form_error ( $elements , $t ( 'An illegal choice has been detected. Please contact the site administrator.' ));
2011-07-04 16:58:33 +00:00
watchdog ( 'form' , 'Illegal choice %choice in %name element.' , array ( '%choice' => $elements [ '#value' ], '%name' => empty ( $elements [ '#title' ]) ? $elements [ '#parents' ][ 0 ] : $elements [ '#title' ]), WATCHDOG_ERROR );
2006-03-05 02:46:55 +00:00
}
2005-12-19 14:30:53 +00:00
}
}
2010-01-02 23:30:53 +00:00
// 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
2010-03-26 18:58:12 +00:00
// have its submit handlers triggered. The triggering element's
2010-01-02 23:30:53 +00:00
// #limit_validation_errors property contains the information for which
// errors are needed, and all other errors are to be suppressed. The
2010-03-26 18:58:12 +00:00
// #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' ]);
2010-01-02 23:30:53 +00:00
}
2010-03-26 18:58:12 +00:00
// 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,
2011-02-19 00:09:11 +00:00
// which is particularly useful during an Ajax submission triggered by a
2010-03-26 18:58:12 +00:00
// 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.
2010-01-02 23:30:53 +00:00
else {
drupal_static_reset ( 'form_set_error:limit_validation_errors' );
}
2010-03-26 18:58:12 +00:00
2010-01-02 23:30:53 +00:00
// Make sure a value is passed when the field is required.
2010-09-24 21:36:22 +00:00
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 );
}
}
2010-01-02 23:30:53 +00:00
}
2009-07-06 13:31:47 +00:00
// Call user-defined form level validators.
2007-05-14 13:43:38 +00:00
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 ) {
2011-08-01 03:41:25 +00:00
$function ( $elements , $form_state , $form_state [ 'complete_form' ]);
2010-02-04 03:34:21 +00:00
}
2005-10-07 06:11:12 +00:00
}
2005-10-11 19:44:35 +00:00
$elements [ '#validated' ] = TRUE ;
2005-10-07 06:11:12 +00:00
}
2010-01-02 23:30:53 +00:00
// 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' );
2005-10-07 06:11:12 +00:00
}
2007-05-14 13:43:38 +00:00
/**
* 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 ;
2009-12-01 03:07:34 +00:00
// If there was a button pressed, use its handlers.
2008-04-14 17:48:46 +00:00
if ( isset ( $form_state [ $type . '_handlers' ])) {
$handlers = $form_state [ $type . '_handlers' ];
2007-05-14 13:43:38 +00:00
}
2009-12-01 03:07:34 +00:00
// Otherwise, check for a form-level handler.
2008-04-14 17:48:46 +00:00
elseif ( isset ( $form [ '#' . $type ])) {
$handlers = $form [ '#' . $type ];
2007-05-14 13:43:38 +00:00
}
else {
$handlers = array ();
}
foreach ( $handlers as $function ) {
2010-02-04 03:34:21 +00:00
// 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().
2010-02-01 19:26:13 +00:00
$batch [ 'sets' ][] = array ( 'form_submit' => $function );
2010-02-04 03:34:21 +00:00
$batch [ 'has_form_submits' ] = TRUE ;
2010-02-01 19:26:13 +00:00
}
else {
$function ( $form , $form_state );
2007-05-14 13:43:38 +00:00
}
2010-02-01 19:26:13 +00:00
$return = TRUE ;
2007-05-14 13:43:38 +00:00
}
return $return ;
}
2005-10-13 10:02:31 +00:00
/**
2009-12-04 16:29:15 +00:00
* Files an error against a form element .
2007-12-23 12:33:13 +00:00
*
2010-06-03 13:48:04 +00:00
* 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
*
2007-12-23 12:33:13 +00:00
* @ 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 .
2010-01-02 23:30:53 +00:00
* @ param $limit_validation_errors
* Internal use only . The #limit_validation_errors property of the clicked
2010-06-03 13:48:04 +00:00
* button , if it exists .
2010-01-02 23:30:53 +00:00
*
2007-12-23 12:33:13 +00:00
* @ return
2010-01-02 23:30:53 +00:00
* Return value is for internal use only . To get a list of errors , use
2009-12-04 16:29:15 +00:00
* form_get_errors () or form_get_error () .
2005-10-13 10:02:31 +00:00
*/
2010-01-02 23:30:53 +00:00
function form_set_error ( $name = NULL , $message = '' , $limit_validation_errors = NULL ) {
2009-06-02 13:47:26 +00:00
$form = & drupal_static ( __FUNCTION__ , array ());
2010-01-02 23:30:53 +00:00
$sections = & drupal_static ( __FUNCTION__ . ':limit_validation_errors' );
if ( isset ( $limit_validation_errors )) {
$sections = $limit_validation_errors ;
}
2005-10-13 10:02:31 +00:00
if ( isset ( $name ) && ! isset ( $form [ $name ])) {
2010-01-02 23:30:53 +00:00
$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,
2010-11-15 10:06:32 +00:00
// so errors for this element must be recorded. As the exploded array
// will all be strings, we need to cast every value of the section
// array to string.
2010-11-17 04:10:52 +00:00
if ( array_slice ( explode ( '][' , $name ), 0 , count ( $section )) === array_map ( 'strval' , $section )) {
2010-01-02 23:30:53 +00:00
$record = TRUE ;
break ;
}
}
}
if ( $record ) {
$form [ $name ] = $message ;
if ( $message ) {
drupal_set_message ( $message , 'error' );
}
2006-01-24 10:15:03 +00:00
}
2005-10-13 10:02:31 +00:00
}
2010-01-02 23:30:53 +00:00
2005-10-13 10:02:31 +00:00
return $form ;
}
2009-06-02 13:47:26 +00:00
/**
* Clear all errors against all form elements made by form_set_error () .
*/
function form_clear_error () {
drupal_static_reset ( 'form_set_error' );
}
2005-10-13 10:02:31 +00:00
/**
* Return an associative array of all errors .
*/
function form_get_errors () {
$form = form_set_error ();
if ( ! empty ( $form )) {
return $form ;
}
}
/**
2010-09-16 20:14:49 +00:00
* 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 .
2005-10-13 10:02:31 +00:00
*/
function form_get_error ( $element ) {
$form = form_set_error ();
2010-09-16 20:14:49 +00:00
$parents = array ();
foreach ( $element [ '#parents' ] as $parent ) {
$parents [] = $parent ;
$key = implode ( '][' , $parents );
if ( isset ( $form [ $key ])) {
return $form [ $key ];
}
2005-10-13 10:02:31 +00:00
}
}
2005-10-07 06:11:12 +00:00
/**
* Flag an element as having an error .
*/
2006-01-24 10:15:03 +00:00
function form_error ( & $element , $message = '' ) {
2005-10-13 10:02:31 +00:00
form_set_error ( implode ( '][' , $element [ '#parents' ]), $message );
2005-10-07 06:11:12 +00:00
}
/**
2010-07-02 12:37:57 +00:00
* 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
2010-12-18 06:29:46 +00:00
* functions must then be implemented to use the $form_state data to rebuild
2010-07-02 12:37:57 +00:00
* 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).
2006-02-17 10:51:57 +00:00
*
* @ param $form_id
2006-07-22 19:26:58 +00:00
* A unique string identifying the form for validation , submission ,
* theming , and hook_form_alter functions .
2009-07-06 13:31:47 +00:00
* @ param $element
* An associative array containing the structure of the current element .
2007-05-14 13:43:38 +00:00
* @ 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 .
2005-10-07 06:11:12 +00:00
*/
2010-10-13 13:43:21 +00:00
function form_builder ( $form_id , & $element , & $form_state ) {
2006-06-23 08:31:23 +00:00
// Initialize as unprocessed.
2009-07-06 13:31:47 +00:00
$element [ '#processed' ] = FALSE ;
2006-06-23 08:31:23 +00:00
2007-12-31 08:54:37 +00:00
// Use element defaults.
2010-04-24 14:49:14 +00:00
if ( isset ( $element [ '#type' ]) && empty ( $element [ '#defaults_loaded' ]) && ( $info = element_info ( $element [ '#type' ]))) {
2009-07-06 13:31:47 +00:00
// Overlay $info onto $element, retaining preexisting keys in $element.
$element += $info ;
$element [ '#defaults_loaded' ] = TRUE ;
}
2009-11-16 05:11:01 +00:00
// Assign basic defaults common for all form elements.
$element += array (
'#required' => FALSE ,
'#attributes' => array (),
2009-12-02 15:09:16 +00:00
'#title_display' => 'before' ,
2009-11-16 05:11:01 +00:00
);
2009-07-06 13:31:47 +00:00
// Special handling if we're on the top level form element.
if ( isset ( $element [ '#type' ]) && $element [ '#type' ] == 'form' ) {
2009-09-05 13:05:31 +00:00
if ( ! empty ( $element [ '#https' ]) && variable_get ( 'https' , FALSE ) &&
2009-11-01 23:02:13 +00:00
! url_is_external ( $element [ '#action' ])) {
2009-09-05 13:05:31 +00:00
global $base_root ;
// Not an external URL so ensure that it is secure.
$element [ '#action' ] = str_replace ( 'http://' , 'https://' , $base_root ) . $element [ '#action' ];
}
2010-10-13 13:43:21 +00:00
// Store a reference to the complete form in $form_state prior to building
// the form. This allows advanced #process and #after_build callbacks to
// perform changes elsewhere in the form.
2011-08-01 03:41:25 +00:00
$form_state [ 'complete_form' ] = & $element ;
2010-10-13 13:43:21 +00:00
2009-07-06 13:31:47 +00:00
// 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 ;
2007-10-16 14:06:23 +00:00
}
2010-12-08 06:55:01 +00:00
// All form elements should have an #array_parents property.
$element [ '#array_parents' ] = array ();
2007-06-17 12:07:51 +00:00
}
2009-07-06 13:31:47 +00:00
if ( ! isset ( $element [ '#id' ])) {
2009-10-05 01:18:26 +00:00
$element [ '#id' ] = drupal_html_id ( 'edit-' . implode ( '-' , $element [ '#parents' ]));
2009-04-11 22:19:46 +00:00
}
2009-07-06 13:31:47 +00:00
// Handle input elements.
if ( ! empty ( $element [ '#input' ])) {
_form_builder_handle_input_element ( $form_id , $element , $form_state );
2005-12-19 14:43:42 +00:00
}
2009-04-11 22:19:46 +00:00
// Allow for elements to expand to multiple elements, e.g., radios,
// checkboxes and files.
2009-07-06 13:31:47 +00:00
if ( isset ( $element [ '#process' ]) && ! $element [ '#processed' ]) {
foreach ( $element [ '#process' ] as $process ) {
2011-08-01 03:41:25 +00:00
$element = $process ( $element , $form_state , $form_state [ 'complete_form' ]);
2009-04-11 22:19:46 +00:00
}
2009-07-06 13:31:47 +00:00
$element [ '#processed' ] = TRUE ;
2009-04-11 22:19:46 +00:00
}
2005-12-19 14:43:42 +00:00
2007-01-10 23:30:07 +00:00
// We start off assuming all form elements are in the correct order.
2009-07-06 13:31:47 +00:00
$element [ '#sorted' ] = TRUE ;
2007-01-10 23:30:07 +00:00
2005-10-07 06:11:12 +00:00
// Recurse through all child elements.
2007-01-10 23:30:07 +00:00
$count = 0 ;
2009-07-06 13:31:47 +00:00
foreach ( element_children ( $element ) as $key ) {
2010-05-17 21:23:32 +00:00
// 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 ;
}
2007-01-23 19:17:55 +00:00
// Don't squash an existing tree value.
2009-07-06 13:31:47 +00:00
if ( ! isset ( $element [ $key ][ '#tree' ])) {
$element [ $key ][ '#tree' ] = $element [ '#tree' ];
2005-11-18 13:48:09 +00:00
}
2005-10-26 01:24:09 +00:00
2007-01-23 19:17:55 +00:00
// Deny access to child elements if parent is denied.
2009-07-06 13:31:47 +00:00
if ( isset ( $element [ '#access' ]) && ! $element [ '#access' ]) {
$element [ $key ][ '#access' ] = FALSE ;
2006-08-22 11:13:04 +00:00
}
2010-07-10 00:03:37 +00:00
// 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 ];
}
}
2007-01-23 19:17:55 +00:00
// Don't squash existing parents value.
2009-07-06 13:31:47 +00:00
if ( ! isset ( $element [ $key ][ '#parents' ])) {
2007-01-23 19:17:55 +00:00
// Check to see if a tree of child elements is present. If so,
// continue down the tree if required.
2009-07-06 13:31:47 +00:00
$element [ $key ][ '#parents' ] = $element [ $key ][ '#tree' ] && $element [ '#tree' ] ? array_merge ( $element [ '#parents' ], array ( $key )) : array ( $key );
2005-10-26 01:24:09 +00:00
}
2009-11-22 07:32:47 +00:00
// Ensure #array_parents follows the actual form structure.
2010-12-08 06:55:01 +00:00
$array_parents = $element [ '#array_parents' ];
2009-11-22 07:32:47 +00:00
$array_parents [] = $key ;
$element [ $key ][ '#array_parents' ] = $array_parents ;
2005-10-26 01:24:09 +00:00
2007-01-23 19:17:55 +00:00
// Assign a decimal placeholder weight to preserve original array order.
2009-07-06 13:31:47 +00:00
if ( ! isset ( $element [ $key ][ '#weight' ])) {
$element [ $key ][ '#weight' ] = $count / 1000 ;
2005-11-18 13:48:09 +00:00
}
2007-01-10 23:30:07 +00:00
else {
2007-01-23 19:17:55 +00:00
// If one of the child elements has a weight then we will need to sort
// later.
2009-07-06 13:31:47 +00:00
unset ( $element [ '#sorted' ]);
2007-01-10 23:30:07 +00:00
}
2009-07-06 13:31:47 +00:00
$element [ $key ] = form_builder ( $form_id , $element [ $key ], $form_state );
2005-10-07 06:11:12 +00:00
$count ++ ;
}
2007-05-14 13:43:38 +00:00
// The #after_build flag allows any piece of a form to be altered
// after normal input parsing has been completed.
2009-07-06 13:31:47 +00:00
if ( isset ( $element [ '#after_build' ]) && ! isset ( $element [ '#after_build_done' ])) {
foreach ( $element [ '#after_build' ] as $function ) {
$element = $function ( $element , $form_state );
2007-05-14 13:43:38 +00:00
}
2010-01-02 10:41:56 +00:00
$element [ '#after_build_done' ] = TRUE ;
2007-05-14 13:43:38 +00:00
}
2009-07-06 13:31:47 +00:00
// If there is a file element, we need to flip a flag so later the
2008-11-15 13:01:11 +00:00
// form encoding can be set.
2009-07-06 13:31:47 +00:00
if ( isset ( $element [ '#type' ]) && $element [ '#type' ] == 'file' ) {
$form_state [ 'has_file_element' ] = TRUE ;
2008-11-15 13:01:11 +00:00
}
2009-07-06 13:31:47 +00:00
2010-03-26 18:58:12 +00:00
// Final tasks for the form element after form_builder() has run for all other
// elements.
2009-07-06 13:31:47 +00:00
if ( isset ( $element [ '#type' ]) && $element [ '#type' ] == 'form' ) {
2008-11-24 00:40:45 +00:00
// If there is a file element, we set the form encoding.
2009-07-06 13:31:47 +00:00
if ( isset ( $form_state [ 'has_file_element' ])) {
$element [ '#attributes' ][ 'enctype' ] = 'multipart/form-data' ;
2008-11-24 00:40:45 +00:00
}
2010-03-26 18:58:12 +00:00
// 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' ];
}
2007-11-19 19:23:28 +00:00
}
2009-07-06 13:31:47 +00:00
return $element ;
2007-05-14 13:43:38 +00:00
}
/**
* Populate the #value and #name properties of input elements so they
2009-04-11 22:19:46 +00:00
* can be processed and rendered .
2007-05-14 13:43:38 +00:00
*/
2009-07-06 13:31:47 +00:00
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' ) {
2007-05-14 13:43:38 +00:00
// 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.
2009-07-06 13:31:47 +00:00
$element [ '#name' ] = 'files[' . $element [ '#name' ] . ']' ;
2007-05-14 13:43:38 +00:00
}
2009-07-06 13:31:47 +00:00
elseif ( count ( $element [ '#parents' ])) {
$element [ '#name' ] .= '[' . implode ( '][' , $element [ '#parents' ]) . ']' ;
2007-05-14 13:43:38 +00:00
}
2009-07-06 13:31:47 +00:00
array_unshift ( $element [ '#parents' ], $name );
2007-05-14 13:43:38 +00:00
}
2010-04-11 19:00:27 +00:00
// 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.
2009-07-06 13:31:47 +00:00
if ( ! empty ( $element [ '#disabled' ])) {
2010-04-11 19:00:27 +00:00
if ( ! empty ( $element [ '#allow_focus' ])) {
$element [ '#attributes' ][ 'readonly' ] = 'readonly' ;
}
else {
$element [ '#attributes' ][ 'disabled' ] = 'disabled' ;
}
2007-05-14 13:43:38 +00:00
}
2010-07-02 12:37:57 +00:00
// 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
2011-09-24 20:44:06 +00:00
// treated as high-privilege users instead.
2010-07-02 12:37:57 +00:00
$process_input = empty ( $element [ '#disabled' ]) && ( $form_state [ 'programmed' ] || ( $form_state [ 'process_input' ] && ( ! isset ( $element [ '#access' ]) || $element [ '#access' ])));
2009-07-06 13:31:47 +00:00
// 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' ;
2010-07-02 12:37:57 +00:00
if ( $process_input ) {
2009-12-01 03:07:34 +00:00
// Get the input for the current element. NULL values in the input need to
// be explicitly distinguished from missing input. (see below)
2010-09-10 07:58:40 +00:00
$input_exists = NULL ;
$input = drupal_array_get_nested_value ( $form_state [ 'input' ], $element [ '#parents' ], $input_exists );
2009-12-01 03:07:34 +00:00
// 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' ]) {
2010-08-27 11:54:32 +00:00
// 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 );
2009-12-01 03:07:34 +00:00
$input_exists = TRUE ;
2007-05-14 13:43:38 +00:00
}
2009-12-01 03:07:34 +00:00
// If we have input for the current element, assign it to the #value
// property, optionally filtered through $value_callback.
if ( $input_exists ) {
2009-08-24 00:14:23 +00:00
if ( function_exists ( $value_callback )) {
2009-07-06 13:31:47 +00:00
$element [ '#value' ] = $value_callback ( $element , $input , $form_state );
2007-05-14 13:43:38 +00:00
}
2009-07-06 13:31:47 +00:00
if ( ! isset ( $element [ '#value' ]) && isset ( $input )) {
$element [ '#value' ] = $input ;
2007-05-14 13:43:38 +00:00
}
}
2007-07-04 15:45:37 +00:00
// Mark all posted values for validation.
2009-07-06 13:31:47 +00:00
if ( isset ( $element [ '#value' ]) || ( ! empty ( $element [ '#required' ]))) {
$element [ '#needs_validation' ] = TRUE ;
2007-07-04 15:45:37 +00:00
}
2007-05-14 13:43:38 +00:00
}
2007-07-04 15:45:37 +00:00
// Load defaults.
2009-07-06 13:31:47 +00:00
if ( ! isset ( $element [ '#value' ])) {
2007-07-04 15:45:37 +00:00
// Call #type_value without a second argument to request default_value handling.
2009-08-24 00:14:23 +00:00
if ( function_exists ( $value_callback )) {
2009-08-12 11:45:14 +00:00
$element [ '#value' ] = $value_callback ( $element , FALSE , $form_state );
2007-05-14 13:43:38 +00:00
}
2007-07-04 15:45:37 +00:00
// Final catch. If we haven't set a value yet, use the explicit default value.
2008-01-28 00:15:34 +00:00
// Avoid image buttons (which come with garbage value), so we only get value
// for the button actually clicked.
2009-07-06 13:31:47 +00:00
if ( ! isset ( $element [ '#value' ]) && empty ( $element [ '#has_garbage_value' ])) {
$element [ '#value' ] = isset ( $element [ '#default_value' ]) ? $element [ '#default_value' ] : '' ;
2006-04-20 07:11:37 +00:00
}
}
2005-10-26 01:24:09 +00:00
}
2006-04-06 15:30:19 +00:00
2010-03-26 18:58:12 +00:00
// Determine which element (if any) triggered the submission of the form and
2010-07-02 12:37:57 +00:00
// 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 ) {
2011-02-19 00:09:11 +00:00
// Detect if the element triggered the submission via Ajax.
2010-03-26 18:58:12 +00:00
if ( _form_element_triggered_scripted_submission ( $element , $form_state )) {
$form_state [ 'triggering_element' ] = $element ;
}
2011-02-19 00:09:11 +00:00
// If the form was submitted by the browser rather than via Ajax, then it
2010-03-26 18:58:12 +00:00
// 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.
2010-07-02 12:37:57 +00:00
$form_state [ 'buttons' ][] = $element ;
2010-03-26 18:58:12 +00:00
if ( _form_button_was_clicked ( $element , $form_state )) {
$form_state [ 'triggering_element' ] = $element ;
2007-05-14 13:43:38 +00:00
}
}
}
2010-03-26 18:58:12 +00:00
2010-03-07 23:14:20 +00:00
// 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).
2010-09-10 07:58:40 +00:00
if ( ! drupal_array_nested_key_exists ( $form_state [ 'values' ], $element [ '#parents' ])) {
2010-03-07 23:14:20 +00:00
form_set_value ( $element , $element [ '#value' ], $form_state );
}
2007-05-14 13:43:38 +00:00
}
2007-07-29 17:28:23 +00:00
/**
2010-03-26 18:58:12 +00:00
* Helper function to handle the convoluted logic of button click detection .
2007-07-29 17:28:23 +00:00
*
2010-03-26 18:58:12 +00:00
* This detects button or non - button controls that trigger a form submission via
2011-02-19 00:09:11 +00:00
* Ajax or some other scriptable environment . These environments can set the
2010-03-26 18:58:12 +00:00
* 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 .
2007-07-29 17:28:23 +00:00
*/
2010-03-26 18:58:12 +00:00
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 .
*
2011-02-19 00:09:11 +00:00
* This does not detect button clicks processed by Ajax ( that is done in
2010-03-26 18:58:12 +00:00
* _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 ) {
2007-07-29 17:28:23 +00:00
// 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.
2010-03-26 18:58:12 +00:00
if ( isset ( $form_state [ 'input' ][ $element [ '#name' ]]) && $form_state [ 'input' ][ $element [ '#name' ]] == $element [ '#value' ]) {
2007-07-29 17:28:23 +00:00
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.
2010-03-26 18:58:12 +00:00
elseif ( ! empty ( $element [ '#has_garbage_value' ]) && isset ( $element [ '#value' ]) && $element [ '#value' ] !== '' ) {
2007-07-29 17:28:23 +00:00
return TRUE ;
}
return FALSE ;
}
2009-10-15 11:47:25 +00:00
/**
* 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
*
2011-05-08 19:50:38 +00:00
* @ param $form_state
2009-10-15 11:47:25 +00:00
* 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.
2010-03-26 18:58:12 +00:00
// 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' ];
$last_parent = array_pop ( $parents );
2011-11-03 07:32:28 +00:00
$key_exists = NULL ;
$values = & drupal_array_get_nested_value ( $form_state [ 'values' ], $parents , $key_exists );
if ( $key_exists && is_array ( $values )) {
unset ( $values [ $last_parent ]);
2009-10-15 11:47:25 +00:00
}
}
}
2007-08-10 10:51:17 +00:00
/**
* Helper function to determine the value for an image button form element .
*
* @ param $form
* The form element whose value is being populated .
2009-07-06 13:31:47 +00:00
* @ param $input
* The incoming input to populate the form element . If this is FALSE ,
2007-08-10 10:51:17 +00:00
* the element ' s default value should be returned .
2009-06-29 17:27:58 +00:00
* @ param $form_state
2009-03-14 20:13:27 +00:00
* A keyed array containing the current state of the form .
2007-08-10 10:51:17 +00:00
* @ return
* The data that will appear in the $form_state [ 'values' ] collection
* for this element . Return nothing to use the default .
*/
2009-07-06 13:31:47 +00:00
function form_type_image_button_value ( $form , $input , $form_state ) {
if ( $input !== FALSE ) {
if ( ! empty ( $input )) {
2007-08-10 10:51:17 +00:00
// 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'.
2009-07-06 13:31:47 +00:00
$input = $form_state [ 'input' ];
2009-06-29 17:27:58 +00:00
foreach ( explode ( '[' , $form [ '#name' ]) as $element_name ) {
2007-08-10 10:51:17 +00:00
// chop off the ] that may exist.
if ( substr ( $element_name , - 1 ) == ']' ) {
$element_name = substr ( $element_name , 0 , - 1 );
}
2009-07-06 13:31:47 +00:00
if ( ! isset ( $input [ $element_name ])) {
if ( isset ( $input [ $element_name . '_x' ])) {
2007-08-10 10:51:17 +00:00
return $form [ '#return_value' ];
}
return NULL ;
}
2009-07-06 13:31:47 +00:00
$input = $input [ $element_name ];
2007-08-10 10:51:17 +00:00
}
return $form [ '#return_value' ];
}
}
}
2007-07-04 15:45:37 +00:00
/**
* Helper function to determine the value for a checkbox form element .
*
* @ param $form
2007-07-14 15:23:29 +00:00
* The form element whose value is being populated .
2009-07-06 13:31:47 +00:00
* @ param $input
* The incoming input to populate the form element . If this is FALSE ,
2007-07-14 15:23:29 +00:00
* the element ' s default value should be returned .
2007-07-04 15:45:37 +00:00
* @ return
2009-07-06 13:31:47 +00:00
* The data that will appear in the $element_state [ 'values' ] collection
2007-07-14 15:23:29 +00:00
* for this element . Return nothing to use the default .
2007-07-04 15:45:37 +00:00
*/
2009-07-06 13:31:47 +00:00
function form_type_checkbox_value ( $element , $input = FALSE ) {
2010-10-28 02:20:14 +00:00
if ( $input === FALSE ) {
// Use #default_value as the default value of a checkbox, except change
// NULL to 0, because _form_builder_handle_input_element() would otherwise
// replace NULL with empty string, but an empty string is a potentially
// valid value for a checked checkbox.
return isset ( $element [ '#default_value' ]) ? $element [ '#default_value' ] : 0 ;
}
else {
// Checked checkboxes are submitted with a value (possibly '0' or ''):
// http://www.w3.org/TR/html401/interact/forms.html#successful-controls.
// For checked checkboxes, browsers submit the string version of
// #return_value, but we return the original #return_value. For unchecked
// checkboxes, browsers submit nothing at all, but
// _form_builder_handle_input_element() detects this, and calls this
// function with $input=NULL. Returning NULL from a value callback means to
// use the default value, which is not what is wanted when an unchecked
// checkbox is submitted, so we use integer 0 as the value indicating an
// unchecked checkbox. Therefore, modules must not use integer 0 as a
// #return_value, as doing so results in the checkbox always being treated
// as unchecked. The string '0' is allowed for #return_value. The most
// common use-case for setting #return_value to either 0 or '0' is for the
// first option within a 0-indexed array of checkboxes, and for this,
// form_process_checkboxes() uses the string rather than the integer.
2010-04-11 19:00:27 +00:00
return isset ( $input ) ? $element [ '#return_value' ] : 0 ;
2007-07-04 15:45:37 +00:00
}
}
/**
* Helper function to determine the value for a checkboxes form element .
*
2009-07-06 13:31:47 +00:00
* @ param $element
2007-07-14 15:23:29 +00:00
* The form element whose value is being populated .
2009-07-06 13:31:47 +00:00
* @ param $input
* The incoming input to populate the form element . If this is FALSE ,
2007-07-14 15:23:29 +00:00
* the element ' s default value should be returned .
2007-07-04 15:45:37 +00:00
* @ return
2009-07-06 13:31:47 +00:00
* The data that will appear in the $element_state [ 'values' ] collection
2007-07-14 15:23:29 +00:00
* for this element . Return nothing to use the default .
2007-07-04 15:45:37 +00:00
*/
2009-07-06 13:31:47 +00:00
function form_type_checkboxes_value ( $element , $input = FALSE ) {
if ( $input === FALSE ) {
2007-07-04 15:45:37 +00:00
$value = array ();
2009-07-06 13:31:47 +00:00
$element += array ( '#default_value' => array ());
foreach ( $element [ '#default_value' ] as $key ) {
2010-03-13 22:33:05 +00:00
$value [ $key ] = $key ;
2007-07-04 15:45:37 +00:00
}
return $value ;
}
2010-07-26 13:07:59 +00:00
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
2010-10-28 02:20:14 +00:00
// form submissions, since all values in $_POST are strings.
2010-07-26 13:07:59 +00:00
foreach ( $input as $key => $value ) {
2010-09-26 23:31:36 +00:00
if ( ! isset ( $value )) {
2010-07-26 13:07:59 +00:00
unset ( $input [ $key ]);
}
}
return drupal_map_assoc ( $input );
}
2010-06-05 13:18:09 +00:00
else {
2010-07-26 13:07:59 +00:00
return array ();
2007-09-25 11:29:10 +00:00
}
2007-07-04 15:45:37 +00:00
}
2010-10-21 20:46:58 +00:00
/**
* Helper function to determine the value for a tableselect 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_tableselect_value ( $element , $input = FALSE ) {
// If $element['#multiple'] == FALSE, then radio buttons are displayed and
// the default value handling is used.
if ( isset ( $element [ '#multiple' ]) && $element [ '#multiple' ]) {
// Checkboxes are being displayed with the default value coming from the
// keys of the #default_value property. This differs from the checkboxes
// element which uses the array values.
if ( $input === FALSE ) {
$value = array ();
$element += array ( '#default_value' => array ());
foreach ( $element [ '#default_value' ] as $key => $flag ) {
if ( $flag ) {
$value [ $key ] = $key ;
}
}
return $value ;
}
else {
return is_array ( $input ) ? drupal_map_assoc ( $input ) : array ();
}
}
}
2007-07-04 15:45:37 +00:00
/**
* Helper function to determine the value for a password_confirm form
* element .
*
2009-07-06 13:31:47 +00:00
* @ param $element
2007-07-14 15:23:29 +00:00
* The form element whose value is being populated .
2009-07-06 13:31:47 +00:00
* @ param $input
* The incoming input to populate the form element . If this is FALSE ,
2007-07-14 15:23:29 +00:00
* the element ' s default value should be returned .
2007-07-04 15:45:37 +00:00
* @ return
2009-07-06 13:31:47 +00:00
* The data that will appear in the $element_state [ 'values' ] collection
2007-07-14 15:23:29 +00:00
* for this element . Return nothing to use the default .
2007-07-04 15:45:37 +00:00
*/
2009-07-06 13:31:47 +00:00
function form_type_password_confirm_value ( $element , $input = FALSE ) {
if ( $input === FALSE ) {
$element += array ( '#default_value' => array ());
return $element [ '#default_value' ] + array ( 'pass1' => '' , 'pass2' => '' );
2007-07-04 15:45:37 +00:00
}
}
/**
* Helper function to determine the value for a select form element .
*
2009-07-06 13:31:47 +00:00
* @ param $element
2007-07-14 15:23:29 +00:00
* The form element whose value is being populated .
2009-07-06 13:31:47 +00:00
* @ param $input
* The incoming input to populate the form element . If this is FALSE ,
2007-07-14 15:23:29 +00:00
* the element ' s default value should be returned .
2007-07-04 15:45:37 +00:00
* @ return
2009-07-06 13:31:47 +00:00
* The data that will appear in the $element_state [ 'values' ] collection
2007-07-14 15:23:29 +00:00
* for this element . Return nothing to use the default .
2007-07-04 15:45:37 +00:00
*/
2009-07-06 13:31:47 +00:00
function form_type_select_value ( $element , $input = FALSE ) {
if ( $input !== FALSE ) {
if ( isset ( $element [ '#multiple' ]) && $element [ '#multiple' ]) {
2010-03-12 14:38:37 +00:00
// 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 ();
}
2007-07-04 15:45:37 +00:00
}
2010-10-04 18:00:46 +00:00
// 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' ];
}
2007-07-04 15:45:37 +00:00
else {
2009-07-06 13:31:47 +00:00
return $input ;
2007-07-04 15:45:37 +00:00
}
}
}
/**
* Helper function to determine the value for a textfield form element .
*
2009-07-06 13:31:47 +00:00
* @ param $element
2007-07-14 15:23:29 +00:00
* The form element whose value is being populated .
2009-07-06 13:31:47 +00:00
* @ param $input
* The incoming input to populate the form element . If this is FALSE ,
2007-07-14 15:23:29 +00:00
* the element ' s default value should be returned .
2007-07-04 15:45:37 +00:00
* @ return
2009-07-06 13:31:47 +00:00
* The data that will appear in the $element_state [ 'values' ] collection
2007-07-14 15:23:29 +00:00
* for this element . Return nothing to use the default .
2007-07-04 15:45:37 +00:00
*/
2009-07-06 13:31:47 +00:00
function form_type_textfield_value ( $element , $input = FALSE ) {
2010-03-12 14:38:37 +00:00
if ( $input !== FALSE && $input !== NULL ) {
2009-07-06 13:31:47 +00:00
// Equate $input to the form value to ensure it's marked for
2007-07-04 15:45:37 +00:00
// validation.
2009-07-06 13:31:47 +00:00
return str_replace ( array ( " \r " , " \n " ), '' , $input );
2007-07-04 15:45:37 +00:00
}
}
/**
* Helper function to determine the value for form ' s token value .
*
2009-07-06 13:31:47 +00:00
* @ param $element
2007-07-14 15:23:29 +00:00
* The form element whose value is being populated .
2009-07-06 13:31:47 +00:00
* @ param $input
* The incoming input to populate the form element . If this is FALSE ,
2007-07-14 15:23:29 +00:00
* the element ' s default value should be returned .
2007-07-04 15:45:37 +00:00
* @ return
2009-07-06 13:31:47 +00:00
* The data that will appear in the $element_state [ 'values' ] collection
2007-07-14 15:23:29 +00:00
* for this element . Return nothing to use the default .
2007-07-04 15:45:37 +00:00
*/
2009-07-06 13:31:47 +00:00
function form_type_token_value ( $element , $input = FALSE ) {
if ( $input !== FALSE ) {
2010-05-06 05:59:31 +00:00
return ( string ) $input ;
2007-07-04 15:45:37 +00:00
}
}
2006-04-15 21:52:44 +00:00
/**
2009-12-05 16:03:51 +00:00
* Change submitted form values during form validation .
2006-04-15 21:52:44 +00:00
*
2009-12-05 16:03:51 +00:00
* 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 .
2006-04-15 21:52:44 +00:00
*
2009-12-05 16:03:51 +00:00
* 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' ] .
2008-04-14 17:51:38 +00:00
*
2009-07-06 13:31:47 +00:00
* @ param $element
2009-12-05 16:03:51 +00:00
* 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' ) .
2006-04-15 21:52:44 +00:00
* @ param $value
2009-12-05 16:03:51 +00:00
* The new value for the form element .
2008-04-14 17:51:38 +00:00
* @ param $form_state
2009-12-05 16:03:51 +00:00
* Form state array where the value change should be recorded .
2006-04-15 21:52:44 +00:00
*/
2009-07-06 13:31:47 +00:00
function form_set_value ( $element , $value , & $form_state ) {
2010-11-13 14:04:08 +00:00
drupal_array_set_nested_value ( $form_state [ 'values' ], $element [ '#parents' ], $value , TRUE );
2006-04-15 21:52:44 +00:00
}
2010-06-26 18:11:27 +00:00
/**
* 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 .
*/
2009-12-29 20:16:09 +00:00
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 );
}
2006-01-06 07:15:47 +00:00
2010-06-26 18:11:27 +00:00
/**
* 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 .
*/
2009-12-29 20:16:09 +00:00
function _form_options_flatten ( $array ) {
$return = & drupal_static ( __FUNCTION__ );
2006-01-06 07:15:47 +00:00
foreach ( $array as $key => $value ) {
2006-10-31 07:37:25 +00:00
if ( is_object ( $value )) {
2009-12-29 20:16:09 +00:00
_form_options_flatten ( $value -> option );
2006-10-31 07:37:25 +00:00
}
2008-10-12 04:30:09 +00:00
elseif ( is_array ( $value )) {
2009-12-29 20:16:09 +00:00
_form_options_flatten ( $value );
2006-01-06 07:15:47 +00:00
}
else {
$return [ $key ] = 1 ;
}
}
return $return ;
}
2010-09-24 21:36:22 +00:00
/**
* 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)
2010-10-04 18:00:46 +00:00
* or not ( FALSE ) . Defaults to FALSE .
2010-09-24 21:36:22 +00:00
* - #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
2010-10-04 18:00:46 +00:00
* 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 .
2010-09-24 21:36:22 +00:00
*
* @ 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 {
2010-10-04 18:00:46 +00:00
$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' ])) {
2010-09-24 21:36:22 +00:00
$element += array (
'#empty_value' => '' ,
2011-01-18 18:23:40 +00:00
'#empty_option' => $required ? t ( '- Select -' ) : t ( '- None -' ),
2010-09-24 21:36:22 +00:00
);
// 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 ;
}
2005-10-07 06:11:12 +00:00
/**
2010-04-13 15:23:03 +00:00
* 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 .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ 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.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_select ( $variables ) {
$element = $variables [ 'element' ];
2010-09-16 20:14:49 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , 'size' ));
2006-04-07 13:22:12 +00:00
_form_set_class ( $element , array ( 'form-select' ));
2010-09-16 20:14:49 +00:00
return '<select' . drupal_attributes ( $element [ '#attributes' ]) . '>' . form_select_options ( $element ) . '</select>' ;
2006-01-19 09:22:16 +00:00
}
2009-08-19 08:15:36 +00:00
/**
2010-06-26 18:11:27 +00:00
* Converts a select form element ' s options array into an HTML .
2009-08-19 08:15:36 +00:00
*
* @ 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 .
*/
2006-01-19 09:22:16 +00:00
function form_select_options ( $element , $choices = NULL ) {
if ( ! isset ( $choices )) {
$choices = $element [ '#options' ];
}
2006-04-11 11:33:15 +00:00
// array_key_exists() accommodates the rare event where $element['#value'] is NULL.
2006-01-13 07:44:59 +00:00
// isset() fails in this situation.
$value_valid = isset ( $element [ '#value' ]) || array_key_exists ( '#value' , $element );
2010-09-16 20:14:49 +00:00
$value_is_array = $value_valid && is_array ( $element [ '#value' ]);
2006-01-19 09:22:16 +00:00
$options = '' ;
foreach ( $choices as $key => $choice ) {
2005-10-07 06:11:12 +00:00
if ( is_array ( $choice )) {
2008-04-14 17:48:46 +00:00
$options .= '<optgroup label="' . $key . '">' ;
2006-01-19 09:22:16 +00:00
$options .= form_select_options ( $element , $choice );
$options .= '</optgroup>' ;
2005-10-07 06:11:12 +00:00
}
2006-10-20 20:55:03 +00:00
elseif ( is_object ( $choice )) {
$options .= form_select_options ( $element , $choice -> option );
}
2005-10-07 06:11:12 +00:00
else {
2010-05-06 05:59:31 +00:00
$key = ( string ) $key ;
if ( $value_valid && ( ! $value_is_array && ( string ) $element [ '#value' ] === $key || ( $value_is_array && in_array ( $key , $element [ '#value' ])))) {
2006-01-06 07:15:47 +00:00
$selected = ' selected="selected"' ;
}
else {
$selected = '' ;
}
2008-04-14 17:48:46 +00:00
$options .= '<option value="' . check_plain ( $key ) . '"' . $selected . '>' . check_plain ( $choice ) . '</option>' ;
2005-10-07 06:11:12 +00:00
}
}
2006-01-19 09:22:16 +00:00
return $options ;
2005-10-07 06:11:12 +00:00
}
2006-12-29 00:19:58 +00:00
/**
2007-01-05 19:08:30 +00:00
* 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
2007-01-23 19:17:55 +00:00
* already in a form element ; for example , to remove choices which are
2007-01-05 19:08:30 +00:00
* 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 .
2006-12-29 00:19:58 +00:00
*
* @ param $element
2007-01-05 19:08:30 +00:00
* The select element to search .
2006-12-29 00:19:58 +00:00
* @ param $key
* The key to look for .
* @ return
2007-01-05 19:08:30 +00:00
* An array of indexes that match the given $key . Array will be
* empty if no elements were found . FALSE if optgroups were found .
2006-12-29 00:19:58 +00:00
*/
2007-01-05 19:08:30 +00:00
function form_get_options ( $element , $key ) {
$keys = array ();
foreach ( $element [ '#options' ] as $index => $choice ) {
if ( is_array ( $choice )) {
return FALSE ;
}
2008-10-12 04:30:09 +00:00
elseif ( is_object ( $choice )) {
2007-01-05 19:08:30 +00:00
if ( isset ( $choice -> option [ $key ])) {
$keys [] = $index ;
}
}
2008-10-12 04:30:09 +00:00
elseif ( $index == $key ) {
2007-01-05 19:08:30 +00:00
$keys [] = $index ;
2006-12-29 00:19:58 +00:00
}
}
2007-01-05 19:08:30 +00:00
return $keys ;
2006-12-29 00:19:58 +00:00
}
2005-10-07 06:11:12 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a fieldset form element and its children .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ 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.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_fieldset ( $variables ) {
$element = $variables [ 'element' ];
2010-09-16 20:14:49 +00:00
element_set_attributes ( $element , array ( 'id' ));
_form_set_class ( $element , array ( 'form-wrapper' ));
2009-11-16 05:11:01 +00:00
$output = '<fieldset' . drupal_attributes ( $element [ '#attributes' ]) . '>' ;
if ( ! empty ( $element [ '#title' ])) {
2010-03-03 19:46:26 +00:00
// Always wrap fieldset legends in a SPAN for CSS positioning.
$output .= '<legend><span class="fieldset-legend">' . $element [ '#title' ] . '</span></legend>' ;
2009-11-16 05:11:01 +00:00
}
2010-03-03 19:46:26 +00:00
$output .= '<div class="fieldset-wrapper">' ;
2009-11-16 05:11:01 +00:00
if ( ! empty ( $element [ '#description' ])) {
$output .= '<div class="fieldset-description">' . $element [ '#description' ] . '</div>' ;
}
$output .= $element [ '#children' ];
if ( isset ( $element [ '#value' ])) {
$output .= $element [ '#value' ];
}
2010-03-03 19:46:26 +00:00
$output .= '</div>' ;
2009-11-16 05:11:01 +00:00
$output .= " </fieldset> \n " ;
return $output ;
2005-10-07 06:11:12 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a radio button form element .
2005-10-07 06:11:12 +00:00
*
2010-12-30 22:52:24 +00:00
* Note : The input " name " attribute needs to be sanitized before output , which
* is currently done by passing all attributes to drupal_attributes () .
*
2009-10-09 01:00:08 +00:00
* @ 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
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_radio ( $variables ) {
$element = $variables [ 'element' ];
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'type' ] = 'radio' ;
2010-09-16 20:14:49 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , '#return_value' => 'value' ));
2010-12-30 22:52:24 +00:00
if ( isset ( $element [ '#return_value' ]) && $element [ '#value' ] !== FALSE && $element [ '#value' ] == $element [ '#return_value' ]) {
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'checked' ] = 'checked' ;
}
2006-04-05 18:12:48 +00:00
_form_set_class ( $element , array ( 'form-radio' ));
2009-11-16 05:11:01 +00:00
2010-07-31 04:01:51 +00:00
return '<input' . drupal_attributes ( $element [ '#attributes' ]) . ' />' ;
2005-10-07 06:11:12 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a set of radio button form elements .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ 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.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_radios ( $variables ) {
$element = $variables [ 'element' ];
2010-03-26 12:16:18 +00:00
$attributes = array ();
2010-09-16 20:14:49 +00:00
if ( isset ( $element [ '#id' ])) {
2010-03-26 12:16:18 +00:00
$attributes [ 'id' ] = $element [ '#id' ];
}
$attributes [ 'class' ] = 'form-radios' ;
2009-08-22 14:34:23 +00:00
if ( ! empty ( $element [ '#attributes' ][ 'class' ])) {
2010-03-26 12:16:18 +00:00
$attributes [ 'class' ] .= ' ' . implode ( ' ' , $element [ '#attributes' ][ 'class' ]);
2006-12-12 10:01:38 +00:00
}
2010-03-26 12:16:18 +00:00
return '<div' . drupal_attributes ( $attributes ) . '>' . ( ! empty ( $element [ '#children' ]) ? $element [ '#children' ] : '' ) . '</div>' ;
2006-01-02 08:35:59 +00:00
}
2007-12-31 08:54:37 +00:00
/**
2006-04-20 16:35:29 +00:00
* Expand a password_confirm field into two text boxes .
*/
2008-07-18 07:06:24 +00:00
function form_process_password_confirm ( $element ) {
2006-08-03 14:08:30 +00:00
$element [ 'pass1' ] = array (
'#type' => 'password' ,
'#title' => t ( 'Password' ),
2007-05-14 13:43:38 +00:00
'#value' => empty ( $element [ '#value' ]) ? NULL : $element [ '#value' ][ 'pass1' ],
2007-05-31 12:36:18 +00:00
'#required' => $element [ '#required' ],
2009-08-22 14:34:23 +00:00
'#attributes' => array ( 'class' => array ( 'password-field' )),
2006-08-03 14:08:30 +00:00
);
$element [ 'pass2' ] = array (
'#type' => 'password' ,
'#title' => t ( 'Confirm password' ),
2007-05-14 13:43:38 +00:00
'#value' => empty ( $element [ '#value' ]) ? NULL : $element [ '#value' ][ 'pass2' ],
2007-05-31 12:36:18 +00:00
'#required' => $element [ '#required' ],
2009-08-22 14:34:23 +00:00
'#attributes' => array ( 'class' => array ( 'password-confirm' )),
2006-08-03 14:08:30 +00:00
);
2007-05-14 13:43:38 +00:00
$element [ '#element_validate' ] = array ( 'password_confirm_validate' );
2006-04-20 16:35:29 +00:00
$element [ '#tree' ] = TRUE ;
2007-01-11 03:24:42 +00:00
if ( isset ( $element [ '#size' ])) {
$element [ 'pass1' ][ '#size' ] = $element [ 'pass2' ][ '#size' ] = $element [ '#size' ];
}
2006-04-20 16:35:29 +00:00
return $element ;
}
2006-01-02 08:35:59 +00:00
/**
2006-01-24 10:15:03 +00:00
* Validate password_confirm element .
2006-01-02 08:35:59 +00:00
*/
2009-07-06 13:31:47 +00:00
function password_confirm_validate ( $element , & $element_state ) {
$pass1 = trim ( $element [ 'pass1' ][ '#value' ]);
2010-05-07 14:48:10 +00:00
$pass2 = trim ( $element [ 'pass2' ][ '#value' ]);
if ( ! empty ( $pass1 ) || ! empty ( $pass2 )) {
2009-05-22 07:39:09 +00:00
if ( strcmp ( $pass1 , $pass2 )) {
2009-07-06 13:31:47 +00:00
form_error ( $element , t ( 'The specified passwords do not match.' ));
2006-01-02 08:35:59 +00:00
}
2006-01-24 10:15:03 +00:00
}
2009-07-06 13:31:47 +00:00
elseif ( $element [ '#required' ] && ! empty ( $element_state [ 'input' ])) {
form_error ( $element , t ( 'Password field is required.' ));
2006-01-02 08:35:59 +00:00
}
2006-04-20 16:35:29 +00:00
2006-04-25 20:44:00 +00:00
// Password field must be converted from a two-element array into a single
// string regardless of validation results.
2009-07-06 13:31:47 +00:00
form_set_value ( $element [ 'pass1' ], NULL , $element_state );
form_set_value ( $element [ 'pass2' ], NULL , $element_state );
form_set_value ( $element , $pass1 , $element_state );
2006-04-25 20:44:00 +00:00
2009-07-06 13:31:47 +00:00
return $element ;
2007-05-28 06:08:47 +00:00
2006-01-02 08:35:59 +00:00
}
2005-10-07 06:11:12 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a date selection form element .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #title, #value, #options, #description, #required,
* #attributes.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_date ( $variables ) {
$element = $variables [ 'element' ];
2011-09-05 19:08:11 +00:00
$attributes = array ();
if ( isset ( $element [ '#id' ])) {
$attributes [ 'id' ] = $element [ '#id' ];
}
if ( ! empty ( $element [ '#attributes' ][ 'class' ])) {
$attributes [ 'class' ] = ( array ) $element [ '#attributes' ][ 'class' ];
}
$attributes [ 'class' ][] = 'container-inline' ;
return '<div' . drupal_attributes ( $attributes ) . '>' . drupal_render_children ( $element ) . '</div>' ;
2005-10-07 06:11:12 +00:00
}
/**
2005-11-21 09:42:14 +00:00
* Roll out a single date element .
2005-10-07 06:11:12 +00:00
*/
2008-07-18 07:06:24 +00:00
function form_process_date ( $element ) {
2005-10-07 06:11:12 +00:00
// Default to current date
2007-05-07 07:29:23 +00:00
if ( empty ( $element [ '#value' ])) {
2010-09-16 20:14:49 +00:00
$element [ '#value' ] = array (
'day' => format_date ( REQUEST_TIME , 'custom' , 'j' ),
'month' => format_date ( REQUEST_TIME , 'custom' , 'n' ),
'year' => format_date ( REQUEST_TIME , 'custom' , 'Y' ),
);
2005-10-07 06:11:12 +00:00
}
2006-01-02 08:04:02 +00:00
$element [ '#tree' ] = TRUE ;
2005-10-07 06:11:12 +00:00
// Determine the order of day, month, year in the site's chosen date format.
2006-09-06 06:53:39 +00:00
$format = variable_get ( 'date_format_short' , 'm/d/Y - H:i' );
2005-10-07 06:11:12 +00:00
$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 );
2007-01-23 19:17:55 +00:00
// Output multi-selector for date.
2005-10-07 06:11:12 +00:00
foreach ( $order as $type ) {
switch ( $type ) {
case 'day' :
$options = drupal_map_assoc ( range ( 1 , 31 ));
2010-10-20 01:31:07 +00:00
$title = t ( 'Day' );
2005-10-07 06:11:12 +00:00
break ;
2010-09-16 20:14:49 +00:00
2005-10-07 06:11:12 +00:00
case 'month' :
2005-11-21 09:42:14 +00:00
$options = drupal_map_assoc ( range ( 1 , 12 ), 'map_month' );
2010-10-20 01:31:07 +00:00
$title = t ( 'Month' );
2005-10-07 06:11:12 +00:00
break ;
2010-09-16 20:14:49 +00:00
2005-10-07 06:11:12 +00:00
case 'year' :
$options = drupal_map_assoc ( range ( 1900 , 2050 ));
2010-10-20 01:31:07 +00:00
$title = t ( 'Year' );
2005-10-07 06:11:12 +00:00
break ;
}
2009-11-22 18:27:11 +00:00
2005-12-14 13:22:19 +00:00
$element [ $type ] = array (
'#type' => 'select' ,
2010-10-20 01:31:07 +00:00
'#title' => $title ,
'#title_display' => 'invisible' ,
2005-12-14 13:22:19 +00:00
'#value' => $element [ '#value' ][ $type ],
'#attributes' => $element [ '#attributes' ],
'#options' => $options ,
);
2005-10-07 06:11:12 +00:00
}
return $element ;
}
2006-05-15 06:27:32 +00:00
/**
* Validates the date type to stop dates like February 30 , 2006.
*/
2011-06-30 06:41:16 +00:00
function date_validate ( $element ) {
if ( ! checkdate ( $element [ '#value' ][ 'month' ], $element [ '#value' ][ 'day' ], $element [ '#value' ][ 'year' ])) {
form_error ( $element , t ( 'The specified date is invalid.' ));
2006-05-15 06:27:32 +00:00
}
}
2005-11-21 09:42:14 +00:00
/**
* Helper function for usage with drupal_map_assoc to display month names .
*/
function map_month ( $month ) {
2009-06-02 13:47:26 +00:00
$months = & drupal_static ( __FUNCTION__ , array (
2008-11-03 09:54:43 +00:00
1 => 'Jan' ,
2 => 'Feb' ,
3 => 'Mar' ,
4 => 'Apr' ,
5 => 'May' ,
6 => 'Jun' ,
7 => 'Jul' ,
8 => 'Aug' ,
9 => 'Sep' ,
10 => 'Oct' ,
11 => 'Nov' ,
12 => 'Dec' ,
2009-06-02 13:47:26 +00:00
));
2008-11-03 09:54:43 +00:00
return t ( $months [ $month ]);
2005-11-21 09:42:14 +00:00
}
2005-10-07 06:11:12 +00:00
2006-01-26 13:39:48 +00:00
/**
* 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 ;
}
}
2005-10-07 06:11:12 +00:00
/**
2006-01-18 19:04:12 +00:00
* Roll out a single radios element to a list of radios ,
* using the options array as index .
2005-10-07 06:11:12 +00:00
*/
2008-07-18 07:06:24 +00:00
function form_process_radios ( $element ) {
2005-10-11 19:44:35 +00:00
if ( count ( $element [ '#options' ]) > 0 ) {
2010-11-07 21:46:09 +00:00
$weight = 0 ;
2005-10-11 19:44:35 +00:00
foreach ( $element [ '#options' ] as $key => $choice ) {
2010-11-07 21:46:09 +00:00
// Maintain order of options as defined in #options, in case the element
// defines custom option sub-elements, but does not define all option
// sub-elements.
$weight += 0.001 ;
$element += array ( $key => array ());
// 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 ,
2010-12-30 22:52:24 +00:00
// The key is sanitized in drupal_attributes() during output from the
// theme function.
'#return_value' => $key ,
2010-11-07 21:46:09 +00:00
'#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 ,
'#weight' => $weight ,
);
2005-10-07 06:11:12 +00:00
}
}
return $element ;
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a checkbox form element .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ 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.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_checkbox ( $variables ) {
$element = $variables [ 'element' ];
2009-11-15 21:36:06 +00:00
$t = get_t ();
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'type' ] = 'checkbox' ;
2010-09-16 20:14:49 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , '#return_value' => 'value' ));
2010-04-11 19:00:27 +00:00
// Unchecked checkbox has #value of integer 0.
2010-10-28 02:20:14 +00:00
if ( ! empty ( $element [ '#checked' ])) {
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'checked' ] = 'checked' ;
2009-11-15 21:36:06 +00:00
}
2010-07-31 04:01:51 +00:00
_form_set_class ( $element , array ( 'form-checkbox' ));
2005-10-11 19:44:35 +00:00
2010-07-31 04:01:51 +00:00
return '<input' . drupal_attributes ( $element [ '#attributes' ]) . ' />' ;
2005-10-07 06:11:12 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a set of checkbox form elements .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #children, #attributes.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_checkboxes ( $variables ) {
$element = $variables [ 'element' ];
2010-03-26 12:16:18 +00:00
$attributes = array ();
2010-09-16 20:14:49 +00:00
if ( isset ( $element [ '#id' ])) {
2010-03-26 12:16:18 +00:00
$attributes [ 'id' ] = $element [ '#id' ];
}
2010-09-16 20:14:49 +00:00
$attributes [ 'class' ][] = 'form-checkboxes' ;
2009-08-22 14:34:23 +00:00
if ( ! empty ( $element [ '#attributes' ][ 'class' ])) {
2010-09-16 20:14:49 +00:00
$attributes [ 'class' ] = array_merge ( $attributes [ 'class' ], $element [ '#attributes' ][ 'class' ]);
2006-12-12 10:01:38 +00:00
}
2010-03-26 12:16:18 +00:00
return '<div' . drupal_attributes ( $attributes ) . '>' . ( ! empty ( $element [ '#children' ]) ? $element [ '#children' ] : '' ) . '</div>' ;
2009-02-03 18:55:32 +00:00
}
/**
2009-08-04 06:38:57 +00:00
* Add form_element theming to an element if title or description is set .
2009-02-03 18:55:32 +00:00
*
* This is used as a pre render function for checkboxes and radios .
*/
function form_pre_render_conditional_form_element ( $element ) {
2009-12-02 15:09:16 +00:00
// 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' ) . ')' ;
}
}
2009-11-16 05:11:01 +00:00
if ( isset ( $element [ '#title' ]) || isset ( $element [ '#description' ])) {
2009-08-04 06:38:57 +00:00
$element [ '#theme_wrappers' ][] = 'form_element' ;
2005-10-07 06:11:12 +00:00
}
2009-02-03 18:55:32 +00:00
return $element ;
2005-10-07 06:11:12 +00:00
}
2010-10-28 02:20:14 +00:00
/**
* Sets the #checked property of a checkbox element.
*/
function form_process_checkbox ( $element , $form_state ) {
$value = $element [ '#value' ];
$return_value = $element [ '#return_value' ];
// On form submission, the #value of an available and enabled checked
// checkbox is #return_value, and the #value of an available and enabled
// unchecked checkbox is integer 0. On not submitted forms, and for
// checkboxes with #access=FALSE or #disabled=TRUE, the #value is
// #default_value (integer 0 if #default_value is NULL). Most of the time,
// a string comparison of #value and #return_value is sufficient for
// determining the "checked" state, but a value of TRUE always means checked
// (even if #return_value is 'foo'), and a value of FALSE or integer 0 always
// means unchecked (even if #return_value is '' or '0').
if ( $value === TRUE || $value === FALSE || $value === 0 ) {
$element [ '#checked' ] = ( bool ) $value ;
}
else {
// Compare as strings, so that 15 is not considered equal to '15foo', but 1
// is considered equal to '1'. This cast does not imply that either #value
// or #return_value is expected to be a string.
$element [ '#checked' ] = (( string ) $value === ( string ) $return_value );
}
return $element ;
}
2008-07-18 07:06:24 +00:00
function form_process_checkboxes ( $element ) {
2005-10-11 19:44:35 +00:00
$value = is_array ( $element [ '#value' ]) ? $element [ '#value' ] : array ();
2005-10-26 01:24:09 +00:00
$element [ '#tree' ] = TRUE ;
2005-10-11 19:44:35 +00:00
if ( count ( $element [ '#options' ]) > 0 ) {
if ( ! isset ( $element [ '#default_value' ]) || $element [ '#default_value' ] == 0 ) {
$element [ '#default_value' ] = array ();
2005-10-07 06:11:12 +00:00
}
2010-11-07 21:46:09 +00:00
$weight = 0 ;
2005-10-11 19:44:35 +00:00
foreach ( $element [ '#options' ] as $key => $choice ) {
2010-11-07 21:46:09 +00:00
// Integer 0 is not a valid #return_value, so use '0' instead.
// @see form_type_checkbox_value().
// @todo For Drupal 8, cast all integer keys to strings for consistency
// with form_process_radios().
if ( $key === 0 ) {
$key = '0' ;
2005-10-07 06:11:12 +00:00
}
2010-11-07 21:46:09 +00:00
// Maintain order of options as defined in #options, in case the element
// defines custom option sub-elements, but does not define all option
// sub-elements.
$weight += 0.001 ;
$element += array ( $key => array ());
$element [ $key ] += array (
'#type' => 'checkbox' ,
'#title' => $choice ,
'#return_value' => $key ,
'#default_value' => isset ( $value [ $key ]) ? $key : NULL ,
'#attributes' => $element [ '#attributes' ],
'#ajax' => isset ( $element [ '#ajax' ]) ? $element [ '#ajax' ] : NULL ,
'#weight' => $weight ,
);
2005-10-07 06:11:12 +00:00
}
}
return $element ;
}
2010-04-24 14:49:14 +00:00
/**
* 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 ;
}
2009-10-16 19:20:34 +00:00
/**
* 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 ) {
2010-01-03 21:01:04 +00:00
// 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' );
}
2009-10-16 19:20:34 +00:00
return $element ;
}
/**
2010-12-08 06:55:01 +00:00
* Returns HTML to wrap child elements in a container .
*
* Used for grouped form items . Can also be used as a #theme_wrapper for any
* renderable element , to surround it with a < div > and add attributes such as
* classes or an HTML id .
2009-10-16 19:20:34 +00:00
*
2010-01-03 21:01:04 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #id, #attributes, #children.
2009-10-16 19:20:34 +00:00
*
* @ ingroup themeable
*/
function theme_container ( $variables ) {
$element = $variables [ 'element' ];
2010-12-08 06:55:01 +00:00
// Special handling for form elements.
if ( isset ( $element [ '#array_parents' ])) {
// Assign an html ID.
if ( ! isset ( $element [ '#attributes' ][ 'id' ])) {
$element [ '#attributes' ][ 'id' ] = $element [ '#id' ];
}
// Add the 'form-wrapper' class.
$element [ '#attributes' ][ 'class' ][] = 'form-wrapper' ;
2009-11-11 08:52:53 +00:00
}
2010-12-08 06:55:01 +00:00
2009-11-11 08:52:53 +00:00
return '<div' . drupal_attributes ( $element [ '#attributes' ]) . '>' . $element [ '#children' ] . '</div>' ;
2009-10-16 19:20:34 +00:00
}
2009-01-28 07:43:26 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a table with radio buttons or checkboxes .
2009-01-28 07:43:26 +00:00
*
2010-04-13 15:23:03 +00:00
* An example of per - row options :
2009-07-07 22:32:17 +00:00
* @ code
* $options = array ();
* $options [ 0 ][ 'title' ] = " A red row "
2009-08-22 14:34:23 +00:00
* $options [ 0 ][ '#attributes' ] = array ( 'class' => array ( 'red-row' ));
2009-07-07 22:32:17 +00:00
* $options [ 1 ][ 'title' ] = " A blue row "
2009-08-22 14:34:23 +00:00
* $options [ 1 ][ '#attributes' ] = array ( 'class' => array ( 'blue-row' ));
2009-07-07 22:32:17 +00:00
*
* $form [ 'myselector' ] = array (
* '#type' => 'tableselect' ,
* '#title' => 'My Selector'
* '#options' => $options ,
* );
2010-01-30 07:54:01 +00:00
* @ endcode
*
2010-04-13 15:23:03 +00:00
* @ 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 () .
2010-01-30 07:54:01 +00:00
*
2009-01-28 07:43:26 +00:00
* @ ingroup themeable
*/
2009-10-09 01:00:08 +00:00
function theme_tableselect ( $variables ) {
$element = $variables [ 'element' ];
2009-01-28 07:43:26 +00:00
$rows = array ();
2009-12-02 14:56:32 +00:00
$header = $element [ '#header' ];
2009-01-28 07:43:26 +00:00
if ( ! empty ( $element [ '#options' ])) {
// Generate a table row for each selectable item in #options.
2010-10-07 17:31:39 +00:00
foreach ( element_children ( $element ) as $key ) {
2009-01-28 07:43:26 +00:00
$row = array ();
2009-07-07 22:32:17 +00:00
$row [ 'data' ] = array ();
2010-10-07 17:31:39 +00:00
if ( isset ( $element [ '#options' ][ $key ][ '#attributes' ])) {
$row += $element [ '#options' ][ $key ][ '#attributes' ];
2009-07-07 22:32:17 +00:00
}
2009-01-28 07:43:26 +00:00
// Render the checkbox / radio element.
2009-07-07 22:32:17 +00:00
$row [ 'data' ][] = drupal_render ( $element [ $key ]);
2009-01-28 07:43:26 +00:00
// 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 ) {
2009-07-07 22:32:17 +00:00
$row [ 'data' ][] = $element [ '#options' ][ $key ][ $fieldname ];
2009-01-28 07:43:26 +00:00
}
$rows [] = $row ;
}
// Add an empty header or a "Select all" checkbox to provide room for the
// checkboxes/radios in the first table column.
2010-05-19 19:22:24 +00:00
if ( $element [ '#js_select' ]) {
2010-10-03 01:04:51 +00:00
// Add a "Select all" checkbox.
2011-10-31 04:05:57 +00:00
drupal_add_js ( 'core/misc/tableselect.js' );
2010-05-19 19:22:24 +00:00
array_unshift ( $header , array ( 'class' => array ( 'select-all' )));
}
2010-08-30 17:07:49 +00:00
else {
2010-10-03 01:04:51 +00:00
// Add an empty header when radio buttons are displayed or a "Select all"
// checkbox is not desired.
2010-08-30 17:07:49 +00:00
array_unshift ( $header , '' );
}
2009-01-28 07:43:26 +00:00
}
2009-12-13 14:18:31 +00:00
return theme ( 'table' , array ( 'header' => $header , 'rows' => $rows , 'empty' => $element [ '#empty' ], 'attributes' => $element [ '#attributes' ]));
2009-01-28 07:43:26 +00:00
}
/**
* 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' ]) {
2010-10-20 01:31:07 +00:00
$title = '' ;
if ( ! empty ( $element [ '#options' ][ $key ][ 'title' ][ 'data' ][ '#title' ])) {
$title = t ( 'Update @title' , array (
'@title' => $element [ '#options' ][ $key ][ 'title' ][ 'data' ][ '#title' ],
));
}
2009-01-28 07:43:26 +00:00
$element [ $key ] = array (
'#type' => 'checkbox' ,
2010-10-20 01:31:07 +00:00
'#title' => $title ,
'#title_display' => 'invisible' ,
2009-01-28 07:43:26 +00:00
'#return_value' => $key ,
2009-11-15 21:36:06 +00:00
'#default_value' => isset ( $value [ $key ]) ? $key : NULL ,
2009-01-28 07:43:26 +00:00
'#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' ],
2009-10-05 01:18:26 +00:00
'#id' => drupal_html_id ( 'edit-' . implode ( '-' , $parents_for_id )),
2010-07-10 00:03:37 +00:00
'#ajax' => isset ( $element [ '#ajax' ]) ? $element [ '#ajax' ] : NULL ,
2009-01-28 07:43:26 +00:00
);
}
2010-10-07 17:31:39 +00:00
if ( isset ( $element [ '#options' ][ $key ][ '#weight' ])) {
$element [ $key ][ '#weight' ] = $element [ '#options' ][ $key ][ '#weight' ];
}
2009-01-28 07:43:26 +00:00
}
}
}
else {
$element [ '#value' ] = array ();
}
return $element ;
}
2010-10-13 13:43:21 +00:00
/**
* Processes a machine - readable name form element .
*
* @ param $element
* The form element to process . Properties used :
* - #machine_name: An associative array containing:
* - exists : A function name to invoke for checking whether a submitted
* machine name value already exists . The submitted value is passed as
* argument . In most cases , an existing API or menu argument loader
* function can be re - used . The callback is only invoked , if the submitted
* value differs from the element ' s #default_value.
* - source : ( optional ) The #array_parents of the form element containing
* the human - readable name ( i . e . , as contained in the $form structure ) to
* use as source for the machine name . Defaults to array ( 'name' ) .
* - label : ( optional ) A text to display as label for the machine name value
* after the human - readable name form element . Defaults to " Machine name " .
* - replace_pattern : ( optional ) A regular expression ( without delimiters )
* matching disallowed characters in the machine name . Defaults to
* '[^a-z0-9_]+' .
* - replace : ( optional ) A character to replace disallowed characters in the
* machine name via JavaScript . Defaults to '_' ( underscore ) . When using a
* different character , 'replace_pattern' needs to be set accordingly .
* - error : ( optional ) A custom form error message string to show , if the
* machine name contains disallowed characters .
* - #maxlength: (optional) Should be set to the maximum allowed length of the
* machine name . Defaults to 64.
* - #disabled: (optional) Should be set to TRUE in case an existing machine
* name must not be changed after initial creation .
*/
function form_process_machine_name ( $element , & $form_state ) {
// Apply default form element properties.
$element += array (
'#title' => t ( 'Machine-readable name' ),
'#description' => t ( 'A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.' ),
'#machine_name' => array (),
);
// A form element that only wants to set one #machine_name property (usually
// 'source' only) would leave all other properties undefined, if the defaults
// were defined in hook_element_info(). Therefore, we apply the defaults here.
$element [ '#machine_name' ] += array (
'source' => array ( 'name' ),
'target' => '#' . $element [ '#id' ],
'label' => t ( 'Machine name' ),
'replace_pattern' => '[^a-z0-9_]+' ,
'replace' => '_' ,
);
2011-12-08 03:29:13 +00:00
// By default, machine names are restricted to Latin alphanumeric characters.
// So, default to LTR directionality.
if ( ! isset ( $element [ '#attributes' ])) {
$element [ '#attributes' ] = array ();
}
$element [ '#attributes' ] += array ( 'dir' => 'ltr' );
2010-10-13 13:43:21 +00:00
// The source element defaults to array('name'), but may have been overidden.
if ( empty ( $element [ '#machine_name' ][ 'source' ])) {
return $element ;
}
// Retrieve the form element containing the human-readable name from the
// complete form in $form_state. By reference, because we need to append
// a #field_suffix that will hold the live preview.
$key_exists = NULL ;
2011-08-01 03:41:25 +00:00
$source = drupal_array_get_nested_value ( $form_state [ 'complete_form' ], $element [ '#machine_name' ][ 'source' ], $key_exists );
2010-10-13 13:43:21 +00:00
if ( ! $key_exists ) {
return $element ;
}
// Append a field suffix to the source form element, which will contain
// the live preview of the machine name.
$suffix_id = $source [ '#id' ] . '-machine-name-suffix' ;
$source += array ( '#field_suffix' => '' );
$source [ '#field_suffix' ] .= ' <small id="' . $suffix_id . '"> </small>' ;
$parents = array_merge ( $element [ '#machine_name' ][ 'source' ], array ( '#field_suffix' ));
2011-08-01 03:41:25 +00:00
drupal_array_set_nested_value ( $form_state [ 'complete_form' ], $parents , $source [ '#field_suffix' ]);
2010-10-13 13:43:21 +00:00
$element [ '#machine_name' ][ 'suffix' ] = '#' . $suffix_id ;
$js_settings = array (
'type' => 'setting' ,
'data' => array (
'machineName' => array (
'#' . $source [ '#id' ] => $element [ '#machine_name' ],
),
),
);
2011-10-31 04:05:57 +00:00
$element [ '#attached' ][ 'js' ][] = 'core/misc/machine-name.js' ;
2010-10-13 13:43:21 +00:00
$element [ '#attached' ][ 'js' ][] = $js_settings ;
return $element ;
}
/**
* Form element validation handler for #type 'machine_name'.
*
* Note that #maxlength is validated by _form_validate() already.
*/
function form_validate_machine_name ( & $element , & $form_state ) {
// Verify that the machine name not only consists of replacement tokens.
if ( preg_match ( '@^' . $element [ '#machine_name' ][ 'replace' ] . '+$@' , $element [ '#value' ])) {
form_error ( $element , t ( 'The machine-readable name must contain unique characters.' ));
}
// Verify that the machine name contains no disallowed characters.
if ( preg_match ( '@' . $element [ '#machine_name' ][ 'replace_pattern' ] . '@' , $element [ '#value' ])) {
if ( ! isset ( $element [ '#machine_name' ][ 'error' ])) {
// Since a hyphen is the most common alternative replacement character,
// a corresponding validation error message is supported here.
if ( $element [ '#machine_name' ][ 'replace' ] == '-' ) {
form_error ( $element , t ( 'The machine-readable name must contain only lowercase letters, numbers, and hyphens.' ));
}
// Otherwise, we assume the default (underscore).
else {
form_error ( $element , t ( 'The machine-readable name must contain only lowercase letters, numbers, and underscores.' ));
}
}
else {
form_error ( $element , $element [ '#machine_name' ][ 'error' ]);
}
}
// Verify that the machine name is unique.
if ( $element [ '#default_value' ] !== $element [ '#value' ]) {
$function = $element [ '#machine_name' ][ 'exists' ];
2011-01-04 16:46:24 +00:00
if ( $function ( $element [ '#value' ], $element , $form_state )) {
2010-10-13 13:43:21 +00:00
form_error ( $element , t ( 'The machine-readable name is already in use. It must be unique.' ));
}
}
}
2009-04-11 22:19:46 +00:00
/**
* Adds fieldsets to the specified group or adds group members to this
* fieldset .
*
2011-05-08 19:50:38 +00:00
* @ param $element
2009-04-11 22:19:46 +00:00
* An associative array containing the properties and children of the
2009-12-14 13:32:53 +00:00
* fieldset . Note that $element must be taken by reference here , so processed
* child elements are taken over into $form_state .
2009-04-11 22:19:46 +00:00
* @ 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' ]);
2009-12-14 13:32:53 +00:00
// Each fieldset forms a new group. The #type 'vertical_tabs' basically only
// injects a new fieldset.
2009-04-11 22:19:46 +00:00
$form_state [ 'groups' ][ $parents ][ '#group_exists' ] = TRUE ;
2009-12-14 13:32:53 +00:00
$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 ;
2009-04-11 22:19:46 +00:00
}
2010-07-07 17:56:42 +00:00
// Contains form element summary functionalities.
2010-11-30 17:16:37 +00:00
$element [ '#attached' ][ 'library' ][] = array ( 'system' , 'drupal.form' );
2010-07-07 17:56:42 +00:00
2010-04-29 03:34:00 +00:00
// The .form-wrapper class is required for #states to treat fieldsets like
// containers.
if ( ! isset ( $element [ '#attributes' ][ 'class' ])) {
$element [ '#attributes' ][ 'class' ] = array ();
}
2009-11-08 13:37:34 +00:00
// Collapsible fieldsets
2009-08-29 16:30:14 +00:00
if ( ! empty ( $element [ '#collapsible' ])) {
2010-11-30 17:16:37 +00:00
$element [ '#attached' ][ 'library' ][] = array ( 'system' , 'drupal.collapse' );
2009-11-08 13:37:34 +00:00
$element [ '#attributes' ][ 'class' ][] = 'collapsible' ;
if ( ! empty ( $element [ '#collapsed' ])) {
$element [ '#attributes' ][ 'class' ][] = 'collapsed' ;
}
2009-08-29 16:30:14 +00:00
}
2009-04-11 22:19:46 +00:00
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 .
2009-12-14 13:32:53 +00:00
*
2009-04-11 22:19:46 +00:00
* @ return
* The modified element with all group members .
*/
function form_pre_render_fieldset ( $element ) {
2010-09-16 20:14:49 +00:00
// Fieldsets may be rendered outside of a Form API context.
if ( ! isset ( $element [ '#parents' ]) || ! isset ( $element [ '#groups' ])) {
return $element ;
}
2009-12-14 13:32:53 +00:00
// 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 ;
2009-04-11 22:19:46 +00:00
}
}
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 ) {
2009-12-14 13:32:53 +00:00
// Inject a new fieldset as child, so that form_process_fieldset() processes
// this fieldset like any other fieldset.
2009-04-11 22:19:46 +00:00
$element [ 'group' ] = array (
'#type' => 'fieldset' ,
2009-08-04 06:38:57 +00:00
'#theme_wrappers' => array (),
2009-04-11 22:19:46 +00:00
'#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' ],
2009-08-22 14:34:23 +00:00
'#attributes' => array ( 'class' => array ( 'vertical-tabs-active-tab' )),
2009-04-11 22:19:46 +00:00
);
return $element ;
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for an element ' s children fieldsets as vertical tabs .
2009-04-11 22:19:46 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
2009-12-14 13:32:53 +00:00
* - element : An associative array containing the properties and children of the
* fieldset . Properties used : #children.
2009-10-09 01:00:08 +00:00
*
2009-08-19 08:15:36 +00:00
* @ ingroup themeable
2009-04-11 22:19:46 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_vertical_tabs ( $variables ) {
$element = $variables [ 'element' ];
2009-04-11 22:19:46 +00:00
// Add required JavaScript and Stylesheet.
2010-11-30 17:16:37 +00:00
drupal_add_library ( 'system' , 'drupal.vertical-tabs' );
2009-04-11 22:19:46 +00:00
2010-03-30 07:05:58 +00:00
$output = '<h2 class="element-invisible">' . t ( 'Vertical Tabs' ) . '</h2>' ;
$output .= '<div class="vertical-tabs-panes">' . $element [ '#children' ] . '</div>' ;
return $output ;
2009-04-11 22:19:46 +00:00
}
2007-12-06 09:58:34 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a submit button form element .
2009-08-19 08:15:36 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #attributes, #button_type, #name, #value.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
*/
2009-10-09 01:00:08 +00:00
function theme_submit ( $variables ) {
2010-09-16 20:14:49 +00:00
return theme ( 'button' , $variables [ 'element' ]);
2005-10-07 06:11:12 +00:00
}
2007-12-06 09:58:34 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a button form element .
2009-08-19 08:15:36 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #attributes, #button_type, #name, #value.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
*/
2009-10-09 01:00:08 +00:00
function theme_button ( $variables ) {
$element = $variables [ 'element' ];
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'type' ] = 'submit' ;
2010-09-16 20:14:49 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , 'value' ));
2009-08-22 14:34:23 +00:00
$element [ '#attributes' ][ 'class' ][] = 'form-' . $element [ '#button_type' ];
2010-07-31 04:01:51 +00:00
if ( ! empty ( $element [ '#attributes' ][ 'disabled' ])) {
$element [ '#attributes' ][ 'class' ][] = 'form-button-disabled' ;
}
2006-03-29 23:29:41 +00:00
2010-07-31 04:01:51 +00:00
return '<input' . drupal_attributes ( $element [ '#attributes' ]) . ' />' ;
2005-10-07 06:11:12 +00:00
}
2007-07-29 17:28:23 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for an image button form element .
2007-12-06 09:58:34 +00:00
*
2009-10-09 01:00:08 +00:00
* @ 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.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2007-07-29 17:28:23 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_image_button ( $variables ) {
$element = $variables [ 'element' ];
2010-08-20 01:08:18 +00:00
$element [ '#attributes' ][ 'type' ] = 'image' ;
2010-09-16 20:14:49 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , 'value' ));
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'src' ] = file_create_url ( $element [ '#src' ]);
if ( ! empty ( $element [ '#title' ])) {
$element [ '#attributes' ][ 'alt' ] = $element [ '#title' ];
$element [ '#attributes' ][ 'title' ] = $element [ '#title' ];
}
2009-08-22 14:34:23 +00:00
$element [ '#attributes' ][ 'class' ][] = 'form-' . $element [ '#button_type' ];
2010-07-31 04:01:51 +00:00
if ( ! empty ( $element [ '#attributes' ][ 'disabled' ])) {
$element [ '#attributes' ][ 'class' ][] = 'form-button-disabled' ;
}
2007-07-29 17:28:23 +00:00
2010-07-31 04:01:51 +00:00
return '<input' . drupal_attributes ( $element [ '#attributes' ]) . ' />' ;
2007-07-29 17:28:23 +00:00
}
2005-10-07 06:11:12 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a hidden form element .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #name, #value, #attributes.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_hidden ( $variables ) {
$element = $variables [ 'element' ];
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'type' ] = 'hidden' ;
2010-09-22 03:34:57 +00:00
element_set_attributes ( $element , array ( 'name' , 'value' ));
2010-07-31 04:01:51 +00:00
return '<input' . drupal_attributes ( $element [ '#attributes' ]) . " /> \n " ;
2005-10-07 06:11:12 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a textfield form element .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #title, #value, #description, #size, #maxlength,
2011-08-06 22:03:31 +00:00
* #placeholder, #required, #attributes, #autocomplete_path.
2009-10-09 01:00:08 +00:00
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_textfield ( $variables ) {
$element = $variables [ 'element' ];
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'type' ] = 'text' ;
2011-08-06 22:03:31 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , 'value' , 'size' , 'maxlength' , 'placeholder' ));
2010-07-31 04:01:51 +00:00
_form_set_class ( $element , array ( 'form-text' ));
2006-08-26 09:56:17 +00:00
2010-07-31 04:01:51 +00:00
$extra = '' ;
2009-12-17 21:59:31 +00:00
if ( $element [ '#autocomplete_path' ] && drupal_valid_path ( $element [ '#autocomplete_path' ])) {
2010-11-30 17:16:37 +00:00
drupal_add_library ( 'system' , 'drupal.autocomplete' );
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'class' ][] = 'form-autocomplete' ;
$attributes = array ();
$attributes [ 'type' ] = 'hidden' ;
2010-09-16 20:14:49 +00:00
$attributes [ 'id' ] = $element [ '#attributes' ][ 'id' ] . '-autocomplete' ;
2010-07-31 04:01:51 +00:00
$attributes [ 'value' ] = url ( $element [ '#autocomplete_path' ], array ( 'absolute' => TRUE ));
2012-01-19 12:55:48 +00:00
$attributes [ 'disabled' ] = 'disabled' ;
$attributes [ 'class' ][] = 'autocomplete' ;
$extra = '<input' . drupal_attributes ( $attributes ) . ' />' ;
}
$output = '<input' . drupal_attributes ( $element [ '#attributes' ]) . ' />' ;
return $output . $extra ;
}
/**
* Returns HTML for a tel 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,
* #placeholder, #required, #attributes, #autocomplete_path.
*
* @ ingroup themeable
*/
function theme_tel ( $variables ) {
$element = $variables [ 'element' ];
$element [ '#attributes' ][ 'type' ] = 'tel' ;
element_set_attributes ( $element , array ( 'id' , 'name' , 'value' , 'size' , 'maxlength' , 'placeholder' ));
_form_set_class ( $element , array ( 'form-tel' ));
$extra = '' ;
if ( $element [ '#autocomplete_path' ] && drupal_valid_path ( $element [ '#autocomplete_path' ])) {
drupal_add_library ( 'system' , 'drupal.autocomplete' );
$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 ));
2010-07-31 04:01:51 +00:00
$attributes [ 'disabled' ] = 'disabled' ;
$attributes [ 'class' ][] = 'autocomplete' ;
$extra = '<input' . drupal_attributes ( $attributes ) . ' />' ;
2005-10-07 06:11:12 +00:00
}
2006-08-26 09:56:17 +00:00
2010-07-31 04:01:51 +00:00
$output = '<input' . drupal_attributes ( $element [ '#attributes' ]) . ' />' ;
2006-08-26 09:56:17 +00:00
2009-02-03 18:55:32 +00:00
return $output . $extra ;
2005-10-07 06:11:12 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a form .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #action, #method, #attributes, #children
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_form ( $variables ) {
$element = $variables [ 'element' ];
2010-09-16 20:14:49 +00:00
if ( isset ( $element [ '#action' ])) {
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'action' ] = drupal_strip_dangerous_protocols ( $element [ '#action' ]);
}
2010-09-16 20:14:49 +00:00
element_set_attributes ( $element , array ( 'method' , 'id' ));
2010-06-25 20:19:13 +00:00
if ( empty ( $element [ '#attributes' ][ 'accept-charset' ])) {
$element [ '#attributes' ][ 'accept-charset' ] = " UTF-8 " ;
}
2010-07-31 04:01:51 +00:00
// Anonymous DIV to satisfy XHTML compliance.
return '<form' . drupal_attributes ( $element [ '#attributes' ]) . '><div>' . $element [ '#children' ] . '</div></form>' ;
2005-10-07 06:11:12 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a textarea form element .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
2011-08-06 22:03:31 +00:00
* Properties used : #title, #value, #description, #rows, #cols,
* #placeholder, #required, #attributes
2009-10-09 01:00:08 +00:00
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_textarea ( $variables ) {
$element = $variables [ 'element' ];
2011-08-06 22:03:31 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , 'rows' , 'cols' , 'placeholder' ));
2010-07-31 04:01:51 +00:00
_form_set_class ( $element , array ( 'form-textarea' ));
2010-03-09 11:45:37 +00:00
$wrapper_attributes = array (
'class' => array ( 'form-textarea-wrapper' ),
);
2007-04-09 13:58:03 +00:00
2010-03-09 11:45:37 +00:00
// Add resizable behavior.
if ( ! empty ( $element [ '#resizable' ])) {
2010-11-30 17:16:37 +00:00
drupal_add_library ( 'system' , 'drupal.textarea' );
2010-03-09 11:45:37 +00:00
$wrapper_attributes [ 'class' ][] = 'resizable' ;
2005-12-29 03:59:30 +00:00
}
2010-03-09 11:45:37 +00:00
$output = '<div' . drupal_attributes ( $wrapper_attributes ) . '>' ;
2010-07-31 04:01:51 +00:00
$output .= '<textarea' . drupal_attributes ( $element [ '#attributes' ]) . '>' . check_plain ( $element [ '#value' ]) . '</textarea>' ;
2010-03-09 11:45:37 +00:00
$output .= '</div>' ;
return $output ;
2005-10-07 06:11:12 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a password form element .
2007-05-04 09:41:37 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #title, #value, #description, #size, #maxlength,
2011-08-06 22:03:31 +00:00
* #placeholder, #required, #attributes.
2009-10-09 01:00:08 +00:00
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2007-05-04 09:41:37 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_password ( $variables ) {
$element = $variables [ 'element' ];
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'type' ] = 'password' ;
2011-08-06 22:03:31 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , 'size' , 'maxlength' , 'placeholder' ));
2006-04-05 18:12:48 +00:00
_form_set_class ( $element , array ( 'form-text' ));
2010-07-31 04:01:51 +00:00
return '<input' . drupal_attributes ( $element [ '#attributes' ]) . ' />' ;
2005-10-07 06:11:12 +00:00
}
/**
2006-05-04 09:57:14 +00:00
* Expand weight elements into selects .
2005-10-07 06:11:12 +00:00
*/
2008-07-18 07:06:24 +00:00
function form_process_weight ( $element ) {
2006-05-04 09:57:14 +00:00
$element [ '#is_weight' ] = TRUE ;
2011-12-02 05:05:37 +00:00
// If the number of options is small enough, use a select field.
$max_elements = variable_get ( 'drupal_weight_select_max' , DRUPAL_WEIGHT_SELECT_MAX );
if ( $element [ '#delta' ] <= $max_elements ) {
$element [ '#type' ] = 'select' ;
for ( $n = ( - 1 * $element [ '#delta' ]); $n <= $element [ '#delta' ]; $n ++ ) {
$weights [ $n ] = $n ;
}
$element [ '#options' ] = $weights ;
$element += element_info ( 'select' );
}
// Otherwise, use a text field.
else {
$element [ '#type' ] = 'textfield' ;
// Use a field big enough to fit most weights.
$element [ '#size' ] = 10 ;
$element [ '#element_validate' ] = array ( 'element_validate_integer' );
$element += element_info ( 'textfield' );
}
2006-05-04 09:57:14 +00:00
return $element ;
2005-10-07 06:11:12 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a file upload form element .
*
* For assistance with handling the uploaded file correctly , see the API
* provided by file . inc .
2005-10-07 06:11:12 +00:00
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
* Properties used : #title, #name, #size, #description, #required,
* #attributes.
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2005-10-07 06:11:12 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_file ( $variables ) {
$element = $variables [ 'element' ];
2010-07-31 04:01:51 +00:00
$element [ '#attributes' ][ 'type' ] = 'file' ;
2010-09-16 20:14:49 +00:00
element_set_attributes ( $element , array ( 'id' , 'name' , 'size' ));
2006-04-05 18:12:48 +00:00
_form_set_class ( $element , array ( 'form-file' ));
2010-07-31 04:01:51 +00:00
return '<input' . drupal_attributes ( $element [ '#attributes' ]) . ' />' ;
2006-05-02 09:26:33 +00:00
}
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a form element .
2006-05-02 09:26:33 +00:00
*
2010-07-31 04:01:51 +00:00
* 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.
2009-12-02 15:09:16 +00:00
*
* 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 .
2010-04-07 04:39:59 +00:00
* - 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 .
2009-12-02 15:09:16 +00:00
* - 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 .
*
2009-10-09 01:00:08 +00:00
* @ param $variables
* An associative array containing :
* - element : An associative array containing the properties of the element .
2009-12-02 15:09:16 +00:00
* Properties used : #title, #title_display, #description, #id, #required,
* #children, #type, #name.
2009-10-09 01:00:08 +00:00
*
2007-12-06 09:58:34 +00:00
* @ ingroup themeable
2006-05-02 09:26:33 +00:00
*/
2009-10-09 01:00:08 +00:00
function theme_form_element ( $variables ) {
2010-09-16 20:14:49 +00:00
$element = & $variables [ 'element' ];
2007-11-11 16:14:45 +00:00
// This is also used in the installer, pre-database setup.
$t = get_t ();
2007-11-13 14:04:08 +00:00
2010-09-16 20:14:49 +00:00
// 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' ,
);
2010-05-01 00:19:35 +00:00
// Add element #id for #type 'item'.
if ( isset ( $element [ '#markup' ]) && ! empty ( $element [ '#id' ])) {
$attributes [ 'id' ] = $element [ '#id' ];
}
2009-06-14 08:04:25 +00:00
// Add element's #type and #name as class to aid with JS/CSS selectors.
2010-05-01 00:19:35 +00:00
$attributes [ 'class' ] = array ( 'form-item' );
2009-06-14 08:04:25 +00:00
if ( ! empty ( $element [ '#type' ])) {
2010-05-01 00:19:35 +00:00
$attributes [ 'class' ][] = 'form-type-' . strtr ( $element [ '#type' ], '_' , '-' );
2009-06-14 08:04:25 +00:00
}
if ( ! empty ( $element [ '#name' ])) {
2010-05-01 00:19:35 +00:00
$attributes [ 'class' ][] = 'form-item-' . strtr ( $element [ '#name' ], array ( ' ' => '-' , '_' => '-' , '[' => '-' , ']' => '' ));
2009-06-14 08:04:25 +00:00
}
2010-07-31 04:01:51 +00:00
// Add a class for disabled elements to facilitate cross-browser styling.
if ( ! empty ( $element [ '#attributes' ][ 'disabled' ])) {
$attributes [ 'class' ][] = 'form-disabled' ;
}
2010-05-01 00:19:35 +00:00
$output = '<div' . drupal_attributes ( $attributes ) . '>' . " \n " ;
2009-06-14 08:04:25 +00:00
2009-12-02 15:09:16 +00:00
// If #title is not set, we don't display any label or required marker.
if ( ! isset ( $element [ '#title' ])) {
$element [ '#title_display' ] = 'none' ;
}
2010-04-28 16:11:22 +00:00
$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>' : '' ;
2010-03-11 22:46:32 +00:00
2009-12-02 15:09:16 +00:00
switch ( $element [ '#title_display' ]) {
case 'before' :
2010-10-01 22:28:55 +00:00
case 'invisible' :
2009-12-02 15:09:16 +00:00
$output .= ' ' . theme ( 'form_element_label' , $variables );
2010-04-28 16:11:22 +00:00
$output .= ' ' . $prefix . $element [ '#children' ] . $suffix . " \n " ;
2009-12-02 15:09:16 +00:00
break ;
case 'after' :
2010-04-28 16:11:22 +00:00
$output .= ' ' . $prefix . $element [ '#children' ] . $suffix ;
2009-12-02 15:09:16 +00:00
$output .= ' ' . theme ( 'form_element_label' , $variables ) . " \n " ;
break ;
2006-05-02 09:26:33 +00:00
2009-12-02 15:09:16 +00:00
case 'none' :
case 'attribute' :
// Output no label and no required marker, only the children.
2010-04-28 16:11:22 +00:00
$output .= ' ' . $prefix . $element [ '#children' ] . $suffix . " \n " ;
2009-12-02 15:09:16 +00:00
break ;
}
2006-05-02 09:26:33 +00:00
if ( ! empty ( $element [ '#description' ])) {
2010-09-16 20:14:49 +00:00
$output .= '<div class="description">' . $element [ '#description' ] . " </div> \n " ;
2006-05-02 09:26:33 +00:00
}
$output .= " </div> \n " ;
return $output ;
2005-10-07 06:11:12 +00:00
}
2006-01-24 10:15:03 +00:00
2009-11-02 03:00:28 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a marker for required form elements .
2009-11-02 03:00:28 +00:00
*
* @ 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.' ),
);
2011-07-05 13:44:58 +00:00
return '<abbr' . drupal_attributes ( $attributes ) . '>*</abbr>' ;
2009-11-02 03:00:28 +00:00
}
2009-12-02 15:09:16 +00:00
/**
2010-04-13 15:23:03 +00:00
* Returns HTML for a form element label and required marker .
2009-12-02 15:09:16 +00:00
*
* 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.
2011-12-12 16:21:23 +00:00
if (( ! isset ( $element [ '#title' ]) || $element [ '#title' ] === '' ) && empty ( $element [ '#required' ])) {
2009-12-02 15:09:16 +00:00
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 ();
2010-04-07 04:39:59 +00:00
// Style the label as class option to display inline with the element.
2009-12-02 15:09:16 +00:00
if ( $element [ '#title_display' ] == 'after' ) {
$attributes [ 'class' ] = 'option' ;
}
2010-04-07 04:39:59 +00:00
// Show label only to screen readers to avoid disruption in visual flows.
elseif ( $element [ '#title_display' ] == 'invisible' ) {
$attributes [ 'class' ] = 'element-invisible' ;
}
2009-12-02 15:09:16 +00:00
if ( ! empty ( $element [ '#id' ])) {
$attributes [ 'for' ] = $element [ '#id' ];
}
// The leading whitespace helps visually separate fields from inline labels.
2010-08-18 00:44:52 +00:00
return ' <label' . drupal_attributes ( $attributes ) . '>' . $t ( '!title !required' , array ( '!title' => $title , '!required' => $required )) . " </label> \n " ;
2009-12-02 15:09:16 +00:00
}
2006-04-05 18:12:48 +00:00
/**
* Sets a form element ' s class attribute .
*
* Adds 'required' and 'error' classes as needed .
*
2011-05-08 19:50:38 +00:00
* @ param $element
2007-01-23 19:17:55 +00:00
* The form element .
2006-04-05 18:12:48 +00:00
* @ param $name
2007-01-23 19:17:55 +00:00
* Array of new class names to be added .
2006-04-05 18:12:48 +00:00
*/
function _form_set_class ( & $element , $class = array ()) {
2009-08-22 14:34:23 +00:00
if ( ! empty ( $class )) {
if ( ! isset ( $element [ '#attributes' ][ 'class' ])) {
$element [ '#attributes' ][ 'class' ] = array ();
}
$element [ '#attributes' ][ 'class' ] = array_merge ( $element [ '#attributes' ][ 'class' ], $class );
}
2010-09-16 20:14:49 +00:00
// 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' ])) {
2009-08-22 14:34:23 +00:00
$element [ '#attributes' ][ 'class' ][] = 'required' ;
2006-04-05 18:12:48 +00:00
}
2010-09-16 20:14:49 +00:00
if ( isset ( $element [ '#parents' ]) && form_get_error ( $element )) {
2009-08-22 14:34:23 +00:00
$element [ '#attributes' ][ 'class' ][] = 'error' ;
2006-04-05 18:12:48 +00:00
}
2005-10-07 06:11:12 +00:00
}
2011-08-01 01:54:41 +00:00
/**
* Helper form element validator : integer .
*/
function element_validate_integer ( $element , & $form_state ) {
$value = $element [ '#value' ];
if ( $value !== '' && ( ! is_numeric ( $value ) || intval ( $value ) != $value )) {
form_error ( $element , t ( '%name must be an integer.' , array ( '%name' => $element [ '#title' ])));
}
}
/**
* Helper form element validator : integer > 0.
*/
function element_validate_integer_positive ( $element , & $form_state ) {
$value = $element [ '#value' ];
if ( $value !== '' && ( ! is_numeric ( $value ) || intval ( $value ) != $value || $value <= 0 )) {
form_error ( $element , t ( '%name must be a positive integer.' , array ( '%name' => $element [ '#title' ])));
}
}
/**
* Helper form element validator : number .
*/
function element_validate_number ( $element , & $form_state ) {
$value = $element [ '#value' ];
if ( $value != '' && ! is_numeric ( $value )) {
form_error ( $element , t ( '%name must be a number.' , array ( '%name' => $element [ '#title' ])));
}
}
2005-10-07 06:11:12 +00:00
/**
2008-01-21 15:17:01 +00:00
* @ } End of " defgroup form_api " .
2005-10-07 06:11:12 +00:00
*/
2007-05-04 09:41:37 +00:00
/**
* @ defgroup batch Batch operations
* @ {
2011-01-03 18:03:54 +00:00
* Create and process batch operations .
*
2007-05-04 09:41:37 +00:00
* 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
2010-06-15 15:52:04 +00:00
* workflow , but can also be used by non - Form API scripts ( like update . php )
2007-05-04 09:41:37 +00:00
* 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' ,
2009-11-07 14:44:04 +00:00
* 'file' => 'path_to_file_containing_myfunctions' ,
2007-05-04 09:41:37 +00:00
* );
* batch_set ( $batch );
2011-07-28 19:29:53 +00:00
* // Only needed if not inside a form _submit handler.
* // Setting redirect in batch_process.
* batch_process ( 'node/1' );
2007-05-04 09:41:37 +00:00
* @ endcode
*
2009-11-07 14:44:04 +00:00
* Note : if the batch 'title' , 'init_message' , 'progress_message' , or
* 'error_message' could contain any user input , it is the responsibility of
2008-10-14 11:01:08 +00:00
* the code calling batch_set () to sanitize them first with a function like
2011-05-01 10:37:37 +00:00
* check_plain () or filter_xss () . Furthermore , if the batch operation
* returns any user input in the 'results' or 'message' keys of $context ,
* it must also sanitize them first .
2008-10-14 11:01:08 +00:00
*
2007-05-04 09:41:37 +00:00
* 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
2007-07-02 14:41:37 +00:00
* // the batch processing, for the current operation to append its own.
2007-05-04 09:41:37 +00:00
* // '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.
2007-05-07 10:15:57 +00:00
* // 1 (or no value explicitly set) means the operation is finished
* // and the batch processing can continue to the next operation.
*
2007-05-04 09:41:37 +00:00
* $node = node_load ( array ( 'uid' => $uid , 'type' => $type ));
2011-05-01 10:37:37 +00:00
* $context [ 'results' ][] = $node -> nid . ' : ' . check_plain ( $node -> title );
* $context [ 'message' ] = check_plain ( $node -> title );
2007-05-04 09:41:37 +00:00
* }
*
2007-07-02 14:41:37 +00:00
* // More advanced example: multi-step operation - load all nodes, five by five
2007-05-04 09:41:37 +00:00
* function my_function_2 ( & $context ) {
* if ( empty ( $context [ 'sandbox' ])) {
* $context [ 'sandbox' ][ 'progress' ] = 0 ;
* $context [ 'sandbox' ][ 'current_node' ] = 0 ;
2009-03-14 16:27:58 +00:00
* $context [ 'sandbox' ][ 'max' ] = db_query ( 'SELECT COUNT(DISTINCT nid) FROM {node}' ) -> fetchField ();
2007-05-04 09:41:37 +00:00
* }
* $limit = 5 ;
2009-03-14 16:27:58 +00:00
* $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 );
2011-05-01 10:37:37 +00:00
* $context [ 'results' ][] = $node -> nid . ' : ' . check_plain ( $node -> title );
2007-05-04 09:41:37 +00:00
* $context [ 'sandbox' ][ 'progress' ] ++ ;
* $context [ 'sandbox' ][ 'current_node' ] = $node -> nid ;
2011-05-01 10:37:37 +00:00
* $context [ 'message' ] = check_plain ( $node -> title );
2007-05-04 09:41:37 +00:00
* }
* 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 ) {
2011-06-01 08:39:51 +00:00
* // The 'success' parameter means no fatal PHP errors were detected. All
* // other error management should be handled using 'results'.
2007-05-04 09:41:37 +00:00
* if ( $success ) {
2007-11-12 19:06:33 +00:00
* $message = format_plural ( count ( $results ), 'One post processed.' , '@count posts processed.' );
2007-05-04 09:41:37 +00:00
* }
* else {
* $message = t ( 'Finished with an error.' );
* }
* drupal_set_message ( $message );
2007-07-02 14:41:37 +00:00
* // Providing data for the redirected page is done through $_SESSION.
2007-05-04 09:41:37 +00:00
* foreach ( $results as $result ) {
* $items [] = t ( 'Loaded node %title.' , array ( '%title' => $result ));
* }
2009-06-02 06:58:17 +00:00
* $_SESSION [ 'my_batch_results' ] = $items ;
2007-05-04 09:41:37 +00:00
* }
* @ endcode
*/
/**
2009-11-07 14:44:04 +00:00
* Opens a new batch .
2007-05-04 09:41:37 +00:00
*
* @ param $batch
2009-11-07 14:44:04 +00:00
* 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 .
2007-05-04 09:41:37 +00:00
*
* Operations are added as new batch sets . Batch sets are used to ensure
2007-07-02 14:41:37 +00:00
* clean code independence , ensuring that several batches submitted by
2007-05-04 09:41:37 +00:00
* different parts of the code ( core / contrib modules ) can be processed
* correctly while not interfering or having to cope with each other . Each
2007-11-26 16:36:44 +00:00
* batch set gets to specify his own UI messages , operates on its own set
* of operations and results , and triggers its own 'finished' callback .
2007-05-04 09:41:37 +00:00
* 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 ();
2010-01-08 06:36:34 +00:00
// Initialize the batch if needed.
2007-05-04 09:41:37 +00:00
if ( empty ( $batch )) {
$batch = array (
'sets' => array (),
2010-01-08 06:36:34 +00:00
'has_form_submits' => FALSE ,
2007-05-04 09:41:37 +00:00
);
}
2010-01-08 06:36:34 +00:00
// Base and default properties for the batch set.
// Use get_t() to allow batches at install time.
$t = get_t ();
2007-05-04 09:41:37 +00:00
$init = array (
'sandbox' => array (),
'results' => array (),
'success' => FALSE ,
2010-01-08 06:36:34 +00:00
'start' => 0 ,
2009-01-12 06:23:57 +00:00
'elapsed' => 0 ,
2007-05-04 09:41:37 +00:00
);
$defaults = array (
'title' => $t ( 'Processing' ),
'init_message' => $t ( 'Initializing.' ),
2008-10-09 22:54:08 +00:00
'progress_message' => $t ( 'Completed @current of @total.' ),
2007-05-04 09:41:37 +00:00
'error_message' => $t ( 'An error has occurred.' ),
2008-06-24 21:51:03 +00:00
'css' => array (),
2007-05-04 09:41:37 +00:00
);
$batch_set = $init + $batch_definition + $defaults ;
2010-01-08 06:36:34 +00:00
// Tweak init_message to avoid the bottom of the page flickering down after
// init phase.
2007-05-04 09:41:37 +00:00
$batch_set [ 'init_message' ] .= '<br/> ' ;
2010-01-08 06:36:34 +00:00
// The non-concurrent workflow of batch execution allows us to save
// numberOfItems() queries by handling our own counter.
2007-05-04 09:41:37 +00:00
$batch_set [ 'total' ] = count ( $batch_set [ 'operations' ]);
2010-01-08 06:36:34 +00:00
$batch_set [ 'count' ] = $batch_set [ 'total' ];
2007-05-04 09:41:37 +00:00
2010-01-08 06:36:34 +00:00
// 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 ;
2007-05-04 09:41:37 +00:00
}
else {
2010-01-08 06:36:34 +00:00
// 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 );
2007-05-04 09:41:37 +00:00
}
}
}
/**
2009-11-07 14:44:04 +00:00
* Processes the batch .
2007-05-06 05:47:52 +00:00
*
2007-07-02 14:41:37 +00:00
* Unless the batch has been marked with 'progressive' = FALSE , the function
2007-10-15 15:18:39 +00:00
* issues a drupal_goto and thus ends page execution .
2007-05-04 09:41:37 +00:00
*
2008-10-15 14:17:27 +00:00
* This function is generally not needed in form submit handlers ;
* Form API takes care of batches that were set during form submission .
2007-05-04 09:41:37 +00:00
*
* @ param $redirect
* ( optional ) Path to redirect to when the batch has finished processing .
* @ param $url
2007-07-02 14:41:37 +00:00
* ( optional - should only be used for separate scripts like update . php )
2007-05-04 09:41:37 +00:00
* URL of the batch processing page .
2009-10-03 20:17:46 +00:00
* @ 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 .
2007-05-04 09:41:37 +00:00
*/
2009-10-03 20:17:46 +00:00
function batch_process ( $redirect = NULL , $url = 'batch' , $redirect_callback = 'drupal_goto' ) {
2007-05-04 09:41:37 +00:00
$batch =& batch_get ();
2009-09-11 04:09:26 +00:00
drupal_theme_initialize ();
2010-01-02 23:30:53 +00:00
2007-05-04 09:41:37 +00:00
if ( isset ( $batch )) {
// Add process information
$process_info = array (
'current_set' => 0 ,
'progressive' => TRUE ,
2009-10-03 20:17:46 +00:00
'url' => $url ,
2009-10-27 04:12:39 +00:00
'url_options' => array (),
2009-11-02 00:42:06 +00:00
'source_url' => $_GET [ 'q' ],
2007-05-04 09:41:37 +00:00
'redirect' => $redirect ,
2009-09-11 04:09:26 +00:00
'theme' => $GLOBALS [ 'theme_key' ],
2009-10-03 20:17:46 +00:00
'redirect_callback' => $redirect_callback ,
2007-05-04 09:41:37 +00:00
);
$batch += $process_info ;
2010-01-08 06:36:34 +00:00
// 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
2010-01-25 10:38:35 +00:00
// environments.
2009-10-03 20:17:46 +00:00
drupal_alter ( 'batch' , $batch );
2010-01-08 06:36:34 +00:00
// 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.
2007-05-04 09:41:37 +00:00
if ( $batch [ 'progressive' ]) {
2010-01-08 06:36:34 +00:00
// 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' )))));
2009-09-21 06:44:14 +00:00
// 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' ]);
2007-05-04 09:41:37 +00:00
}
2007-07-20 05:44:13 +00:00
2010-01-08 06:36:34 +00:00
// Store the batch.
db_insert ( 'batch' )
2008-10-27 10:34:09 +00:00
-> fields ( array (
2010-01-08 06:36:34 +00:00
'bid' => $batch [ 'id' ],
2008-10-27 10:34:09 +00:00
'timestamp' => REQUEST_TIME ,
'token' => drupal_get_token ( $batch [ 'id' ]),
'batch' => serialize ( $batch ),
))
-> execute ();
2007-07-20 05:44:13 +00:00
2009-06-02 06:58:17 +00:00
// Set the batch number in the session to guarantee that it will stay alive.
$_SESSION [ 'batches' ][ $batch [ 'id' ]] = TRUE ;
2010-01-08 06:36:34 +00:00
// Redirect for processing.
2009-10-03 20:17:46 +00:00
$function = $batch [ 'redirect_callback' ];
2011-12-15 17:33:38 +00:00
$function ( $batch [ 'url' ], array ( 'query' => array ( 'op' => 'start' , 'id' => $batch [ 'id' ])));
2007-05-04 09:41:37 +00:00
}
else {
// Non-progressive execution: bypass the whole progressbar workflow
// and execute the batch in one pass.
2011-10-31 04:05:57 +00:00
require_once DRUPAL_ROOT . '/core/includes/batch.inc' ;
2007-05-04 09:41:37 +00:00
_batch_process ();
}
}
}
/**
2009-11-07 14:44:04 +00:00
* Retrieves the current batch .
2007-05-04 09:41:37 +00:00
*/
function & batch_get () {
2009-11-21 14:06:46 +00:00
// 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 ();
2007-05-04 09:41:37 +00:00
return $batch ;
}
2010-01-08 06:36:34 +00:00
/**
* Populates a job queue with the operations of a batch set .
*
* Depending on whether the batch is progressive or not , the BatchQueue or
2010-05-31 09:11:32 +00:00
* BatchMemoryQueue handler classes will be used .
2010-01-08 06:36:34 +00:00
*
* @ 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.
2010-09-26 23:31:36 +00:00
if ( ! isset ( $queues )) {
2010-01-08 06:36:34 +00:00
$queues = array ();
2011-10-31 04:05:57 +00:00
require_once DRUPAL_ROOT . '/core/modules/system/system.queue.inc' ;
require_once DRUPAL_ROOT . '/core/includes/batch.queue.inc' ;
2010-01-08 06:36:34 +00:00
}
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 ];
}
}
2007-05-04 09:41:37 +00:00
/**
* @ } End of " defgroup batch " .
*/