summaryrefslogtreecommitdiff
path: root/includes/form.inc
diff options
context:
space:
mode:
Diffstat (limited to 'includes/form.inc')
-rw-r--r--includes/form.inc192
1 files changed, 141 insertions, 51 deletions
diff --git a/includes/form.inc b/includes/form.inc
index 291933d1f..c99f423c0 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -4,59 +4,138 @@
/**
* @defgroup form Form generation
* @{
- * Functions to enable output of HTML forms and form elements.
+ * Functions to enable the processing and display of HTML forms.
*
- * Drupal uses these functions to achieve consistency in its form presentation,
- * while at the same time simplifying code and reducing the amount of HTML that
- * must be explicitly generated by modules. See the reference at
+ * Drupal uses these functions to achieve consistency in its form processing and
+ * presentation, while simplifying code and reducing the amount of HTML that
+ * must be explicitly generated by modules.
+ *
+ * The drupal_get_form() function handles retrieving, processing, and
+ * displaying a rendered HTML form for modules automatically. For example:
+ *
+ * // display the user registration form
+ * $output = drupal_get_form('user_register');
+ *
+ * Forms can also be built and submitted programmatically without any user input
+ * by populating $form['#post']['edit'] with values to be submitted. For example:
+ *
+ * // register a new user
+ * $form = drupal_retrieve_form('user_register');
+ * $form['#post']['edit']['name'] = 'robo-user';
+ * $form['#post']['edit']['mail'] = 'robouser@example.com';
+ * $form['#post']['edit']['pass'] = 'password';
+ * drupal_process_form('user_register', $form);
+ *
+ * Calling form_get_errors() will list any validation errors that prevented the
+ * form from being submitted.
+ *
+ * For information on the format of the structured arrays used to define forms,
+ * and more detailed explanations of the Form API workflow, see the reference at
* http://api.drupal.org/api/HEAD/file/developer/topics/forms_api_reference.html
* and the quickstart guide at
* http://api.drupal.org/api/HEAD/file/developer/topics/forms_api.html
*/
/**
- * Processes a form array and produces the HTML output of a form.
- * If there is input in the $_POST['edit'] variable, this function
- * will attempt to validate it, using drupal_validate_form(),
- * and then submit the form using drupal_submit_form().
+ * Retrieves a form from a builder function, passes it on for
+ * processing, and renders the form or redirects to its destination
+ * as appropriate.
+ *
+ * @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 building function. Examples
+ * may be found in node_forms(), search_forms(), and user_forms().
+ * @param ...
+ * Any additional arguments needed by the form building function.
+ * @return
+ * The rendered form.
+ */
+function drupal_get_form($form_id) {
+ $args = func_get_args();
+ $form = call_user_func_array('drupal_retrieve_form', $args);
+
+ $redirect = drupal_process_form($form_id, $form);
+
+ if (isset($redirect)) {
+ drupal_redirect_form($form, $redirect);
+ }
+ return drupal_render_form($form_id, $form);
+}
+
+/**
+ * Retrieves the structured array that defines a given form.
+ *
+ * @param $form_id
+ * The unique string identifying the desired form. If a function
+ * with that name exists, it is called to build the form array.
+ * Modules that need to generate the same form (or very similar forms)
+ * using different $form_ids can implement hook_forms(), which maps
+ * different $form_id values to the proper form building function.
+ * @param ...
+ * Any additional arguments needed by the form building function.
+ */
+function drupal_retrieve_form($form_id) {
+ static $forms;
+
+ $args = func_get_args();
+ array_shift($args);
+ if (!function_exists($form_id)) {
+ if (!isset($forms)) {
+ $forms = module_invoke_all('forms');
+ }
+ $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'];
+ }
+ }
+ // $callback comes from a hook_forms() implementation
+ return call_user_func_array(isset($callback) ? $callback : $form_id, $args);
+}
+
+/**
+ * This function is the heart of form API. The form gets built, validated and in
+ * appropriate cases, submitted.
*
* @param $form_id
- * A unique string identifying the form. Allows each form to be
- * themed. Pass NULL to suppress the form_id parameter (produces
- * a shorter URL with method=get)
+ * The unique string identifying the current form.
* @param $form
* An associative array containing the structure of the form.
- * @param $callback
- * An optional callback that will be used in addition to the form_id.
- *
+ * @return
+ * The path to redirect the user to upon completion.
*/
-function drupal_get_form($form_id, &$form, $callback = NULL) {
+function drupal_process_form($form_id, &$form) {
global $form_values, $form_submitted, $user, $form_button_counter;
static $saved_globals = array();
-
- // Save globals in case of indirect recursive call
+ // In some scenerios, this function can be called recursively. Pushing any pre-existing
+ // $form_values and form submission data lets us start fresh without clobbering work done
+ // in earlier recursive calls.
array_push($saved_globals, array($form_values, $form_submitted, $form_button_counter));
$form_values = array();
$form_submitted = FALSE;
$form_button_counter = array(0, 0);
- $form = drupal_build_form($form_id, $form, $callback);
-
- if (!empty($_POST['edit']) && (($_POST['edit']['form_id'] == $form_id) || ($_POST['edit']['form_id'] == $callback))) {
- drupal_validate_form($form_id, $form, $callback);
+ drupal_prepare_form($form_id, $form);
+ if (($form['#programmed']) || (!empty($_POST['edit']) && (($_POST['edit']['form_id'] == $form_id) || ($_POST['edit']['form_id'] == $form['#base'])))) {
+ drupal_validate_form($form_id, $form);
// IE does not send a button value when there is only one submit button (and no non-submit buttons)
// and you submit by pressing enter.
// In that case we accept a submission without button values.
- if (($form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
- $redirect = drupal_submit_form($form_id, $form, $callback);
- drupal_redirect_form($form, $redirect);
+ if ((($form['#programmed']) || $form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
+ $redirect = drupal_submit_form($form_id, $form);
}
}
- $output = drupal_render_form($form_id, $form, $callback);
+ // We've finished calling functions that alter the global values, so we can
+ // restore the ones that were there before this function was called.
list($form_values, $form_submitted, $form_button_counter) = array_pop($saved_globals);
- return $output;
+ return $redirect;
}
/**
@@ -69,12 +148,25 @@ function drupal_get_form($form_id, &$form, $callback = NULL) {
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.
- * @param $callback
- * An optional callback that will be used in addition to the form_id.
- *
*/
-function drupal_build_form($form_id, &$form, $callback = NULL) {
+function drupal_prepare_form($form_id, &$form) {
$form['#type'] = 'form';
+
+ if (!isset($form['#post'])) {
+ $form['#post'] = $_POST;
+ $form['#programmed'] = FALSE;
+ }
+ else {
+ $form['#programmed'] = TRUE;
+ }
+
+ // If $base is set, it is used in place of $form_id when constructing validation,
+ // submission, and theming functions. Useful for mapping many similar or duplicate
+ // forms with different $form_ids to the same processing functions.
+ if (isset($form['#base'])) {
+ $base = $form['#base'];
+ }
+
if (isset($form['#token'])) {
// If the page cache is on and an anonymous user issues a GET request,
// unset the token because the token in the cached page would not match,
@@ -105,8 +197,8 @@ function drupal_build_form($form_id, &$form, $callback = NULL) {
if (function_exists($form_id .'_validate')) {
$form['#validate'] = array($form_id .'_validate' => array());
}
- elseif (function_exists($callback .'_validate')) {
- $form['#validate'] = array($callback .'_validate' => array());
+ elseif (function_exists($base .'_validate')) {
+ $form['#validate'] = array($base .'_validate' => array());
}
}
@@ -116,8 +208,8 @@ function drupal_build_form($form_id, &$form, $callback = NULL) {
// $form_values because it will change later
$form['#submit'] = array($form_id .'_submit' => array());
}
- elseif (function_exists($callback .'_submit')) {
- $form['#submit'] = array($callback .'_submit' => array());
+ elseif (function_exists($base .'_submit')) {
+ $form['#submit'] = array($base .'_submit' => array());
}
}
@@ -127,8 +219,6 @@ function drupal_build_form($form_id, &$form, $callback = NULL) {
}
$form = form_builder($form_id, $form);
-
- return $form;
}
@@ -141,11 +231,9 @@ function drupal_build_form($form_id, &$form, $callback = NULL) {
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.
- * @param $callback
- * An optional callback that will be used in addition to the form_id.
*
*/
-function drupal_validate_form($form_id, $form, $callback = NULL) {
+function drupal_validate_form($form_id, $form) {
global $form_values;
static $validated_forms = array();
@@ -153,7 +241,7 @@ function drupal_validate_form($form_id, $form, $callback = NULL) {
return;
}
- // If the session token was set by drupal_build_form(), ensure that it
+ // If the session token was set by drupal_prepare_form(), ensure that it
// matches the current user's session
if (isset($form['#token'])) {
if ($form_values['form_token'] != md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))) {
@@ -175,14 +263,12 @@ function drupal_validate_form($form_id, $form, $callback = NULL) {
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.
- * @param $callback
- * An optional callback that will be used in addition to the form_id.
* @return
* A string containing the path of the page to display when processing
* is complete.
*
*/
-function drupal_submit_form($form_id, $form, $callback = NULL) {
+function drupal_submit_form($form_id, $form) {
global $form_values;
$default_args = array($form_id, &$form_values);
@@ -209,21 +295,23 @@ function drupal_submit_form($form_id, $form, $callback = NULL) {
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form.
- * @param $callback
- * An optional callback that will be used in addition to the form_id.
* @return
* A string containing the path of the page to display when processing
* is complete.
*
*/
-function drupal_render_form($form_id, &$form, $callback = NULL) {
+function drupal_render_form($form_id, &$form) {
// Don't override #theme if someone already set it.
+ if (isset($form['#base'])) {
+ $base = $form['#base'];
+ }
+
if (!isset($form['#theme'])) {
if (theme_get_function($form_id)) {
$form['#theme'] = $form_id;
}
- elseif (theme_get_function($callback)) {
- $form['#theme'] = $callback;
+ elseif (theme_get_function($base)) {
+ $form['#theme'] = $base;
}
}
@@ -408,8 +496,8 @@ function form_builder($form_id, $form) {
$form['#id'] = 'edit-' . implode('-', $form['#parents']);
}
- $posted = (isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id));
- $edit = $posted ? $_POST['edit'] : array();
+ $posted = (($form['#programmed']) || (isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id)));
+ $edit = $posted ? $form['#post']['edit'] : array();
foreach ($form['#parents'] as $parent) {
$edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
}
@@ -490,6 +578,8 @@ function form_builder($form_id, $form) {
// Recurse through all child elements.
$count = 0;
foreach (element_children($form) as $key) {
+ $form[$key]['#post'] = $form['#post'];
+ $form[$key]['#programmed'] = $form['#programmed'];
// don't squash an existing tree value
if (!isset($form[$key]['#tree'])) {
$form[$key]['#tree'] = $form['#tree'];
@@ -769,7 +859,7 @@ function password_confirm_validate($form) {
form_error($form, t('The specified passwords do not match.'));
}
}
- elseif ($form['#required'] && !empty($_POST['edit'])) {
+ elseif ($form['#required'] && !empty($form['#post']['edit'])) {
form_error($form, t('Password field is required.'));
}