Parent Directory
|
Revision Log
|
Revision Graph
#571086 follow-up by sun: Allow specifying a 'wrapper callback' before executing a form builder function.
| 1 | <?php |
| 2 | // $Id: form.inc,v 1.392 2009/11/04 04:56:54 webchick Exp $ |
| 3 | |
| 4 | /** |
| 5 | * @defgroup forms Form builder functions |
| 6 | * @{ |
| 7 | * Functions that build an abstract representation of a HTML form. |
| 8 | * |
| 9 | * All modules should declare their form builder functions to be in this |
| 10 | * group and each builder function should reference its validate and submit |
| 11 | * functions using \@see. Conversely, validate and submit functions should |
| 12 | * reference the form builder function using \@see. For examples, of this see |
| 13 | * system_modules_uninstall() or user_pass(), the latter of which has the |
| 14 | * following in its doxygen documentation: |
| 15 | * |
| 16 | * \@ingroup forms |
| 17 | * \@see user_pass_validate(). |
| 18 | * \@see user_pass_submit(). |
| 19 | * |
| 20 | * @} End of "defgroup forms". |
| 21 | */ |
| 22 | |
| 23 | /** |
| 24 | * @defgroup form_api Form generation |
| 25 | * @{ |
| 26 | * Functions to enable the processing and display of HTML forms. |
| 27 | * |
| 28 | * Drupal uses these functions to achieve consistency in its form processing and |
| 29 | * presentation, while simplifying code and reducing the amount of HTML that |
| 30 | * must be explicitly generated by modules. |
| 31 | * |
| 32 | * The drupal_get_form() function handles retrieving and processing an HTML |
| 33 | * form for modules automatically. For example: |
| 34 | * |
| 35 | * @code |
| 36 | * // Display the user registration form. |
| 37 | * $output = drupal_get_form('user_register_form'); |
| 38 | * @endcode |
| 39 | * |
| 40 | * Forms can also be built and submitted programmatically without any user input |
| 41 | * using the drupal_form_submit() function. |
| 42 | * |
| 43 | * For information on the format of the structured arrays used to define forms, |
| 44 | * and more detailed explanations of the Form API workflow, see the |
| 45 | * @link http://api.drupal.org/api/file/developer/topics/forms_api_reference.html reference @endlink |
| 46 | * and the @link http://api.drupal.org/api/file/developer/topics/forms_api.html quickstart guide. @endlink |
| 47 | */ |
| 48 | |
| 49 | /** |
| 50 | * Wrapper for drupal_build_form() for use when $form_state is not needed. |
| 51 | * |
| 52 | * @param $form_id |
| 53 | * The unique string identifying the desired form. If a function with that |
| 54 | * name exists, it is called to build the form array. Modules that need to |
| 55 | * generate the same form (or very similar forms) using different $form_ids |
| 56 | * can implement hook_forms(), which maps different $form_id values to the |
| 57 | * proper form constructor function. Examples may be found in node_forms(), |
| 58 | * search_forms(), and user_forms(). |
| 59 | * @param ... |
| 60 | * Any additional arguments are passed on to the functions called by |
| 61 | * drupal_get_form(), including the unique form constructor function. For |
| 62 | * example, the node_edit form requires that a node object is passed in here |
| 63 | * when it is called. |
| 64 | * @return |
| 65 | * The form array. |
| 66 | * |
| 67 | * @see drupal_build_form() |
| 68 | */ |
| 69 | function drupal_get_form($form_id) { |
| 70 | $form_state = array(); |
| 71 | |
| 72 | $args = func_get_args(); |
| 73 | // Remove $form_id from the arguments. |
| 74 | array_shift($args); |
| 75 | $form_state['build_info']['args'] = $args; |
| 76 | |
| 77 | return drupal_build_form($form_id, $form_state); |
| 78 | } |
| 79 | |
| 80 | /** |
| 81 | * Build and process a form based on a form id. |
| 82 | * |
| 83 | * The form may also be retrieved from the cache if the form was built in a |
| 84 | * previous page-load. The form is then passed on for processing, validation |
| 85 | * and submission if there is proper input. |
| 86 | * |
| 87 | * @param $form_id |
| 88 | * The unique string identifying the desired form. If a function with that |
| 89 | * name exists, it is called to build the form array. Modules that need to |
| 90 | * generate the same form (or very similar forms) using different $form_ids |
| 91 | * can implement hook_forms(), which maps different $form_id values to the |
| 92 | * proper form constructor function. Examples may be found in node_forms(), |
| 93 | * search_forms(), and user_forms(). |
| 94 | * @param &$form_state |
| 95 | * An array which stores information about the form. This is passed as a |
| 96 | * reference so that the caller can use it to examine what in the form changed |
| 97 | * when the form submission process is complete. |
| 98 | * The following parameters may be set in $form_state to affect how the form |
| 99 | * is rendered: |
| 100 | * - build_info: A keyed array of build information that is necessary to |
| 101 | * rebuild the form from cache when the original context may no longer be |
| 102 | * available: |
| 103 | * - args: An array of arguments to pass to the form builder. |
| 104 | * - file: An optional include file that contains the form and is |
| 105 | * automatically loaded by form_get_cache(). Defaults to the current menu |
| 106 | * router item's 'file' definition, if existent. |
| 107 | * - input: An array of input that corresponds to $_POST or $_GET, depending |
| 108 | * on the 'method' chosen (see below). |
| 109 | * - method: The HTTP form method to use for finding the input for this form. |
| 110 | * May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method |
| 111 | * forms do not use form ids so are always considered to be submitted, which |
| 112 | * can have unexpected effects. The 'get' method should only be used on |
| 113 | * forms that do not change data, as that is exclusively the domain of post. |
| 114 | * - no_redirect: If set to TRUE the form will NOT perform a drupal_goto(), |
| 115 | * even if a redirect is set. |
| 116 | * - always_process: If TRUE and the method is GET, a form_id is not |
| 117 | * necessary. This should only be used on RESTful GET forms that do NOT |
| 118 | * write data, as this could lead to security issues. It is useful so that |
| 119 | * searches do not need to have a form_id in their query arguments to |
| 120 | * trigger the search. |
| 121 | * - must_validate: Ordinarily, a form is only validated once but there are |
| 122 | * times when a form is resubmitted internally and should be validated |
| 123 | * again. Setting this to TRUE will force that to happen. This is most |
| 124 | * likely to occur during AHAH or AJAX operations. |
| 125 | * - wrapper_callback: Modules that wish to pre-populate certain forms with |
| 126 | * common elements, such as back/next/save buttons in multi-step form |
| 127 | * wizards, may define a form builder function name that returns a form |
| 128 | * structure, which is passed on to the actual form builder function. |
| 129 | * Such implementations may either define the 'wrapper_callback' via |
| 130 | * hook_forms() or have to invoke drupal_build_form() (instead of |
| 131 | * drupal_get_form()) on their own in a custom menu callback to prepare |
| 132 | * $form_state accordingly. |
| 133 | * Further $form_state properties controlling the redirection behavior after |
| 134 | * form submission may be found in drupal_redirect_form(). |
| 135 | * |
| 136 | * @return |
| 137 | * The rendered form or NULL, depending upon the $form_state flags that were set. |
| 138 | * |
| 139 | * @see drupal_redirect_form() |
| 140 | */ |
| 141 | function drupal_build_form($form_id, &$form_state) { |
| 142 | // Ensure some defaults; if already set they will not be overridden. |
| 143 | $form_state += form_state_defaults(); |
| 144 | |
| 145 | if (!isset($form_state['input'])) { |
| 146 | $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST; |
| 147 | } |
| 148 | |
| 149 | $cacheable = FALSE; |
| 150 | |
| 151 | if (isset($_SESSION['batch_form_state'])) { |
| 152 | // We've been redirected here after a batch processing : the form has |
| 153 | // already been processed, so we grab the post-process $form_state value |
| 154 | // and move on to form display. See _batch_finished() function. |
| 155 | $form_state = $_SESSION['batch_form_state']; |
| 156 | unset($_SESSION['batch_form_state']); |
| 157 | } |
| 158 | else { |
| 159 | // If the incoming input contains a form_build_id, we'll check the |
| 160 | // cache for a copy of the form in question. If it's there, we don't |
| 161 | // have to rebuild the form to proceed. In addition, if there is stored |
| 162 | // form_state data from a previous step, we'll retrieve it so it can |
| 163 | // be passed on to the form processing code. |
| 164 | if (isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id'])) { |
| 165 | $form = form_get_cache($form_state['input']['form_build_id'], $form_state); |
| 166 | } |
| 167 | |
| 168 | // If the previous bit of code didn't result in a populated $form |
| 169 | // object, we're hitting the form for the first time and we need |
| 170 | // to build it from scratch. |
| 171 | if (!isset($form)) { |
| 172 | // Record the filepath of the include file containing the original form, |
| 173 | // so the form builder callbacks can be loaded when the form is being |
| 174 | // rebuilt from cache on a different path (such as 'system/ajax'). |
| 175 | // @see form_get_cache() |
| 176 | // menu_get_item() is not available at installation time. |
| 177 | if (!isset($form_state['build_info']['file']) && !defined('MAINTENANCE_MODE')) { |
| 178 | $item = menu_get_item(); |
| 179 | if (!empty($item['file'])) { |
| 180 | $form_state['build_info']['file'] = $item['file']; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | $form = drupal_retrieve_form($form_id, $form_state); |
| 185 | $form_build_id = 'form-' . md5(uniqid(mt_rand(), TRUE)); |
| 186 | $form['#build_id'] = $form_build_id; |
| 187 | |
| 188 | // Fix the form method, if it is 'get' in $form_state, but not in $form. |
| 189 | if ($form_state['method'] == 'get' && !isset($form['#method'])) { |
| 190 | $form['#method'] = 'get'; |
| 191 | } |
| 192 | |
| 193 | drupal_prepare_form($form_id, $form, $form_state); |
| 194 | // Store a copy of the unprocessed form for caching and indicate that it |
| 195 | // is cacheable if #cache will be set. |
| 196 | $original_form = $form; |
| 197 | $cacheable = TRUE; |
| 198 | } |
| 199 | |
| 200 | // Now that we know we have a form, we'll process it (validating, |
| 201 | // submitting, and handling the results returned by its submission |
| 202 | // handlers. Submit handlers accumulate data in the form_state by |
| 203 | // altering the $form_state variable, which is passed into them by |
| 204 | // reference. |
| 205 | drupal_process_form($form_id, $form, $form_state); |
| 206 | |
| 207 | if ($cacheable && !empty($form_state['cache']) && empty($form['#no_cache'])) { |
| 208 | // Caching is done past drupal_process_form so #process callbacks can |
| 209 | // set #cache. |
| 210 | form_set_cache($form_build_id, $original_form, $form_state); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | // Most simple, single-step forms will be finished by this point -- |
| 215 | // drupal_process_form() usually redirects to another page (or to |
| 216 | // a 'fresh' copy of the form) once processing is complete. If one |
| 217 | // of the form's handlers has set $form_state['redirect'] to FALSE, |
| 218 | // the form will simply be re-rendered with the values still in its |
| 219 | // fields. |
| 220 | // |
| 221 | // If $form_state['storage'] or $form_state['rebuild'] has been set |
| 222 | // and the form has been submitted, we know that we're in a complex |
| 223 | // multi-part process of some sort and the form's workflow is NOT |
| 224 | // complete. We need to construct a fresh copy of the form, passing |
| 225 | // in the latest $form_state in addition to any other variables passed |
| 226 | // into drupal_get_form(). |
| 227 | if ((!empty($form_state['storage']) || $form_state['rebuild']) && $form_state['submitted'] && !form_get_errors()) { |
| 228 | $form = drupal_rebuild_form($form_id, $form_state); |
| 229 | } |
| 230 | |
| 231 | // Don't override #theme if someone already set it. |
| 232 | if (!isset($form['#theme'])) { |
| 233 | drupal_theme_initialize(); |
| 234 | $registry = theme_get_registry(); |
| 235 | if (isset($registry[$form_id])) { |
| 236 | $form['#theme'] = $form_id; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | return $form; |
| 241 | } |
| 242 | |
| 243 | /** |
| 244 | * Retrieve default values for the $form_state array. |
| 245 | */ |
| 246 | function form_state_defaults() { |
| 247 | return array( |
| 248 | 'args' => array(), |
| 249 | 'rebuild' => FALSE, |
| 250 | 'redirect' => NULL, |
| 251 | 'build_info' => array(), |
| 252 | 'storage' => NULL, |
| 253 | 'submitted' => FALSE, |
| 254 | 'programmed' => FALSE, |
| 255 | 'cache'=> FALSE, |
| 256 | 'method' => 'post', |
| 257 | 'groups' => array(), |
| 258 | ); |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * Retrieves a form, caches it and processes it with an empty $_POST. |
| 263 | * |
| 264 | * This function clears $_POST and passes the empty $_POST to the form_builder. |
| 265 | * To preserve some parts from $_POST, pass them in $form_state. |
| 266 | * |
| 267 | * If your AHAH callback simulates the pressing of a button, then your AHAH |
| 268 | * callback will need to do the same as what drupal_get_form would do when the |
| 269 | * button is pressed: get the form from the cache, run drupal_process_form over |
| 270 | * it and then if it needs rebuild, run drupal_rebuild_form over it. Then send |
| 271 | * back a part of the returned form. |
| 272 | * $form_state['clicked_button']['#array_parents'] will help you to find which |
| 273 | * part. |
| 274 | * |
| 275 | * @param $form_id |
| 276 | * The unique string identifying the desired form. If a function |
| 277 | * with that name exists, it is called to build the form array. |
| 278 | * Modules that need to generate the same form (or very similar forms) |
| 279 | * using different $form_ids can implement hook_forms(), which maps |
| 280 | * different $form_id values to the proper form constructor function. Examples |
| 281 | * may be found in node_forms(), search_forms(), and user_forms(). |
| 282 | * @param $form_state |
| 283 | * A keyed array containing the current state of the form. Most |
| 284 | * important is the $form_state['storage'] collection. |
| 285 | * @param $form_build_id |
| 286 | * If the AHAH callback calling this function only alters part of the form, |
| 287 | * then pass in the existing form_build_id so we can re-cache with the same |
| 288 | * csid. |
| 289 | * @return |
| 290 | * The newly built form. |
| 291 | */ |
| 292 | function drupal_rebuild_form($form_id, &$form_state, $form_build_id = NULL) { |
| 293 | $form = drupal_retrieve_form($form_id, $form_state); |
| 294 | |
| 295 | if (!isset($form_build_id)) { |
| 296 | // We need a new build_id for the new version of the form. |
| 297 | $form_build_id = 'form-' . md5(mt_rand()); |
| 298 | } |
| 299 | $form['#build_id'] = $form_build_id; |
| 300 | drupal_prepare_form($form_id, $form, $form_state); |
| 301 | |
| 302 | if (empty($form['#no_cache'])) { |
| 303 | // We cache the form structure so it can be retrieved later for validation. |
| 304 | // If $form_state['storage'] is populated, we also cache it so that it can |
| 305 | // be used to resume complex multi-step processes. |
| 306 | form_set_cache($form_build_id, $form, $form_state); |
| 307 | } |
| 308 | |
| 309 | // Clear out all post data, as we don't want the previous step's |
| 310 | // data to pollute this one and trigger validate/submit handling, |
| 311 | // then process the form for rendering. |
| 312 | $form_state['input'] = array(); |
| 313 | |
| 314 | // Also clear out all group associations as these might be different |
| 315 | // when rerendering the form. |
| 316 | $form_state['groups'] = array(); |
| 317 | |
| 318 | // Do not call drupal_process_form(), since it would prevent the rebuilt form |
| 319 | // to submit. |
| 320 | $form = form_builder($form_id, $form, $form_state); |
| 321 | return $form; |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * Fetch a form from cache. |
| 326 | */ |
| 327 | function form_get_cache($form_build_id, &$form_state) { |
| 328 | if ($cached = cache_get('form_' . $form_build_id, 'cache_form')) { |
| 329 | $form = $cached->data; |
| 330 | |
| 331 | global $user; |
| 332 | if ((isset($form['#cache_token']) && drupal_valid_token($form['#cache_token'])) || (!isset($form['#cache_token']) && !$user->uid)) { |
| 333 | if ($cached = cache_get('form_state_' . $form_build_id, 'cache_form')) { |
| 334 | // Re-populate $form_state for subsequent rebuilds. |
| 335 | $form_state['build_info'] = $cached->data['build_info']; |
| 336 | $form_state['storage'] = $cached->data['storage']; |
| 337 | |
| 338 | // If the original form is contained in an include file, load the file. |
| 339 | // @see drupal_build_form() |
| 340 | if (!empty($form_state['build_info']['file']) && file_exists($form_state['build_info']['file'])) { |
| 341 | require_once DRUPAL_ROOT . '/' . $form_state['build_info']['file']; |
| 342 | } |
| 343 | } |
| 344 | return $form; |
| 345 | } |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | /** |
| 350 | * Store a form in the cache. |
| 351 | */ |
| 352 | function form_set_cache($form_build_id, $form, $form_state) { |
| 353 | // 6 hours cache life time for forms should be plenty. |
| 354 | $expire = 21600; |
| 355 | global $user; |
| 356 | if ($user->uid) { |
| 357 | $form['#cache_token'] = drupal_get_token(); |
| 358 | } |
| 359 | cache_set('form_' . $form_build_id, $form, 'cache_form', REQUEST_TIME + $expire); |
| 360 | if (!empty($form_state['build_info']) || !empty($form_state['storage'])) { |
| 361 | $data = array( |
| 362 | 'build_info' => $form_state['build_info'], |
| 363 | 'storage' => $form_state['storage'], |
| 364 | ); |
| 365 | cache_set('form_state_' . $form_build_id, $data, 'cache_form', REQUEST_TIME + $expire); |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | /** |
| 370 | * Retrieves a form using a form_id, populates it with $form_state['values'], |
| 371 | * processes it, and returns any validation errors encountered. This |
| 372 | * function is the programmatic counterpart to drupal_get_form(). |
| 373 | * |
| 374 | * @param $form_id |
| 375 | * The unique string identifying the desired form. If a function |
| 376 | * with that name exists, it is called to build the form array. |
| 377 | * Modules that need to generate the same form (or very similar forms) |
| 378 | * using different $form_ids can implement hook_forms(), which maps |
| 379 | * different $form_id values to the proper form constructor function. Examples |
| 380 | * may be found in node_forms(), search_forms(), and user_forms(). |
| 381 | * @param $form_state |
| 382 | * A keyed array containing the current state of the form. Most |
| 383 | * important is the $form_state['values'] collection, a tree of data |
| 384 | * used to simulate the incoming $_POST information from a user's |
| 385 | * form submission. |
| 386 | * @param ... |
| 387 | * Any additional arguments are passed on to the functions called by |
| 388 | * drupal_form_submit(), including the unique form constructor function. |
| 389 | * For example, the node_edit form requires that a node object be passed |
| 390 | * in here when it is called. |
| 391 | * For example: |
| 392 | * |
| 393 | * @code |
| 394 | * // register a new user |
| 395 | * $form_state = array(); |
| 396 | * $form_state['values']['name'] = 'robo-user'; |
| 397 | * $form_state['values']['mail'] = 'robouser@example.com'; |
| 398 | * $form_state['values']['pass'] = 'password'; |
| 399 | * $form_state['values']['op'] = t('Create new account'); |
| 400 | * drupal_form_submit('user_register_form', $form_state); |
| 401 | * |
| 402 | * // Create a new node |
| 403 | * $form_state = array(); |
| 404 | * module_load_include('inc', 'node', 'node.pages'); |
| 405 | * $node = array('type' => 'story'); |
| 406 | * $form_state['values']['title'] = 'My node'; |
| 407 | * $form_state['values']['body'] = 'This is the body text!'; |
| 408 | * $form_state['values']['name'] = 'robo-user'; |
| 409 | * $form_state['values']['op'] = t('Save'); |
| 410 | * drupal_form_submit('story_node_form', $form_state, (object)$node); |
| 411 | * @endcode |
| 412 | */ |
| 413 | function drupal_form_submit($form_id, &$form_state) { |
| 414 | if (!isset($form_state['build_info']['args'])) { |
| 415 | $args = func_get_args(); |
| 416 | array_shift($args); |
| 417 | array_shift($args); |
| 418 | $form_state['build_info']['args'] = $args; |
| 419 | } |
| 420 | |
| 421 | $form = drupal_retrieve_form($form_id, $form_state); |
| 422 | $form_state['input'] = $form_state['values']; |
| 423 | $form_state['programmed'] = TRUE; |
| 424 | // Programmed forms are always submitted. |
| 425 | $form_state['submitted'] = TRUE; |
| 426 | // Merge in default values. |
| 427 | $form_state += form_state_defaults(); |
| 428 | |
| 429 | drupal_prepare_form($form_id, $form, $form_state); |
| 430 | drupal_process_form($form_id, $form, $form_state); |
| 431 | } |
| 432 | |
| 433 | /** |
| 434 | * Retrieves the structured array that defines a given form. |
| 435 | * |
| 436 | * @param $form_id |
| 437 | * The unique string identifying the desired form. If a function |
| 438 | * with that name exists, it is called to build the form array. |
| 439 | * Modules that need to generate the same form (or very similar forms) |
| 440 | * using different $form_ids can implement hook_forms(), which maps |
| 441 | * different $form_id values to the proper form constructor function. |
| 442 | * @param $form_state |
| 443 | * A keyed array containing the current state of the form. |
| 444 | * @param ... |
| 445 | * Any additional arguments needed by the unique form constructor |
| 446 | * function. Generally, these are any arguments passed into the |
| 447 | * drupal_get_form() or drupal_form_submit() functions after the first |
| 448 | * argument. If a module implements hook_forms(), it can examine |
| 449 | * these additional arguments and conditionally return different |
| 450 | * builder functions as well. |
| 451 | */ |
| 452 | function drupal_retrieve_form($form_id, &$form_state) { |
| 453 | $forms = &drupal_static(__FUNCTION__); |
| 454 | |
| 455 | // We save two copies of the incoming arguments: one for modules to use |
| 456 | // when mapping form ids to constructor functions, and another to pass to |
| 457 | // the constructor function itself. |
| 458 | $args = $form_state['build_info']['args']; |
| 459 | |
| 460 | // We first check to see if there's a function named after the $form_id. |
| 461 | // If there is, we simply pass the arguments on to it to get the form. |
| 462 | if (!function_exists($form_id)) { |
| 463 | // In cases where many form_ids need to share a central constructor function, |
| 464 | // such as the node editing form, modules can implement hook_forms(). It |
| 465 | // maps one or more form_ids to the correct constructor functions. |
| 466 | // |
| 467 | // We cache the results of that hook to save time, but that only works |
| 468 | // for modules that know all their form_ids in advance. (A module that |
| 469 | // adds a small 'rate this comment' form to each comment in a list |
| 470 | // would need a unique form_id for each one, for example.) |
| 471 | // |
| 472 | // So, we call the hook if $forms isn't yet populated, OR if it doesn't |
| 473 | // yet have an entry for the requested form_id. |
| 474 | if (!isset($forms) || !isset($forms[$form_id])) { |
| 475 | $forms = module_invoke_all('forms', $form_id, $args); |
| 476 | } |
| 477 | $form_definition = $forms[$form_id]; |
| 478 | if (isset($form_definition['callback arguments'])) { |
| 479 | $args = array_merge($form_definition['callback arguments'], $args); |
| 480 | } |
| 481 | if (isset($form_definition['callback'])) { |
| 482 | $callback = $form_definition['callback']; |
| 483 | } |
| 484 | // In case $form_state['wrapper_callback'] is not defined already, we also |
| 485 | // allow hook_forms() to define one. |
| 486 | if (!isset($form_state['wrapper_callback']) && isset($form_definition['wrapper_callback'])) { |
| 487 | $form_state['wrapper_callback'] = $form_definition['wrapper_callback']; |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | $form = array(); |
| 492 | // We need to pass $form_state by reference in order for forms to modify it, |
| 493 | // since call_user_func_array() requires that referenced variables are passed |
| 494 | // explicitly. |
| 495 | $args = array_merge(array($form, &$form_state), $args); |
| 496 | |
| 497 | // When the passed $form_state (not using drupal_get_form()) defines a |
| 498 | // 'wrapper_callback', then it requests to invoke a separate (wrapping) form |
| 499 | // builder function to pre-populate the $form array with form elements, which |
| 500 | // the actual form builder function ($callback) expects. This allows for |
| 501 | // pre-populating a form with common elements for certain forms, such as |
| 502 | // back/next/save buttons in multi-step form wizards. |
| 503 | // @see drupal_build_form() |
| 504 | if (isset($form_state['wrapper_callback']) && function_exists($form_state['wrapper_callback'])) { |
| 505 | $form = call_user_func_array($form_state['wrapper_callback'], $args); |
| 506 | // Put the prepopulated $form into $args. |
| 507 | $args[0] = $form; |
| 508 | } |
| 509 | |
| 510 | // If $callback was returned by a hook_forms() implementation, call it. |
| 511 | // Otherwise, call the function named after the form id. |
| 512 | $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args); |
| 513 | $form['#form_id'] = $form_id; |
| 514 | |
| 515 | return $form; |
| 516 | } |
| 517 | |
| 518 | /** |
| 519 | * Processes a form submission. |
| 520 | * |
| 521 | * This function is the heart of form API. The form gets built, validated and in |
| 522 | * appropriate cases, submitted. |
| 523 | * |
| 524 | * @param $form_id |
| 525 | * The unique string identifying the current form. |
| 526 | * @param $form |
| 527 | * An associative array containing the structure of the form. |
| 528 | * @param $form_state |
| 529 | * A keyed array containing the current state of the form. This |
| 530 | * includes the current persistent storage data for the form, and |
| 531 | * any data passed along by earlier steps when displaying a |
| 532 | * multi-step form. Additional information, like the sanitized $_POST |
| 533 | * data, is also accumulated here. |
| 534 | */ |
| 535 | function drupal_process_form($form_id, &$form, &$form_state) { |
| 536 | $form_state['values'] = array(); |
| 537 | |
| 538 | // With $_GET, these forms are always submitted if requested. |
| 539 | if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) { |
| 540 | if (!isset($form_state['input']['form_build_id'])) { |
| 541 | $form_state['input']['form_build_id'] = $form['#build_id']; |
| 542 | } |
| 543 | if (!isset($form_state['input']['form_id'])) { |
| 544 | $form_state['input']['form_id'] = $form_id; |
| 545 | } |
| 546 | if (!isset($form_state['input']['form_token']) && isset($form['#token'])) { |
| 547 | $form_state['input']['form_token'] = drupal_get_token($form['#token']); |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | // Build the form. |
| 552 | $form = form_builder($form_id, $form, $form_state); |
| 553 | |
| 554 | // Only process the input if we have a correct form submission. |
| 555 | if ($form_state['process_input']) { |
| 556 | drupal_validate_form($form_id, $form, $form_state); |
| 557 | |
| 558 | // drupal_html_id() maintains a cache of element IDs it has seen, |
| 559 | // so it can prevent duplicates. We want to be sure we reset that |
| 560 | // cache when a form is processed, so scenarios that result in |
| 561 | // the form being built behind the scenes and again for the |
| 562 | // browser don't increment all the element IDs needlessly. |
| 563 | drupal_static_reset('drupal_html_id'); |
| 564 | |
| 565 | if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) { |
| 566 | // Execute form submit handlers. |
| 567 | form_execute_handlers('submit', $form, $form_state); |
| 568 | |
| 569 | // We'll clear out the cached copies of the form and its stored data |
| 570 | // here, as we've finished with them. The in-memory copies are still |
| 571 | // here, though. |
| 572 | if (variable_get('cache', CACHE_DISABLED) == CACHE_DISABLED && !empty($form_state['values']['form_build_id'])) { |
| 573 | cache_clear_all('form_' . $form_state['values']['form_build_id'], 'cache_form'); |
| 574 | cache_clear_all('storage_' . $form_state['values']['form_build_id'], 'cache_form'); |
| 575 | } |
| 576 | |
| 577 | // If batches were set in the submit handlers, we process them now, |
| 578 | // possibly ending execution. We make sure we do not react to the batch |
| 579 | // that is already being processed (if a batch operation performs a |
| 580 | // drupal_form_submit). |
| 581 | if ($batch =& batch_get() && !isset($batch['current_set'])) { |
| 582 | // The batch uses its own copies of $form and $form_state for |
| 583 | // late execution of submit handlers and post-batch redirection. |
| 584 | $batch['form'] = $form; |
| 585 | $batch['form_state'] = $form_state; |
| 586 | $batch['progressive'] = !$form_state['programmed']; |
| 587 | batch_process(); |
| 588 | // Execution continues only for programmatic forms. |
| 589 | // For 'regular' forms, we get redirected to the batch processing |
| 590 | // page. Form redirection will be handled in _batch_finished(), |
| 591 | // after the batch is processed. |
| 592 | } |
| 593 | |
| 594 | // Set a flag to indicate the the form has been processed and executed. |
| 595 | $form_state['executed'] = TRUE; |
| 596 | |
| 597 | // Redirect the form based on values in $form_state. |
| 598 | drupal_redirect_form($form_state); |
| 599 | } |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | /** |
| 604 | * Prepares a structured form array by adding required elements, |
| 605 | * executing any hook_form_alter functions, and optionally inserting |
| 606 | * a validation token to prevent tampering. |
| 607 | * |
| 608 | * @param $form_id |
| 609 | * A unique string identifying the form for validation, submission, |
| 610 | * theming, and hook_form_alter functions. |
| 611 | * @param $form |
| 612 | * An associative array containing the structure of the form. |
| 613 | * @param $form_state |
| 614 | * A keyed array containing the current state of the form. Passed |
| 615 | * in here so that hook_form_alter() calls can use it, as well. |
| 616 | */ |
| 617 | function drupal_prepare_form($form_id, &$form, &$form_state) { |
| 618 | global $user; |
| 619 | |
| 620 | $form['#type'] = 'form'; |
| 621 | $form_state['programmed'] = isset($form_state['programmed']) ? $form_state['programmed'] : FALSE; |
| 622 | |
| 623 | if (isset($form['#build_id'])) { |
| 624 | $form['form_build_id'] = array( |
| 625 | '#type' => 'hidden', |
| 626 | '#value' => $form['#build_id'], |
| 627 | '#id' => $form['#build_id'], |
| 628 | '#name' => 'form_build_id', |
| 629 | ); |
| 630 | } |
| 631 | |
| 632 | // Add a token, based on either #token or form_id, to any form displayed to |
| 633 | // authenticated users. This ensures that any submitted form was actually |
| 634 | // requested previously by the user and protects against cross site request |
| 635 | // forgeries. |
| 636 | if (isset($form['#token'])) { |
| 637 | if ($form['#token'] === FALSE || $user->uid == 0 || $form_state['programmed']) { |
| 638 | unset($form['#token']); |
| 639 | } |
| 640 | else { |
| 641 | $form['form_token'] = array('#type' => 'token', '#default_value' => drupal_get_token($form['#token'])); |
| 642 | } |
| 643 | } |
| 644 | elseif (isset($user->uid) && $user->uid && !$form_state['programmed']) { |
| 645 | $form['#token'] = $form_id; |
| 646 | $form['form_token'] = array( |
| 647 | '#id' => drupal_html_id('edit-' . $form_id . '-form-token'), |
| 648 | '#type' => 'token', |
| 649 | '#default_value' => drupal_get_token($form['#token']), |
| 650 | ); |
| 651 | } |
| 652 | |
| 653 | if (isset($form_id)) { |
| 654 | $form['form_id'] = array( |
| 655 | '#type' => 'hidden', |
| 656 | '#value' => $form_id, |
| 657 | '#id' => drupal_html_id("edit-$form_id"), |
| 658 | ); |
| 659 | } |
| 660 | if (!isset($form['#id'])) { |
| 661 | $form['#id'] = drupal_html_id($form_id); |
| 662 | } |
| 663 | |
| 664 | $form += element_info('form'); |
| 665 | $form += array('#tree' => FALSE, '#parents' => array()); |
| 666 | |
| 667 | if (!isset($form['#validate'])) { |
| 668 | if (function_exists($form_id . '_validate')) { |
| 669 | $form['#validate'] = array($form_id . '_validate'); |
| 670 | } |
| 671 | } |
| 672 | |
| 673 | if (!isset($form['#submit'])) { |
| 674 | if (function_exists($form_id . '_submit')) { |
| 675 | // We set submit here so that it can be altered. |
| 676 | $form['#submit'] = array($form_id . '_submit'); |
| 677 | } |
| 678 | } |
| 679 | |
| 680 | // Invoke hook_form_FORM_ID_alter() implementations. |
| 681 | drupal_alter('form_' . $form_id, $form, $form_state); |
| 682 | |
| 683 | // Invoke hook_form_alter() implementations. |
| 684 | drupal_alter('form', $form, $form_state, $form_id); |
| 685 | } |
| 686 | |
| 687 | |
| 688 | /** |
| 689 | * Validates user-submitted form data from the $form_state using |
| 690 | * the validate functions defined in a structured form array. |
| 691 | * |
| 692 | * @param $form_id |
| 693 | * A unique string identifying the form for validation, submission, |
| 694 | * theming, and hook_form_alter functions. |
| 695 | * @param $form |
| 696 | * An associative array containing the structure of the form. |
| 697 | * @param $form_state |
| 698 | * A keyed array containing the current state of the form. The current |
| 699 | * user-submitted data is stored in $form_state['values'], though |
| 700 | * form validation functions are passed an explicit copy of the |
| 701 | * values for the sake of simplicity. Validation handlers can also |
| 702 | * $form_state to pass information on to submit handlers. For example: |
| 703 | * $form_state['data_for_submision'] = $data; |
| 704 | * This technique is useful when validation requires file parsing, |
| 705 | * web service requests, or other expensive requests that should |
| 706 | * not be repeated in the submission step. |
| 707 | */ |
| 708 | function drupal_validate_form($form_id, $form, &$form_state) { |
| 709 | $validated_forms = &drupal_static(__FUNCTION__, array()); |
| 710 | |
| 711 | if (isset($validated_forms[$form_id]) && empty($form_state['must_validate'])) { |
| 712 | return; |
| 713 | } |
| 714 | |
| 715 | // If the session token was set by drupal_prepare_form(), ensure that it |
| 716 | // matches the current user's session. |
| 717 | if (isset($form['#token'])) { |
| 718 | if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) { |
| 719 | // Setting this error will cause the form to fail validation. |
| 720 | form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); |
| 721 | } |
| 722 | } |
| 723 | |
| 724 | _form_validate($form, $form_state, $form_id); |
| 725 | $validated_forms[$form_id] = TRUE; |
| 726 | } |
| 727 | |
| 728 | /** |
| 729 | * Redirects the user to a URL after a form has been processed. |
| 730 | * |
| 731 | * After a form was executed, the data in $form_state controls whether the form |
| 732 | * is redirected. By default, we redirect to a new destination page. The path of |
| 733 | * the destination page can be set in $form_state['redirect']. If that is not |
| 734 | * set, the user is redirected to the current page to display a fresh, |
| 735 | * unpopulated copy of the form. |
| 736 | * |
| 737 | * There are several triggers that may prevent a redirection though: |
| 738 | * - If $form_state['redirect'] is FALSE, a form builder function or form |
| 739 | * validation/submit handler does not want a user to be redirected, which |
| 740 | * means that drupal_goto() is not invoked. For most forms, the redirection |
| 741 | * logic will be the same regardless of whether $form_state['redirect'] is |
| 742 | * undefined or FALSE. However, in case it was not defined and the current |
| 743 | * request contains a 'destination' query string, drupal_goto() will redirect |
| 744 | * to that given destination instead. Only setting $form_state['redirect'] to |
| 745 | * FALSE will prevent any redirection. |
| 746 | * - If $form_state['no_redirect'] is TRUE, then the callback that originally |
| 747 | * built the form explicitly disallows any redirection, regardless of the |
| 748 | * redirection value in $form_state['redirect']. For example, ajax_get_form() |
| 749 | * defines $form_state['no_redirect'] when building a form in an AJAX |
| 750 | * callback to prevent any redirection. $form_state['no_redirect'] should NOT |
| 751 | * be altered by form builder functions or form validation/submit handlers. |
| 752 | * - If $form_state['programmed'] is TRUE, the form submission was usually |
| 753 | * invoked via drupal_form_submit(), so any redirection would break the script |
| 754 | * that invoked drupal_form_submit(). |
| 755 | * - If $form_state['rebuild'] is TRUE or $form_state['storage'] is populated, |
| 756 | * the form is most probably a multi-step form and needs to be rebuilt without |
| 757 | * redirection. |
| 758 | * |
| 759 | * @param $form_state |
| 760 | * A keyed array containing the current state of the form. |
| 761 | * |
| 762 | * @see drupal_process_form() |
| 763 | * @see drupal_build_form() |
| 764 | */ |
| 765 | function drupal_redirect_form($form_state) { |
| 766 | // Skip redirection for form submissions invoked via drupal_form_submit(). |
| 767 | if (!empty($form_state['programmed'])) { |
| 768 | return; |
| 769 | } |
| 770 | // Skip redirection for multi-step forms. |
| 771 | if (!empty($form_state['rebuild']) || !empty($form_state['storage'])) { |
| 772 | return; |
| 773 | } |
| 774 | // Skip redirection if it was explicitly disallowed. |
| 775 | if (!empty($form_state['no_redirect'])) { |
| 776 | return; |
| 777 | } |
| 778 | // Only invoke drupal_goto() if redirect value was not set to FALSE. |
| 779 | if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) { |
| 780 | if (isset($form_state['redirect'])) { |
| 781 | if (is_array($form_state['redirect'])) { |
| 782 | call_user_func_array('drupal_goto', $form_state['redirect']); |
| 783 | } |
| 784 | else { |
| 785 | // This function can be called from the installer, which guarantees |
| 786 | // that $redirect will always be a string, so catch that case here |
| 787 | // and use the appropriate redirect function. |
| 788 | $function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto'; |
| 789 | $function($form_state['redirect']); |
| 790 | } |
| 791 | } |
| 792 | drupal_goto($_GET['q']); |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | /** |
| 797 | * Performs validation on form elements. First ensures required fields are |
| 798 | * completed, #maxlength is not exceeded, and selected options were in the |
| 799 | * list of options given to the user. Then calls user-defined validators. |
| 800 | * |
| 801 | * @param $elements |
| 802 | * An associative array containing the structure of the form. |
| 803 | * @param $form_state |
| 804 | * A keyed array containing the current state of the form. The current |
| 805 | * user-submitted data is stored in $form_state['values'], though |
| 806 | * form validation functions are passed an explicit copy of the |
| 807 | * values for the sake of simplicity. Validation handlers can also |
| 808 | * $form_state to pass information on to submit handlers. For example: |
| 809 | * $form_state['data_for_submision'] = $data; |
| 810 | * This technique is useful when validation requires file parsing, |
| 811 | * web service requests, or other expensive requests that should |
| 812 | * not be repeated in the submission step. |
| 813 | * @param $form_id |
| 814 | * A unique string identifying the form for validation, submission, |
| 815 | * theming, and hook_form_alter functions. |
| 816 | */ |
| 817 | function _form_validate($elements, &$form_state, $form_id = NULL) { |
| 818 | // Also used in the installer, pre-database setup. |
| 819 | $t = get_t(); |
| 820 | |
| 821 | // Recurse through all children. |
| 822 | foreach (element_children($elements) as $key) { |
| 823 | if (isset($elements[$key]) && $elements[$key]) { |
| 824 | _form_validate($elements[$key], $form_state); |
| 825 | } |
| 826 | } |
| 827 | // Validate the current input. |
| 828 | if (!isset($elements['#validated']) || !$elements['#validated']) { |
| 829 | if (isset($elements['#needs_validation'])) { |
| 830 | // Make sure a value is passed when the field is required. |
| 831 | // A simple call to empty() will not cut it here as some fields, like |
| 832 | // checkboxes, can return a valid value of '0'. Instead, check the |
| 833 | // length if it's a string, and the item count if it's an array. |
| 834 | if ($elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0))) { |
| 835 | form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); |
| 836 | } |
| 837 | |
| 838 | // Verify that the value is not longer than #maxlength. |
| 839 | if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) { |
| 840 | 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'])))); |
| 841 | } |
| 842 | |
| 843 | if (isset($elements['#options']) && isset($elements['#value'])) { |
| 844 | if ($elements['#type'] == 'select') { |
| 845 | $options = form_options_flatten($elements['#options']); |
| 846 | } |
| 847 | else { |
| 848 | $options = $elements['#options']; |
| 849 | } |
| 850 | if (is_array($elements['#value'])) { |
| 851 | $value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value']; |
| 852 | foreach ($value as $v) { |
| 853 | if (!isset($options[$v])) { |
| 854 | form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.')); |
| 855 | watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR); |
| 856 | } |
| 857 | } |
| 858 | } |
| 859 | elseif (!isset($options[$elements['#value']])) { |
| 860 | form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.')); |
| 861 | watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR); |
| 862 | } |
| 863 | } |
| 864 | } |
| 865 | |
| 866 | // Call user-defined form level validators. |
| 867 | if (isset($form_id)) { |
| 868 | form_execute_handlers('validate', $elements, $form_state); |
| 869 | } |
| 870 | // Call any element-specific validators. These must act on the element |
| 871 | // #value data. |
| 872 | elseif (isset($elements['#element_validate'])) { |
| 873 | foreach ($elements['#element_validate'] as $function) { |
| 874 | if (function_exists($function)) { |
| 875 | $function($elements, $form_state, $form_state['complete form']); |
| 876 | } |
| 877 | } |
| 878 | } |
| 879 | $elements['#validated'] = TRUE; |
| 880 | } |
| 881 | } |
| 882 | |
| 883 | /** |
| 884 | * A helper function used to execute custom validation and submission |
| 885 | * handlers for a given form. Button-specific handlers are checked |
| 886 | * first. If none exist, the function falls back to form-level handlers. |
| 887 | * |
| 888 | * @param $type |
| 889 | * The type of handler to execute. 'validate' or 'submit' are the |
| 890 | * defaults used by Form API. |
| 891 | * @param $form |
| 892 | * An associative array containing the structure of the form. |
| 893 | * @param $form_state |
| 894 | * A keyed array containing the current state of the form. If the user |
| 895 | * submitted the form by clicking a button with custom handler functions |
| 896 | * defined, those handlers will be stored here. |
| 897 | */ |
| 898 | function form_execute_handlers($type, &$form, &$form_state) { |
| 899 | $return = FALSE; |
| 900 | if (isset($form_state[$type . '_handlers'])) { |
| 901 | $handlers = $form_state[$type . '_handlers']; |
| 902 | } |
| 903 | elseif (isset($form['#' . $type])) { |
| 904 | $handlers = $form['#' . $type]; |
| 905 | } |
| 906 | else { |
| 907 | $handlers = array(); |
| 908 | } |
| 909 | |
| 910 | foreach ($handlers as $function) { |
| 911 | if (function_exists($function)) { |
| 912 | // Check to see if a previous _submit handler has set a batch, but |
| 913 | // make sure we do not react to a batch that is already being processed |
| 914 | // (for instance if a batch operation performs a drupal_form_submit()). |
| 915 | if ($type == 'submit' && ($batch =& batch_get()) && !isset($batch['current_set'])) { |
| 916 | // Some previous _submit handler has set a batch. We store the call |
| 917 | // in a special 'control' batch set, for execution at the correct |
| 918 | // time during the batch processing workflow. |
| 919 | $batch['sets'][] = array('form_submit' => $function); |
| 920 | } |
| 921 | else { |
| 922 | $function($form, $form_state); |
| 923 | } |
| 924 | $return = TRUE; |
| 925 | } |
| 926 | } |
| 927 | return $return; |
| 928 | } |
| 929 | |
| 930 | /** |
| 931 | * File an error against a form element. |
| 932 | * |
| 933 | * @param $name |
| 934 | * The name of the form element. If the #parents property of your form |
| 935 | * element is array('foo', 'bar', 'baz') then you may set an error on 'foo' |
| 936 | * or 'foo][bar][baz'. Setting an error on 'foo' sets an error for every |
| 937 | * element where the #parents array starts with 'foo'. |
| 938 | * @param $message |
| 939 | * The error message to present to the user. |
| 940 | * @param $reset |
| 941 | * Reset the form errors static cache. |
| 942 | * @return |
| 943 | * Never use the return value of this function, use form_get_errors and |
| 944 | * form_get_error instead. |
| 945 | */ |
| 946 | function form_set_error($name = NULL, $message = '') { |
| 947 | $form = &drupal_static(__FUNCTION__, array()); |
| 948 | if (isset($name) && !isset($form[$name])) { |
| 949 | $form[$name] = $message; |
| 950 | if ($message) { |
| 951 | drupal_set_message($message, 'error'); |
| 952 | } |
| 953 | } |
| 954 | return $form; |
| 955 | } |
| 956 | |
| 957 | /** |
| 958 | * Clear all errors against all form elements made by form_set_error(). |
| 959 | */ |
| 960 | function form_clear_error() { |
| 961 | drupal_static_reset('form_set_error'); |
| 962 | } |
| 963 | |
| 964 | /** |
| 965 | * Return an associative array of all errors. |
| 966 | */ |
| 967 | function form_get_errors() { |
| 968 | $form = form_set_error(); |
| 969 | if (!empty($form)) { |
| 970 | return $form; |
| 971 | } |
| 972 | } |
| 973 | |
| 974 | /** |
| 975 | * Return the error message filed against the form with the specified name. |
| 976 | */ |
| 977 | function form_get_error($element) { |
| 978 | $form = form_set_error(); |
| 979 | $key = $element['#parents'][0]; |
| 980 | if (isset($form[$key])) { |
| 981 | return $form[$key]; |
| 982 | } |
| 983 | $key = implode('][', $element['#parents']); |
| 984 | if (isset($form[$key])) { |
| 985 | return $form[$key]; |
| 986 | } |
| 987 | } |
| 988 | |
| 989 | /** |
| 990 | * Flag an element as having an error. |
| 991 | */ |
| 992 | function form_error(&$element, $message = '') { |
| 993 | form_set_error(implode('][', $element['#parents']), $message); |
| 994 | } |
| 995 | |
| 996 | /** |
| 997 | * Walk through the structured form array, adding any required |
| 998 | * properties to each element and mapping the incoming input |
| 999 | * data to the proper elements. Also, execute any #process handlers |
| 1000 | * attached to a specific element. |
| 1001 | * |
| 1002 | * @param $form_id |
| 1003 | * A unique string identifying the form for validation, submission, |
| 1004 | * theming, and hook_form_alter functions. |
| 1005 | * @param $element |
| 1006 | * An associative array containing the structure of the current element. |
| 1007 | * @param $form_state |
| 1008 | * A keyed array containing the current state of the form. In this |
| 1009 | * context, it is used to accumulate information about which button |
| 1010 | * was clicked when the form was submitted, as well as the sanitized |
| 1011 | * $_POST data. |
| 1012 | */ |
| 1013 | function form_builder($form_id, $element, &$form_state) { |
| 1014 | // Initialize as unprocessed. |
| 1015 | $element['#processed'] = FALSE; |
| 1016 | |
| 1017 | // Use element defaults. |
| 1018 | if ((!empty($element['#type'])) && ($info = element_info($element['#type']))) { |
| 1019 | // Overlay $info onto $element, retaining preexisting keys in $element. |
| 1020 | $element += $info; |
| 1021 | $element['#defaults_loaded'] = TRUE; |
| 1022 | } |
| 1023 | |
| 1024 | // Special handling if we're on the top level form element. |
| 1025 | if (isset($element['#type']) && $element['#type'] == 'form') { |
| 1026 | if (!empty($element['#https']) && variable_get('https', FALSE) && |
| 1027 | !url_is_external($element['#action'])) { |
| 1028 | global $base_root; |
| 1029 | |
| 1030 | // Not an external URL so ensure that it is secure. |
| 1031 | $element['#action'] = str_replace('http://', 'https://', $base_root) . $element['#action']; |
| 1032 | } |
| 1033 | |
| 1034 | // Store a complete copy of the form in form_state prior to building the form. |
| 1035 | $form_state['complete form'] = $element; |
| 1036 | // Set a flag if we have a correct form submission. This is always TRUE for |
| 1037 | // programmed forms coming from drupal_form_submit(), or if the form_id coming |
| 1038 | // from the POST data is set and matches the current form_id. |
| 1039 | if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) { |
| 1040 | $form_state['process_input'] = TRUE; |
| 1041 | } |
| 1042 | else { |
| 1043 | $form_state['process_input'] = FALSE; |
| 1044 | } |
| 1045 | } |
| 1046 | |
| 1047 | if (!isset($element['#id'])) { |
| 1048 | $element['#id'] = drupal_html_id('edit-' . implode('-', $element['#parents'])); |
| 1049 | } |
| 1050 | // Handle input elements. |
| 1051 | if (!empty($element['#input'])) { |
| 1052 | _form_builder_handle_input_element($form_id, $element, $form_state); |
| 1053 | } |
| 1054 | // Allow for elements to expand to multiple elements, e.g., radios, |
| 1055 | // checkboxes and files. |
| 1056 | if (isset($element['#process']) && !$element['#processed']) { |
| 1057 | foreach ($element['#process'] as $process) { |
| 1058 | if (function_exists($process)) { |
| 1059 | $element = $process($element, $form_state, $form_state['complete form']); |
| 1060 | } |
| 1061 | } |
| 1062 | $element['#processed'] = TRUE; |
| 1063 | } |
| 1064 | |
| 1065 | // We start off assuming all form elements are in the correct order. |
| 1066 | $element['#sorted'] = TRUE; |
| 1067 | |
| 1068 | // Recurse through all child elements. |
| 1069 | $count = 0; |
| 1070 | foreach (element_children($element) as $key) { |
| 1071 | // Don't squash an existing tree value. |
| 1072 | if (!isset($element[$key]['#tree'])) { |
| 1073 | $element[$key]['#tree'] = $element['#tree']; |
| 1074 | } |
| 1075 | |
| 1076 | // Deny access to child elements if parent is denied. |
| 1077 | if (isset($element['#access']) && !$element['#access']) { |
| 1078 | $element[$key]['#access'] = FALSE; |
| 1079 | } |
| 1080 | |
| 1081 | // Don't squash existing parents value. |
| 1082 | if (!isset($element[$key]['#parents'])) { |
| 1083 | // Check to see if a tree of child elements is present. If so, |
| 1084 | // continue down the tree if required. |
| 1085 | $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key); |
| 1086 | $array_parents = isset($element['#array_parents']) ? $element['#array_parents'] : array(); |
| 1087 | $array_parents[] = $key; |
| 1088 | $element[$key]['#array_parents'] = $array_parents; |
| 1089 | } |
| 1090 | |
| 1091 | // Assign a decimal placeholder weight to preserve original array order. |
| 1092 | if (!isset($element[$key]['#weight'])) { |
| 1093 | $element[$key]['#weight'] = $count/1000; |
| 1094 | } |
| 1095 | else { |
| 1096 | // If one of the child elements has a weight then we will need to sort |
| 1097 | // later. |
| 1098 | unset($element['#sorted']); |
| 1099 | } |
| 1100 | $element[$key] = form_builder($form_id, $element[$key], $form_state); |
| 1101 | $count++; |
| 1102 | } |
| 1103 | |
| 1104 | // The #after_build flag allows any piece of a form to be altered |
| 1105 | // after normal input parsing has been completed. |
| 1106 | if (isset($element['#after_build']) && !isset($element['#after_build_done'])) { |
| 1107 | foreach ($element['#after_build'] as $function) { |
| 1108 | $element = $function($element, $form_state); |
| 1109 | $element['#after_build_done'] = TRUE; |
| 1110 | } |
| 1111 | } |
| 1112 | |
| 1113 | // Now that we've processed everything, we can go back to handle the funky |
| 1114 | // Internet Explorer button-click scenario. |
| 1115 | _form_builder_ie_cleanup($element, $form_state); |
| 1116 | |
| 1117 | // If some callback set #cache, we need to flip a flag so later it |
| 1118 | // can be found. |
| 1119 | if (!empty($element['#cache'])) { |
| 1120 | $form_state['cache'] = $element['#cache']; |
| 1121 | } |
| 1122 | |
| 1123 | // If there is a file element, we need to flip a flag so later the |
| 1124 | // form encoding can be set. |
| 1125 | if (isset($element['#type']) && $element['#type'] == 'file') { |
| 1126 | $form_state['has_file_element'] = TRUE; |
| 1127 | } |
| 1128 | |
| 1129 | if (isset($element['#type']) && $element['#type'] == 'form') { |
| 1130 | // We are on the top form. |
| 1131 | // If there is a file element, we set the form encoding. |
| 1132 | if (isset($form_state['has_file_element'])) { |
| 1133 | $element['#attributes']['enctype'] = 'multipart/form-data'; |
| 1134 | } |
| 1135 | // Update the copy of the complete form for usage in validation handlers. |
| 1136 | $form_state['complete form'] = $element; |
| 1137 | } |
| 1138 | return $element; |
| 1139 | } |
| 1140 | |
| 1141 | /** |
| 1142 | * Populate the #value and #name properties of input elements so they |
| 1143 | * can be processed and rendered. |
| 1144 | */ |
| 1145 | function _form_builder_handle_input_element($form_id, &$element, &$form_state) { |
| 1146 | if (!isset($element['#name'])) { |
| 1147 | $name = array_shift($element['#parents']); |
| 1148 | $element['#name'] = $name; |
| 1149 | if ($element['#type'] == 'file') { |
| 1150 | // To make it easier to handle $_FILES in file.inc, we place all |
| 1151 | // file fields in the 'files' array. Also, we do not support |
| 1152 | // nested file names. |
| 1153 | $element['#name'] = 'files[' . $element['#name'] . ']'; |
| 1154 | } |
| 1155 | elseif (count($element['#parents'])) { |
| 1156 | $element['#name'] .= '[' . implode('][', $element['#parents']) . ']'; |
| 1157 | } |
| 1158 | array_unshift($element['#parents'], $name); |
| 1159 | } |
| 1160 | |
| 1161 | if (!empty($element['#disabled'])) { |
| 1162 | $element['#attributes']['disabled'] = 'disabled'; |
| 1163 | } |
| 1164 | |
| 1165 | // Set the element's #value property. |
| 1166 | if (!isset($element['#value']) && !array_key_exists('#value', $element)) { |
| 1167 | $value_callback = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value'; |
| 1168 | |
| 1169 | if ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))) { |
| 1170 | $input = $form_state['input']; |
| 1171 | foreach ($element['#parents'] as $parent) { |
| 1172 | $input = isset($input[$parent]) ? $input[$parent] : NULL; |
| 1173 | } |
| 1174 | // If we have input for the current element, assign it to the #value property. |
| 1175 | if (!$form_state['programmed'] || isset($input)) { |
| 1176 | // Call #type_value to set the form value; |
| 1177 | if (function_exists($value_callback)) { |
| 1178 | $element['#value'] = $value_callback($element, $input, $form_state); |
| 1179 | } |
| 1180 | if (!isset($element['#value']) && isset($input)) { |
| 1181 | $element['#value'] = $input; |
| 1182 | } |
| 1183 | } |
| 1184 | // Mark all posted values for validation. |
| 1185 | if (isset($element['#value']) || (!empty($element['#required']))) { |
| 1186 | $element['#needs_validation'] = TRUE; |
| 1187 | } |
| 1188 | } |
| 1189 | // Load defaults. |
| 1190 | if (!isset($element['#value'])) { |
| 1191 | // Call #type_value without a second argument to request default_value handling. |
| 1192 | if (function_exists($value_callback)) { |
| 1193 | $element['#value'] = $value_callback($element, FALSE, $form_state); |
| 1194 | } |
| 1195 | // Final catch. If we haven't set a value yet, use the explicit default value. |
| 1196 | // Avoid image buttons (which come with garbage value), so we only get value |
| 1197 | // for the button actually clicked. |
| 1198 | if (!isset($element['#value']) && empty($element['#has_garbage_value'])) { |
| 1199 | $element['#value'] = isset($element['#default_value']) ? $element['#default_value'] : ''; |
| 1200 | } |
| 1201 | } |
| 1202 | } |
| 1203 | |
| 1204 | // Determine which button (if any) was clicked to submit the form. |
| 1205 | // We compare the incoming values with the buttons defined in the form, |
| 1206 | // and flag the one that matches. We have to do some funky tricks to |
| 1207 | // deal with Internet Explorer's handling of single-button forms, though. |
| 1208 | if (!empty($form_state['input']) && isset($element['#executes_submit_callback'])) { |
| 1209 | // First, accumulate a collection of buttons, divided into two bins: |
| 1210 | // those that execute full submit callbacks and those that only validate. |
| 1211 | $button_type = $element['#executes_submit_callback'] ? 'submit' : 'button'; |
| 1212 | $form_state['buttons'][$button_type][] = $element; |
| 1213 | |
| 1214 | if (_form_button_was_clicked($element, $form_state)) { |
| 1215 | $form_state['submitted'] = $form_state['submitted'] || $element['#executes_submit_callback']; |
| 1216 | |
| 1217 | // In most cases, we want to use form_set_value() to manipulate |
| 1218 | // the global variables. In this special case, we want to make sure that |
| 1219 | // the value of this element is listed in $form_variables under 'op'. |
| 1220 | $form_state['values'][$element['#name']] = $element['#value']; |
| 1221 | $form_state['clicked_button'] = $element; |
| 1222 | |
| 1223 | if (isset($element['#validate'])) { |
| 1224 | $form_state['validate_handlers'] = $element['#validate']; |
| 1225 | } |
| 1226 | if (isset($element['#submit'])) { |
| 1227 | $form_state['submit_handlers'] = $element['#submit']; |
| 1228 | } |
| 1229 | } |
| 1230 | } |
| 1231 | form_set_value($element, $element['#value'], $form_state); |
| 1232 | } |
| 1233 | |
| 1234 | /** |
| 1235 | * Helper function to handle the sometimes-convoluted logic of button |
| 1236 | * click detection. |
| 1237 | * |
| 1238 | * In Internet Explorer, if ONLY one submit button is present, AND the |
| 1239 | * enter key is used to submit the form, no form value is sent for it |
| 1240 | * and we'll never detect a match. That special case is handled by |
| 1241 | * _form_builder_ie_cleanup(). |
| 1242 | */ |
| 1243 | function _form_button_was_clicked($form, &$form_state) { |
| 1244 | // First detect normal 'vanilla' button clicks. Traditionally, all |
| 1245 | // standard buttons on a form share the same name (usually 'op'), |
| 1246 | // and the specific return value is used to determine which was |
| 1247 | // clicked. This ONLY works as long as $form['#name'] puts the |
| 1248 | // value at the top level of the tree of $_POST data. |
| 1249 | if (isset($form_state['input'][$form['#name']]) && $form_state['input'][$form['#name']] == $form['#value']) { |
| 1250 | return TRUE; |
| 1251 | } |
| 1252 | // When image buttons are clicked, browsers do NOT pass the form element |
| 1253 | // value in $_POST. Instead they pass an integer representing the |
| 1254 | // coordinates of the click on the button image. This means that image |
| 1255 | // buttons MUST have unique $form['#name'] values, but the details of |
| 1256 | // their $_POST data should be ignored. |
| 1257 | elseif (!empty($form['#has_garbage_value']) && isset($form['#value']) && $form['#value'] !== '') { |
| 1258 | return TRUE; |
| 1259 | } |
| 1260 | return FALSE; |
| 1261 | } |
| 1262 | |
| 1263 | /** |
| 1264 | * In IE, if only one submit button is present, AND the enter key is |
| 1265 | * used to submit the form, no form value is sent for it and our normal |
| 1266 | * button detection code will never detect a match. We call this |
| 1267 | * function after all other button-detection is complete to check |
| 1268 | * for the proper conditions, and treat the single button on the form |
| 1269 | * as 'clicked' if they are met. |
| 1270 | */ |
| 1271 | function _form_builder_ie_cleanup($form, &$form_state) { |
| 1272 | // Quick check to make sure we're always looking at the full form |
| 1273 | // and not a sub-element. |
| 1274 | if (!empty |