|
1 <?php |
|
2 /** |
|
3 * @file |
|
4 * Functions for form and batch generation and processing. |
|
5 */ |
|
6 |
|
7 /** |
|
8 * @defgroup forms Form builder functions |
|
9 * @{ |
|
10 * Functions that build an abstract representation of a HTML form. |
|
11 * |
|
12 * All modules should declare their form builder functions to be in this |
|
13 * group and each builder function should reference its validate and submit |
|
14 * functions using \@see. Conversely, validate and submit functions should |
|
15 * reference the form builder function using \@see. For examples, of this see |
|
16 * system_modules_uninstall() or user_pass(), the latter of which has the |
|
17 * following in its doxygen documentation: |
|
18 * - \@ingroup forms |
|
19 * - \@see user_pass_validate() |
|
20 * - \@see user_pass_submit() |
|
21 * |
|
22 * @} |
|
23 */ |
|
24 |
|
25 /** |
|
26 * @defgroup form_api Form generation |
|
27 * @{ |
|
28 * Functions to enable the processing and display of HTML forms. |
|
29 * |
|
30 * Drupal uses these functions to achieve consistency in its form processing and |
|
31 * presentation, while simplifying code and reducing the amount of HTML that |
|
32 * must be explicitly generated by modules. |
|
33 * |
|
34 * The primary function used with forms is drupal_get_form(), which is |
|
35 * used for forms presented interactively to a user. Forms can also be built and |
|
36 * submitted programmatically without any user input using the |
|
37 * drupal_form_submit() function. |
|
38 * |
|
39 * drupal_get_form() handles retrieving, processing, and displaying a rendered |
|
40 * HTML form for modules automatically. |
|
41 * |
|
42 * Here is an example of how to use drupal_get_form() and a form builder |
|
43 * function: |
|
44 * @code |
|
45 * $form = drupal_get_form('my_module_example_form'); |
|
46 * ... |
|
47 * function my_module_example_form($form, &$form_state) { |
|
48 * $form['submit'] = array( |
|
49 * '#type' => 'submit', |
|
50 * '#value' => t('Submit'), |
|
51 * ); |
|
52 * return $form; |
|
53 * } |
|
54 * function my_module_example_form_validate($form, &$form_state) { |
|
55 * // Validation logic. |
|
56 * } |
|
57 * function my_module_example_form_submit($form, &$form_state) { |
|
58 * // Submission logic. |
|
59 * } |
|
60 * @endcode |
|
61 * |
|
62 * Or with any number of additional arguments: |
|
63 * @code |
|
64 * $extra = "extra"; |
|
65 * $form = drupal_get_form('my_module_example_form', $extra); |
|
66 * ... |
|
67 * function my_module_example_form($form, &$form_state, $extra) { |
|
68 * $form['submit'] = array( |
|
69 * '#type' => 'submit', |
|
70 * '#value' => $extra, |
|
71 * ); |
|
72 * return $form; |
|
73 * } |
|
74 * @endcode |
|
75 * |
|
76 * The $form argument to form-related functions is a structured array containing |
|
77 * the elements and properties of the form. For information on the array |
|
78 * components and format, and more detailed explanations of the Form API |
|
79 * workflow, see the |
|
80 * @link forms_api_reference.html Form API reference @endlink |
|
81 * and the |
|
82 * @link http://drupal.org/node/37775 Form API documentation section. @endlink |
|
83 * In addition, there is a set of Form API tutorials in |
|
84 * @link form_example_tutorial.inc the Form Example Tutorial @endlink which |
|
85 * provide basics all the way up through multistep forms. |
|
86 * |
|
87 * In the form builder, validation, submission, and other form functions, |
|
88 * $form_state is the primary influence on the processing of the form and is |
|
89 * passed by reference to most functions, so they use it to communicate with |
|
90 * the form system and each other. |
|
91 * |
|
92 * See drupal_build_form() for documentation of $form_state keys. |
|
93 */ |
|
94 |
|
95 /** |
|
96 * Returns a renderable form array for a given form ID. |
|
97 * |
|
98 * This function should be used instead of drupal_build_form() when $form_state |
|
99 * is not needed (i.e., when initially rendering the form) and is often |
|
100 * used as a menu callback. |
|
101 * |
|
102 * @param $form_id |
|
103 * The unique string identifying the desired form. If a function with that |
|
104 * name exists, it is called to build the form array. Modules that need to |
|
105 * generate the same form (or very similar forms) using different $form_ids |
|
106 * can implement hook_forms(), which maps different $form_id values to the |
|
107 * proper form constructor function. Examples may be found in node_forms(), |
|
108 * and search_forms(). hook_forms() can also be used to define forms in |
|
109 * classes. |
|
110 * @param ... |
|
111 * Any additional arguments are passed on to the functions called by |
|
112 * drupal_get_form(), including the unique form constructor function. For |
|
113 * example, the node_edit form requires that a node object is passed in here |
|
114 * when it is called. These are available to implementations of |
|
115 * hook_form_alter() and hook_form_FORM_ID_alter() as the array |
|
116 * $form_state['build_info']['args']. |
|
117 * |
|
118 * @return |
|
119 * The form array. |
|
120 * |
|
121 * @see drupal_build_form() |
|
122 */ |
|
123 function drupal_get_form($form_id) { |
|
124 $form_state = array(); |
|
125 |
|
126 $args = func_get_args(); |
|
127 // Remove $form_id from the arguments. |
|
128 array_shift($args); |
|
129 $form_state['build_info']['args'] = $args; |
|
130 |
|
131 return drupal_build_form($form_id, $form_state); |
|
132 } |
|
133 |
|
134 /** |
|
135 * Builds and process a form based on a form id. |
|
136 * |
|
137 * The form may also be retrieved from the cache if the form was built in a |
|
138 * previous page-load. The form is then passed on for processing, validation |
|
139 * and submission if there is proper input. |
|
140 * |
|
141 * @param $form_id |
|
142 * The unique string identifying the desired form. If a function with that |
|
143 * name exists, it is called to build the form array. Modules that need to |
|
144 * generate the same form (or very similar forms) using different $form_ids |
|
145 * can implement hook_forms(), which maps different $form_id values to the |
|
146 * proper form constructor function. Examples may be found in node_forms(), |
|
147 * and search_forms(). |
|
148 * @param $form_state |
|
149 * An array which stores information about the form. This is passed as a |
|
150 * reference so that the caller can use it to examine what in the form changed |
|
151 * when the form submission process is complete. Furthermore, it may be used |
|
152 * to store information related to the processed data in the form, which will |
|
153 * persist across page requests when the 'cache' or 'rebuild' flag is set. |
|
154 * The following parameters may be set in $form_state to affect how the form |
|
155 * is rendered: |
|
156 * - build_info: Internal. An associative array of information stored by Form |
|
157 * API that is necessary to build and rebuild the form from cache when the |
|
158 * original context may no longer be available: |
|
159 * - args: A list of arguments to pass to the form constructor. |
|
160 * - files: An optional array defining include files that need to be loaded |
|
161 * for building the form. Each array entry may be the path to a file or |
|
162 * another array containing values for the parameters 'type', 'module' and |
|
163 * 'name' as needed by module_load_include(). The files listed here are |
|
164 * automatically loaded by form_get_cache(). By default the current menu |
|
165 * router item's 'file' definition is added, if any. Use |
|
166 * form_load_include() to add include files from a form constructor. |
|
167 * - form_id: Identification of the primary form being constructed and |
|
168 * processed. |
|
169 * - base_form_id: Identification for a base form, as declared in a |
|
170 * hook_forms() implementation. |
|
171 * - immutable: If this flag is set to TRUE, a new form build id is |
|
172 * generated when the form is loaded from the cache. If it is subsequently |
|
173 * saved to the cache again, it will have another cache id and therefore |
|
174 * the original form and form-state will remain unaltered. This is |
|
175 * important when page caching is enabled in order to prevent form state |
|
176 * from leaking between anonymous users. |
|
177 * - rebuild_info: Internal. Similar to 'build_info', but pertaining to |
|
178 * drupal_rebuild_form(). |
|
179 * - rebuild: Normally, after the entire form processing is completed and |
|
180 * submit handlers have run, a form is considered to be done and |
|
181 * drupal_redirect_form() will redirect the user to a new page using a GET |
|
182 * request (so a browser refresh does not re-submit the form). However, if |
|
183 * 'rebuild' has been set to TRUE, then a new copy of the form is |
|
184 * immediately built and sent to the browser, instead of a redirect. This is |
|
185 * used for multi-step forms, such as wizards and confirmation forms. |
|
186 * Normally, $form_state['rebuild'] is set by a submit handler, since it is |
|
187 * usually logic within a submit handler that determines whether a form is |
|
188 * done or requires another step. However, a validation handler may already |
|
189 * set $form_state['rebuild'] to cause the form processing to bypass submit |
|
190 * handlers and rebuild the form instead, even if there are no validation |
|
191 * errors. |
|
192 * - redirect: Used to redirect the form on submission. It may either be a |
|
193 * string containing the destination URL, or an array of arguments |
|
194 * compatible with drupal_goto(). See drupal_redirect_form() for complete |
|
195 * information. |
|
196 * - no_redirect: If set to TRUE the form will NOT perform a drupal_goto(), |
|
197 * even if 'redirect' is set. |
|
198 * - method: The HTTP form method to use for finding the input for this form. |
|
199 * May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method |
|
200 * forms do not use form ids so are always considered to be submitted, which |
|
201 * can have unexpected effects. The 'get' method should only be used on |
|
202 * forms that do not change data, as that is exclusively the domain of |
|
203 * 'post.' |
|
204 * - cache: If set to TRUE the original, unprocessed form structure will be |
|
205 * cached, which allows the entire form to be rebuilt from cache. A typical |
|
206 * form workflow involves two page requests; first, a form is built and |
|
207 * rendered for the user to fill in. Then, the user fills the form in and |
|
208 * submits it, triggering a second page request in which the form must be |
|
209 * built and processed. By default, $form and $form_state are built from |
|
210 * scratch during each of these page requests. Often, it is necessary or |
|
211 * desired to persist the $form and $form_state variables from the initial |
|
212 * page request to the one that processes the submission. 'cache' can be set |
|
213 * to TRUE to do this. A prominent example is an Ajax-enabled form, in which |
|
214 * ajax_process_form() enables form caching for all forms that include an |
|
215 * element with the #ajax property. (The Ajax handler has no way to build |
|
216 * the form itself, so must rely on the cached version.) Note that the |
|
217 * persistence of $form and $form_state happens automatically for |
|
218 * (multi-step) forms having the 'rebuild' flag set, regardless of the value |
|
219 * for 'cache'. |
|
220 * - no_cache: If set to TRUE the form will NOT be cached, even if 'cache' is |
|
221 * set. |
|
222 * - values: An associative array of values submitted to the form. The |
|
223 * validation functions and submit functions use this array for nearly all |
|
224 * their decision making. (Note that #tree determines whether the values are |
|
225 * a flat array or an array whose structure parallels the $form array. See |
|
226 * @link forms_api_reference.html Form API reference @endlink for more |
|
227 * information.) These are raw and unvalidated, so should not be used |
|
228 * without a thorough understanding of security implications. In almost all |
|
229 * cases, code should use the data in the 'values' array exclusively. The |
|
230 * most common use of this key is for multi-step forms that need to clear |
|
231 * some of the user input when setting 'rebuild'. The values correspond to |
|
232 * $_POST or $_GET, depending on the 'method' chosen. |
|
233 * - always_process: If TRUE and the method is GET, a form_id is not |
|
234 * necessary. This should only be used on RESTful GET forms that do NOT |
|
235 * write data, as this could lead to security issues. It is useful so that |
|
236 * searches do not need to have a form_id in their query arguments to |
|
237 * trigger the search. |
|
238 * - must_validate: Ordinarily, a form is only validated once, but there are |
|
239 * times when a form is resubmitted internally and should be validated |
|
240 * again. Setting this to TRUE will force that to happen. This is most |
|
241 * likely to occur during Ajax operations. |
|
242 * - programmed: If TRUE, the form was submitted programmatically, usually |
|
243 * invoked via drupal_form_submit(). Defaults to FALSE. |
|
244 * - programmed_bypass_access_check: If TRUE, programmatic form submissions |
|
245 * are processed without taking #access into account. Set this to FALSE |
|
246 * when submitting a form programmatically with values that may have been |
|
247 * input by the user executing the current request; this will cause #access |
|
248 * to be respected as it would on a normal form submission. Defaults to |
|
249 * TRUE. |
|
250 * - process_input: Boolean flag. TRUE signifies correct form submission. |
|
251 * This is always TRUE for programmed forms coming from drupal_form_submit() |
|
252 * (see 'programmed' key), or if the form_id coming from the $_POST data is |
|
253 * set and matches the current form_id. |
|
254 * - submitted: If TRUE, the form has been submitted. Defaults to FALSE. |
|
255 * - executed: If TRUE, the form was submitted and has been processed and |
|
256 * executed. Defaults to FALSE. |
|
257 * - triggering_element: (read-only) The form element that triggered |
|
258 * submission. This is the same as the deprecated |
|
259 * $form_state['clicked_button']. It is the element that caused submission, |
|
260 * which may or may not be a button (in the case of Ajax forms). This key is |
|
261 * often used to distinguish between various buttons in a submit handler, |
|
262 * and is also used in Ajax handlers. |
|
263 * - clicked_button: Deprecated. Use triggering_element instead. |
|
264 * - has_file_element: Internal. If TRUE, there is a file element and Form API |
|
265 * will set the appropriate 'enctype' HTML attribute on the form. |
|
266 * - groups: Internal. An array containing references to fieldsets to render |
|
267 * them within vertical tabs. |
|
268 * - storage: $form_state['storage'] is not a special key, and no specific |
|
269 * support is provided for it in the Form API. By tradition it was |
|
270 * the location where application-specific data was stored for communication |
|
271 * between the submit, validation, and form builder functions, especially |
|
272 * in a multi-step-style form. Form implementations may use any key(s) |
|
273 * within $form_state (other than the keys listed here and other reserved |
|
274 * ones used by Form API internals) for this kind of storage. The |
|
275 * recommended way to ensure that the chosen key doesn't conflict with ones |
|
276 * used by the Form API or other modules is to use the module name as the |
|
277 * key name or a prefix for the key name. For example, the Node module uses |
|
278 * $form_state['node'] in node editing forms to store information about the |
|
279 * node being edited, and this information stays available across successive |
|
280 * clicks of the "Preview" button as well as when the "Save" button is |
|
281 * finally clicked. |
|
282 * - buttons: A list containing copies of all submit and button elements in |
|
283 * the form. |
|
284 * - complete form: A reference to the $form variable containing the complete |
|
285 * form structure. #process, #after_build, #element_validate, and other |
|
286 * handlers being invoked on a form element may use this reference to access |
|
287 * other information in the form the element is contained in. |
|
288 * - temporary: An array holding temporary data accessible during the current |
|
289 * page request only. All $form_state properties that are not reserved keys |
|
290 * (see form_state_keys_no_cache()) persist throughout a multistep form |
|
291 * sequence. Form API provides this key for modules to communicate |
|
292 * information across form-related functions during a single page request. |
|
293 * It may be used to temporarily save data that does not need to or should |
|
294 * not be cached during the whole form workflow; e.g., data that needs to be |
|
295 * accessed during the current form build process only. There is no use-case |
|
296 * for this functionality in Drupal core. |
|
297 * - wrapper_callback: Modules that wish to pre-populate certain forms with |
|
298 * common elements, such as back/next/save buttons in multi-step form |
|
299 * wizards, may define a form builder function name that returns a form |
|
300 * structure, which is passed on to the actual form builder function. |
|
301 * Such implementations may either define the 'wrapper_callback' via |
|
302 * hook_forms() or have to invoke drupal_build_form() (instead of |
|
303 * drupal_get_form()) on their own in a custom menu callback to prepare |
|
304 * $form_state accordingly. |
|
305 * Information on how certain $form_state properties control redirection |
|
306 * behavior after form submission may be found in drupal_redirect_form(). |
|
307 * |
|
308 * @return |
|
309 * The rendered form. This function may also perform a redirect and hence may |
|
310 * not return at all, depending upon the $form_state flags that were set. |
|
311 * |
|
312 * @see drupal_redirect_form() |
|
313 */ |
|
314 function drupal_build_form($form_id, &$form_state) { |
|
315 // Ensure some defaults; if already set they will not be overridden. |
|
316 $form_state += form_state_defaults(); |
|
317 |
|
318 if (!isset($form_state['input'])) { |
|
319 $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST; |
|
320 } |
|
321 |
|
322 if (isset($_SESSION['batch_form_state'])) { |
|
323 // We've been redirected here after a batch processing. The form has |
|
324 // already been processed, but needs to be rebuilt. See _batch_finished(). |
|
325 $form_state = $_SESSION['batch_form_state']; |
|
326 unset($_SESSION['batch_form_state']); |
|
327 return drupal_rebuild_form($form_id, $form_state); |
|
328 } |
|
329 |
|
330 // If the incoming input contains a form_build_id, we'll check the cache for a |
|
331 // copy of the form in question. If it's there, we don't have to rebuild the |
|
332 // form to proceed. In addition, if there is stored form_state data from a |
|
333 // previous step, we'll retrieve it so it can be passed on to the form |
|
334 // processing code. |
|
335 $check_cache = isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id']); |
|
336 if ($check_cache) { |
|
337 $form = form_get_cache($form_state['input']['form_build_id'], $form_state); |
|
338 } |
|
339 |
|
340 // If the previous bit of code didn't result in a populated $form object, we |
|
341 // are hitting the form for the first time and we need to build it from |
|
342 // scratch. |
|
343 if (!isset($form)) { |
|
344 // If we attempted to serve the form from cache, uncacheable $form_state |
|
345 // keys need to be removed after retrieving and preparing the form, except |
|
346 // any that were already set prior to retrieving the form. |
|
347 if ($check_cache) { |
|
348 $form_state_before_retrieval = $form_state; |
|
349 } |
|
350 |
|
351 $form = drupal_retrieve_form($form_id, $form_state); |
|
352 drupal_prepare_form($form_id, $form, $form_state); |
|
353 |
|
354 // form_set_cache() removes uncacheable $form_state keys defined in |
|
355 // form_state_keys_no_cache() in order for multi-step forms to work |
|
356 // properly. This means that form processing logic for single-step forms |
|
357 // using $form_state['cache'] may depend on data stored in those keys |
|
358 // during drupal_retrieve_form()/drupal_prepare_form(), but form |
|
359 // processing should not depend on whether the form is cached or not, so |
|
360 // $form_state is adjusted to match what it would be after a |
|
361 // form_set_cache()/form_get_cache() sequence. These exceptions are |
|
362 // allowed to survive here: |
|
363 // - always_process: Does not make sense in conjunction with form caching |
|
364 // in the first place, since passing form_build_id as a GET parameter is |
|
365 // not desired. |
|
366 // - temporary: Any assigned data is expected to survives within the same |
|
367 // page request. |
|
368 if ($check_cache) { |
|
369 $uncacheable_keys = array_flip(array_diff(form_state_keys_no_cache(), array('always_process', 'temporary'))); |
|
370 $form_state = array_diff_key($form_state, $uncacheable_keys); |
|
371 $form_state += $form_state_before_retrieval; |
|
372 } |
|
373 } |
|
374 |
|
375 // Now that we have a constructed form, process it. This is where: |
|
376 // - Element #process functions get called to further refine $form. |
|
377 // - User input, if any, gets incorporated in the #value property of the |
|
378 // corresponding elements and into $form_state['values']. |
|
379 // - Validation and submission handlers are called. |
|
380 // - If this submission is part of a multistep workflow, the form is rebuilt |
|
381 // to contain the information of the next step. |
|
382 // - If necessary, the form and form state are cached or re-cached, so that |
|
383 // appropriate information persists to the next page request. |
|
384 // All of the handlers in the pipeline receive $form_state by reference and |
|
385 // can use it to know or update information about the state of the form. |
|
386 drupal_process_form($form_id, $form, $form_state); |
|
387 |
|
388 // If this was a successful submission of a single-step form or the last step |
|
389 // of a multi-step form, then drupal_process_form() issued a redirect to |
|
390 // another page, or back to this page, but as a new request. Therefore, if |
|
391 // we're here, it means that this is either a form being viewed initially |
|
392 // before any user input, or there was a validation error requiring the form |
|
393 // to be re-displayed, or we're in a multi-step workflow and need to display |
|
394 // the form's next step. In any case, we have what we need in $form, and can |
|
395 // return it for rendering. |
|
396 return $form; |
|
397 } |
|
398 |
|
399 /** |
|
400 * Retrieves default values for the $form_state array. |
|
401 */ |
|
402 function form_state_defaults() { |
|
403 return array( |
|
404 'rebuild' => FALSE, |
|
405 'rebuild_info' => array(), |
|
406 'redirect' => NULL, |
|
407 // @todo 'args' is usually set, so no other default 'build_info' keys are |
|
408 // appended via += form_state_defaults(). |
|
409 'build_info' => array( |
|
410 'args' => array(), |
|
411 'files' => array(), |
|
412 ), |
|
413 'temporary' => array(), |
|
414 'submitted' => FALSE, |
|
415 'executed' => FALSE, |
|
416 'programmed' => FALSE, |
|
417 'programmed_bypass_access_check' => TRUE, |
|
418 'cache'=> FALSE, |
|
419 'method' => 'post', |
|
420 'groups' => array(), |
|
421 'buttons' => array(), |
|
422 ); |
|
423 } |
|
424 |
|
425 /** |
|
426 * Constructs a new $form from the information in $form_state. |
|
427 * |
|
428 * This is the key function for making multi-step forms advance from step to |
|
429 * step. It is called by drupal_process_form() when all user input processing, |
|
430 * including calling validation and submission handlers, for the request is |
|
431 * finished. If a validate or submit handler set $form_state['rebuild'] to TRUE, |
|
432 * and if other conditions don't preempt a rebuild from happening, then this |
|
433 * function is called to generate a new $form, the next step in the form |
|
434 * workflow, to be returned for rendering. |
|
435 * |
|
436 * Ajax form submissions are almost always multi-step workflows, so that is one |
|
437 * common use-case during which form rebuilding occurs. See ajax_form_callback() |
|
438 * for more information about creating Ajax-enabled forms. |
|
439 * |
|
440 * @param $form_id |
|
441 * The unique string identifying the desired form. If a function |
|
442 * with that name exists, it is called to build the form array. |
|
443 * Modules that need to generate the same form (or very similar forms) |
|
444 * using different $form_ids can implement hook_forms(), which maps |
|
445 * different $form_id values to the proper form constructor function. Examples |
|
446 * may be found in node_forms() and search_forms(). |
|
447 * @param $form_state |
|
448 * A keyed array containing the current state of the form. |
|
449 * @param $old_form |
|
450 * (optional) A previously built $form. Used to retain the #build_id and |
|
451 * #action properties in Ajax callbacks and similar partial form rebuilds. The |
|
452 * only properties copied from $old_form are the ones which both exist in |
|
453 * $old_form and for which $form_state['rebuild_info']['copy'][PROPERTY] is |
|
454 * TRUE. If $old_form is not passed, the entire $form is rebuilt freshly. |
|
455 * 'rebuild_info' needs to be a separate top-level property next to |
|
456 * 'build_info', since the contained data must not be cached. |
|
457 * |
|
458 * @return |
|
459 * The newly built form. |
|
460 * |
|
461 * @see drupal_process_form() |
|
462 * @see ajax_form_callback() |
|
463 */ |
|
464 function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) { |
|
465 $form = drupal_retrieve_form($form_id, $form_state); |
|
466 |
|
467 // If only parts of the form will be returned to the browser (e.g., Ajax or |
|
468 // RIA clients), or if the form already had a new build ID regenerated when it |
|
469 // was retrieved from the form cache, reuse the existing #build_id. |
|
470 // Otherwise, a new #build_id is generated, to not clobber the previous |
|
471 // build's data in the form cache; also allowing the user to go back to an |
|
472 // earlier build, make changes, and re-submit. |
|
473 // @see drupal_prepare_form() |
|
474 $enforce_old_build_id = isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id']); |
|
475 $old_form_is_mutable_copy = isset($old_form['#build_id_old']); |
|
476 if ($enforce_old_build_id || $old_form_is_mutable_copy) { |
|
477 $form['#build_id'] = $old_form['#build_id']; |
|
478 if ($old_form_is_mutable_copy) { |
|
479 $form['#build_id_old'] = $old_form['#build_id_old']; |
|
480 } |
|
481 } |
|
482 else { |
|
483 if (isset($old_form['#build_id'])) { |
|
484 $form['#build_id_old'] = $old_form['#build_id']; |
|
485 } |
|
486 $form['#build_id'] = 'form-' . drupal_random_key(); |
|
487 } |
|
488 |
|
489 // #action defaults to request_uri(), but in case of Ajax and other partial |
|
490 // rebuilds, the form is submitted to an alternate URL, and the original |
|
491 // #action needs to be retained. |
|
492 if (isset($old_form['#action']) && !empty($form_state['rebuild_info']['copy']['#action'])) { |
|
493 $form['#action'] = $old_form['#action']; |
|
494 } |
|
495 |
|
496 drupal_prepare_form($form_id, $form, $form_state); |
|
497 |
|
498 // Caching is normally done in drupal_process_form(), but what needs to be |
|
499 // cached is the $form structure before it passes through form_builder(), |
|
500 // so we need to do it here. |
|
501 // @todo For Drupal 8, find a way to avoid this code duplication. |
|
502 if (empty($form_state['no_cache'])) { |
|
503 form_set_cache($form['#build_id'], $form, $form_state); |
|
504 } |
|
505 |
|
506 // Clear out all group associations as these might be different when |
|
507 // re-rendering the form. |
|
508 $form_state['groups'] = array(); |
|
509 |
|
510 // Return a fully built form that is ready for rendering. |
|
511 return form_builder($form_id, $form, $form_state); |
|
512 } |
|
513 |
|
514 /** |
|
515 * Fetches a form from cache. |
|
516 */ |
|
517 function form_get_cache($form_build_id, &$form_state) { |
|
518 if ($cached = cache_get('form_' . $form_build_id, 'cache_form')) { |
|
519 $form = $cached->data; |
|
520 |
|
521 global $user; |
|
522 if ((isset($form['#cache_token']) && drupal_valid_token($form['#cache_token'])) || (!isset($form['#cache_token']) && !$user->uid)) { |
|
523 if ($cached = cache_get('form_state_' . $form_build_id, 'cache_form')) { |
|
524 // Re-populate $form_state for subsequent rebuilds. |
|
525 $form_state = $cached->data + $form_state; |
|
526 |
|
527 // If the original form is contained in include files, load the files. |
|
528 // @see form_load_include() |
|
529 $form_state['build_info'] += array('files' => array()); |
|
530 foreach ($form_state['build_info']['files'] as $file) { |
|
531 if (is_array($file)) { |
|
532 $file += array('type' => 'inc', 'name' => $file['module']); |
|
533 module_load_include($file['type'], $file['module'], $file['name']); |
|
534 } |
|
535 elseif (file_exists($file)) { |
|
536 require_once DRUPAL_ROOT . '/' . $file; |
|
537 } |
|
538 } |
|
539 } |
|
540 // Generate a new #build_id if the cached form was rendered on a cacheable |
|
541 // page. |
|
542 if (!empty($form_state['build_info']['immutable'])) { |
|
543 $form['#build_id_old'] = $form['#build_id']; |
|
544 $form['#build_id'] = 'form-' . drupal_random_key(); |
|
545 $form['form_build_id']['#value'] = $form['#build_id']; |
|
546 $form['form_build_id']['#id'] = $form['#build_id']; |
|
547 unset($form_state['build_info']['immutable']); |
|
548 } |
|
549 return $form; |
|
550 } |
|
551 } |
|
552 } |
|
553 |
|
554 /** |
|
555 * Stores a form in the cache. |
|
556 */ |
|
557 function form_set_cache($form_build_id, $form, $form_state) { |
|
558 // 6 hours cache life time for forms should be plenty. |
|
559 $expire = 21600; |
|
560 |
|
561 // Ensure that the form build_id embedded in the form structure is the same as |
|
562 // the one passed in as a parameter. This is an additional safety measure to |
|
563 // prevent legacy code operating directly with form_get_cache and |
|
564 // form_set_cache from accidentally overwriting immutable form state. |
|
565 if ($form['#build_id'] != $form_build_id) { |
|
566 watchdog('form', 'Form build-id mismatch detected while attempting to store a form in the cache.', array(), WATCHDOG_ERROR); |
|
567 return; |
|
568 } |
|
569 |
|
570 // Cache form structure. |
|
571 if (isset($form)) { |
|
572 if ($GLOBALS['user']->uid) { |
|
573 $form['#cache_token'] = drupal_get_token(); |
|
574 } |
|
575 unset($form['#build_id_old']); |
|
576 cache_set('form_' . $form_build_id, $form, 'cache_form', REQUEST_TIME + $expire); |
|
577 } |
|
578 |
|
579 // Cache form state. |
|
580 if (variable_get('cache', 0) && drupal_page_is_cacheable()) { |
|
581 $form_state['build_info']['immutable'] = TRUE; |
|
582 } |
|
583 if ($data = array_diff_key($form_state, array_flip(form_state_keys_no_cache()))) { |
|
584 cache_set('form_state_' . $form_build_id, $data, 'cache_form', REQUEST_TIME + $expire); |
|
585 } |
|
586 } |
|
587 |
|
588 /** |
|
589 * Returns an array of $form_state keys that shouldn't be cached. |
|
590 */ |
|
591 function form_state_keys_no_cache() { |
|
592 return array( |
|
593 // Public properties defined by form constructors and form handlers. |
|
594 'always_process', |
|
595 'must_validate', |
|
596 'rebuild', |
|
597 'rebuild_info', |
|
598 'redirect', |
|
599 'no_redirect', |
|
600 'temporary', |
|
601 // Internal properties defined by form processing. |
|
602 'buttons', |
|
603 'triggering_element', |
|
604 'clicked_button', |
|
605 'complete form', |
|
606 'groups', |
|
607 'input', |
|
608 'method', |
|
609 'submit_handlers', |
|
610 'submitted', |
|
611 'executed', |
|
612 'validate_handlers', |
|
613 'values', |
|
614 ); |
|
615 } |
|
616 |
|
617 /** |
|
618 * Ensures an include file is loaded whenever the form is processed. |
|
619 * |
|
620 * Example: |
|
621 * @code |
|
622 * // Load node.admin.inc from Node module. |
|
623 * form_load_include($form_state, 'inc', 'node', 'node.admin'); |
|
624 * @endcode |
|
625 * |
|
626 * Use this function instead of module_load_include() from inside a form |
|
627 * constructor or any form processing logic as it ensures that the include file |
|
628 * is loaded whenever the form is processed. In contrast to using |
|
629 * module_load_include() directly, form_load_include() makes sure the include |
|
630 * file is correctly loaded also if the form is cached. |
|
631 * |
|
632 * @param $form_state |
|
633 * The current state of the form. |
|
634 * @param $type |
|
635 * The include file's type (file extension). |
|
636 * @param $module |
|
637 * The module to which the include file belongs. |
|
638 * @param $name |
|
639 * (optional) The base file name (without the $type extension). If omitted, |
|
640 * $module is used; i.e., resulting in "$module.$type" by default. |
|
641 * |
|
642 * @return |
|
643 * The filepath of the loaded include file, or FALSE if the include file was |
|
644 * not found or has been loaded already. |
|
645 * |
|
646 * @see module_load_include() |
|
647 */ |
|
648 function form_load_include(&$form_state, $type, $module, $name = NULL) { |
|
649 if (!isset($name)) { |
|
650 $name = $module; |
|
651 } |
|
652 if (!isset($form_state['build_info']['files']["$module:$name.$type"])) { |
|
653 // Only add successfully included files to the form state. |
|
654 if ($result = module_load_include($type, $module, $name)) { |
|
655 $form_state['build_info']['files']["$module:$name.$type"] = array( |
|
656 'type' => $type, |
|
657 'module' => $module, |
|
658 'name' => $name, |
|
659 ); |
|
660 return $result; |
|
661 } |
|
662 } |
|
663 return FALSE; |
|
664 } |
|
665 |
|
666 /** |
|
667 * Retrieves, populates, and processes a form. |
|
668 * |
|
669 * This function allows you to supply values for form elements and submit a |
|
670 * form for processing. Compare to drupal_get_form(), which also builds and |
|
671 * processes a form, but does not allow you to supply values. |
|
672 * |
|
673 * There is no return value, but you can check to see if there are errors |
|
674 * by calling form_get_errors(). |
|
675 * |
|
676 * @param $form_id |
|
677 * The unique string identifying the desired form. If a function |
|
678 * with that name exists, it is called to build the form array. |
|
679 * Modules that need to generate the same form (or very similar forms) |
|
680 * using different $form_ids can implement hook_forms(), which maps |
|
681 * different $form_id values to the proper form constructor function. Examples |
|
682 * may be found in node_forms() and search_forms(). |
|
683 * @param $form_state |
|
684 * A keyed array containing the current state of the form. Most important is |
|
685 * the $form_state['values'] collection, a tree of data used to simulate the |
|
686 * incoming $_POST information from a user's form submission. If a key is not |
|
687 * filled in $form_state['values'], then the default value of the respective |
|
688 * element is used. To submit an unchecked checkbox or other control that |
|
689 * browsers submit by not having a $_POST entry, include the key, but set the |
|
690 * value to NULL. |
|
691 * @param ... |
|
692 * Any additional arguments are passed on to the functions called by |
|
693 * drupal_form_submit(), including the unique form constructor function. |
|
694 * For example, the node_edit form requires that a node object be passed |
|
695 * in here when it is called. Arguments that need to be passed by reference |
|
696 * should not be included here, but rather placed directly in the $form_state |
|
697 * build info array so that the reference can be preserved. For example, a |
|
698 * form builder function with the following signature: |
|
699 * @code |
|
700 * function mymodule_form($form, &$form_state, &$object) { |
|
701 * } |
|
702 * @endcode |
|
703 * would be called via drupal_form_submit() as follows: |
|
704 * @code |
|
705 * $form_state['values'] = $my_form_values; |
|
706 * $form_state['build_info']['args'] = array(&$object); |
|
707 * drupal_form_submit('mymodule_form', $form_state); |
|
708 * @endcode |
|
709 * For example: |
|
710 * @code |
|
711 * // register a new user |
|
712 * $form_state = array(); |
|
713 * $form_state['values']['name'] = 'robo-user'; |
|
714 * $form_state['values']['mail'] = 'robouser@example.com'; |
|
715 * $form_state['values']['pass']['pass1'] = 'password'; |
|
716 * $form_state['values']['pass']['pass2'] = 'password'; |
|
717 * $form_state['values']['op'] = t('Create new account'); |
|
718 * drupal_form_submit('user_register_form', $form_state); |
|
719 * @endcode |
|
720 */ |
|
721 function drupal_form_submit($form_id, &$form_state) { |
|
722 if (!isset($form_state['build_info']['args'])) { |
|
723 $args = func_get_args(); |
|
724 array_shift($args); |
|
725 array_shift($args); |
|
726 $form_state['build_info']['args'] = $args; |
|
727 } |
|
728 // Merge in default values. |
|
729 $form_state += form_state_defaults(); |
|
730 |
|
731 // Populate $form_state['input'] with the submitted values before retrieving |
|
732 // the form, to be consistent with what drupal_build_form() does for |
|
733 // non-programmatic submissions (form builder functions may expect it to be |
|
734 // there). |
|
735 $form_state['input'] = $form_state['values']; |
|
736 |
|
737 $form_state['programmed'] = TRUE; |
|
738 $form = drupal_retrieve_form($form_id, $form_state); |
|
739 // Programmed forms are always submitted. |
|
740 $form_state['submitted'] = TRUE; |
|
741 |
|
742 // Reset form validation. |
|
743 $form_state['must_validate'] = TRUE; |
|
744 form_clear_error(); |
|
745 |
|
746 drupal_prepare_form($form_id, $form, $form_state); |
|
747 drupal_process_form($form_id, $form, $form_state); |
|
748 } |
|
749 |
|
750 /** |
|
751 * Retrieves the structured array that defines a given form. |
|
752 * |
|
753 * @param $form_id |
|
754 * The unique string identifying the desired form. If a function |
|
755 * with that name exists, it is called to build the form array. |
|
756 * Modules that need to generate the same form (or very similar forms) |
|
757 * using different $form_ids can implement hook_forms(), which maps |
|
758 * different $form_id values to the proper form constructor function. |
|
759 * @param $form_state |
|
760 * A keyed array containing the current state of the form, including the |
|
761 * additional arguments to drupal_get_form() or drupal_form_submit() in the |
|
762 * 'args' component of the array. |
|
763 */ |
|
764 function drupal_retrieve_form($form_id, &$form_state) { |
|
765 $forms = &drupal_static(__FUNCTION__); |
|
766 |
|
767 // Record the $form_id. |
|
768 $form_state['build_info']['form_id'] = $form_id; |
|
769 |
|
770 // Record the filepath of the include file containing the original form, so |
|
771 // the form builder callbacks can be loaded when the form is being rebuilt |
|
772 // from cache on a different path (such as 'system/ajax'). See |
|
773 // form_get_cache(). Don't do this in maintenance mode as Drupal may not be |
|
774 // fully bootstrapped (i.e. during installation) in which case |
|
775 // menu_get_item() is not available. |
|
776 if (!isset($form_state['build_info']['files']['menu']) && !defined('MAINTENANCE_MODE')) { |
|
777 $item = menu_get_item(); |
|
778 if (!empty($item['include_file'])) { |
|
779 // Do not use form_load_include() here, as the file is already loaded. |
|
780 // Anyway, form_get_cache() is able to handle filepaths too. |
|
781 $form_state['build_info']['files']['menu'] = $item['include_file']; |
|
782 } |
|
783 } |
|
784 |
|
785 // We save two copies of the incoming arguments: one for modules to use |
|
786 // when mapping form ids to constructor functions, and another to pass to |
|
787 // the constructor function itself. |
|
788 $args = $form_state['build_info']['args']; |
|
789 |
|
790 // We first check to see if there's a function named after the $form_id. |
|
791 // If there is, we simply pass the arguments on to it to get the form. |
|
792 if (!function_exists($form_id)) { |
|
793 // In cases where many form_ids need to share a central constructor function, |
|
794 // such as the node editing form, modules can implement hook_forms(). It |
|
795 // maps one or more form_ids to the correct constructor functions. |
|
796 // |
|
797 // We cache the results of that hook to save time, but that only works |
|
798 // for modules that know all their form_ids in advance. (A module that |
|
799 // adds a small 'rate this comment' form to each comment in a list |
|
800 // would need a unique form_id for each one, for example.) |
|
801 // |
|
802 // So, we call the hook if $forms isn't yet populated, OR if it doesn't |
|
803 // yet have an entry for the requested form_id. |
|
804 if (!isset($forms) || !isset($forms[$form_id])) { |
|
805 $forms = module_invoke_all('forms', $form_id, $args); |
|
806 } |
|
807 $form_definition = $forms[$form_id]; |
|
808 if (isset($form_definition['callback arguments'])) { |
|
809 $args = array_merge($form_definition['callback arguments'], $args); |
|
810 } |
|
811 if (isset($form_definition['callback'])) { |
|
812 $callback = $form_definition['callback']; |
|
813 $form_state['build_info']['base_form_id'] = isset($form_definition['base_form_id']) ? $form_definition['base_form_id'] : $callback; |
|
814 } |
|
815 // In case $form_state['wrapper_callback'] is not defined already, we also |
|
816 // allow hook_forms() to define one. |
|
817 if (!isset($form_state['wrapper_callback']) && isset($form_definition['wrapper_callback'])) { |
|
818 $form_state['wrapper_callback'] = $form_definition['wrapper_callback']; |
|
819 } |
|
820 } |
|
821 |
|
822 $form = array(); |
|
823 // We need to pass $form_state by reference in order for forms to modify it, |
|
824 // since call_user_func_array() requires that referenced variables are passed |
|
825 // explicitly. |
|
826 $args = array_merge(array($form, &$form_state), $args); |
|
827 |
|
828 // When the passed $form_state (not using drupal_get_form()) defines a |
|
829 // 'wrapper_callback', then it requests to invoke a separate (wrapping) form |
|
830 // builder function to pre-populate the $form array with form elements, which |
|
831 // the actual form builder function ($callback) expects. This allows for |
|
832 // pre-populating a form with common elements for certain forms, such as |
|
833 // back/next/save buttons in multi-step form wizards. See drupal_build_form(). |
|
834 if (isset($form_state['wrapper_callback']) && is_callable($form_state['wrapper_callback'])) { |
|
835 $form = call_user_func_array($form_state['wrapper_callback'], $args); |
|
836 // Put the prepopulated $form into $args. |
|
837 $args[0] = $form; |
|
838 } |
|
839 |
|
840 // If $callback was returned by a hook_forms() implementation, call it. |
|
841 // Otherwise, call the function named after the form id. |
|
842 $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args); |
|
843 $form['#form_id'] = $form_id; |
|
844 |
|
845 return $form; |
|
846 } |
|
847 |
|
848 /** |
|
849 * Processes a form submission. |
|
850 * |
|
851 * This function is the heart of form API. The form gets built, validated and in |
|
852 * appropriate cases, submitted and rebuilt. |
|
853 * |
|
854 * @param $form_id |
|
855 * The unique string identifying the current form. |
|
856 * @param $form |
|
857 * An associative array containing the structure of the form. |
|
858 * @param $form_state |
|
859 * A keyed array containing the current state of the form. This |
|
860 * includes the current persistent storage data for the form, and |
|
861 * any data passed along by earlier steps when displaying a |
|
862 * multi-step form. Additional information, like the sanitized $_POST |
|
863 * data, is also accumulated here. |
|
864 */ |
|
865 function drupal_process_form($form_id, &$form, &$form_state) { |
|
866 $form_state['values'] = array(); |
|
867 |
|
868 // With $_GET, these forms are always submitted if requested. |
|
869 if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) { |
|
870 if (!isset($form_state['input']['form_build_id'])) { |
|
871 $form_state['input']['form_build_id'] = $form['#build_id']; |
|
872 } |
|
873 if (!isset($form_state['input']['form_id'])) { |
|
874 $form_state['input']['form_id'] = $form_id; |
|
875 } |
|
876 if (!isset($form_state['input']['form_token']) && isset($form['#token'])) { |
|
877 $form_state['input']['form_token'] = drupal_get_token($form['#token']); |
|
878 } |
|
879 } |
|
880 |
|
881 // form_builder() finishes building the form by calling element #process |
|
882 // functions and mapping user input, if any, to #value properties, and also |
|
883 // storing the values in $form_state['values']. We need to retain the |
|
884 // unprocessed $form in case it needs to be cached. |
|
885 $unprocessed_form = $form; |
|
886 $form = form_builder($form_id, $form, $form_state); |
|
887 |
|
888 // Only process the input if we have a correct form submission. |
|
889 if ($form_state['process_input']) { |
|
890 drupal_validate_form($form_id, $form, $form_state); |
|
891 |
|
892 // drupal_html_id() maintains a cache of element IDs it has seen, |
|
893 // so it can prevent duplicates. We want to be sure we reset that |
|
894 // cache when a form is processed, so scenarios that result in |
|
895 // the form being built behind the scenes and again for the |
|
896 // browser don't increment all the element IDs needlessly. |
|
897 if (!form_get_errors()) { |
|
898 // In case of errors, do not break HTML IDs of other forms. |
|
899 drupal_static_reset('drupal_html_id'); |
|
900 } |
|
901 |
|
902 if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) { |
|
903 // Execute form submit handlers. |
|
904 form_execute_handlers('submit', $form, $form_state); |
|
905 |
|
906 // We'll clear out the cached copies of the form and its stored data |
|
907 // here, as we've finished with them. The in-memory copies are still |
|
908 // here, though. |
|
909 if (!variable_get('cache', 0) && !empty($form_state['values']['form_build_id'])) { |
|
910 cache_clear_all('form_' . $form_state['values']['form_build_id'], 'cache_form'); |
|
911 cache_clear_all('form_state_' . $form_state['values']['form_build_id'], 'cache_form'); |
|
912 } |
|
913 |
|
914 // If batches were set in the submit handlers, we process them now, |
|
915 // possibly ending execution. We make sure we do not react to the batch |
|
916 // that is already being processed (if a batch operation performs a |
|
917 // drupal_form_submit). |
|
918 if ($batch =& batch_get() && !isset($batch['current_set'])) { |
|
919 // Store $form_state information in the batch definition. |
|
920 // We need the full $form_state when either: |
|
921 // - Some submit handlers were saved to be called during batch |
|
922 // processing. See form_execute_handlers(). |
|
923 // - The form is multistep. |
|
924 // In other cases, we only need the information expected by |
|
925 // drupal_redirect_form(). |
|
926 if ($batch['has_form_submits'] || !empty($form_state['rebuild'])) { |
|
927 $batch['form_state'] = $form_state; |
|
928 } |
|
929 else { |
|
930 $batch['form_state'] = array_intersect_key($form_state, array_flip(array('programmed', 'rebuild', 'storage', 'no_redirect', 'redirect'))); |
|
931 } |
|
932 |
|
933 $batch['progressive'] = !$form_state['programmed']; |
|
934 batch_process(); |
|
935 |
|
936 // Execution continues only for programmatic forms. |
|
937 // For 'regular' forms, we get redirected to the batch processing |
|
938 // page. Form redirection will be handled in _batch_finished(), |
|
939 // after the batch is processed. |
|
940 } |
|
941 |
|
942 // Set a flag to indicate that the form has been processed and executed. |
|
943 $form_state['executed'] = TRUE; |
|
944 |
|
945 // Redirect the form based on values in $form_state. |
|
946 drupal_redirect_form($form_state); |
|
947 } |
|
948 |
|
949 // Don't rebuild or cache form submissions invoked via drupal_form_submit(). |
|
950 if (!empty($form_state['programmed'])) { |
|
951 return; |
|
952 } |
|
953 |
|
954 // If $form_state['rebuild'] has been set and input has been processed |
|
955 // without validation errors, we are in a multi-step workflow that is not |
|
956 // yet complete. A new $form needs to be constructed based on the changes |
|
957 // made to $form_state during this request. Normally, a submit handler sets |
|
958 // $form_state['rebuild'] if a fully executed form requires another step. |
|
959 // However, for forms that have not been fully executed (e.g., Ajax |
|
960 // submissions triggered by non-buttons), there is no submit handler to set |
|
961 // $form_state['rebuild']. It would not make sense to redisplay the |
|
962 // identical form without an error for the user to correct, so we also |
|
963 // rebuild error-free non-executed forms, regardless of |
|
964 // $form_state['rebuild']. |
|
965 // @todo D8: Simplify this logic; considering Ajax and non-HTML front-ends, |
|
966 // along with element-level #submit properties, it makes no sense to have |
|
967 // divergent form execution based on whether the triggering element has |
|
968 // #executes_submit_callback set to TRUE. |
|
969 if (($form_state['rebuild'] || !$form_state['executed']) && !form_get_errors()) { |
|
970 // Form building functions (e.g., _form_builder_handle_input_element()) |
|
971 // may use $form_state['rebuild'] to determine if they are running in the |
|
972 // context of a rebuild, so ensure it is set. |
|
973 $form_state['rebuild'] = TRUE; |
|
974 $form = drupal_rebuild_form($form_id, $form_state, $form); |
|
975 } |
|
976 } |
|
977 |
|
978 // After processing the form, the form builder or a #process callback may |
|
979 // have set $form_state['cache'] to indicate that the form and form state |
|
980 // shall be cached. But the form may only be cached if the 'no_cache' property |
|
981 // is not set to TRUE. Only cache $form as it was prior to form_builder(), |
|
982 // because form_builder() must run for each request to accommodate new user |
|
983 // input. Rebuilt forms are not cached here, because drupal_rebuild_form() |
|
984 // already takes care of that. |
|
985 if (!$form_state['rebuild'] && $form_state['cache'] && empty($form_state['no_cache'])) { |
|
986 form_set_cache($form['#build_id'], $unprocessed_form, $form_state); |
|
987 } |
|
988 } |
|
989 |
|
990 /** |
|
991 * Prepares a structured form array. |
|
992 * |
|
993 * Adds required elements, executes any hook_form_alter functions, and |
|
994 * optionally inserts a validation token to prevent tampering. |
|
995 * |
|
996 * @param $form_id |
|
997 * A unique string identifying the form for validation, submission, |
|
998 * theming, and hook_form_alter functions. |
|
999 * @param $form |
|
1000 * An associative array containing the structure of the form. |
|
1001 * @param $form_state |
|
1002 * A keyed array containing the current state of the form. Passed |
|
1003 * in here so that hook_form_alter() calls can use it, as well. |
|
1004 */ |
|
1005 function drupal_prepare_form($form_id, &$form, &$form_state) { |
|
1006 global $user; |
|
1007 |
|
1008 $form['#type'] = 'form'; |
|
1009 $form_state['programmed'] = isset($form_state['programmed']) ? $form_state['programmed'] : FALSE; |
|
1010 |
|
1011 // Fix the form method, if it is 'get' in $form_state, but not in $form. |
|
1012 if ($form_state['method'] == 'get' && !isset($form['#method'])) { |
|
1013 $form['#method'] = 'get'; |
|
1014 } |
|
1015 |
|
1016 // Generate a new #build_id for this form, if none has been set already. The |
|
1017 // form_build_id is used as key to cache a particular build of the form. For |
|
1018 // multi-step forms, this allows the user to go back to an earlier build, make |
|
1019 // changes, and re-submit. |
|
1020 // @see drupal_build_form() |
|
1021 // @see drupal_rebuild_form() |
|
1022 if (!isset($form['#build_id'])) { |
|
1023 $form['#build_id'] = 'form-' . drupal_random_key(); |
|
1024 } |
|
1025 $form['form_build_id'] = array( |
|
1026 '#type' => 'hidden', |
|
1027 '#value' => $form['#build_id'], |
|
1028 '#id' => $form['#build_id'], |
|
1029 '#name' => 'form_build_id', |
|
1030 // Form processing and validation requires this value, so ensure the |
|
1031 // submitted form value appears literally, regardless of custom #tree |
|
1032 // and #parents being set elsewhere. |
|
1033 '#parents' => array('form_build_id'), |
|
1034 ); |
|
1035 |
|
1036 // Add a token, based on either #token or form_id, to any form displayed to |
|
1037 // authenticated users. This ensures that any submitted form was actually |
|
1038 // requested previously by the user and protects against cross site request |
|
1039 // forgeries. |
|
1040 // This does not apply to programmatically submitted forms. Furthermore, since |
|
1041 // tokens are session-bound and forms displayed to anonymous users are very |
|
1042 // likely cached, we cannot assign a token for them. |
|
1043 // During installation, there is no $user yet. |
|
1044 if (!empty($user->uid) && !$form_state['programmed']) { |
|
1045 // Form constructors may explicitly set #token to FALSE when cross site |
|
1046 // request forgery is irrelevant to the form, such as search forms. |
|
1047 if (isset($form['#token']) && $form['#token'] === FALSE) { |
|
1048 unset($form['#token']); |
|
1049 } |
|
1050 // Otherwise, generate a public token based on the form id. |
|
1051 else { |
|
1052 $form['#token'] = $form_id; |
|
1053 $form['form_token'] = array( |
|
1054 '#id' => drupal_html_id('edit-' . $form_id . '-form-token'), |
|
1055 '#type' => 'token', |
|
1056 '#default_value' => drupal_get_token($form['#token']), |
|
1057 // Form processing and validation requires this value, so ensure the |
|
1058 // submitted form value appears literally, regardless of custom #tree |
|
1059 // and #parents being set elsewhere. |
|
1060 '#parents' => array('form_token'), |
|
1061 ); |
|
1062 } |
|
1063 } |
|
1064 |
|
1065 if (isset($form_id)) { |
|
1066 $form['form_id'] = array( |
|
1067 '#type' => 'hidden', |
|
1068 '#value' => $form_id, |
|
1069 '#id' => drupal_html_id("edit-$form_id"), |
|
1070 // Form processing and validation requires this value, so ensure the |
|
1071 // submitted form value appears literally, regardless of custom #tree |
|
1072 // and #parents being set elsewhere. |
|
1073 '#parents' => array('form_id'), |
|
1074 ); |
|
1075 } |
|
1076 if (!isset($form['#id'])) { |
|
1077 $form['#id'] = drupal_html_id($form_id); |
|
1078 } |
|
1079 |
|
1080 $form += element_info('form'); |
|
1081 $form += array('#tree' => FALSE, '#parents' => array()); |
|
1082 |
|
1083 if (!isset($form['#validate'])) { |
|
1084 // Ensure that modules can rely on #validate being set. |
|
1085 $form['#validate'] = array(); |
|
1086 // Check for a handler specific to $form_id. |
|
1087 if (function_exists($form_id . '_validate')) { |
|
1088 $form['#validate'][] = $form_id . '_validate'; |
|
1089 } |
|
1090 // Otherwise check whether this is a shared form and whether there is a |
|
1091 // handler for the shared $form_id. |
|
1092 elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_validate')) { |
|
1093 $form['#validate'][] = $form_state['build_info']['base_form_id'] . '_validate'; |
|
1094 } |
|
1095 } |
|
1096 |
|
1097 if (!isset($form['#submit'])) { |
|
1098 // Ensure that modules can rely on #submit being set. |
|
1099 $form['#submit'] = array(); |
|
1100 // Check for a handler specific to $form_id. |
|
1101 if (function_exists($form_id . '_submit')) { |
|
1102 $form['#submit'][] = $form_id . '_submit'; |
|
1103 } |
|
1104 // Otherwise check whether this is a shared form and whether there is a |
|
1105 // handler for the shared $form_id. |
|
1106 elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_submit')) { |
|
1107 $form['#submit'][] = $form_state['build_info']['base_form_id'] . '_submit'; |
|
1108 } |
|
1109 } |
|
1110 |
|
1111 // If no #theme has been set, automatically apply theme suggestions. |
|
1112 // theme_form() itself is in #theme_wrappers and not #theme. Therefore, the |
|
1113 // #theme function only has to care for rendering the inner form elements, |
|
1114 // not the form itself. |
|
1115 if (!isset($form['#theme'])) { |
|
1116 $form['#theme'] = array($form_id); |
|
1117 if (isset($form_state['build_info']['base_form_id'])) { |
|
1118 $form['#theme'][] = $form_state['build_info']['base_form_id']; |
|
1119 } |
|
1120 } |
|
1121 |
|
1122 // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and |
|
1123 // hook_form_FORM_ID_alter() implementations. |
|
1124 $hooks = array('form'); |
|
1125 if (isset($form_state['build_info']['base_form_id'])) { |
|
1126 $hooks[] = 'form_' . $form_state['build_info']['base_form_id']; |
|
1127 } |
|
1128 $hooks[] = 'form_' . $form_id; |
|
1129 drupal_alter($hooks, $form, $form_state, $form_id); |
|
1130 } |
|
1131 |
|
1132 /** |
|
1133 * Helper function to call form_set_error() if there is a token error. |
|
1134 */ |
|
1135 function _drupal_invalid_token_set_form_error() { |
|
1136 $path = current_path(); |
|
1137 $query = drupal_get_query_parameters(); |
|
1138 $url = url($path, array('query' => $query)); |
|
1139 |
|
1140 // Setting this error will cause the form to fail validation. |
|
1141 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))); |
|
1142 } |
|
1143 |
|
1144 /** |
|
1145 * Validates user-submitted form data in the $form_state array. |
|
1146 * |
|
1147 * @param $form_id |
|
1148 * A unique string identifying the form for validation, submission, |
|
1149 * theming, and hook_form_alter functions. |
|
1150 * @param $form |
|
1151 * An associative array containing the structure of the form, which is passed |
|
1152 * by reference. Form validation handlers are able to alter the form structure |
|
1153 * (like #process and #after_build callbacks during form building) in case of |
|
1154 * a validation error. If a validation handler alters the form structure, it |
|
1155 * is responsible for validating the values of changed form elements in |
|
1156 * $form_state['values'] to prevent form submit handlers from receiving |
|
1157 * unvalidated values. |
|
1158 * @param $form_state |
|
1159 * A keyed array containing the current state of the form. The current |
|
1160 * user-submitted data is stored in $form_state['values'], though |
|
1161 * form validation functions are passed an explicit copy of the |
|
1162 * values for the sake of simplicity. Validation handlers can also use |
|
1163 * $form_state to pass information on to submit handlers. For example: |
|
1164 * $form_state['data_for_submission'] = $data; |
|
1165 * This technique is useful when validation requires file parsing, |
|
1166 * web service requests, or other expensive requests that should |
|
1167 * not be repeated in the submission step. |
|
1168 */ |
|
1169 function drupal_validate_form($form_id, &$form, &$form_state) { |
|
1170 $validated_forms = &drupal_static(__FUNCTION__, array()); |
|
1171 |
|
1172 if (isset($validated_forms[$form_id]) && empty($form_state['must_validate'])) { |
|
1173 return; |
|
1174 } |
|
1175 |
|
1176 // If the session token was set by drupal_prepare_form(), ensure that it |
|
1177 // matches the current user's session. This is duplicate to code in |
|
1178 // form_builder() but left to protect any custom form handling code. |
|
1179 if (!empty($form['#token'])) { |
|
1180 if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) { |
|
1181 _drupal_invalid_token_set_form_error(); |
|
1182 // Stop here and don't run any further validation handlers, because they |
|
1183 // could invoke non-safe operations which opens the door for CSRF |
|
1184 // vulnerabilities. |
|
1185 $validated_forms[$form_id] = TRUE; |
|
1186 return; |
|
1187 } |
|
1188 } |
|
1189 |
|
1190 _form_validate($form, $form_state, $form_id); |
|
1191 $validated_forms[$form_id] = TRUE; |
|
1192 |
|
1193 // If validation errors are limited then remove any non validated form values, |
|
1194 // so that only values that passed validation are left for submit callbacks. |
|
1195 if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) { |
|
1196 $values = array(); |
|
1197 foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) { |
|
1198 // If the section exists within $form_state['values'], even if the value |
|
1199 // is NULL, copy it to $values. |
|
1200 $section_exists = NULL; |
|
1201 $value = drupal_array_get_nested_value($form_state['values'], $section, $section_exists); |
|
1202 if ($section_exists) { |
|
1203 drupal_array_set_nested_value($values, $section, $value); |
|
1204 } |
|
1205 } |
|
1206 // A button's #value does not require validation, so for convenience we |
|
1207 // allow the value of the clicked button to be retained in its normal |
|
1208 // $form_state['values'] locations, even if these locations are not included |
|
1209 // in #limit_validation_errors. |
|
1210 if (isset($form_state['triggering_element']['#button_type'])) { |
|
1211 $button_value = $form_state['triggering_element']['#value']; |
|
1212 |
|
1213 // Like all input controls, the button value may be in the location |
|
1214 // dictated by #parents. If it is, copy it to $values, but do not override |
|
1215 // what may already be in $values. |
|
1216 $parents = $form_state['triggering_element']['#parents']; |
|
1217 if (!drupal_array_nested_key_exists($values, $parents) && drupal_array_get_nested_value($form_state['values'], $parents) === $button_value) { |
|
1218 drupal_array_set_nested_value($values, $parents, $button_value); |
|
1219 } |
|
1220 |
|
1221 // Additionally, form_builder() places the button value in |
|
1222 // $form_state['values'][BUTTON_NAME]. If it's still there, after |
|
1223 // validation handlers have run, copy it to $values, but do not override |
|
1224 // what may already be in $values. |
|
1225 $name = $form_state['triggering_element']['#name']; |
|
1226 if (!isset($values[$name]) && isset($form_state['values'][$name]) && $form_state['values'][$name] === $button_value) { |
|
1227 $values[$name] = $button_value; |
|
1228 } |
|
1229 } |
|
1230 $form_state['values'] = $values; |
|
1231 } |
|
1232 } |
|
1233 |
|
1234 /** |
|
1235 * Redirects the user to a URL after a form has been processed. |
|
1236 * |
|
1237 * After a form is submitted and processed, normally the user should be |
|
1238 * redirected to a new destination page. This function figures out what that |
|
1239 * destination should be, based on the $form_state array and the 'destination' |
|
1240 * query string in the request URL, and redirects the user there. |
|
1241 * |
|
1242 * Usually (for exceptions, see below) $form_state['redirect'] determines where |
|
1243 * to redirect the user. This can be set either to a string (the path to |
|
1244 * redirect to), or an array of arguments for drupal_goto(). If |
|
1245 * $form_state['redirect'] is missing, the user is usually (again, see below for |
|
1246 * exceptions) redirected back to the page they came from, where they should see |
|
1247 * a fresh, unpopulated copy of the form. |
|
1248 * |
|
1249 * Here is an example of how to set up a form to redirect to the path 'node': |
|
1250 * @code |
|
1251 * $form_state['redirect'] = 'node'; |
|
1252 * @endcode |
|
1253 * And here is an example of how to redirect to 'node/123?foo=bar#baz': |
|
1254 * @code |
|
1255 * $form_state['redirect'] = array( |
|
1256 * 'node/123', |
|
1257 * array( |
|
1258 * 'query' => array( |
|
1259 * 'foo' => 'bar', |
|
1260 * ), |
|
1261 * 'fragment' => 'baz', |
|
1262 * ), |
|
1263 * ); |
|
1264 * @endcode |
|
1265 * |
|
1266 * There are several exceptions to the "usual" behavior described above: |
|
1267 * - If $form_state['programmed'] is TRUE, the form submission was usually |
|
1268 * invoked via drupal_form_submit(), so any redirection would break the script |
|
1269 * that invoked drupal_form_submit() and no redirection is done. |
|
1270 * - If $form_state['rebuild'] is TRUE, the form is being rebuilt, and no |
|
1271 * redirection is done. |
|
1272 * - If $form_state['no_redirect'] is TRUE, redirection is disabled. This is |
|
1273 * set, for instance, by ajax_get_form() to prevent redirection in Ajax |
|
1274 * callbacks. $form_state['no_redirect'] should never be set or altered by |
|
1275 * form builder functions or form validation/submit handlers. |
|
1276 * - If $form_state['redirect'] is set to FALSE, redirection is disabled. |
|
1277 * - If none of the above conditions has prevented redirection, then the |
|
1278 * redirect is accomplished by calling drupal_goto(), passing in the value of |
|
1279 * $form_state['redirect'] if it is set, or the current path if it is |
|
1280 * not. drupal_goto() preferentially uses the value of $_GET['destination'] |
|
1281 * (the 'destination' URL query string) if it is present, so this will |
|
1282 * override any values set by $form_state['redirect']. Note that during |
|
1283 * installation, install_goto() is called in place of drupal_goto(). |
|
1284 * |
|
1285 * @param $form_state |
|
1286 * An associative array containing the current state of the form. |
|
1287 * |
|
1288 * @see drupal_process_form() |
|
1289 * @see drupal_build_form() |
|
1290 */ |
|
1291 function drupal_redirect_form($form_state) { |
|
1292 // Skip redirection for form submissions invoked via drupal_form_submit(). |
|
1293 if (!empty($form_state['programmed'])) { |
|
1294 return; |
|
1295 } |
|
1296 // Skip redirection if rebuild is activated. |
|
1297 if (!empty($form_state['rebuild'])) { |
|
1298 return; |
|
1299 } |
|
1300 // Skip redirection if it was explicitly disallowed. |
|
1301 if (!empty($form_state['no_redirect'])) { |
|
1302 return; |
|
1303 } |
|
1304 // Only invoke drupal_goto() if redirect value was not set to FALSE. |
|
1305 if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) { |
|
1306 if (isset($form_state['redirect'])) { |
|
1307 if (is_array($form_state['redirect'])) { |
|
1308 call_user_func_array('drupal_goto', $form_state['redirect']); |
|
1309 } |
|
1310 else { |
|
1311 // This function can be called from the installer, which guarantees |
|
1312 // that $redirect will always be a string, so catch that case here |
|
1313 // and use the appropriate redirect function. |
|
1314 $function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto'; |
|
1315 $function($form_state['redirect']); |
|
1316 } |
|
1317 } |
|
1318 drupal_goto(current_path(), array('query' => drupal_get_query_parameters())); |
|
1319 } |
|
1320 } |
|
1321 |
|
1322 /** |
|
1323 * Performs validation on form elements. |
|
1324 * |
|
1325 * First ensures required fields are completed, #maxlength is not exceeded, and |
|
1326 * selected options were in the list of options given to the user. Then calls |
|
1327 * user-defined validators. |
|
1328 * |
|
1329 * @param $elements |
|
1330 * An associative array containing the structure of the form. |
|
1331 * @param $form_state |
|
1332 * A keyed array containing the current state of the form. The current |
|
1333 * user-submitted data is stored in $form_state['values'], though |
|
1334 * form validation functions are passed an explicit copy of the |
|
1335 * values for the sake of simplicity. Validation handlers can also |
|
1336 * $form_state to pass information on to submit handlers. For example: |
|
1337 * $form_state['data_for_submission'] = $data; |
|
1338 * This technique is useful when validation requires file parsing, |
|
1339 * web service requests, or other expensive requests that should |
|
1340 * not be repeated in the submission step. |
|
1341 * @param $form_id |
|
1342 * A unique string identifying the form for validation, submission, |
|
1343 * theming, and hook_form_alter functions. |
|
1344 */ |
|
1345 function _form_validate(&$elements, &$form_state, $form_id = NULL) { |
|
1346 // Also used in the installer, pre-database setup. |
|
1347 $t = get_t(); |
|
1348 |
|
1349 // Recurse through all children. |
|
1350 foreach (element_children($elements) as $key) { |
|
1351 if (isset($elements[$key]) && $elements[$key]) { |
|
1352 _form_validate($elements[$key], $form_state); |
|
1353 } |
|
1354 } |
|
1355 |
|
1356 // Validate the current input. |
|
1357 if (!isset($elements['#validated']) || !$elements['#validated']) { |
|
1358 // The following errors are always shown. |
|
1359 if (isset($elements['#needs_validation'])) { |
|
1360 // Verify that the value is not longer than #maxlength. |
|
1361 if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) { |
|
1362 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'])))); |
|
1363 } |
|
1364 |
|
1365 if (isset($elements['#options']) && isset($elements['#value'])) { |
|
1366 if ($elements['#type'] == 'select') { |
|
1367 $options = form_options_flatten($elements['#options']); |
|
1368 } |
|
1369 else { |
|
1370 $options = $elements['#options']; |
|
1371 } |
|
1372 if (is_array($elements['#value'])) { |
|
1373 $value = in_array($elements['#type'], array('checkboxes', 'tableselect')) ? array_keys($elements['#value']) : $elements['#value']; |
|
1374 foreach ($value as $v) { |
|
1375 if (!isset($options[$v])) { |
|
1376 form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.')); |
|
1377 watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR); |
|
1378 } |
|
1379 } |
|
1380 } |
|
1381 // Non-multiple select fields always have a value in HTML. If the user |
|
1382 // does not change the form, it will be the value of the first option. |
|
1383 // Because of this, form validation for the field will almost always |
|
1384 // pass, even if the user did not select anything. To work around this |
|
1385 // browser behavior, required select fields without a #default_value get |
|
1386 // an additional, first empty option. In case the submitted value is |
|
1387 // identical to the empty option's value, we reset the element's value |
|
1388 // to NULL to trigger the regular #required handling below. |
|
1389 // @see form_process_select() |
|
1390 elseif ($elements['#type'] == 'select' && !$elements['#multiple'] && $elements['#required'] && !isset($elements['#default_value']) && $elements['#value'] === $elements['#empty_value']) { |
|
1391 $elements['#value'] = NULL; |
|
1392 form_set_value($elements, NULL, $form_state); |
|
1393 } |
|
1394 elseif (!isset($options[$elements['#value']])) { |
|
1395 form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.')); |
|
1396 watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR); |
|
1397 } |
|
1398 } |
|
1399 } |
|
1400 |
|
1401 // While this element is being validated, it may be desired that some calls |
|
1402 // to form_set_error() be suppressed and not result in a form error, so |
|
1403 // that a button that implements low-risk functionality (such as "Previous" |
|
1404 // or "Add more") that doesn't require all user input to be valid can still |
|
1405 // have its submit handlers triggered. The triggering element's |
|
1406 // #limit_validation_errors property contains the information for which |
|
1407 // errors are needed, and all other errors are to be suppressed. The |
|
1408 // #limit_validation_errors property is ignored if submit handlers will run, |
|
1409 // but the element doesn't have a #submit property, because it's too large a |
|
1410 // security risk to have any invalid user input when executing form-level |
|
1411 // submit handlers. |
|
1412 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']))) { |
|
1413 form_set_error(NULL, '', $form_state['triggering_element']['#limit_validation_errors']); |
|
1414 } |
|
1415 // If submit handlers won't run (due to the submission having been triggered |
|
1416 // by an element whose #executes_submit_callback property isn't TRUE), then |
|
1417 // it's safe to suppress all validation errors, and we do so by default, |
|
1418 // which is particularly useful during an Ajax submission triggered by a |
|
1419 // non-button. An element can override this default by setting the |
|
1420 // #limit_validation_errors property. For button element types, |
|
1421 // #limit_validation_errors defaults to FALSE (via system_element_info()), |
|
1422 // so that full validation is their default behavior. |
|
1423 elseif (isset($form_state['triggering_element']) && !isset($form_state['triggering_element']['#limit_validation_errors']) && !$form_state['submitted']) { |
|
1424 form_set_error(NULL, '', array()); |
|
1425 } |
|
1426 // As an extra security measure, explicitly turn off error suppression if |
|
1427 // one of the above conditions wasn't met. Since this is also done at the |
|
1428 // end of this function, doing it here is only to handle the rare edge case |
|
1429 // where a validate handler invokes form processing of another form. |
|
1430 else { |
|
1431 drupal_static_reset('form_set_error:limit_validation_errors'); |
|
1432 } |
|
1433 |
|
1434 // Make sure a value is passed when the field is required. |
|
1435 if (isset($elements['#needs_validation']) && $elements['#required']) { |
|
1436 // A simple call to empty() will not cut it here as some fields, like |
|
1437 // checkboxes, can return a valid value of '0'. Instead, check the |
|
1438 // length if it's a string, and the item count if it's an array. |
|
1439 // An unchecked checkbox has a #value of integer 0, different than string |
|
1440 // '0', which could be a valid value. |
|
1441 $is_empty_multiple = (!count($elements['#value'])); |
|
1442 $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); |
|
1443 $is_empty_value = ($elements['#value'] === 0); |
|
1444 if ($is_empty_multiple || $is_empty_string || $is_empty_value) { |
|
1445 // Although discouraged, a #title is not mandatory for form elements. In |
|
1446 // case there is no #title, we cannot set a form error message. |
|
1447 // Instead of setting no #title, form constructors are encouraged to set |
|
1448 // #title_display to 'invisible' to improve accessibility. |
|
1449 if (isset($elements['#title'])) { |
|
1450 form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); |
|
1451 } |
|
1452 else { |
|
1453 form_error($elements); |
|
1454 } |
|
1455 } |
|
1456 } |
|
1457 |
|
1458 // Call user-defined form level validators. |
|
1459 if (isset($form_id)) { |
|
1460 form_execute_handlers('validate', $elements, $form_state); |
|
1461 } |
|
1462 // Call any element-specific validators. These must act on the element |
|
1463 // #value data. |
|
1464 elseif (isset($elements['#element_validate'])) { |
|
1465 foreach ($elements['#element_validate'] as $function) { |
|
1466 $function($elements, $form_state, $form_state['complete form']); |
|
1467 } |
|
1468 } |
|
1469 $elements['#validated'] = TRUE; |
|
1470 } |
|
1471 |
|
1472 // Done validating this element, so turn off error suppression. |
|
1473 // _form_validate() turns it on again when starting on the next element, if |
|
1474 // it's still appropriate to do so. |
|
1475 drupal_static_reset('form_set_error:limit_validation_errors'); |
|
1476 } |
|
1477 |
|
1478 /** |
|
1479 * Executes custom validation and submission handlers for a given form. |
|
1480 * |
|
1481 * Button-specific handlers are checked first. If none exist, the function |
|
1482 * falls back to form-level handlers. |
|
1483 * |
|
1484 * @param $type |
|
1485 * The type of handler to execute. 'validate' or 'submit' are the |
|
1486 * defaults used by Form API. |
|
1487 * @param $form |
|
1488 * An associative array containing the structure of the form. |
|
1489 * @param $form_state |
|
1490 * A keyed array containing the current state of the form. If the user |
|
1491 * submitted the form by clicking a button with custom handler functions |
|
1492 * defined, those handlers will be stored here. |
|
1493 */ |
|
1494 function form_execute_handlers($type, &$form, &$form_state) { |
|
1495 $return = FALSE; |
|
1496 // If there was a button pressed, use its handlers. |
|
1497 if (isset($form_state[$type . '_handlers'])) { |
|
1498 $handlers = $form_state[$type . '_handlers']; |
|
1499 } |
|
1500 // Otherwise, check for a form-level handler. |
|
1501 elseif (isset($form['#' . $type])) { |
|
1502 $handlers = $form['#' . $type]; |
|
1503 } |
|
1504 else { |
|
1505 $handlers = array(); |
|
1506 } |
|
1507 |
|
1508 foreach ($handlers as $function) { |
|
1509 // Check if a previous _submit handler has set a batch, but make sure we |
|
1510 // do not react to a batch that is already being processed (for instance |
|
1511 // if a batch operation performs a drupal_form_submit()). |
|
1512 if ($type == 'submit' && ($batch =& batch_get()) && !isset($batch['id'])) { |
|
1513 // Some previous submit handler has set a batch. To ensure correct |
|
1514 // execution order, store the call in a special 'control' batch set. |
|
1515 // See _batch_next_set(). |
|
1516 $batch['sets'][] = array('form_submit' => $function); |
|
1517 $batch['has_form_submits'] = TRUE; |
|
1518 } |
|
1519 else { |
|
1520 $function($form, $form_state); |
|
1521 } |
|
1522 $return = TRUE; |
|
1523 } |
|
1524 return $return; |
|
1525 } |
|
1526 |
|
1527 /** |
|
1528 * Files an error against a form element. |
|
1529 * |
|
1530 * When a validation error is detected, the validator calls form_set_error() to |
|
1531 * indicate which element needs to be changed and provide an error message. This |
|
1532 * causes the Form API to not execute the form submit handlers, and instead to |
|
1533 * re-display the form to the user with the corresponding elements rendered with |
|
1534 * an 'error' CSS class (shown as red by default). |
|
1535 * |
|
1536 * The standard form_set_error() behavior can be changed if a button provides |
|
1537 * the #limit_validation_errors property. Multistep forms not wanting to |
|
1538 * validate the whole form can set #limit_validation_errors on buttons to |
|
1539 * limit validation errors to only certain elements. For example, pressing the |
|
1540 * "Previous" button in a multistep form should not fire validation errors just |
|
1541 * because the current step has invalid values. If #limit_validation_errors is |
|
1542 * set on a clicked button, the button must also define a #submit property |
|
1543 * (may be set to an empty array). Any #submit handlers will be executed even if |
|
1544 * there is invalid input, so extreme care should be taken with respect to any |
|
1545 * actions taken by them. This is typically not a problem with buttons like |
|
1546 * "Previous" or "Add more" that do not invoke persistent storage of the |
|
1547 * submitted form values. Do not use the #limit_validation_errors property on |
|
1548 * buttons that trigger saving of form values to the database. |
|
1549 * |
|
1550 * The #limit_validation_errors property is a list of "sections" within |
|
1551 * $form_state['values'] that must contain valid values. Each "section" is an |
|
1552 * array with the ordered set of keys needed to reach that part of |
|
1553 * $form_state['values'] (i.e., the #parents property of the element). |
|
1554 * |
|
1555 * Example 1: Allow the "Previous" button to function, regardless of whether any |
|
1556 * user input is valid. |
|
1557 * |
|
1558 * @code |
|
1559 * $form['actions']['previous'] = array( |
|
1560 * '#type' => 'submit', |
|
1561 * '#value' => t('Previous'), |
|
1562 * '#limit_validation_errors' => array(), // No validation. |
|
1563 * '#submit' => array('some_submit_function'), // #submit required. |
|
1564 * ); |
|
1565 * @endcode |
|
1566 * |
|
1567 * Example 2: Require some, but not all, user input to be valid to process the |
|
1568 * submission of a "Previous" button. |
|
1569 * |
|
1570 * @code |
|
1571 * $form['actions']['previous'] = array( |
|
1572 * '#type' => 'submit', |
|
1573 * '#value' => t('Previous'), |
|
1574 * '#limit_validation_errors' => array( |
|
1575 * array('step1'), // Validate $form_state['values']['step1']. |
|
1576 * array('foo', 'bar'), // Validate $form_state['values']['foo']['bar']. |
|
1577 * ), |
|
1578 * '#submit' => array('some_submit_function'), // #submit required. |
|
1579 * ); |
|
1580 * @endcode |
|
1581 * |
|
1582 * This will require $form_state['values']['step1'] and everything within it |
|
1583 * (for example, $form_state['values']['step1']['choice']) to be valid, so |
|
1584 * calls to form_set_error('step1', $message) or |
|
1585 * form_set_error('step1][choice', $message) will prevent the submit handlers |
|
1586 * from running, and result in the error message being displayed to the user. |
|
1587 * However, calls to form_set_error('step2', $message) and |
|
1588 * form_set_error('step2][groupX][choiceY', $message) will be suppressed, |
|
1589 * resulting in the message not being displayed to the user, and the submit |
|
1590 * handlers will run despite $form_state['values']['step2'] and |
|
1591 * $form_state['values']['step2']['groupX']['choiceY'] containing invalid |
|
1592 * values. Errors for an invalid $form_state['values']['foo'] will be |
|
1593 * suppressed, but errors flagging invalid values for |
|
1594 * $form_state['values']['foo']['bar'] and everything within it will be |
|
1595 * flagged and submission prevented. |
|
1596 * |
|
1597 * Partial form validation is implemented by suppressing errors rather than by |
|
1598 * skipping the input processing and validation steps entirely, because some |
|
1599 * forms have button-level submit handlers that call Drupal API functions that |
|
1600 * assume that certain data exists within $form_state['values'], and while not |
|
1601 * doing anything with that data that requires it to be valid, PHP errors |
|
1602 * would be triggered if the input processing and validation steps were fully |
|
1603 * skipped. |
|
1604 * |
|
1605 * @param $name |
|
1606 * The name of the form element. If the #parents property of your form |
|
1607 * element is array('foo', 'bar', 'baz') then you may set an error on 'foo' |
|
1608 * or 'foo][bar][baz'. Setting an error on 'foo' sets an error for every |
|
1609 * element where the #parents array starts with 'foo'. |
|
1610 * @param $message |
|
1611 * The error message to present to the user. |
|
1612 * @param $limit_validation_errors |
|
1613 * Internal use only. The #limit_validation_errors property of the clicked |
|
1614 * button, if it exists. |
|
1615 * |
|
1616 * @return |
|
1617 * Return value is for internal use only. To get a list of errors, use |
|
1618 * form_get_errors() or form_get_error(). |
|
1619 * |
|
1620 * @see http://drupal.org/node/370537 |
|
1621 * @see http://drupal.org/node/763376 |
|
1622 */ |
|
1623 function form_set_error($name = NULL, $message = '', $limit_validation_errors = NULL) { |
|
1624 $form = &drupal_static(__FUNCTION__, array()); |
|
1625 $sections = &drupal_static(__FUNCTION__ . ':limit_validation_errors'); |
|
1626 if (isset($limit_validation_errors)) { |
|
1627 $sections = $limit_validation_errors; |
|
1628 } |
|
1629 |
|
1630 if (isset($name) && !isset($form[$name])) { |
|
1631 $record = TRUE; |
|
1632 if (isset($sections)) { |
|
1633 // #limit_validation_errors is an array of "sections" within which user |
|
1634 // input must be valid. If the element is within one of these sections, |
|
1635 // the error must be recorded. Otherwise, it can be suppressed. |
|
1636 // #limit_validation_errors can be an empty array, in which case all |
|
1637 // errors are suppressed. For example, a "Previous" button might want its |
|
1638 // submit action to be triggered even if none of the submitted values are |
|
1639 // valid. |
|
1640 $record = FALSE; |
|
1641 foreach ($sections as $section) { |
|
1642 // Exploding by '][' reconstructs the element's #parents. If the |
|
1643 // reconstructed #parents begin with the same keys as the specified |
|
1644 // section, then the element's values are within the part of |
|
1645 // $form_state['values'] that the clicked button requires to be valid, |
|
1646 // so errors for this element must be recorded. As the exploded array |
|
1647 // will all be strings, we need to cast every value of the section |
|
1648 // array to string. |
|
1649 if (array_slice(explode('][', $name), 0, count($section)) === array_map('strval', $section)) { |
|
1650 $record = TRUE; |
|
1651 break; |
|
1652 } |
|
1653 } |
|
1654 } |
|
1655 if ($record) { |
|
1656 $form[$name] = $message; |
|
1657 if ($message) { |
|
1658 drupal_set_message($message, 'error'); |
|
1659 } |
|
1660 } |
|
1661 } |
|
1662 |
|
1663 return $form; |
|
1664 } |
|
1665 |
|
1666 /** |
|
1667 * Clears all errors against all form elements made by form_set_error(). |
|
1668 */ |
|
1669 function form_clear_error() { |
|
1670 drupal_static_reset('form_set_error'); |
|
1671 } |
|
1672 |
|
1673 /** |
|
1674 * Returns an associative array of all errors. |
|
1675 */ |
|
1676 function form_get_errors() { |
|
1677 $form = form_set_error(); |
|
1678 if (!empty($form)) { |
|
1679 return $form; |
|
1680 } |
|
1681 } |
|
1682 |
|
1683 /** |
|
1684 * Returns the error message filed against the given form element. |
|
1685 * |
|
1686 * Form errors higher up in the form structure override deeper errors as well as |
|
1687 * errors on the element itself. |
|
1688 */ |
|
1689 function form_get_error($element) { |
|
1690 $form = form_set_error(); |
|
1691 $parents = array(); |
|
1692 foreach ($element['#parents'] as $parent) { |
|
1693 $parents[] = $parent; |
|
1694 $key = implode('][', $parents); |
|
1695 if (isset($form[$key])) { |
|
1696 return $form[$key]; |
|
1697 } |
|
1698 } |
|
1699 } |
|
1700 |
|
1701 /** |
|
1702 * Flags an element as having an error. |
|
1703 */ |
|
1704 function form_error(&$element, $message = '') { |
|
1705 form_set_error(implode('][', $element['#parents']), $message); |
|
1706 } |
|
1707 |
|
1708 /** |
|
1709 * Builds and processes all elements in the structured form array. |
|
1710 * |
|
1711 * Adds any required properties to each element, maps the incoming input data |
|
1712 * to the proper elements, and executes any #process handlers attached to a |
|
1713 * specific element. |
|
1714 * |
|
1715 * This is one of the three primary functions that recursively iterates a form |
|
1716 * array. This one does it for completing the form building process. The other |
|
1717 * two are _form_validate() (invoked via drupal_validate_form() and used to |
|
1718 * invoke validation logic for each element) and drupal_render() (for rendering |
|
1719 * each element). Each of these three pipelines provides ample opportunity for |
|
1720 * modules to customize what happens. For example, during this function's life |
|
1721 * cycle, the following functions get called for each element: |
|
1722 * - $element['#value_callback']: A function that implements how user input is |
|
1723 * mapped to an element's #value property. This defaults to a function named |
|
1724 * 'form_type_TYPE_value' where TYPE is $element['#type']. |
|
1725 * - $element['#process']: An array of functions called after user input has |
|
1726 * been mapped to the element's #value property. These functions can be used |
|
1727 * to dynamically add child elements: for example, for the 'date' element |
|
1728 * type, one of the functions in this array is form_process_date(), which adds |
|
1729 * the individual 'year', 'month', 'day', etc. child elements. These functions |
|
1730 * can also be used to set additional properties or implement special logic |
|
1731 * other than adding child elements: for example, for the 'fieldset' element |
|
1732 * type, one of the functions in this array is form_process_fieldset(), which |
|
1733 * adds the attributes and JavaScript needed to make the fieldset collapsible |
|
1734 * if the #collapsible property is set. The #process functions are called in |
|
1735 * preorder traversal, meaning they are called for the parent element first, |
|
1736 * then for the child elements. |
|
1737 * - $element['#after_build']: An array of functions called after form_builder() |
|
1738 * is done with its processing of the element. These are called in postorder |
|
1739 * traversal, meaning they are called for the child elements first, then for |
|
1740 * the parent element. |
|
1741 * There are similar properties containing callback functions invoked by |
|
1742 * _form_validate() and drupal_render(), appropriate for those operations. |
|
1743 * |
|
1744 * Developers are strongly encouraged to integrate the functionality needed by |
|
1745 * their form or module within one of these three pipelines, using the |
|
1746 * appropriate callback property, rather than implementing their own recursive |
|
1747 * traversal of a form array. This facilitates proper integration between |
|
1748 * multiple modules. For example, module developers are familiar with the |
|
1749 * relative order in which hook_form_alter() implementations and #process |
|
1750 * functions run. A custom traversal function that affects the building of a |
|
1751 * form is likely to not integrate with hook_form_alter() and #process in the |
|
1752 * expected way. Also, deep recursion within PHP is both slow and memory |
|
1753 * intensive, so it is best to minimize how often it's done. |
|
1754 * |
|
1755 * As stated above, each element's #process functions are executed after its |
|
1756 * #value has been set. This enables those functions to execute conditional |
|
1757 * logic based on the current value. However, all of form_builder() runs before |
|
1758 * drupal_validate_form() is called, so during #process function execution, the |
|
1759 * element's #value has not yet been validated, so any code that requires |
|
1760 * validated values must reside within a submit handler. |
|
1761 * |
|
1762 * As a security measure, user input is used for an element's #value only if the |
|
1763 * element exists within $form, is not disabled (as per the #disabled property), |
|
1764 * and can be accessed (as per the #access property, except that forms submitted |
|
1765 * using drupal_form_submit() bypass #access restrictions). When user input is |
|
1766 * ignored due to #disabled and #access restrictions, the element's default |
|
1767 * value is used. |
|
1768 * |
|
1769 * Because of the preorder traversal, where #process functions of an element run |
|
1770 * before user input for its child elements is processed, and because of the |
|
1771 * Form API security of user input processing with respect to #access and |
|
1772 * #disabled described above, this generally means that #process functions |
|
1773 * should not use an element's (unvalidated) #value to affect the #disabled or |
|
1774 * #access of child elements. Use-cases where a developer may be tempted to |
|
1775 * implement such conditional logic usually fall into one of two categories: |
|
1776 * - Where user input from the current submission must affect the structure of a |
|
1777 * form, including properties like #access and #disabled that affect how the |
|
1778 * next submission needs to be processed, a multi-step workflow is needed. |
|
1779 * This is most commonly implemented with a submit handler setting persistent |
|
1780 * data within $form_state based on *validated* values in |
|
1781 * $form_state['values'] and setting $form_state['rebuild']. The form building |
|
1782 * functions must then be implemented to use the $form_state data to rebuild |
|
1783 * the form with the structure appropriate for the new state. |
|
1784 * - Where user input must affect the rendering of the form without affecting |
|
1785 * its structure, the necessary conditional rendering logic should reside |
|
1786 * within functions that run during the rendering phase (#pre_render, #theme, |
|
1787 * #theme_wrappers, and #post_render). |
|
1788 * |
|
1789 * @param $form_id |
|
1790 * A unique string identifying the form for validation, submission, |
|
1791 * theming, and hook_form_alter functions. |
|
1792 * @param $element |
|
1793 * An associative array containing the structure of the current element. |
|
1794 * @param $form_state |
|
1795 * A keyed array containing the current state of the form. In this |
|
1796 * context, it is used to accumulate information about which button |
|
1797 * was clicked when the form was submitted, as well as the sanitized |
|
1798 * $_POST data. |
|
1799 */ |
|
1800 function form_builder($form_id, &$element, &$form_state) { |
|
1801 // Initialize as unprocessed. |
|
1802 $element['#processed'] = FALSE; |
|
1803 |
|
1804 // Use element defaults. |
|
1805 if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = element_info($element['#type']))) { |
|
1806 // Overlay $info onto $element, retaining preexisting keys in $element. |
|
1807 $element += $info; |
|
1808 $element['#defaults_loaded'] = TRUE; |
|
1809 } |
|
1810 // Assign basic defaults common for all form elements. |
|
1811 $element += array( |
|
1812 '#required' => FALSE, |
|
1813 '#attributes' => array(), |
|
1814 '#title_display' => 'before', |
|
1815 ); |
|
1816 |
|
1817 // Special handling if we're on the top level form element. |
|
1818 if (isset($element['#type']) && $element['#type'] == 'form') { |
|
1819 if (!empty($element['#https']) && variable_get('https', FALSE) && |
|
1820 !url_is_external($element['#action'])) { |
|
1821 global $base_root; |
|
1822 |
|
1823 // Not an external URL so ensure that it is secure. |
|
1824 $element['#action'] = str_replace('http://', 'https://', $base_root) . $element['#action']; |
|
1825 } |
|
1826 |
|
1827 // Store a reference to the complete form in $form_state prior to building |
|
1828 // the form. This allows advanced #process and #after_build callbacks to |
|
1829 // perform changes elsewhere in the form. |
|
1830 $form_state['complete form'] = &$element; |
|
1831 |
|
1832 // Set a flag if we have a correct form submission. This is always TRUE for |
|
1833 // programmed forms coming from drupal_form_submit(), or if the form_id coming |
|
1834 // from the POST data is set and matches the current form_id. |
|
1835 if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) { |
|
1836 $form_state['process_input'] = TRUE; |
|
1837 // If the session token was set by drupal_prepare_form(), ensure that it |
|
1838 // matches the current user's session. |
|
1839 $form_state['invalid_token'] = FALSE; |
|
1840 if (!empty($element['#token'])) { |
|
1841 if (empty($form_state['input']['form_token']) || !drupal_valid_token($form_state['input']['form_token'], $element['#token'])) { |
|
1842 // Set an early form error to block certain input processing since that |
|
1843 // opens the door for CSRF vulnerabilities. |
|
1844 _drupal_invalid_token_set_form_error(); |
|
1845 // This value is checked in _form_builder_handle_input_element(). |
|
1846 $form_state['invalid_token'] = TRUE; |
|
1847 // Make sure file uploads do not get processed. |
|
1848 $_FILES = array(); |
|
1849 } |
|
1850 } |
|
1851 } |
|
1852 else { |
|
1853 $form_state['process_input'] = FALSE; |
|
1854 } |
|
1855 |
|
1856 // All form elements should have an #array_parents property. |
|
1857 $element['#array_parents'] = array(); |
|
1858 } |
|
1859 |
|
1860 if (!isset($element['#id'])) { |
|
1861 $element['#id'] = drupal_html_id('edit-' . implode('-', $element['#parents'])); |
|
1862 } |
|
1863 // Handle input elements. |
|
1864 if (!empty($element['#input'])) { |
|
1865 _form_builder_handle_input_element($form_id, $element, $form_state); |
|
1866 } |
|
1867 // Allow for elements to expand to multiple elements, e.g., radios, |
|
1868 // checkboxes and files. |
|
1869 if (isset($element['#process']) && !$element['#processed']) { |
|
1870 foreach ($element['#process'] as $process) { |
|
1871 $element = $process($element, $form_state, $form_state['complete form']); |
|
1872 } |
|
1873 $element['#processed'] = TRUE; |
|
1874 } |
|
1875 |
|
1876 // We start off assuming all form elements are in the correct order. |
|
1877 $element['#sorted'] = TRUE; |
|
1878 |
|
1879 // Recurse through all child elements. |
|
1880 $count = 0; |
|
1881 foreach (element_children($element) as $key) { |
|
1882 // Prior to checking properties of child elements, their default properties |
|
1883 // need to be loaded. |
|
1884 if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = element_info($element[$key]['#type']))) { |
|
1885 $element[$key] += $info; |
|
1886 $element[$key]['#defaults_loaded'] = TRUE; |
|
1887 } |
|
1888 |
|
1889 // Don't squash an existing tree value. |
|
1890 if (!isset($element[$key]['#tree'])) { |
|
1891 $element[$key]['#tree'] = $element['#tree']; |
|
1892 } |
|
1893 |
|
1894 // Deny access to child elements if parent is denied. |
|
1895 if (isset($element['#access']) && !$element['#access']) { |
|
1896 $element[$key]['#access'] = FALSE; |
|
1897 } |
|
1898 |
|
1899 // Make child elements inherit their parent's #disabled and #allow_focus |
|
1900 // values unless they specify their own. |
|
1901 foreach (array('#disabled', '#allow_focus') as $property) { |
|
1902 if (isset($element[$property]) && !isset($element[$key][$property])) { |
|
1903 $element[$key][$property] = $element[$property]; |
|
1904 } |
|
1905 } |
|
1906 |
|
1907 // Don't squash existing parents value. |
|
1908 if (!isset($element[$key]['#parents'])) { |
|
1909 // Check to see if a tree of child elements is present. If so, |
|
1910 // continue down the tree if required. |
|
1911 $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key); |
|
1912 } |
|
1913 // Ensure #array_parents follows the actual form structure. |
|
1914 $array_parents = $element['#array_parents']; |
|
1915 $array_parents[] = $key; |
|
1916 $element[$key]['#array_parents'] = $array_parents; |
|
1917 |
|
1918 // Assign a decimal placeholder weight to preserve original array order. |
|
1919 if (!isset($element[$key]['#weight'])) { |
|
1920 $element[$key]['#weight'] = $count/1000; |
|
1921 } |
|
1922 else { |
|
1923 // If one of the child elements has a weight then we will need to sort |
|
1924 // later. |
|
1925 unset($element['#sorted']); |
|
1926 } |
|
1927 $element[$key] = form_builder($form_id, $element[$key], $form_state); |
|
1928 $count++; |
|
1929 } |
|
1930 |
|
1931 // The #after_build flag allows any piece of a form to be altered |
|
1932 // after normal input parsing has been completed. |
|
1933 if (isset($element['#after_build']) && !isset($element['#after_build_done'])) { |
|
1934 foreach ($element['#after_build'] as $function) { |
|
1935 $element = $function($element, $form_state); |
|
1936 } |
|
1937 $element['#after_build_done'] = TRUE; |
|
1938 } |
|
1939 |
|
1940 // If there is a file element, we need to flip a flag so later the |
|
1941 // form encoding can be set. |
|
1942 if (isset($element['#type']) && $element['#type'] == 'file') { |
|
1943 $form_state['has_file_element'] = TRUE; |
|
1944 } |
|
1945 |
|
1946 // Final tasks for the form element after form_builder() has run for all other |
|
1947 // elements. |
|
1948 if (isset($element['#type']) && $element['#type'] == 'form') { |
|
1949 // If there is a file element, we set the form encoding. |
|
1950 if (isset($form_state['has_file_element'])) { |
|
1951 $element['#attributes']['enctype'] = 'multipart/form-data'; |
|
1952 } |
|
1953 |
|
1954 // Allow Ajax submissions to the form action to bypass verification. This is |
|
1955 // especially useful for multipart forms, which cannot be verified via a |
|
1956 // response header. |
|
1957 $element['#attached']['js'][] = array( |
|
1958 'type' => 'setting', |
|
1959 'data' => array( |
|
1960 'urlIsAjaxTrusted' => array( |
|
1961 $element['#action'] => TRUE, |
|
1962 ), |
|
1963 ), |
|
1964 ); |
|
1965 |
|
1966 // If a form contains a single textfield, and the ENTER key is pressed |
|
1967 // within it, Internet Explorer submits the form with no POST data |
|
1968 // identifying any submit button. Other browsers submit POST data as though |
|
1969 // the user clicked the first button. Therefore, to be as consistent as we |
|
1970 // can be across browsers, if no 'triggering_element' has been identified |
|
1971 // yet, default it to the first button. |
|
1972 if (!$form_state['programmed'] && !isset($form_state['triggering_element']) && !empty($form_state['buttons'])) { |
|
1973 $form_state['triggering_element'] = $form_state['buttons'][0]; |
|
1974 } |
|
1975 |
|
1976 // If the triggering element specifies "button-level" validation and submit |
|
1977 // handlers to run instead of the default form-level ones, then add those to |
|
1978 // the form state. |
|
1979 foreach (array('validate', 'submit') as $type) { |
|
1980 if (isset($form_state['triggering_element']['#' . $type])) { |
|
1981 $form_state[$type . '_handlers'] = $form_state['triggering_element']['#' . $type]; |
|
1982 } |
|
1983 } |
|
1984 |
|
1985 // If the triggering element executes submit handlers, then set the form |
|
1986 // state key that's needed for those handlers to run. |
|
1987 if (!empty($form_state['triggering_element']['#executes_submit_callback'])) { |
|
1988 $form_state['submitted'] = TRUE; |
|
1989 } |
|
1990 |
|
1991 // Special processing if the triggering element is a button. |
|
1992 if (isset($form_state['triggering_element']['#button_type'])) { |
|
1993 // Because there are several ways in which the triggering element could |
|
1994 // have been determined (including from input variables set by JavaScript |
|
1995 // or fallback behavior implemented for IE), and because buttons often |
|
1996 // have their #name property not derived from their #parents property, we |
|
1997 // can't assume that input processing that's happened up until here has |
|
1998 // resulted in $form_state['values'][BUTTON_NAME] being set. But it's |
|
1999 // common for forms to have several buttons named 'op' and switch on |
|
2000 // $form_state['values']['op'] during submit handler execution. |
|
2001 $form_state['values'][$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value']; |
|
2002 |
|
2003 // @todo Legacy support. Remove in Drupal 8. |
|
2004 $form_state['clicked_button'] = $form_state['triggering_element']; |
|
2005 } |
|
2006 } |
|
2007 return $element; |
|
2008 } |
|
2009 |
|
2010 /** |
|
2011 * Adds the #name and #value properties of an input element before rendering. |
|
2012 */ |
|
2013 function _form_builder_handle_input_element($form_id, &$element, &$form_state) { |
|
2014 static $safe_core_value_callbacks = array( |
|
2015 'form_type_token_value', |
|
2016 'form_type_textarea_value', |
|
2017 'form_type_textfield_value', |
|
2018 'form_type_checkbox_value', |
|
2019 'form_type_checkboxes_value', |
|
2020 'form_type_radios_value', |
|
2021 'form_type_password_confirm_value', |
|
2022 'form_type_select_value', |
|
2023 'form_type_tableselect_value', |
|
2024 'list_boolean_allowed_values_callback', |
|
2025 ); |
|
2026 |
|
2027 if (!isset($element['#name'])) { |
|
2028 $name = array_shift($element['#parents']); |
|
2029 $element['#name'] = $name; |
|
2030 if ($element['#type'] == 'file') { |
|
2031 // To make it easier to handle $_FILES in file.inc, we place all |
|
2032 // file fields in the 'files' array. Also, we do not support |
|
2033 // nested file names. |
|
2034 $element['#name'] = 'files[' . $element['#name'] . ']'; |
|
2035 } |
|
2036 elseif (count($element['#parents'])) { |
|
2037 $element['#name'] .= '[' . implode('][', $element['#parents']) . ']'; |
|
2038 } |
|
2039 array_unshift($element['#parents'], $name); |
|
2040 } |
|
2041 |
|
2042 // Setting #disabled to TRUE results in user input being ignored, regardless |
|
2043 // of how the element is themed or whether JavaScript is used to change the |
|
2044 // control's attributes. However, it's good UI to let the user know that input |
|
2045 // is not wanted for the control. HTML supports two attributes for this: |
|
2046 // http://www.w3.org/TR/html401/interact/forms.html#h-17.12. If a form wants |
|
2047 // to start a control off with one of these attributes for UI purposes only, |
|
2048 // but still allow input to be processed if it's sumitted, it can set the |
|
2049 // desired attribute in #attributes directly rather than using #disabled. |
|
2050 // However, developers should think carefully about the accessibility |
|
2051 // implications of doing so: if the form expects input to be enterable under |
|
2052 // some condition triggered by JavaScript, how would someone who has |
|
2053 // JavaScript disabled trigger that condition? Instead, developers should |
|
2054 // consider whether a multi-step form would be more appropriate (#disabled can |
|
2055 // be changed from step to step). If one still decides to use JavaScript to |
|
2056 // affect when a control is enabled, then it is best for accessibility for the |
|
2057 // control to be enabled in the HTML, and disabled by JavaScript on document |
|
2058 // ready. |
|
2059 if (!empty($element['#disabled'])) { |
|
2060 if (!empty($element['#allow_focus'])) { |
|
2061 $element['#attributes']['readonly'] = 'readonly'; |
|
2062 } |
|
2063 else { |
|
2064 $element['#attributes']['disabled'] = 'disabled'; |
|
2065 } |
|
2066 } |
|
2067 |
|
2068 // With JavaScript or other easy hacking, input can be submitted even for |
|
2069 // elements with #access=FALSE or #disabled=TRUE. For security, these must |
|
2070 // not be processed. Forms that set #disabled=TRUE on an element do not |
|
2071 // expect input for the element, and even forms submitted with |
|
2072 // drupal_form_submit() must not be able to get around this. Forms that set |
|
2073 // #access=FALSE on an element usually allow access for some users, so forms |
|
2074 // submitted with drupal_form_submit() may bypass access restriction and be |
|
2075 // treated as high-privilege users instead. |
|
2076 $process_input = empty($element['#disabled']) && (($form_state['programmed'] && $form_state['programmed_bypass_access_check']) || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))); |
|
2077 |
|
2078 // Set the element's #value property. |
|
2079 if (!isset($element['#value']) && !array_key_exists('#value', $element)) { |
|
2080 $value_callback = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value'; |
|
2081 if ($process_input) { |
|
2082 // Get the input for the current element. NULL values in the input need to |
|
2083 // be explicitly distinguished from missing input. (see below) |
|
2084 $input_exists = NULL; |
|
2085 $input = drupal_array_get_nested_value($form_state['input'], $element['#parents'], $input_exists); |
|
2086 // For browser-submitted forms, the submitted values do not contain values |
|
2087 // for certain elements (empty multiple select, unchecked checkbox). |
|
2088 // During initial form processing, we add explicit NULL values for such |
|
2089 // elements in $form_state['input']. When rebuilding the form, we can |
|
2090 // distinguish elements having NULL input from elements that were not part |
|
2091 // of the initially submitted form and can therefore use default values |
|
2092 // for the latter, if required. Programmatically submitted forms can |
|
2093 // submit explicit NULL values when calling drupal_form_submit(), so we do |
|
2094 // not modify $form_state['input'] for them. |
|
2095 if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) { |
|
2096 // Add the necessary parent keys to $form_state['input'] and sets the |
|
2097 // element's input value to NULL. |
|
2098 drupal_array_set_nested_value($form_state['input'], $element['#parents'], NULL); |
|
2099 $input_exists = TRUE; |
|
2100 } |
|
2101 // If we have input for the current element, assign it to the #value |
|
2102 // property, optionally filtered through $value_callback. |
|
2103 if ($input_exists) { |
|
2104 if (function_exists($value_callback)) { |
|
2105 // Skip all value callbacks except safe ones like text if the CSRF |
|
2106 // token was invalid. |
|
2107 if (empty($form_state['invalid_token']) || in_array($value_callback, $safe_core_value_callbacks)) { |
|
2108 $element['#value'] = $value_callback($element, $input, $form_state); |
|
2109 } |
|
2110 else { |
|
2111 $input = NULL; |
|
2112 } |
|
2113 } |
|
2114 if (!isset($element['#value']) && isset($input)) { |
|
2115 $element['#value'] = $input; |
|
2116 } |
|
2117 } |
|
2118 // Mark all posted values for validation. |
|
2119 if (isset($element['#value']) || (!empty($element['#required']))) { |
|
2120 $element['#needs_validation'] = TRUE; |
|
2121 } |
|
2122 } |
|
2123 // Load defaults. |
|
2124 if (!isset($element['#value'])) { |
|
2125 // Call #type_value without a second argument to request default_value handling. |
|
2126 if (function_exists($value_callback)) { |
|
2127 $element['#value'] = $value_callback($element, FALSE, $form_state); |
|
2128 } |
|
2129 // Final catch. If we haven't set a value yet, use the explicit default value. |
|
2130 // Avoid image buttons (which come with garbage value), so we only get value |
|
2131 // for the button actually clicked. |
|
2132 if (!isset($element['#value']) && empty($element['#has_garbage_value'])) { |
|
2133 $element['#value'] = isset($element['#default_value']) ? $element['#default_value'] : ''; |
|
2134 } |
|
2135 } |
|
2136 } |
|
2137 |
|
2138 // Determine which element (if any) triggered the submission of the form and |
|
2139 // keep track of all the clickable buttons in the form for |
|
2140 // form_state_values_clean(). Enforce the same input processing restrictions |
|
2141 // as above. |
|
2142 if ($process_input) { |
|
2143 // Detect if the element triggered the submission via Ajax. |
|
2144 if (_form_element_triggered_scripted_submission($element, $form_state)) { |
|
2145 $form_state['triggering_element'] = $element; |
|
2146 } |
|
2147 |
|
2148 // If the form was submitted by the browser rather than via Ajax, then it |
|
2149 // can only have been triggered by a button, and we need to determine which |
|
2150 // button within the constraints of how browsers provide this information. |
|
2151 if (isset($element['#button_type'])) { |
|
2152 // All buttons in the form need to be tracked for |
|
2153 // form_state_values_clean() and for the form_builder() code that handles |
|
2154 // a form submission containing no button information in $_POST. |
|
2155 $form_state['buttons'][] = $element; |
|
2156 if (_form_button_was_clicked($element, $form_state)) { |
|
2157 $form_state['triggering_element'] = $element; |
|
2158 } |
|
2159 } |
|
2160 } |
|
2161 |
|
2162 // Set the element's value in $form_state['values'], but only, if its key |
|
2163 // does not exist yet (a #value_callback may have already populated it). |
|
2164 if (!drupal_array_nested_key_exists($form_state['values'], $element['#parents'])) { |
|
2165 form_set_value($element, $element['#value'], $form_state); |
|
2166 } |
|
2167 } |
|
2168 |
|
2169 /** |
|
2170 * Detects if an element triggered the form submission via Ajax. |
|
2171 * |
|
2172 * This detects button or non-button controls that trigger a form submission via |
|
2173 * Ajax or some other scriptable environment. These environments can set the |
|
2174 * special input key '_triggering_element_name' to identify the triggering |
|
2175 * element. If the name alone doesn't identify the element uniquely, the input |
|
2176 * key '_triggering_element_value' may also be set to require a match on element |
|
2177 * value. An example where this is needed is if there are several buttons all |
|
2178 * named 'op', and only differing in their value. |
|
2179 */ |
|
2180 function _form_element_triggered_scripted_submission($element, &$form_state) { |
|
2181 if (!empty($form_state['input']['_triggering_element_name']) && $element['#name'] == $form_state['input']['_triggering_element_name']) { |
|
2182 if (empty($form_state['input']['_triggering_element_value']) || $form_state['input']['_triggering_element_value'] == $element['#value']) { |
|
2183 return TRUE; |
|
2184 } |
|
2185 } |
|
2186 return FALSE; |
|
2187 } |
|
2188 |
|
2189 /** |
|
2190 * Determines if a given button triggered the form submission. |
|
2191 * |
|
2192 * This detects button controls that trigger a form submission by being clicked |
|
2193 * and having the click processed by the browser rather than being captured by |
|
2194 * JavaScript. Essentially, it detects if the button's name and value are part |
|
2195 * of the POST data, but with extra code to deal with the convoluted way in |
|
2196 * which browsers submit data for image button clicks. |
|
2197 * |
|
2198 * This does not detect button clicks processed by Ajax (that is done in |
|
2199 * _form_element_triggered_scripted_submission()) and it does not detect form |
|
2200 * submissions from Internet Explorer in response to an ENTER key pressed in a |
|
2201 * textfield (form_builder() has extra code for that). |
|
2202 * |
|
2203 * Because this function contains only part of the logic needed to determine |
|
2204 * $form_state['triggering_element'], it should not be called from anywhere |
|
2205 * other than within the Form API. Form validation and submit handlers needing |
|
2206 * to know which button was clicked should get that information from |
|
2207 * $form_state['triggering_element']. |
|
2208 */ |
|
2209 function _form_button_was_clicked($element, &$form_state) { |
|
2210 // First detect normal 'vanilla' button clicks. Traditionally, all |
|
2211 // standard buttons on a form share the same name (usually 'op'), |
|
2212 // and the specific return value is used to determine which was |
|
2213 // clicked. This ONLY works as long as $form['#name'] puts the |
|
2214 // value at the top level of the tree of $_POST data. |
|
2215 if (isset($form_state['input'][$element['#name']]) && $form_state['input'][$element['#name']] == $element['#value']) { |
|
2216 return TRUE; |
|
2217 } |
|
2218 // When image buttons are clicked, browsers do NOT pass the form element |
|
2219 // value in $_POST. Instead they pass an integer representing the |
|
2220 // coordinates of the click on the button image. This means that image |
|
2221 // buttons MUST have unique $form['#name'] values, but the details of |
|
2222 // their $_POST data should be ignored. |
|
2223 elseif (!empty($element['#has_garbage_value']) && isset($element['#value']) && $element['#value'] !== '') { |
|
2224 return TRUE; |
|
2225 } |
|
2226 return FALSE; |
|
2227 } |
|
2228 |
|
2229 /** |
|
2230 * Removes internal Form API elements and buttons from submitted form values. |
|
2231 * |
|
2232 * This function can be used when a module wants to store all submitted form |
|
2233 * values, for example, by serializing them into a single database column. In |
|
2234 * such cases, all internal Form API values and all form button elements should |
|
2235 * not be contained, and this function allows to remove them before the module |
|
2236 * proceeds to storage. Next to button elements, the following internal values |
|
2237 * are removed: |
|
2238 * - form_id |
|
2239 * - form_token |
|
2240 * - form_build_id |
|
2241 * - op |
|
2242 * |
|
2243 * @param $form_state |
|
2244 * A keyed array containing the current state of the form, including |
|
2245 * submitted form values; altered by reference. |
|
2246 */ |
|
2247 function form_state_values_clean(&$form_state) { |
|
2248 // Remove internal Form API values. |
|
2249 unset($form_state['values']['form_id'], $form_state['values']['form_token'], $form_state['values']['form_build_id'], $form_state['values']['op']); |
|
2250 |
|
2251 // Remove button values. |
|
2252 // form_builder() collects all button elements in a form. We remove the button |
|
2253 // value separately for each button element. |
|
2254 foreach ($form_state['buttons'] as $button) { |
|
2255 // Remove this button's value from the submitted form values by finding |
|
2256 // the value corresponding to this button. |
|
2257 // We iterate over the #parents of this button and move a reference to |
|
2258 // each parent in $form_state['values']. For example, if #parents is: |
|
2259 // array('foo', 'bar', 'baz') |
|
2260 // then the corresponding $form_state['values'] part will look like this: |
|
2261 // array( |
|
2262 // 'foo' => array( |
|
2263 // 'bar' => array( |
|
2264 // 'baz' => 'button_value', |
|
2265 // ), |
|
2266 // ), |
|
2267 // ) |
|
2268 // We start by (re)moving 'baz' to $last_parent, so we are able unset it |
|
2269 // at the end of the iteration. Initially, $values will contain a |
|
2270 // reference to $form_state['values'], but in the iteration we move the |
|
2271 // reference to $form_state['values']['foo'], and finally to |
|
2272 // $form_state['values']['foo']['bar'], which is the level where we can |
|
2273 // unset 'baz' (that is stored in $last_parent). |
|
2274 $parents = $button['#parents']; |
|
2275 $last_parent = array_pop($parents); |
|
2276 $key_exists = NULL; |
|
2277 $values = &drupal_array_get_nested_value($form_state['values'], $parents, $key_exists); |
|
2278 if ($key_exists && is_array($values)) { |
|
2279 unset($values[$last_parent]); |
|
2280 } |
|
2281 } |
|
2282 } |
|
2283 |
|
2284 /** |
|
2285 * Determines the value for an image button form element. |
|
2286 * |
|
2287 * @param $form |
|
2288 * The form element whose value is being populated. |
|
2289 * @param $input |
|
2290 * The incoming input to populate the form element. If this is FALSE, |
|
2291 * the element's default value should be returned. |
|
2292 * @param $form_state |
|
2293 * A keyed array containing the current state of the form. |
|
2294 * |
|
2295 * @return |
|
2296 * The data that will appear in the $form_state['values'] collection |
|
2297 * for this element. Return nothing to use the default. |
|
2298 */ |
|
2299 function form_type_image_button_value($form, $input, $form_state) { |
|
2300 if ($input !== FALSE) { |
|
2301 if (!empty($input)) { |
|
2302 // If we're dealing with Mozilla or Opera, we're lucky. It will |
|
2303 // return a proper value, and we can get on with things. |
|
2304 return $form['#return_value']; |
|
2305 } |
|
2306 else { |
|
2307 // Unfortunately, in IE we never get back a proper value for THIS |
|
2308 // form element. Instead, we get back two split values: one for the |
|
2309 // X and one for the Y coordinates on which the user clicked the |
|
2310 // button. We'll find this element in the #post data, and search |
|
2311 // in the same spot for its name, with '_x'. |
|
2312 $input = $form_state['input']; |
|
2313 foreach (explode('[', $form['#name']) as $element_name) { |
|
2314 // chop off the ] that may exist. |
|
2315 if (substr($element_name, -1) == ']') { |
|
2316 $element_name = substr($element_name, 0, -1); |
|
2317 } |
|
2318 |
|
2319 if (!isset($input[$element_name])) { |
|
2320 if (isset($input[$element_name . '_x'])) { |
|
2321 return $form['#return_value']; |
|
2322 } |
|
2323 return NULL; |
|
2324 } |
|
2325 $input = $input[$element_name]; |
|
2326 } |
|
2327 return $form['#return_value']; |
|
2328 } |
|
2329 } |
|
2330 } |
|
2331 |
|
2332 /** |
|
2333 * Determines the value for a checkbox form element. |
|
2334 * |
|
2335 * @param $form |
|
2336 * The form element whose value is being populated. |
|
2337 * @param $input |
|
2338 * The incoming input to populate the form element. If this is FALSE, |
|
2339 * the element's default value should be returned. |
|
2340 * |
|
2341 * @return |
|
2342 * The data that will appear in the $element_state['values'] collection |
|
2343 * for this element. Return nothing to use the default. |
|
2344 */ |
|
2345 function form_type_checkbox_value($element, $input = FALSE) { |
|
2346 if ($input === FALSE) { |
|
2347 // Use #default_value as the default value of a checkbox, except change |
|
2348 // NULL to 0, because _form_builder_handle_input_element() would otherwise |
|
2349 // replace NULL with empty string, but an empty string is a potentially |
|
2350 // valid value for a checked checkbox. |
|
2351 return isset($element['#default_value']) ? $element['#default_value'] : 0; |
|
2352 } |
|
2353 else { |
|
2354 // Checked checkboxes are submitted with a value (possibly '0' or ''): |
|
2355 // http://www.w3.org/TR/html401/interact/forms.html#successful-controls. |
|
2356 // For checked checkboxes, browsers submit the string version of |
|
2357 // #return_value, but we return the original #return_value. For unchecked |
|
2358 // checkboxes, browsers submit nothing at all, but |
|
2359 // _form_builder_handle_input_element() detects this, and calls this |
|
2360 // function with $input=NULL. Returning NULL from a value callback means to |
|
2361 // use the default value, which is not what is wanted when an unchecked |
|
2362 // checkbox is submitted, so we use integer 0 as the value indicating an |
|
2363 // unchecked checkbox. Therefore, modules must not use integer 0 as a |
|
2364 // #return_value, as doing so results in the checkbox always being treated |
|
2365 // as unchecked. The string '0' is allowed for #return_value. The most |
|
2366 // common use-case for setting #return_value to either 0 or '0' is for the |
|
2367 // first option within a 0-indexed array of checkboxes, and for this, |
|
2368 // form_process_checkboxes() uses the string rather than the integer. |
|
2369 return isset($input) ? $element['#return_value'] : 0; |
|
2370 } |
|
2371 } |
|
2372 |
|
2373 /** |
|
2374 * Determines the value for a checkboxes form element. |
|
2375 * |
|
2376 * @param $element |
|
2377 * The form element whose value is being populated. |
|
2378 * @param $input |
|
2379 * The incoming input to populate the form element. If this is FALSE, |
|
2380 * the element's default value should be returned. |
|
2381 * |
|
2382 * @return |
|
2383 * The data that will appear in the $element_state['values'] collection |
|
2384 * for this element. Return nothing to use the default. |
|
2385 */ |
|
2386 function form_type_checkboxes_value($element, $input = FALSE) { |
|
2387 if ($input === FALSE) { |
|
2388 $value = array(); |
|
2389 $element += array('#default_value' => array()); |
|
2390 foreach ($element['#default_value'] as $key) { |
|
2391 $value[$key] = $key; |
|
2392 } |
|
2393 return $value; |
|
2394 } |
|
2395 elseif (is_array($input)) { |
|
2396 // Programmatic form submissions use NULL to indicate that a checkbox |
|
2397 // should be unchecked; see drupal_form_submit(). We therefore remove all |
|
2398 // NULL elements from the array before constructing the return value, to |
|
2399 // simulate the behavior of web browsers (which do not send unchecked |
|
2400 // checkboxes to the server at all). This will not affect non-programmatic |
|
2401 // form submissions, since all values in $_POST are strings. |
|
2402 foreach ($input as $key => $value) { |
|
2403 if (!isset($value)) { |
|
2404 unset($input[$key]); |
|
2405 } |
|
2406 } |
|
2407 return drupal_map_assoc($input); |
|
2408 } |
|
2409 else { |
|
2410 return array(); |
|
2411 } |
|
2412 } |
|
2413 |
|
2414 /** |
|
2415 * Determines the value for a tableselect form element. |
|
2416 * |
|
2417 * @param $element |
|
2418 * The form element whose value is being populated. |
|
2419 * @param $input |
|
2420 * The incoming input to populate the form element. If this is FALSE, |
|
2421 * the element's default value should be returned. |
|
2422 * |
|
2423 * @return |
|
2424 * The data that will appear in the $element_state['values'] collection |
|
2425 * for this element. Return nothing to use the default. |
|
2426 */ |
|
2427 function form_type_tableselect_value($element, $input = FALSE) { |
|
2428 // If $element['#multiple'] == FALSE, then radio buttons are displayed and |
|
2429 // the default value handling is used. |
|
2430 if (isset($element['#multiple']) && $element['#multiple']) { |
|
2431 // Checkboxes are being displayed with the default value coming from the |
|
2432 // keys of the #default_value property. This differs from the checkboxes |
|
2433 // element which uses the array values. |
|
2434 if ($input === FALSE) { |
|
2435 $value = array(); |
|
2436 $element += array('#default_value' => array()); |
|
2437 foreach ($element['#default_value'] as $key => $flag) { |
|
2438 if ($flag) { |
|
2439 $value[$key] = $key; |
|
2440 } |
|
2441 } |
|
2442 return $value; |
|
2443 } |
|
2444 else { |
|
2445 return is_array($input) ? drupal_map_assoc($input) : array(); |
|
2446 } |
|
2447 } |
|
2448 } |
|
2449 |
|
2450 /** |
|
2451 * Form value callback: Determines the value for a #type radios form element. |
|
2452 * |
|
2453 * @param $element |
|
2454 * The form element whose value is being populated. |
|
2455 * @param $input |
|
2456 * (optional) The incoming input to populate the form element. If FALSE, the |
|
2457 * element's default value is returned. Defaults to FALSE. |
|
2458 * |
|
2459 * @return |
|
2460 * The data that will appear in the $element_state['values'] collection for |
|
2461 * this element. |
|
2462 */ |
|
2463 function form_type_radios_value(&$element, $input = FALSE) { |
|
2464 if ($input !== FALSE) { |
|
2465 // When there's user input (including NULL), return it as the value. |
|
2466 // However, if NULL is submitted, _form_builder_handle_input_element() will |
|
2467 // apply the default value, and we want that validated against #options |
|
2468 // unless it's empty. (An empty #default_value, such as NULL or FALSE, can |
|
2469 // be used to indicate that no radio button is selected by default.) |
|
2470 if (!isset($input) && !empty($element['#default_value'])) { |
|
2471 $element['#needs_validation'] = TRUE; |
|
2472 } |
|
2473 return $input; |
|
2474 } |
|
2475 else { |
|
2476 // For default value handling, simply return #default_value. Additionally, |
|
2477 // for a NULL default value, set #has_garbage_value to prevent |
|
2478 // _form_builder_handle_input_element() converting the NULL to an empty |
|
2479 // string, so that code can distinguish between nothing selected and the |
|
2480 // selection of a radio button whose value is an empty string. |
|
2481 $value = isset($element['#default_value']) ? $element['#default_value'] : NULL; |
|
2482 if (!isset($value)) { |
|
2483 $element['#has_garbage_value'] = TRUE; |
|
2484 } |
|
2485 return $value; |
|
2486 } |
|
2487 } |
|
2488 |
|
2489 /** |
|
2490 * Determines the value for a password_confirm form element. |
|
2491 * |
|
2492 * @param $element |
|
2493 * The form element whose value is being populated. |
|
2494 * @param $input |
|
2495 * The incoming input to populate the form element. If this is FALSE, |
|
2496 * the element's default value should be returned. |
|
2497 * |
|
2498 * @return |
|
2499 * The data that will appear in the $element_state['values'] collection |
|
2500 * for this element. Return nothing to use the default. |
|
2501 */ |
|
2502 function form_type_password_confirm_value($element, $input = FALSE) { |
|
2503 if ($input === FALSE) { |
|
2504 $element += array('#default_value' => array()); |
|
2505 return $element['#default_value'] + array('pass1' => '', 'pass2' => ''); |
|
2506 } |
|
2507 $value = array('pass1' => '', 'pass2' => ''); |
|
2508 // Throw out all invalid array keys; we only allow pass1 and pass2. |
|
2509 foreach ($value as $allowed_key => $default) { |
|
2510 // These should be strings, but allow other scalars since they might be |
|
2511 // valid input in programmatic form submissions. Any nested array values |
|
2512 // are ignored. |
|
2513 if (isset($input[$allowed_key]) && is_scalar($input[$allowed_key])) { |
|
2514 $value[$allowed_key] = (string) $input[$allowed_key]; |
|
2515 } |
|
2516 } |
|
2517 return $value; |
|
2518 } |
|
2519 |
|
2520 /** |
|
2521 * Determines the value for a select form element. |
|
2522 * |
|
2523 * @param $element |
|
2524 * The form element whose value is being populated. |
|
2525 * @param $input |
|
2526 * The incoming input to populate the form element. If this is FALSE, |
|
2527 * the element's default value should be returned. |
|
2528 * |
|
2529 * @return |
|
2530 * The data that will appear in the $element_state['values'] collection |
|
2531 * for this element. Return nothing to use the default. |
|
2532 */ |
|
2533 function form_type_select_value($element, $input = FALSE) { |
|
2534 if ($input !== FALSE) { |
|
2535 if (isset($element['#multiple']) && $element['#multiple']) { |
|
2536 // If an enabled multi-select submits NULL, it means all items are |
|
2537 // unselected. A disabled multi-select always submits NULL, and the |
|
2538 // default value should be used. |
|
2539 if (empty($element['#disabled'])) { |
|
2540 return (is_array($input)) ? drupal_map_assoc($input) : array(); |
|
2541 } |
|
2542 else { |
|
2543 return (isset($element['#default_value']) && is_array($element['#default_value'])) ? $element['#default_value'] : array(); |
|
2544 } |
|
2545 } |
|
2546 // Non-multiple select elements may have an empty option preprended to them |
|
2547 // (see form_process_select()). When this occurs, usually #empty_value is |
|
2548 // an empty string, but some forms set #empty_value to integer 0 or some |
|
2549 // other non-string constant. PHP receives all submitted form input as |
|
2550 // strings, but if the empty option is selected, set the value to match the |
|
2551 // empty value exactly. |
|
2552 elseif (isset($element['#empty_value']) && $input === (string) $element['#empty_value']) { |
|
2553 return $element['#empty_value']; |
|
2554 } |
|
2555 else { |
|
2556 return $input; |
|
2557 } |
|
2558 } |
|
2559 } |
|
2560 |
|
2561 /** |
|
2562 * Determines the value for a textarea form element. |
|
2563 * |
|
2564 * @param array $element |
|
2565 * The form element whose value is being populated. |
|
2566 * @param mixed $input |
|
2567 * The incoming input to populate the form element. If this is FALSE, |
|
2568 * the element's default value should be returned. |
|
2569 * |
|
2570 * @return string |
|
2571 * The data that will appear in the $element_state['values'] collection |
|
2572 * for this element. Return nothing to use the default. |
|
2573 */ |
|
2574 function form_type_textarea_value($element, $input = FALSE) { |
|
2575 if ($input !== FALSE && $input !== NULL) { |
|
2576 // This should be a string, but allow other scalars since they might be |
|
2577 // valid input in programmatic form submissions. |
|
2578 return is_scalar($input) ? (string) $input : ''; |
|
2579 } |
|
2580 } |
|
2581 |
|
2582 /** |
|
2583 * Determines the value for a textfield form element. |
|
2584 * |
|
2585 * @param $element |
|
2586 * The form element whose value is being populated. |
|
2587 * @param $input |
|
2588 * The incoming input to populate the form element. If this is FALSE, |
|
2589 * the element's default value should be returned. |
|
2590 * |
|
2591 * @return |
|
2592 * The data that will appear in the $element_state['values'] collection |
|
2593 * for this element. Return nothing to use the default. |
|
2594 */ |
|
2595 function form_type_textfield_value($element, $input = FALSE) { |
|
2596 if ($input !== FALSE && $input !== NULL) { |
|
2597 // This should be a string, but allow other scalars since they might be |
|
2598 // valid input in programmatic form submissions. |
|
2599 if (!is_scalar($input)) { |
|
2600 $input = ''; |
|
2601 } |
|
2602 return str_replace(array("\r", "\n"), '', (string) $input); |
|
2603 } |
|
2604 } |
|
2605 |
|
2606 /** |
|
2607 * Determines the value for form's token value. |
|
2608 * |
|
2609 * @param $element |
|
2610 * The form element whose value is being populated. |
|
2611 * @param $input |
|
2612 * The incoming input to populate the form element. If this is FALSE, |
|
2613 * the element's default value should be returned. |
|
2614 * |
|
2615 * @return |
|
2616 * The data that will appear in the $element_state['values'] collection |
|
2617 * for this element. Return nothing to use the default. |
|
2618 */ |
|
2619 function form_type_token_value($element, $input = FALSE) { |
|
2620 if ($input !== FALSE) { |
|
2621 return (string) $input; |
|
2622 } |
|
2623 } |
|
2624 |
|
2625 /** |
|
2626 * Changes submitted form values during form validation. |
|
2627 * |
|
2628 * Use this function to change the submitted value of a form element in a form |
|
2629 * validation function, so that the changed value persists in $form_state |
|
2630 * through the remaining validation and submission handlers. It does not change |
|
2631 * the value in $element['#value'], only in $form_state['values'], which is |
|
2632 * where submitted values are always stored. |
|
2633 * |
|
2634 * Note that form validation functions are specified in the '#validate' |
|
2635 * component of the form array (the value of $form['#validate'] is an array of |
|
2636 * validation function names). If the form does not originate in your module, |
|
2637 * you can implement hook_form_FORM_ID_alter() to add a validation function |
|
2638 * to $form['#validate']. |
|
2639 * |
|
2640 * @param $element |
|
2641 * The form element that should have its value updated; in most cases you can |
|
2642 * just pass in the element from the $form array, although the only component |
|
2643 * that is actually used is '#parents'. If constructing yourself, set |
|
2644 * $element['#parents'] to be an array giving the path through the form |
|
2645 * array's keys to the element whose value you want to update. For instance, |
|
2646 * if you want to update the value of $form['elem1']['elem2'], which should be |
|
2647 * stored in $form_state['values']['elem1']['elem2'], you would set |
|
2648 * $element['#parents'] = array('elem1','elem2'). |
|
2649 * @param $value |
|
2650 * The new value for the form element. |
|
2651 * @param $form_state |
|
2652 * Form state array where the value change should be recorded. |
|
2653 */ |
|
2654 function form_set_value($element, $value, &$form_state) { |
|
2655 drupal_array_set_nested_value($form_state['values'], $element['#parents'], $value, TRUE); |
|
2656 } |
|
2657 |
|
2658 /** |
|
2659 * Allows PHP array processing of multiple select options with the same value. |
|
2660 * |
|
2661 * Used for form select elements which need to validate HTML option groups |
|
2662 * and multiple options which may return the same value. Associative PHP arrays |
|
2663 * cannot handle these structures, since they share a common key. |
|
2664 * |
|
2665 * @param $array |
|
2666 * The form options array to process. |
|
2667 * |
|
2668 * @return |
|
2669 * An array with all hierarchical elements flattened to a single array. |
|
2670 */ |
|
2671 function form_options_flatten($array) { |
|
2672 // Always reset static var when first entering the recursion. |
|
2673 drupal_static_reset('_form_options_flatten'); |
|
2674 return _form_options_flatten($array); |
|
2675 } |
|
2676 |
|
2677 /** |
|
2678 * Iterates over an array and returns a flat array with duplicate keys removed. |
|
2679 * |
|
2680 * This function also handles cases where objects are passed as array values. |
|
2681 */ |
|
2682 function _form_options_flatten($array) { |
|
2683 $return = &drupal_static(__FUNCTION__); |
|
2684 |
|
2685 foreach ($array as $key => $value) { |
|
2686 if (is_object($value)) { |
|
2687 _form_options_flatten($value->option); |
|
2688 } |
|
2689 elseif (is_array($value)) { |
|
2690 _form_options_flatten($value); |
|
2691 } |
|
2692 else { |
|
2693 $return[$key] = 1; |
|
2694 } |
|
2695 } |
|
2696 |
|
2697 return $return; |
|
2698 } |
|
2699 |
|
2700 /** |
|
2701 * Processes a select list form element. |
|
2702 * |
|
2703 * This process callback is mandatory for select fields, since all user agents |
|
2704 * automatically preselect the first available option of single (non-multiple) |
|
2705 * select lists. |
|
2706 * |
|
2707 * @param $element |
|
2708 * The form element to process. Properties used: |
|
2709 * - #multiple: (optional) Indicates whether one or more options can be |
|
2710 * selected. Defaults to FALSE. |
|
2711 * - #default_value: Must be NULL or not set in case there is no value for the |
|
2712 * element yet, in which case a first default option is inserted by default. |
|
2713 * Whether this first option is a valid option depends on whether the field |
|
2714 * is #required or not. |
|
2715 * - #required: (optional) Whether the user needs to select an option (TRUE) |
|
2716 * or not (FALSE). Defaults to FALSE. |
|
2717 * - #empty_option: (optional) The label to show for the first default option. |
|
2718 * By default, the label is automatically set to "- Select -" for a required |
|
2719 * field and "- None -" for an optional field. |
|
2720 * - #empty_value: (optional) The value for the first default option, which is |
|
2721 * used to determine whether the user submitted a value or not. |
|
2722 * - If #required is TRUE, this defaults to '' (an empty string). |
|
2723 * - If #required is not TRUE and this value isn't set, then no extra option |
|
2724 * is added to the select control, leaving the control in a slightly |
|
2725 * illogical state, because there's no way for the user to select nothing, |
|
2726 * since all user agents automatically preselect the first available |
|
2727 * option. But people are used to this being the behavior of select |
|
2728 * controls. |
|
2729 * @todo Address the above issue in Drupal 8. |
|
2730 * - If #required is not TRUE and this value is set (most commonly to an |
|
2731 * empty string), then an extra option (see #empty_option above) |
|
2732 * representing a "non-selection" is added with this as its value. |
|
2733 * |
|
2734 * @see _form_validate() |
|
2735 */ |
|
2736 function form_process_select($element) { |
|
2737 // #multiple select fields need a special #name. |
|
2738 if ($element['#multiple']) { |
|
2739 $element['#attributes']['multiple'] = 'multiple'; |
|
2740 $element['#attributes']['name'] = $element['#name'] . '[]'; |
|
2741 } |
|
2742 // A non-#multiple select needs special handling to prevent user agents from |
|
2743 // preselecting the first option without intention. #multiple select lists do |
|
2744 // not get an empty option, as it would not make sense, user interface-wise. |
|
2745 else { |
|
2746 $required = $element['#required']; |
|
2747 // If the element is required and there is no #default_value, then add an |
|
2748 // empty option that will fail validation, so that the user is required to |
|
2749 // make a choice. Also, if there's a value for #empty_value or |
|
2750 // #empty_option, then add an option that represents emptiness. |
|
2751 if (($required && !isset($element['#default_value'])) || isset($element['#empty_value']) || isset($element['#empty_option'])) { |
|
2752 $element += array( |
|
2753 '#empty_value' => '', |
|
2754 '#empty_option' => $required ? t('- Select -') : t('- None -'), |
|
2755 ); |
|
2756 // The empty option is prepended to #options and purposively not merged |
|
2757 // to prevent another option in #options mistakenly using the same value |
|
2758 // as #empty_value. |
|
2759 $empty_option = array($element['#empty_value'] => $element['#empty_option']); |
|
2760 $element['#options'] = $empty_option + $element['#options']; |
|
2761 } |
|
2762 } |
|
2763 return $element; |
|
2764 } |
|
2765 |
|
2766 /** |
|
2767 * Returns HTML for a select form element. |
|
2768 * |
|
2769 * It is possible to group options together; to do this, change the format of |
|
2770 * $options to an associative array in which the keys are group labels, and the |
|
2771 * values are associative arrays in the normal $options format. |
|
2772 * |
|
2773 * @param $variables |
|
2774 * An associative array containing: |
|
2775 * - element: An associative array containing the properties of the element. |
|
2776 * Properties used: #title, #value, #options, #description, #extra, |
|
2777 * #multiple, #required, #name, #attributes, #size. |
|
2778 * |
|
2779 * @ingroup themeable |
|
2780 */ |
|
2781 function theme_select($variables) { |
|
2782 $element = $variables['element']; |
|
2783 element_set_attributes($element, array('id', 'name', 'size')); |
|
2784 _form_set_class($element, array('form-select')); |
|
2785 |
|
2786 return '<select' . drupal_attributes($element['#attributes']) . '>' . form_select_options($element) . '</select>'; |
|
2787 } |
|
2788 |
|
2789 /** |
|
2790 * Converts an array of options into HTML, for use in select list form elements. |
|
2791 * |
|
2792 * This function calls itself recursively to obtain the values for each optgroup |
|
2793 * within the list of options and when the function encounters an object with |
|
2794 * an 'options' property inside $element['#options']. |
|
2795 * |
|
2796 * @param array $element |
|
2797 * An associative array containing the following key-value pairs: |
|
2798 * - #multiple: Optional Boolean indicating if the user may select more than |
|
2799 * one item. |
|
2800 * - #options: An associative array of options to render as HTML. Each array |
|
2801 * value can be a string, an array, or an object with an 'option' property: |
|
2802 * - A string or integer key whose value is a translated string is |
|
2803 * interpreted as a single HTML option element. Do not use placeholders |
|
2804 * that sanitize data: doing so will lead to double-escaping. Note that |
|
2805 * the key will be visible in the HTML and could be modified by malicious |
|
2806 * users, so don't put sensitive information in it. |
|
2807 * - A translated string key whose value is an array indicates a group of |
|
2808 * options. The translated string is used as the label attribute for the |
|
2809 * optgroup. Do not use placeholders to sanitize data: doing so will lead |
|
2810 * to double-escaping. The array should contain the options you wish to |
|
2811 * group and should follow the syntax of $element['#options']. |
|
2812 * - If the function encounters a string or integer key whose value is an |
|
2813 * object with an 'option' property, the key is ignored, the contents of |
|
2814 * the option property are interpreted as $element['#options'], and the |
|
2815 * resulting HTML is added to the output. |
|
2816 * - #value: Optional integer, string, or array representing which option(s) |
|
2817 * to pre-select when the list is first displayed. The integer or string |
|
2818 * must match the key of an option in the '#options' list. If '#multiple' is |
|
2819 * TRUE, this can be an array of integers or strings. |
|
2820 * @param array|null $choices |
|
2821 * (optional) Either an associative array of options in the same format as |
|
2822 * $element['#options'] above, or NULL. This parameter is only used internally |
|
2823 * and is not intended to be passed in to the initial function call. |
|
2824 * |
|
2825 * @return string |
|
2826 * An HTML string of options and optgroups for use in a select form element. |
|
2827 */ |
|
2828 function form_select_options($element, $choices = NULL) { |
|
2829 if (!isset($choices)) { |
|
2830 $choices = $element['#options']; |
|
2831 } |
|
2832 // array_key_exists() accommodates the rare event where $element['#value'] is NULL. |
|
2833 // isset() fails in this situation. |
|
2834 $value_valid = isset($element['#value']) || array_key_exists('#value', $element); |
|
2835 $value_is_array = $value_valid && is_array($element['#value']); |
|
2836 $options = ''; |
|
2837 foreach ($choices as $key => $choice) { |
|
2838 if (is_array($choice)) { |
|
2839 $options .= '<optgroup label="' . check_plain($key) . '">'; |
|
2840 $options .= form_select_options($element, $choice); |
|
2841 $options .= '</optgroup>'; |
|
2842 } |
|
2843 elseif (is_object($choice)) { |
|
2844 $options .= form_select_options($element, $choice->option); |
|
2845 } |
|
2846 else { |
|
2847 $key = (string) $key; |
|
2848 if ($value_valid && (!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) { |
|
2849 $selected = ' selected="selected"'; |
|
2850 } |
|
2851 else { |
|
2852 $selected = ''; |
|
2853 } |
|
2854 $options .= '<option value="' . check_plain($key) . '"' . $selected . '>' . check_plain($choice) . '</option>'; |
|
2855 } |
|
2856 } |
|
2857 return $options; |
|
2858 } |
|
2859 |
|
2860 /** |
|
2861 * Returns the indexes of a select element's options matching a given key. |
|
2862 * |
|
2863 * This function is useful if you need to modify the options that are |
|
2864 * already in a form element; for example, to remove choices which are |
|
2865 * not valid because of additional filters imposed by another module. |
|
2866 * One example might be altering the choices in a taxonomy selector. |
|
2867 * To correctly handle the case of a multiple hierarchy taxonomy, |
|
2868 * #options arrays can now hold an array of objects, instead of a |
|
2869 * direct mapping of keys to labels, so that multiple choices in the |
|
2870 * selector can have the same key (and label). This makes it difficult |
|
2871 * to manipulate directly, which is why this helper function exists. |
|
2872 * |
|
2873 * This function does not support optgroups (when the elements of the |
|
2874 * #options array are themselves arrays), and will return FALSE if |
|
2875 * arrays are found. The caller must either flatten/restore or |
|
2876 * manually do their manipulations in this case, since returning the |
|
2877 * index is not sufficient, and supporting this would make the |
|
2878 * "helper" too complicated and cumbersome to be of any help. |
|
2879 * |
|
2880 * As usual with functions that can return array() or FALSE, do not |
|
2881 * forget to use === and !== if needed. |
|
2882 * |
|
2883 * @param $element |
|
2884 * The select element to search. |
|
2885 * @param $key |
|
2886 * The key to look for. |
|
2887 * |
|
2888 * @return |
|
2889 * An array of indexes that match the given $key. Array will be |
|
2890 * empty if no elements were found. FALSE if optgroups were found. |
|
2891 */ |
|
2892 function form_get_options($element, $key) { |
|
2893 $keys = array(); |
|
2894 foreach ($element['#options'] as $index => $choice) { |
|
2895 if (is_array($choice)) { |
|
2896 return FALSE; |
|
2897 } |
|
2898 elseif (is_object($choice)) { |
|
2899 if (isset($choice->option[$key])) { |
|
2900 $keys[] = $index; |
|
2901 } |
|
2902 } |
|
2903 elseif ($index == $key) { |
|
2904 $keys[] = $index; |
|
2905 } |
|
2906 } |
|
2907 return $keys; |
|
2908 } |
|
2909 |
|
2910 /** |
|
2911 * Returns HTML for a fieldset form element and its children. |
|
2912 * |
|
2913 * @param $variables |
|
2914 * An associative array containing: |
|
2915 * - element: An associative array containing the properties of the element. |
|
2916 * Properties used: #attributes, #children, #collapsed, #collapsible, |
|
2917 * #description, #id, #title, #value. |
|
2918 * |
|
2919 * @ingroup themeable |
|
2920 */ |
|
2921 function theme_fieldset($variables) { |
|
2922 $element = $variables['element']; |
|
2923 element_set_attributes($element, array('id')); |
|
2924 _form_set_class($element, array('form-wrapper')); |
|
2925 |
|
2926 $output = '<fieldset' . drupal_attributes($element['#attributes']) . '>'; |
|
2927 if (!empty($element['#title'])) { |
|
2928 // Always wrap fieldset legends in a SPAN for CSS positioning. |
|
2929 $output .= '<legend><span class="fieldset-legend">' . $element['#title'] . '</span></legend>'; |
|
2930 } |
|
2931 $output .= '<div class="fieldset-wrapper">'; |
|
2932 if (!empty($element['#description'])) { |
|
2933 $output .= '<div class="fieldset-description">' . $element['#description'] . '</div>'; |
|
2934 } |
|
2935 $output .= $element['#children']; |
|
2936 if (isset($element['#value'])) { |
|
2937 $output .= $element['#value']; |
|
2938 } |
|
2939 $output .= '</div>'; |
|
2940 $output .= "</fieldset>\n"; |
|
2941 return $output; |
|
2942 } |
|
2943 |
|
2944 /** |
|
2945 * Returns HTML for a radio button form element. |
|
2946 * |
|
2947 * Note: The input "name" attribute needs to be sanitized before output, which |
|
2948 * is currently done by passing all attributes to drupal_attributes(). |
|
2949 * |
|
2950 * @param $variables |
|
2951 * An associative array containing: |
|
2952 * - element: An associative array containing the properties of the element. |
|
2953 * Properties used: #required, #return_value, #value, #attributes, #title, |
|
2954 * #description |
|
2955 * |
|
2956 * @ingroup themeable |
|
2957 */ |
|
2958 function theme_radio($variables) { |
|
2959 $element = $variables['element']; |
|
2960 $element['#attributes']['type'] = 'radio'; |
|
2961 element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); |
|
2962 |
|
2963 if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) { |
|
2964 $element['#attributes']['checked'] = 'checked'; |
|
2965 } |
|
2966 _form_set_class($element, array('form-radio')); |
|
2967 |
|
2968 return '<input' . drupal_attributes($element['#attributes']) . ' />'; |
|
2969 } |
|
2970 |
|
2971 /** |
|
2972 * Returns HTML for a set of radio button form elements. |
|
2973 * |
|
2974 * @param $variables |
|
2975 * An associative array containing: |
|
2976 * - element: An associative array containing the properties of the element. |
|
2977 * Properties used: #title, #value, #options, #description, #required, |
|
2978 * #attributes, #children. |
|
2979 * |
|
2980 * @ingroup themeable |
|
2981 */ |
|
2982 function theme_radios($variables) { |
|
2983 $element = $variables['element']; |
|
2984 $attributes = array(); |
|
2985 if (isset($element['#id'])) { |
|
2986 $attributes['id'] = $element['#id']; |
|
2987 } |
|
2988 $attributes['class'] = 'form-radios'; |
|
2989 if (!empty($element['#attributes']['class'])) { |
|
2990 $attributes['class'] .= ' ' . implode(' ', $element['#attributes']['class']); |
|
2991 } |
|
2992 if (isset($element['#attributes']['title'])) { |
|
2993 $attributes['title'] = $element['#attributes']['title']; |
|
2994 } |
|
2995 return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>'; |
|
2996 } |
|
2997 |
|
2998 /** |
|
2999 * Expand a password_confirm field into two text boxes. |
|
3000 */ |
|
3001 function form_process_password_confirm($element) { |
|
3002 $element['pass1'] = array( |
|
3003 '#type' => 'password', |
|
3004 '#title' => t('Password'), |
|
3005 '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'], |
|
3006 '#required' => $element['#required'], |
|
3007 '#attributes' => array('class' => array('password-field')), |
|
3008 ); |
|
3009 $element['pass2'] = array( |
|
3010 '#type' => 'password', |
|
3011 '#title' => t('Confirm password'), |
|
3012 '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'], |
|
3013 '#required' => $element['#required'], |
|
3014 '#attributes' => array('class' => array('password-confirm')), |
|
3015 ); |
|
3016 $element['#element_validate'] = array('password_confirm_validate'); |
|
3017 $element['#tree'] = TRUE; |
|
3018 |
|
3019 if (isset($element['#size'])) { |
|
3020 $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size']; |
|
3021 } |
|
3022 |
|
3023 return $element; |
|
3024 } |
|
3025 |
|
3026 /** |
|
3027 * Validates a password_confirm element. |
|
3028 */ |
|
3029 function password_confirm_validate($element, &$element_state) { |
|
3030 $pass1 = trim($element['pass1']['#value']); |
|
3031 $pass2 = trim($element['pass2']['#value']); |
|
3032 if (strlen($pass1) > 0 || strlen($pass2) > 0) { |
|
3033 if (strcmp($pass1, $pass2)) { |
|
3034 form_error($element, t('The specified passwords do not match.')); |
|
3035 } |
|
3036 } |
|
3037 elseif ($element['#required'] && !empty($element_state['input'])) { |
|
3038 form_error($element, t('Password field is required.')); |
|
3039 } |
|
3040 |
|
3041 // Password field must be converted from a two-element array into a single |
|
3042 // string regardless of validation results. |
|
3043 form_set_value($element['pass1'], NULL, $element_state); |
|
3044 form_set_value($element['pass2'], NULL, $element_state); |
|
3045 form_set_value($element, $pass1, $element_state); |
|
3046 |
|
3047 return $element; |
|
3048 |
|
3049 } |
|
3050 |
|
3051 /** |
|
3052 * Returns HTML for a date selection form element. |
|
3053 * |
|
3054 * @param $variables |
|
3055 * An associative array containing: |
|
3056 * - element: An associative array containing the properties of the element. |
|
3057 * Properties used: #title, #value, #options, #description, #required, |
|
3058 * #attributes. |
|
3059 * |
|
3060 * @ingroup themeable |
|
3061 */ |
|
3062 function theme_date($variables) { |
|
3063 $element = $variables['element']; |
|
3064 |
|
3065 $attributes = array(); |
|
3066 if (isset($element['#id'])) { |
|
3067 $attributes['id'] = $element['#id']; |
|
3068 } |
|
3069 if (!empty($element['#attributes']['class'])) { |
|
3070 $attributes['class'] = (array) $element['#attributes']['class']; |
|
3071 } |
|
3072 $attributes['class'][] = 'container-inline'; |
|
3073 |
|
3074 return '<div' . drupal_attributes($attributes) . '>' . drupal_render_children($element) . '</div>'; |
|
3075 } |
|
3076 |
|
3077 /** |
|
3078 * Expands a date element into year, month, and day select elements. |
|
3079 */ |
|
3080 function form_process_date($element) { |
|
3081 // Default to current date |
|
3082 if (empty($element['#value'])) { |
|
3083 $element['#value'] = array( |
|
3084 'day' => format_date(REQUEST_TIME, 'custom', 'j'), |
|
3085 'month' => format_date(REQUEST_TIME, 'custom', 'n'), |
|
3086 'year' => format_date(REQUEST_TIME, 'custom', 'Y'), |
|
3087 ); |
|
3088 } |
|
3089 |
|
3090 $element['#tree'] = TRUE; |
|
3091 |
|
3092 // Determine the order of day, month, year in the site's chosen date format. |
|
3093 $format = variable_get('date_format_short', 'm/d/Y - H:i'); |
|
3094 $sort = array(); |
|
3095 $sort['day'] = max(strpos($format, 'd'), strpos($format, 'j')); |
|
3096 $sort['month'] = max(strpos($format, 'm'), strpos($format, 'M')); |
|
3097 $sort['year'] = strpos($format, 'Y'); |
|
3098 asort($sort); |
|
3099 $order = array_keys($sort); |
|
3100 |
|
3101 // Output multi-selector for date. |
|
3102 foreach ($order as $type) { |
|
3103 switch ($type) { |
|
3104 case 'day': |
|
3105 $options = drupal_map_assoc(range(1, 31)); |
|
3106 $title = t('Day'); |
|
3107 break; |
|
3108 |
|
3109 case 'month': |
|
3110 $options = drupal_map_assoc(range(1, 12), 'map_month'); |
|
3111 $title = t('Month'); |
|
3112 break; |
|
3113 |
|
3114 case 'year': |
|
3115 $options = drupal_map_assoc(range(1900, 2050)); |
|
3116 $title = t('Year'); |
|
3117 break; |
|
3118 } |
|
3119 |
|
3120 $element[$type] = array( |
|
3121 '#type' => 'select', |
|
3122 '#title' => $title, |
|
3123 '#title_display' => 'invisible', |
|
3124 '#value' => $element['#value'][$type], |
|
3125 '#attributes' => $element['#attributes'], |
|
3126 '#options' => $options, |
|
3127 ); |
|
3128 } |
|
3129 |
|
3130 return $element; |
|
3131 } |
|
3132 |
|
3133 /** |
|
3134 * Validates the date type to prevent invalid dates (e.g., February 30, 2006). |
|
3135 */ |
|
3136 function date_validate($element) { |
|
3137 if (!checkdate($element['#value']['month'], $element['#value']['day'], $element['#value']['year'])) { |
|
3138 form_error($element, t('The specified date is invalid.')); |
|
3139 } |
|
3140 } |
|
3141 |
|
3142 /** |
|
3143 * Helper function for usage with drupal_map_assoc to display month names. |
|
3144 */ |
|
3145 function map_month($month) { |
|
3146 $months = &drupal_static(__FUNCTION__, array( |
|
3147 1 => 'Jan', |
|
3148 2 => 'Feb', |
|
3149 3 => 'Mar', |
|
3150 4 => 'Apr', |
|
3151 5 => 'May', |
|
3152 6 => 'Jun', |
|
3153 7 => 'Jul', |
|
3154 8 => 'Aug', |
|
3155 9 => 'Sep', |
|
3156 10 => 'Oct', |
|
3157 11 => 'Nov', |
|
3158 12 => 'Dec', |
|
3159 )); |
|
3160 return t($months[$month]); |
|
3161 } |
|
3162 |
|
3163 /** |
|
3164 * Sets the value for a weight element, with zero as a default. |
|
3165 */ |
|
3166 function weight_value(&$form) { |
|
3167 if (isset($form['#default_value'])) { |
|
3168 $form['#value'] = $form['#default_value']; |
|
3169 } |
|
3170 else { |
|
3171 $form['#value'] = 0; |
|
3172 } |
|
3173 } |
|
3174 |
|
3175 /** |
|
3176 * Expands a radios element into individual radio elements. |
|
3177 */ |
|
3178 function form_process_radios($element) { |
|
3179 if (count($element['#options']) > 0) { |
|
3180 $weight = 0; |
|
3181 foreach ($element['#options'] as $key => $choice) { |
|
3182 // Maintain order of options as defined in #options, in case the element |
|
3183 // defines custom option sub-elements, but does not define all option |
|
3184 // sub-elements. |
|
3185 $weight += 0.001; |
|
3186 |
|
3187 $element += array($key => array()); |
|
3188 // Generate the parents as the autogenerator does, so we will have a |
|
3189 // unique id for each radio button. |
|
3190 $parents_for_id = array_merge($element['#parents'], array($key)); |
|
3191 $element[$key] += array( |
|
3192 '#type' => 'radio', |
|
3193 '#title' => $choice, |
|
3194 // The key is sanitized in drupal_attributes() during output from the |
|
3195 // theme function. |
|
3196 '#return_value' => $key, |
|
3197 // Use default or FALSE. A value of FALSE means that the radio button is |
|
3198 // not 'checked'. |
|
3199 '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE, |
|
3200 '#attributes' => $element['#attributes'], |
|
3201 '#parents' => $element['#parents'], |
|
3202 '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), |
|
3203 '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, |
|
3204 '#weight' => $weight, |
|
3205 ); |
|
3206 } |
|
3207 } |
|
3208 return $element; |
|
3209 } |
|
3210 |
|
3211 /** |
|
3212 * Returns HTML for a checkbox form element. |
|
3213 * |
|
3214 * @param $variables |
|
3215 * An associative array containing: |
|
3216 * - element: An associative array containing the properties of the element. |
|
3217 * Properties used: #id, #name, #attributes, #checked, #return_value. |
|
3218 * |
|
3219 * @ingroup themeable |
|
3220 */ |
|
3221 function theme_checkbox($variables) { |
|
3222 $element = $variables['element']; |
|
3223 $element['#attributes']['type'] = 'checkbox'; |
|
3224 element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); |
|
3225 |
|
3226 // Unchecked checkbox has #value of integer 0. |
|
3227 if (!empty($element['#checked'])) { |
|
3228 $element['#attributes']['checked'] = 'checked'; |
|
3229 } |
|
3230 _form_set_class($element, array('form-checkbox')); |
|
3231 |
|
3232 return '<input' . drupal_attributes($element['#attributes']) . ' />'; |
|
3233 } |
|
3234 |
|
3235 /** |
|
3236 * Returns HTML for a set of checkbox form elements. |
|
3237 * |
|
3238 * @param $variables |
|
3239 * An associative array containing: |
|
3240 * - element: An associative array containing the properties of the element. |
|
3241 * Properties used: #children, #attributes. |
|
3242 * |
|
3243 * @ingroup themeable |
|
3244 */ |
|
3245 function theme_checkboxes($variables) { |
|
3246 $element = $variables['element']; |
|
3247 $attributes = array(); |
|
3248 if (isset($element['#id'])) { |
|
3249 $attributes['id'] = $element['#id']; |
|
3250 } |
|
3251 $attributes['class'][] = 'form-checkboxes'; |
|
3252 if (!empty($element['#attributes']['class'])) { |
|
3253 $attributes['class'] = array_merge($attributes['class'], $element['#attributes']['class']); |
|
3254 } |
|
3255 if (isset($element['#attributes']['title'])) { |
|
3256 $attributes['title'] = $element['#attributes']['title']; |
|
3257 } |
|
3258 return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>'; |
|
3259 } |
|
3260 |
|
3261 /** |
|
3262 * Adds form element theming to an element if its title or description is set. |
|
3263 * |
|
3264 * This is used as a pre render function for checkboxes and radios. |
|
3265 */ |
|
3266 function form_pre_render_conditional_form_element($element) { |
|
3267 $t = get_t(); |
|
3268 // Set the element's title attribute to show #title as a tooltip, if needed. |
|
3269 if (isset($element['#title']) && $element['#title_display'] == 'attribute') { |
|
3270 $element['#attributes']['title'] = $element['#title']; |
|
3271 if (!empty($element['#required'])) { |
|
3272 // Append an indication that this field is required. |
|
3273 $element['#attributes']['title'] .= ' (' . $t('Required') . ')'; |
|
3274 } |
|
3275 } |
|
3276 |
|
3277 if (isset($element['#title']) || isset($element['#description'])) { |
|
3278 $element['#theme_wrappers'][] = 'form_element'; |
|
3279 } |
|
3280 return $element; |
|
3281 } |
|
3282 |
|
3283 /** |
|
3284 * Sets the #checked property of a checkbox element. |
|
3285 */ |
|
3286 function form_process_checkbox($element, $form_state) { |
|
3287 $value = $element['#value']; |
|
3288 $return_value = $element['#return_value']; |
|
3289 // On form submission, the #value of an available and enabled checked |
|
3290 // checkbox is #return_value, and the #value of an available and enabled |
|
3291 // unchecked checkbox is integer 0. On not submitted forms, and for |
|
3292 // checkboxes with #access=FALSE or #disabled=TRUE, the #value is |
|
3293 // #default_value (integer 0 if #default_value is NULL). Most of the time, |
|
3294 // a string comparison of #value and #return_value is sufficient for |
|
3295 // determining the "checked" state, but a value of TRUE always means checked |
|
3296 // (even if #return_value is 'foo'), and a value of FALSE or integer 0 always |
|
3297 // means unchecked (even if #return_value is '' or '0'). |
|
3298 if ($value === TRUE || $value === FALSE || $value === 0) { |
|
3299 $element['#checked'] = (bool) $value; |
|
3300 } |
|
3301 else { |
|
3302 // Compare as strings, so that 15 is not considered equal to '15foo', but 1 |
|
3303 // is considered equal to '1'. This cast does not imply that either #value |
|
3304 // or #return_value is expected to be a string. |
|
3305 $element['#checked'] = ((string) $value === (string) $return_value); |
|
3306 } |
|
3307 return $element; |
|
3308 } |
|
3309 |
|
3310 /** |
|
3311 * Processes a checkboxes form element. |
|
3312 */ |
|
3313 function form_process_checkboxes($element) { |
|
3314 $value = is_array($element['#value']) ? $element['#value'] : array(); |
|
3315 $element['#tree'] = TRUE; |
|
3316 if (count($element['#options']) > 0) { |
|
3317 if (!isset($element['#default_value']) || $element['#default_value'] == 0) { |
|
3318 $element['#default_value'] = array(); |
|
3319 } |
|
3320 $weight = 0; |
|
3321 foreach ($element['#options'] as $key => $choice) { |
|
3322 // Integer 0 is not a valid #return_value, so use '0' instead. |
|
3323 // @see form_type_checkbox_value(). |
|
3324 // @todo For Drupal 8, cast all integer keys to strings for consistency |
|
3325 // with form_process_radios(). |
|
3326 if ($key === 0) { |
|
3327 $key = '0'; |
|
3328 } |
|
3329 // Maintain order of options as defined in #options, in case the element |
|
3330 // defines custom option sub-elements, but does not define all option |
|
3331 // sub-elements. |
|
3332 $weight += 0.001; |
|
3333 |
|
3334 $element += array($key => array()); |
|
3335 $element[$key] += array( |
|
3336 '#type' => 'checkbox', |
|
3337 '#title' => $choice, |
|
3338 '#return_value' => $key, |
|
3339 '#default_value' => isset($value[$key]) ? $key : NULL, |
|
3340 '#attributes' => $element['#attributes'], |
|
3341 '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, |
|
3342 '#weight' => $weight, |
|
3343 ); |
|
3344 } |
|
3345 } |
|
3346 return $element; |
|
3347 } |
|
3348 |
|
3349 /** |
|
3350 * Processes a form actions container element. |
|
3351 * |
|
3352 * @param $element |
|
3353 * An associative array containing the properties and children of the |
|
3354 * form actions container. |
|
3355 * @param $form_state |
|
3356 * The $form_state array for the form this element belongs to. |
|
3357 * |
|
3358 * @return |
|
3359 * The processed element. |
|
3360 */ |
|
3361 function form_process_actions($element, &$form_state) { |
|
3362 $element['#attributes']['class'][] = 'form-actions'; |
|
3363 return $element; |
|
3364 } |
|
3365 |
|
3366 /** |
|
3367 * Processes a container element. |
|
3368 * |
|
3369 * @param $element |
|
3370 * An associative array containing the properties and children of the |
|
3371 * container. |
|
3372 * @param $form_state |
|
3373 * The $form_state array for the form this element belongs to. |
|
3374 * |
|
3375 * @return |
|
3376 * The processed element. |
|
3377 */ |
|
3378 function form_process_container($element, &$form_state) { |
|
3379 // Generate the ID of the element if it's not explicitly given. |
|
3380 if (!isset($element['#id'])) { |
|
3381 $element['#id'] = drupal_html_id(implode('-', $element['#parents']) . '-wrapper'); |
|
3382 } |
|
3383 return $element; |
|
3384 } |
|
3385 |
|
3386 /** |
|
3387 * Returns HTML to wrap child elements in a container. |
|
3388 * |
|
3389 * Used for grouped form items. Can also be used as a theme wrapper for any |
|
3390 * renderable element, to surround it with a <div> and add attributes such as |
|
3391 * classes or an HTML ID. |
|
3392 * |
|
3393 * See the @link forms_api_reference.html Form API reference @endlink for more |
|
3394 * information on the #theme_wrappers render array property. |
|
3395 * |
|
3396 * @param $variables |
|
3397 * An associative array containing: |
|
3398 * - element: An associative array containing the properties of the element. |
|
3399 * Properties used: #id, #attributes, #children. |
|
3400 * |
|
3401 * @ingroup themeable |
|
3402 */ |
|
3403 function theme_container($variables) { |
|
3404 $element = $variables['element']; |
|
3405 // Ensure #attributes is set. |
|
3406 $element += array('#attributes' => array()); |
|
3407 |
|
3408 // Special handling for form elements. |
|
3409 if (isset($element['#array_parents'])) { |
|
3410 // Assign an html ID. |
|
3411 if (!isset($element['#attributes']['id'])) { |
|
3412 $element['#attributes']['id'] = $element['#id']; |
|
3413 } |
|
3414 // Add the 'form-wrapper' class. |
|
3415 $element['#attributes']['class'][] = 'form-wrapper'; |
|
3416 } |
|
3417 |
|
3418 return '<div' . drupal_attributes($element['#attributes']) . '>' . $element['#children'] . '</div>'; |
|
3419 } |
|
3420 |
|
3421 /** |
|
3422 * Returns HTML for a table with radio buttons or checkboxes. |
|
3423 * |
|
3424 * @param $variables |
|
3425 * An associative array containing: |
|
3426 * - element: An associative array containing the properties and children of |
|
3427 * the tableselect element. Properties used: #header, #options, #empty, |
|
3428 * and #js_select. The #options property is an array of selection options; |
|
3429 * each array element of #options is an array of properties. These |
|
3430 * properties can include #attributes, which is added to the |
|
3431 * table row's HTML attributes; see theme_table(). An example of per-row |
|
3432 * options: |
|
3433 * @code |
|
3434 * $options = array( |
|
3435 * array( |
|
3436 * 'title' => 'How to Learn Drupal', |
|
3437 * 'content_type' => 'Article', |
|
3438 * 'status' => 'published', |
|
3439 * '#attributes' => array('class' => array('article-row')), |
|
3440 * ), |
|
3441 * array( |
|
3442 * 'title' => 'Privacy Policy', |
|
3443 * 'content_type' => 'Page', |
|
3444 * 'status' => 'published', |
|
3445 * '#attributes' => array('class' => array('page-row')), |
|
3446 * ), |
|
3447 * ); |
|
3448 * $header = array( |
|
3449 * 'title' => t('Title'), |
|
3450 * 'content_type' => t('Content type'), |
|
3451 * 'status' => t('Status'), |
|
3452 * ); |
|
3453 * $form['table'] = array( |
|
3454 * '#type' => 'tableselect', |
|
3455 * '#header' => $header, |
|
3456 * '#options' => $options, |
|
3457 * '#empty' => t('No content available.'), |
|
3458 * ); |
|
3459 * @endcode |
|
3460 * |
|
3461 * @ingroup themeable |
|
3462 */ |
|
3463 function theme_tableselect($variables) { |
|
3464 $element = $variables['element']; |
|
3465 $rows = array(); |
|
3466 $header = $element['#header']; |
|
3467 if (!empty($element['#options'])) { |
|
3468 // Generate a table row for each selectable item in #options. |
|
3469 foreach (element_children($element) as $key) { |
|
3470 $row = array(); |
|
3471 |
|
3472 $row['data'] = array(); |
|
3473 if (isset($element['#options'][$key]['#attributes'])) { |
|
3474 $row += $element['#options'][$key]['#attributes']; |
|
3475 } |
|
3476 // Render the checkbox / radio element. |
|
3477 $row['data'][] = drupal_render($element[$key]); |
|
3478 |
|
3479 // As theme_table only maps header and row columns by order, create the |
|
3480 // correct order by iterating over the header fields. |
|
3481 foreach ($element['#header'] as $fieldname => $title) { |
|
3482 $row['data'][] = $element['#options'][$key][$fieldname]; |
|
3483 } |
|
3484 $rows[] = $row; |
|
3485 } |
|
3486 // Add an empty header or a "Select all" checkbox to provide room for the |
|
3487 // checkboxes/radios in the first table column. |
|
3488 if ($element['#js_select']) { |
|
3489 // Add a "Select all" checkbox. |
|
3490 drupal_add_js('misc/tableselect.js'); |
|
3491 array_unshift($header, array('class' => array('select-all'))); |
|
3492 } |
|
3493 else { |
|
3494 // Add an empty header when radio buttons are displayed or a "Select all" |
|
3495 // checkbox is not desired. |
|
3496 array_unshift($header, ''); |
|
3497 } |
|
3498 } |
|
3499 return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => $element['#empty'], 'attributes' => $element['#attributes'])); |
|
3500 } |
|
3501 |
|
3502 /** |
|
3503 * Creates checkbox or radio elements to populate a tableselect table. |
|
3504 * |
|
3505 * @param $element |
|
3506 * An associative array containing the properties and children of the |
|
3507 * tableselect element. |
|
3508 * |
|
3509 * @return |
|
3510 * The processed element. |
|
3511 */ |
|
3512 function form_process_tableselect($element) { |
|
3513 |
|
3514 if ($element['#multiple']) { |
|
3515 $value = is_array($element['#value']) ? $element['#value'] : array(); |
|
3516 } |
|
3517 else { |
|
3518 // Advanced selection behavior makes no sense for radios. |
|
3519 $element['#js_select'] = FALSE; |
|
3520 } |
|
3521 |
|
3522 $element['#tree'] = TRUE; |
|
3523 |
|
3524 if (count($element['#options']) > 0) { |
|
3525 if (!isset($element['#default_value']) || $element['#default_value'] === 0) { |
|
3526 $element['#default_value'] = array(); |
|
3527 } |
|
3528 |
|
3529 // Create a checkbox or radio for each item in #options in such a way that |
|
3530 // the value of the tableselect element behaves as if it had been of type |
|
3531 // checkboxes or radios. |
|
3532 foreach ($element['#options'] as $key => $choice) { |
|
3533 // Do not overwrite manually created children. |
|
3534 if (!isset($element[$key])) { |
|
3535 if ($element['#multiple']) { |
|
3536 $title = ''; |
|
3537 if (!empty($element['#options'][$key]['title']['data']['#title'])) { |
|
3538 $title = t('Update @title', array( |
|
3539 '@title' => $element['#options'][$key]['title']['data']['#title'], |
|
3540 )); |
|
3541 } |
|
3542 $element[$key] = array( |
|
3543 '#type' => 'checkbox', |
|
3544 '#title' => $title, |
|
3545 '#title_display' => 'invisible', |
|
3546 '#return_value' => $key, |
|
3547 '#default_value' => isset($value[$key]) ? $key : NULL, |
|
3548 '#attributes' => $element['#attributes'], |
|
3549 '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, |
|
3550 ); |
|
3551 } |
|
3552 else { |
|
3553 // Generate the parents as the autogenerator does, so we will have a |
|
3554 // unique id for each radio button. |
|
3555 $parents_for_id = array_merge($element['#parents'], array($key)); |
|
3556 $element[$key] = array( |
|
3557 '#type' => 'radio', |
|
3558 '#title' => '', |
|
3559 '#return_value' => $key, |
|
3560 '#default_value' => ($element['#default_value'] == $key) ? $key : NULL, |
|
3561 '#attributes' => $element['#attributes'], |
|
3562 '#parents' => $element['#parents'], |
|
3563 '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), |
|
3564 '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, |
|
3565 ); |
|
3566 } |
|
3567 if (isset($element['#options'][$key]['#weight'])) { |
|
3568 $element[$key]['#weight'] = $element['#options'][$key]['#weight']; |
|
3569 } |
|
3570 } |
|
3571 } |
|
3572 } |
|
3573 else { |
|
3574 $element['#value'] = array(); |
|
3575 } |
|
3576 return $element; |
|
3577 } |
|
3578 |
|
3579 /** |
|
3580 * Processes a machine-readable name form element. |
|
3581 * |
|
3582 * @param $element |
|
3583 * The form element to process. Properties used: |
|
3584 * - #machine_name: An associative array containing: |
|
3585 * - exists: A function name to invoke for checking whether a submitted |
|
3586 * machine name value already exists. The submitted value is passed as |
|
3587 * argument. In most cases, an existing API or menu argument loader |
|
3588 * function can be re-used. The callback is only invoked, if the submitted |
|
3589 * value differs from the element's #default_value. |
|
3590 * - source: (optional) The #array_parents of the form element containing |
|
3591 * the human-readable name (i.e., as contained in the $form structure) to |
|
3592 * use as source for the machine name. Defaults to array('name'). |
|
3593 * - label: (optional) A text to display as label for the machine name value |
|
3594 * after the human-readable name form element. Defaults to "Machine name". |
|
3595 * - replace_pattern: (optional) A regular expression (without delimiters) |
|
3596 * matching disallowed characters in the machine name. Defaults to |
|
3597 * '[^a-z0-9_]+'. |
|
3598 * - replace: (optional) A character to replace disallowed characters in the |
|
3599 * machine name via JavaScript. Defaults to '_' (underscore). When using a |
|
3600 * different character, 'replace_pattern' needs to be set accordingly. |
|
3601 * - error: (optional) A custom form error message string to show, if the |
|
3602 * machine name contains disallowed characters. |
|
3603 * - standalone: (optional) Whether the live preview should stay in its own |
|
3604 * form element rather than in the suffix of the source element. Defaults |
|
3605 * to FALSE. |
|
3606 * - #maxlength: (optional) Should be set to the maximum allowed length of the |
|
3607 * machine name. Defaults to 64. |
|
3608 * - #disabled: (optional) Should be set to TRUE in case an existing machine |
|
3609 * name must not be changed after initial creation. |
|
3610 */ |
|
3611 function form_process_machine_name($element, &$form_state) { |
|
3612 // Apply default form element properties. |
|
3613 $element += array( |
|
3614 '#title' => t('Machine-readable name'), |
|
3615 '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), |
|
3616 '#machine_name' => array(), |
|
3617 '#field_prefix' => '', |
|
3618 '#field_suffix' => '', |
|
3619 '#suffix' => '', |
|
3620 ); |
|
3621 // A form element that only wants to set one #machine_name property (usually |
|
3622 // 'source' only) would leave all other properties undefined, if the defaults |
|
3623 // were defined in hook_element_info(). Therefore, we apply the defaults here. |
|
3624 $element['#machine_name'] += array( |
|
3625 'source' => array('name'), |
|
3626 'target' => '#' . $element['#id'], |
|
3627 'label' => t('Machine name'), |
|
3628 'replace_pattern' => '[^a-z0-9_]+', |
|
3629 'replace' => '_', |
|
3630 'standalone' => FALSE, |
|
3631 'field_prefix' => $element['#field_prefix'], |
|
3632 'field_suffix' => $element['#field_suffix'], |
|
3633 ); |
|
3634 |
|
3635 // By default, machine names are restricted to Latin alphanumeric characters. |
|
3636 // So, default to LTR directionality. |
|
3637 if (!isset($element['#attributes'])) { |
|
3638 $element['#attributes'] = array(); |
|
3639 } |
|
3640 $element['#attributes'] += array('dir' => 'ltr'); |
|
3641 |
|
3642 // The source element defaults to array('name'), but may have been overidden. |
|
3643 if (empty($element['#machine_name']['source'])) { |
|
3644 return $element; |
|
3645 } |
|
3646 |
|
3647 // Retrieve the form element containing the human-readable name from the |
|
3648 // complete form in $form_state. By reference, because we may need to append |
|
3649 // a #field_suffix that will hold the live preview. |
|
3650 $key_exists = NULL; |
|
3651 $source = drupal_array_get_nested_value($form_state['complete form'], $element['#machine_name']['source'], $key_exists); |
|
3652 if (!$key_exists) { |
|
3653 return $element; |
|
3654 } |
|
3655 |
|
3656 $suffix_id = $source['#id'] . '-machine-name-suffix'; |
|
3657 $element['#machine_name']['suffix'] = '#' . $suffix_id; |
|
3658 |
|
3659 if ($element['#machine_name']['standalone']) { |
|
3660 $element['#suffix'] .= ' <small id="' . $suffix_id . '"> </small>'; |
|
3661 } |
|
3662 else { |
|
3663 // Append a field suffix to the source form element, which will contain |
|
3664 // the live preview of the machine name. |
|
3665 $source += array('#field_suffix' => ''); |
|
3666 $source['#field_suffix'] .= ' <small id="' . $suffix_id . '"> </small>'; |
|
3667 |
|
3668 $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); |
|
3669 drupal_array_set_nested_value($form_state['complete form'], $parents, $source['#field_suffix']); |
|
3670 } |
|
3671 |
|
3672 $js_settings = array( |
|
3673 'type' => 'setting', |
|
3674 'data' => array( |
|
3675 'machineName' => array( |
|
3676 '#' . $source['#id'] => $element['#machine_name'], |
|
3677 ), |
|
3678 ), |
|
3679 ); |
|
3680 $element['#attached']['js'][] = 'misc/machine-name.js'; |
|
3681 $element['#attached']['js'][] = $js_settings; |
|
3682 |
|
3683 return $element; |
|
3684 } |
|
3685 |
|
3686 /** |
|
3687 * Form element validation handler for machine_name elements. |
|
3688 * |
|
3689 * Note that #maxlength is validated by _form_validate() already. |
|
3690 */ |
|
3691 function form_validate_machine_name(&$element, &$form_state) { |
|
3692 // Verify that the machine name not only consists of replacement tokens. |
|
3693 if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) { |
|
3694 form_error($element, t('The machine-readable name must contain unique characters.')); |
|
3695 } |
|
3696 |
|
3697 // Verify that the machine name contains no disallowed characters. |
|
3698 if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) { |
|
3699 if (!isset($element['#machine_name']['error'])) { |
|
3700 // Since a hyphen is the most common alternative replacement character, |
|
3701 // a corresponding validation error message is supported here. |
|
3702 if ($element['#machine_name']['replace'] == '-') { |
|
3703 form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.')); |
|
3704 } |
|
3705 // Otherwise, we assume the default (underscore). |
|
3706 else { |
|
3707 form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.')); |
|
3708 } |
|
3709 } |
|
3710 else { |
|
3711 form_error($element, $element['#machine_name']['error']); |
|
3712 } |
|
3713 } |
|
3714 |
|
3715 // Verify that the machine name is unique. |
|
3716 if ($element['#default_value'] !== $element['#value']) { |
|
3717 $function = $element['#machine_name']['exists']; |
|
3718 if ($function($element['#value'], $element, $form_state)) { |
|
3719 form_error($element, t('The machine-readable name is already in use. It must be unique.')); |
|
3720 } |
|
3721 } |
|
3722 } |
|
3723 |
|
3724 /** |
|
3725 * Arranges fieldsets into groups. |
|
3726 * |
|
3727 * @param $element |
|
3728 * An associative array containing the properties and children of the |
|
3729 * fieldset. Note that $element must be taken by reference here, so processed |
|
3730 * child elements are taken over into $form_state. |
|
3731 * @param $form_state |
|
3732 * The $form_state array for the form this fieldset belongs to. |
|
3733 * |
|
3734 * @return |
|
3735 * The processed element. |
|
3736 */ |
|
3737 function form_process_fieldset(&$element, &$form_state) { |
|
3738 $parents = implode('][', $element['#parents']); |
|
3739 |
|
3740 // Each fieldset forms a new group. The #type 'vertical_tabs' basically only |
|
3741 // injects a new fieldset. |
|
3742 $form_state['groups'][$parents]['#group_exists'] = TRUE; |
|
3743 $element['#groups'] = &$form_state['groups']; |
|
3744 |
|
3745 // Process vertical tabs group member fieldsets. |
|
3746 if (isset($element['#group'])) { |
|
3747 // Add this fieldset to the defined group (by reference). |
|
3748 $group = $element['#group']; |
|
3749 $form_state['groups'][$group][] = &$element; |
|
3750 } |
|
3751 |
|
3752 // Contains form element summary functionalities. |
|
3753 $element['#attached']['library'][] = array('system', 'drupal.form'); |
|
3754 |
|
3755 // The .form-wrapper class is required for #states to treat fieldsets like |
|
3756 // containers. |
|
3757 if (!isset($element['#attributes']['class'])) { |
|
3758 $element['#attributes']['class'] = array(); |
|
3759 } |
|
3760 |
|
3761 // Collapsible fieldsets |
|
3762 if (!empty($element['#collapsible'])) { |
|
3763 $element['#attached']['library'][] = array('system', 'drupal.collapse'); |
|
3764 $element['#attributes']['class'][] = 'collapsible'; |
|
3765 if (!empty($element['#collapsed'])) { |
|
3766 $element['#attributes']['class'][] = 'collapsed'; |
|
3767 } |
|
3768 } |
|
3769 |
|
3770 return $element; |
|
3771 } |
|
3772 |
|
3773 /** |
|
3774 * Adds members of this group as actual elements for rendering. |
|
3775 * |
|
3776 * @param $element |
|
3777 * An associative array containing the properties and children of the |
|
3778 * fieldset. |
|
3779 * |
|
3780 * @return |
|
3781 * The modified element with all group members. |
|
3782 */ |
|
3783 function form_pre_render_fieldset($element) { |
|
3784 // Fieldsets may be rendered outside of a Form API context. |
|
3785 if (!isset($element['#parents']) || !isset($element['#groups'])) { |
|
3786 return $element; |
|
3787 } |
|
3788 // Inject group member elements belonging to this group. |
|
3789 $parents = implode('][', $element['#parents']); |
|
3790 $children = element_children($element['#groups'][$parents]); |
|
3791 if (!empty($children)) { |
|
3792 foreach ($children as $key) { |
|
3793 // Break references and indicate that the element should be rendered as |
|
3794 // group member. |
|
3795 $child = (array) $element['#groups'][$parents][$key]; |
|
3796 $child['#group_fieldset'] = TRUE; |
|
3797 // Inject the element as new child element. |
|
3798 $element[] = $child; |
|
3799 |
|
3800 $sort = TRUE; |
|
3801 } |
|
3802 // Re-sort the element's children if we injected group member elements. |
|
3803 if (isset($sort)) { |
|
3804 $element['#sorted'] = FALSE; |
|
3805 } |
|
3806 } |
|
3807 |
|
3808 if (isset($element['#group'])) { |
|
3809 $group = $element['#group']; |
|
3810 // If this element belongs to a group, but the group-holding element does |
|
3811 // not exist, we need to render it (at its original location). |
|
3812 if (!isset($element['#groups'][$group]['#group_exists'])) { |
|
3813 // Intentionally empty to clarify the flow; we simply return $element. |
|
3814 } |
|
3815 // If we injected this element into the group, then we want to render it. |
|
3816 elseif (!empty($element['#group_fieldset'])) { |
|
3817 // Intentionally empty to clarify the flow; we simply return $element. |
|
3818 } |
|
3819 // Otherwise, this element belongs to a group and the group exists, so we do |
|
3820 // not render it. |
|
3821 elseif (element_children($element['#groups'][$group])) { |
|
3822 $element['#printed'] = TRUE; |
|
3823 } |
|
3824 } |
|
3825 |
|
3826 return $element; |
|
3827 } |
|
3828 |
|
3829 /** |
|
3830 * Creates a group formatted as vertical tabs. |
|
3831 * |
|
3832 * @param $element |
|
3833 * An associative array containing the properties and children of the |
|
3834 * fieldset. |
|
3835 * @param $form_state |
|
3836 * The $form_state array for the form this vertical tab widget belongs to. |
|
3837 * |
|
3838 * @return |
|
3839 * The processed element. |
|
3840 */ |
|
3841 function form_process_vertical_tabs($element, &$form_state) { |
|
3842 // Inject a new fieldset as child, so that form_process_fieldset() processes |
|
3843 // this fieldset like any other fieldset. |
|
3844 $element['group'] = array( |
|
3845 '#type' => 'fieldset', |
|
3846 '#theme_wrappers' => array(), |
|
3847 '#parents' => $element['#parents'], |
|
3848 ); |
|
3849 |
|
3850 // The JavaScript stores the currently selected tab in this hidden |
|
3851 // field so that the active tab can be restored the next time the |
|
3852 // form is rendered, e.g. on preview pages or when form validation |
|
3853 // fails. |
|
3854 $name = implode('__', $element['#parents']); |
|
3855 if (isset($form_state['values'][$name . '__active_tab'])) { |
|
3856 $element['#default_tab'] = $form_state['values'][$name . '__active_tab']; |
|
3857 } |
|
3858 $element[$name . '__active_tab'] = array( |
|
3859 '#type' => 'hidden', |
|
3860 '#default_value' => $element['#default_tab'], |
|
3861 '#attributes' => array('class' => array('vertical-tabs-active-tab')), |
|
3862 ); |
|
3863 |
|
3864 return $element; |
|
3865 } |
|
3866 |
|
3867 /** |
|
3868 * Returns HTML for an element's children fieldsets as vertical tabs. |
|
3869 * |
|
3870 * @param $variables |
|
3871 * An associative array containing: |
|
3872 * - element: An associative array containing the properties and children of |
|
3873 * the fieldset. Properties used: #children. |
|
3874 * |
|
3875 * @ingroup themeable |
|
3876 */ |
|
3877 function theme_vertical_tabs($variables) { |
|
3878 $element = $variables['element']; |
|
3879 // Add required JavaScript and Stylesheet. |
|
3880 drupal_add_library('system', 'drupal.vertical-tabs'); |
|
3881 |
|
3882 $output = '<h2 class="element-invisible">' . t('Vertical Tabs') . '</h2>'; |
|
3883 $output .= '<div class="vertical-tabs-panes">' . $element['#children'] . '</div>'; |
|
3884 return $output; |
|
3885 } |
|
3886 |
|
3887 /** |
|
3888 * Returns HTML for a submit button form element. |
|
3889 * |
|
3890 * @param $variables |
|
3891 * An associative array containing: |
|
3892 * - element: An associative array containing the properties of the element. |
|
3893 * Properties used: #attributes, #button_type, #name, #value. |
|
3894 * |
|
3895 * @ingroup themeable |
|
3896 */ |
|
3897 function theme_submit($variables) { |
|
3898 return theme('button', $variables['element']); |
|
3899 } |
|
3900 |
|
3901 /** |
|
3902 * Returns HTML for a button form element. |
|
3903 * |
|
3904 * @param $variables |
|
3905 * An associative array containing: |
|
3906 * - element: An associative array containing the properties of the element. |
|
3907 * Properties used: #attributes, #button_type, #name, #value. |
|
3908 * |
|
3909 * @ingroup themeable |
|
3910 */ |
|
3911 function theme_button($variables) { |
|
3912 $element = $variables['element']; |
|
3913 $element['#attributes']['type'] = 'submit'; |
|
3914 element_set_attributes($element, array('id', 'name', 'value')); |
|
3915 |
|
3916 $element['#attributes']['class'][] = 'form-' . $element['#button_type']; |
|
3917 if (!empty($element['#attributes']['disabled'])) { |
|
3918 $element['#attributes']['class'][] = 'form-button-disabled'; |
|
3919 } |
|
3920 |
|
3921 return '<input' . drupal_attributes($element['#attributes']) . ' />'; |
|
3922 } |
|
3923 |
|
3924 /** |
|
3925 * Returns HTML for an image button form element. |
|
3926 * |
|
3927 * @param $variables |
|
3928 * An associative array containing: |
|
3929 * - element: An associative array containing the properties of the element. |
|
3930 * Properties used: #attributes, #button_type, #name, #value, #title, #src. |
|
3931 * |
|
3932 * @ingroup themeable |
|
3933 */ |
|
3934 function theme_image_button($variables) { |
|
3935 $element = $variables['element']; |
|
3936 $element['#attributes']['type'] = 'image'; |
|
3937 element_set_attributes($element, array('id', 'name', 'value')); |
|
3938 |
|
3939 $element['#attributes']['src'] = file_create_url($element['#src']); |
|
3940 if (!empty($element['#title'])) { |
|
3941 $element['#attributes']['alt'] = $element['#title']; |
|
3942 $element['#attributes']['title'] = $element['#title']; |
|
3943 } |
|
3944 |
|
3945 $element['#attributes']['class'][] = 'form-' . $element['#button_type']; |
|
3946 if (!empty($element['#attributes']['disabled'])) { |
|
3947 $element['#attributes']['class'][] = 'form-button-disabled'; |
|
3948 } |
|
3949 |
|
3950 return '<input' . drupal_attributes($element['#attributes']) . ' />'; |
|
3951 } |
|
3952 |
|
3953 /** |
|
3954 * Returns HTML for a hidden form element. |
|
3955 * |
|
3956 * @param $variables |
|
3957 * An associative array containing: |
|
3958 * - element: An associative array containing the properties of the element. |
|
3959 * Properties used: #name, #value, #attributes. |
|
3960 * |
|
3961 * @ingroup themeable |
|
3962 */ |
|
3963 function theme_hidden($variables) { |
|
3964 $element = $variables['element']; |
|
3965 $element['#attributes']['type'] = 'hidden'; |
|
3966 element_set_attributes($element, array('name', 'value')); |
|
3967 return '<input' . drupal_attributes($element['#attributes']) . " />\n"; |
|
3968 } |
|
3969 |
|
3970 /** |
|
3971 * Process function to prepare autocomplete data. |
|
3972 * |
|
3973 * @param $element |
|
3974 * A textfield or other element with a #autocomplete_path. |
|
3975 * |
|
3976 * @return array |
|
3977 * The processed form element. |
|
3978 */ |
|
3979 function form_process_autocomplete($element) { |
|
3980 $element['#autocomplete_input'] = array(); |
|
3981 if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) { |
|
3982 $element['#autocomplete_input']['#id'] = $element['#id'] .'-autocomplete'; |
|
3983 // Force autocomplete to use non-clean URLs since this protects against the |
|
3984 // browser interpreting the path plus search string as an actual file. |
|
3985 $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL; |
|
3986 $GLOBALS['conf']['clean_url'] = 0; |
|
3987 // Force the script path to 'index.php', in case the server is not |
|
3988 // configured to find it automatically. Normally it is the responsibility |
|
3989 // of the site to do this themselves using hook_url_outbound_alter() (see |
|
3990 // url()) but since this code is forcing non-clean URLs on sites that don't |
|
3991 // normally use them, it is done here instead. |
|
3992 $element['#autocomplete_input']['#url_value'] = url($element['#autocomplete_path'], array('absolute' => TRUE, 'script' => 'index.php')); |
|
3993 $GLOBALS['conf']['clean_url'] = $current_clean_url; |
|
3994 } |
|
3995 return $element; |
|
3996 } |
|
3997 |
|
3998 /** |
|
3999 * Returns HTML for a textfield form element. |
|
4000 * |
|
4001 * @param $variables |
|
4002 * An associative array containing: |
|
4003 * - element: An associative array containing the properties of the element. |
|
4004 * Properties used: #title, #value, #description, #size, #maxlength, |
|
4005 * #required, #attributes, #autocomplete_path. |
|
4006 * |
|
4007 * @ingroup themeable |
|
4008 */ |
|
4009 function theme_textfield($variables) { |
|
4010 $element = $variables['element']; |
|
4011 $element['#attributes']['type'] = 'text'; |
|
4012 element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength')); |
|
4013 _form_set_class($element, array('form-text')); |
|
4014 |
|
4015 $extra = ''; |
|
4016 if ($element['#autocomplete_path'] && !empty($element['#autocomplete_input'])) { |
|
4017 drupal_add_library('system', 'drupal.autocomplete'); |
|
4018 $element['#attributes']['class'][] = 'form-autocomplete'; |
|
4019 |
|
4020 $attributes = array(); |
|
4021 $attributes['type'] = 'hidden'; |
|
4022 $attributes['id'] = $element['#autocomplete_input']['#id']; |
|
4023 $attributes['value'] = $element['#autocomplete_input']['#url_value']; |
|
4024 $attributes['disabled'] = 'disabled'; |
|
4025 $attributes['class'][] = 'autocomplete'; |
|
4026 $extra = '<input' . drupal_attributes($attributes) . ' />'; |
|
4027 } |
|
4028 |
|
4029 $output = '<input' . drupal_attributes($element['#attributes']) . ' />'; |
|
4030 |
|
4031 return $output . $extra; |
|
4032 } |
|
4033 |
|
4034 /** |
|
4035 * Returns HTML for a form. |
|
4036 * |
|
4037 * @param $variables |
|
4038 * An associative array containing: |
|
4039 * - element: An associative array containing the properties of the element. |
|
4040 * Properties used: #action, #method, #attributes, #children |
|
4041 * |
|
4042 * @ingroup themeable |
|
4043 */ |
|
4044 function theme_form($variables) { |
|
4045 $element = $variables['element']; |
|
4046 if (isset($element['#action'])) { |
|
4047 $element['#attributes']['action'] = drupal_strip_dangerous_protocols($element['#action']); |
|
4048 } |
|
4049 element_set_attributes($element, array('method', 'id')); |
|
4050 if (empty($element['#attributes']['accept-charset'])) { |
|
4051 $element['#attributes']['accept-charset'] = "UTF-8"; |
|
4052 } |
|
4053 // Anonymous DIV to satisfy XHTML compliance. |
|
4054 return '<form' . drupal_attributes($element['#attributes']) . '><div>' . $element['#children'] . '</div></form>'; |
|
4055 } |
|
4056 |
|
4057 /** |
|
4058 * Returns HTML for a textarea form element. |
|
4059 * |
|
4060 * @param $variables |
|
4061 * An associative array containing: |
|
4062 * - element: An associative array containing the properties of the element. |
|
4063 * Properties used: #title, #value, #description, #rows, #cols, #required, |
|
4064 * #attributes |
|
4065 * |
|
4066 * @ingroup themeable |
|
4067 */ |
|
4068 function theme_textarea($variables) { |
|
4069 $element = $variables['element']; |
|
4070 element_set_attributes($element, array('id', 'name', 'cols', 'rows')); |
|
4071 _form_set_class($element, array('form-textarea')); |
|
4072 |
|
4073 $wrapper_attributes = array( |
|
4074 'class' => array('form-textarea-wrapper'), |
|
4075 ); |
|
4076 |
|
4077 // Add resizable behavior. |
|
4078 if (!empty($element['#resizable'])) { |
|
4079 drupal_add_library('system', 'drupal.textarea'); |
|
4080 $wrapper_attributes['class'][] = 'resizable'; |
|
4081 } |
|
4082 |
|
4083 $output = '<div' . drupal_attributes($wrapper_attributes) . '>'; |
|
4084 $output .= '<textarea' . drupal_attributes($element['#attributes']) . '>' . check_plain($element['#value']) . '</textarea>'; |
|
4085 $output .= '</div>'; |
|
4086 return $output; |
|
4087 } |
|
4088 |
|
4089 /** |
|
4090 * Returns HTML for a password form element. |
|
4091 * |
|
4092 * @param $variables |
|
4093 * An associative array containing: |
|
4094 * - element: An associative array containing the properties of the element. |
|
4095 * Properties used: #title, #value, #description, #size, #maxlength, |
|
4096 * #required, #attributes. |
|
4097 * |
|
4098 * @ingroup themeable |
|
4099 */ |
|
4100 function theme_password($variables) { |
|
4101 $element = $variables['element']; |
|
4102 $element['#attributes']['type'] = 'password'; |
|
4103 element_set_attributes($element, array('id', 'name', 'size', 'maxlength')); |
|
4104 _form_set_class($element, array('form-text')); |
|
4105 |
|
4106 return '<input' . drupal_attributes($element['#attributes']) . ' />'; |
|
4107 } |
|
4108 |
|
4109 /** |
|
4110 * Expands a weight element into a select element. |
|
4111 */ |
|
4112 function form_process_weight($element) { |
|
4113 $element['#is_weight'] = TRUE; |
|
4114 |
|
4115 // If the number of options is small enough, use a select field. |
|
4116 $max_elements = variable_get('drupal_weight_select_max', DRUPAL_WEIGHT_SELECT_MAX); |
|
4117 if ($element['#delta'] <= $max_elements) { |
|
4118 $element['#type'] = 'select'; |
|
4119 for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) { |
|
4120 $weights[$n] = $n; |
|
4121 } |
|
4122 $element['#options'] = $weights; |
|
4123 $element += element_info('select'); |
|
4124 } |
|
4125 // Otherwise, use a text field. |
|
4126 else { |
|
4127 $element['#type'] = 'textfield'; |
|
4128 // Use a field big enough to fit most weights. |
|
4129 $element['#size'] = 10; |
|
4130 $element['#element_validate'] = array('element_validate_integer'); |
|
4131 $element += element_info('textfield'); |
|
4132 } |
|
4133 |
|
4134 return $element; |
|
4135 } |
|
4136 |
|
4137 /** |
|
4138 * Returns HTML for a file upload form element. |
|
4139 * |
|
4140 * For assistance with handling the uploaded file correctly, see the API |
|
4141 * provided by file.inc. |
|
4142 * |
|
4143 * @param $variables |
|
4144 * An associative array containing: |
|
4145 * - element: An associative array containing the properties of the element. |
|
4146 * Properties used: #title, #name, #size, #description, #required, |
|
4147 * #attributes. |
|
4148 * |
|
4149 * @ingroup themeable |
|
4150 */ |
|
4151 function theme_file($variables) { |
|
4152 $element = $variables['element']; |
|
4153 $element['#attributes']['type'] = 'file'; |
|
4154 element_set_attributes($element, array('id', 'name', 'size')); |
|
4155 _form_set_class($element, array('form-file')); |
|
4156 |
|
4157 return '<input' . drupal_attributes($element['#attributes']) . ' />'; |
|
4158 } |
|
4159 |
|
4160 /** |
|
4161 * Returns HTML for a form element. |
|
4162 * |
|
4163 * Each form element is wrapped in a DIV container having the following CSS |
|
4164 * classes: |
|
4165 * - form-item: Generic for all form elements. |
|
4166 * - form-type-#type: The internal element #type. |
|
4167 * - form-item-#name: The internal form element #name (usually derived from the |
|
4168 * $form structure and set via form_builder()). |
|
4169 * - form-disabled: Only set if the form element is #disabled. |
|
4170 * |
|
4171 * In addition to the element itself, the DIV contains a label for the element |
|
4172 * based on the optional #title_display property, and an optional #description. |
|
4173 * |
|
4174 * The optional #title_display property can have these values: |
|
4175 * - before: The label is output before the element. This is the default. |
|
4176 * The label includes the #title and the required marker, if #required. |
|
4177 * - after: The label is output after the element. For example, this is used |
|
4178 * for radio and checkbox #type elements as set in system_element_info(). |
|
4179 * If the #title is empty but the field is #required, the label will |
|
4180 * contain only the required marker. |
|
4181 * - invisible: Labels are critical for screen readers to enable them to |
|
4182 * properly navigate through forms but can be visually distracting. This |
|
4183 * property hides the label for everyone except screen readers. |
|
4184 * - attribute: Set the title attribute on the element to create a tooltip |
|
4185 * but output no label element. This is supported only for checkboxes |
|
4186 * and radios in form_pre_render_conditional_form_element(). It is used |
|
4187 * where a visual label is not needed, such as a table of checkboxes where |
|
4188 * the row and column provide the context. The tooltip will include the |
|
4189 * title and required marker. |
|
4190 * |
|
4191 * If the #title property is not set, then the label and any required marker |
|
4192 * will not be output, regardless of the #title_display or #required values. |
|
4193 * This can be useful in cases such as the password_confirm element, which |
|
4194 * creates children elements that have their own labels and required markers, |
|
4195 * but the parent element should have neither. Use this carefully because a |
|
4196 * field without an associated label can cause accessibility challenges. |
|
4197 * |
|
4198 * @param $variables |
|
4199 * An associative array containing: |
|
4200 * - element: An associative array containing the properties of the element. |
|
4201 * Properties used: #title, #title_display, #description, #id, #required, |
|
4202 * #children, #type, #name. |
|
4203 * |
|
4204 * @ingroup themeable |
|
4205 */ |
|
4206 function theme_form_element($variables) { |
|
4207 $element = &$variables['element']; |
|
4208 |
|
4209 // This function is invoked as theme wrapper, but the rendered form element |
|
4210 // may not necessarily have been processed by form_builder(). |
|
4211 $element += array( |
|
4212 '#title_display' => 'before', |
|
4213 ); |
|
4214 |
|
4215 // Add element #id for #type 'item'. |
|
4216 if (isset($element['#markup']) && !empty($element['#id'])) { |
|
4217 $attributes['id'] = $element['#id']; |
|
4218 } |
|
4219 // Add element's #type and #name as class to aid with JS/CSS selectors. |
|
4220 $attributes['class'] = array('form-item'); |
|
4221 if (!empty($element['#type'])) { |
|
4222 $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-'); |
|
4223 } |
|
4224 if (!empty($element['#name'])) { |
|
4225 $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); |
|
4226 } |
|
4227 // Add a class for disabled elements to facilitate cross-browser styling. |
|
4228 if (!empty($element['#attributes']['disabled'])) { |
|
4229 $attributes['class'][] = 'form-disabled'; |
|
4230 } |
|
4231 $output = '<div' . drupal_attributes($attributes) . '>' . "\n"; |
|
4232 |
|
4233 // If #title is not set, we don't display any label or required marker. |
|
4234 if (!isset($element['#title'])) { |
|
4235 $element['#title_display'] = 'none'; |
|
4236 } |
|
4237 $prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ' : ''; |
|
4238 $suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>' : ''; |
|
4239 |
|
4240 switch ($element['#title_display']) { |
|
4241 case 'before': |
|
4242 case 'invisible': |
|
4243 $output .= ' ' . theme('form_element_label', $variables); |
|
4244 $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; |
|
4245 break; |
|
4246 |
|
4247 case 'after': |
|
4248 $output .= ' ' . $prefix . $element['#children'] . $suffix; |
|
4249 $output .= ' ' . theme('form_element_label', $variables) . "\n"; |
|
4250 break; |
|
4251 |
|
4252 case 'none': |
|
4253 case 'attribute': |
|
4254 // Output no label and no required marker, only the children. |
|
4255 $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; |
|
4256 break; |
|
4257 } |
|
4258 |
|
4259 if (!empty($element['#description'])) { |
|
4260 $output .= '<div class="description">' . $element['#description'] . "</div>\n"; |
|
4261 } |
|
4262 |
|
4263 $output .= "</div>\n"; |
|
4264 |
|
4265 return $output; |
|
4266 } |
|
4267 |
|
4268 /** |
|
4269 * Returns HTML for a marker for required form elements. |
|
4270 * |
|
4271 * @param $variables |
|
4272 * An associative array containing: |
|
4273 * - element: An associative array containing the properties of the element. |
|
4274 * |
|
4275 * @ingroup themeable |
|
4276 */ |
|
4277 function theme_form_required_marker($variables) { |
|
4278 // This is also used in the installer, pre-database setup. |
|
4279 $t = get_t(); |
|
4280 $attributes = array( |
|
4281 'class' => 'form-required', |
|
4282 'title' => $t('This field is required.'), |
|
4283 ); |
|
4284 return '<span' . drupal_attributes($attributes) . '>*</span>'; |
|
4285 } |
|
4286 |
|
4287 /** |
|
4288 * Returns HTML for a form element label and required marker. |
|
4289 * |
|
4290 * Form element labels include the #title and a #required marker. The label is |
|
4291 * associated with the element itself by the element #id. Labels may appear |
|
4292 * before or after elements, depending on theme_form_element() and |
|
4293 * #title_display. |
|
4294 * |
|
4295 * This function will not be called for elements with no labels, depending on |
|
4296 * #title_display. For elements that have an empty #title and are not required, |
|
4297 * this function will output no label (''). For required elements that have an |
|
4298 * empty #title, this will output the required marker alone within the label. |
|
4299 * The label will use the #id to associate the marker with the field that is |
|
4300 * required. That is especially important for screenreader users to know |
|
4301 * which field is required. |
|
4302 * |
|
4303 * @param $variables |
|
4304 * An associative array containing: |
|
4305 * - element: An associative array containing the properties of the element. |
|
4306 * Properties used: #required, #title, #id, #value, #description. |
|
4307 * |
|
4308 * @ingroup themeable |
|
4309 */ |
|
4310 function theme_form_element_label($variables) { |
|
4311 $element = $variables['element']; |
|
4312 // This is also used in the installer, pre-database setup. |
|
4313 $t = get_t(); |
|
4314 |
|
4315 // If title and required marker are both empty, output no label. |
|
4316 if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) { |
|
4317 return ''; |
|
4318 } |
|
4319 |
|
4320 // If the element is required, a required marker is appended to the label. |
|
4321 $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : ''; |
|
4322 |
|
4323 $title = filter_xss_admin($element['#title']); |
|
4324 |
|
4325 $attributes = array(); |
|
4326 // Style the label as class option to display inline with the element. |
|
4327 if ($element['#title_display'] == 'after') { |
|
4328 $attributes['class'] = 'option'; |
|
4329 } |
|
4330 // Show label only to screen readers to avoid disruption in visual flows. |
|
4331 elseif ($element['#title_display'] == 'invisible') { |
|
4332 $attributes['class'] = 'element-invisible'; |
|
4333 } |
|
4334 |
|
4335 if (!empty($element['#id'])) { |
|
4336 $attributes['for'] = $element['#id']; |
|
4337 } |
|
4338 |
|
4339 // The leading whitespace helps visually separate fields from inline labels. |
|
4340 return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n"; |
|
4341 } |
|
4342 |
|
4343 /** |
|
4344 * Sets a form element's class attribute. |
|
4345 * |
|
4346 * Adds 'required' and 'error' classes as needed. |
|
4347 * |
|
4348 * @param $element |
|
4349 * The form element. |
|
4350 * @param $name |
|
4351 * Array of new class names to be added. |
|
4352 */ |
|
4353 function _form_set_class(&$element, $class = array()) { |
|
4354 if (!empty($class)) { |
|
4355 if (!isset($element['#attributes']['class'])) { |
|
4356 $element['#attributes']['class'] = array(); |
|
4357 } |
|
4358 $element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class); |
|
4359 } |
|
4360 // This function is invoked from form element theme functions, but the |
|
4361 // rendered form element may not necessarily have been processed by |
|
4362 // form_builder(). |
|
4363 if (!empty($element['#required'])) { |
|
4364 $element['#attributes']['class'][] = 'required'; |
|
4365 } |
|
4366 if (isset($element['#parents']) && form_get_error($element) !== NULL && !empty($element['#validated'])) { |
|
4367 $element['#attributes']['class'][] = 'error'; |
|
4368 } |
|
4369 } |
|
4370 |
|
4371 /** |
|
4372 * Form element validation handler for integer elements. |
|
4373 */ |
|
4374 function element_validate_integer($element, &$form_state) { |
|
4375 $value = $element['#value']; |
|
4376 if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) { |
|
4377 form_error($element, t('%name must be an integer.', array('%name' => $element['#title']))); |
|
4378 } |
|
4379 } |
|
4380 |
|
4381 /** |
|
4382 * Form element validation handler for integer elements that must be positive. |
|
4383 */ |
|
4384 function element_validate_integer_positive($element, &$form_state) { |
|
4385 $value = $element['#value']; |
|
4386 if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value <= 0)) { |
|
4387 form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title']))); |
|
4388 } |
|
4389 } |
|
4390 |
|
4391 /** |
|
4392 * Form element validation handler for number elements. |
|
4393 */ |
|
4394 function element_validate_number($element, &$form_state) { |
|
4395 $value = $element['#value']; |
|
4396 if ($value != '' && !is_numeric($value)) { |
|
4397 form_error($element, t('%name must be a number.', array('%name' => $element['#title']))); |
|
4398 } |
|
4399 } |
|
4400 |
|
4401 /** |
|
4402 * @} End of "defgroup form_api". |
|
4403 */ |
|
4404 |
|
4405 /** |
|
4406 * @defgroup batch Batch operations |
|
4407 * @{ |
|
4408 * Creates and processes batch operations. |
|
4409 * |
|
4410 * Functions allowing forms processing to be spread out over several page |
|
4411 * requests, thus ensuring that the processing does not get interrupted |
|
4412 * because of a PHP timeout, while allowing the user to receive feedback |
|
4413 * on the progress of the ongoing operations. |
|
4414 * |
|
4415 * The API is primarily designed to integrate nicely with the Form API |
|
4416 * workflow, but can also be used by non-Form API scripts (like update.php) |
|
4417 * or even simple page callbacks (which should probably be used sparingly). |
|
4418 * |
|
4419 * Example: |
|
4420 * @code |
|
4421 * $batch = array( |
|
4422 * 'title' => t('Exporting'), |
|
4423 * 'operations' => array( |
|
4424 * array('my_function_1', array($account->uid, 'story')), |
|
4425 * array('my_function_2', array()), |
|
4426 * ), |
|
4427 * 'finished' => 'my_finished_callback', |
|
4428 * 'file' => 'path_to_file_containing_myfunctions', |
|
4429 * ); |
|
4430 * batch_set($batch); |
|
4431 * // Only needed if not inside a form _submit handler. |
|
4432 * // Setting redirect in batch_process. |
|
4433 * batch_process('node/1'); |
|
4434 * @endcode |
|
4435 * |
|
4436 * Note: if the batch 'title', 'init_message', 'progress_message', or |
|
4437 * 'error_message' could contain any user input, it is the responsibility of |
|
4438 * the code calling batch_set() to sanitize them first with a function like |
|
4439 * check_plain() or filter_xss(). Furthermore, if the batch operation |
|
4440 * returns any user input in the 'results' or 'message' keys of $context, |
|
4441 * it must also sanitize them first. |
|
4442 * |
|
4443 * Sample callback_batch_operation(): |
|
4444 * @code |
|
4445 * // Simple and artificial: load a node of a given type for a given user |
|
4446 * function my_function_1($uid, $type, &$context) { |
|
4447 * // The $context array gathers batch context information about the execution (read), |
|
4448 * // as well as 'return values' for the current operation (write) |
|
4449 * // The following keys are provided : |
|
4450 * // 'results' (read / write): The array of results gathered so far by |
|
4451 * // the batch processing, for the current operation to append its own. |
|
4452 * // 'message' (write): A text message displayed in the progress page. |
|
4453 * // The following keys allow for multi-step operations : |
|
4454 * // 'sandbox' (read / write): An array that can be freely used to |
|
4455 * // store persistent data between iterations. It is recommended to |
|
4456 * // use this instead of $_SESSION, which is unsafe if the user |
|
4457 * // continues browsing in a separate window while the batch is processing. |
|
4458 * // 'finished' (write): A float number between 0 and 1 informing |
|
4459 * // the processing engine of the completion level for the operation. |
|
4460 * // 1 (or no value explicitly set) means the operation is finished |
|
4461 * // and the batch processing can continue to the next operation. |
|
4462 * |
|
4463 * $node = node_load(array('uid' => $uid, 'type' => $type)); |
|
4464 * $context['results'][] = $node->nid . ' : ' . check_plain($node->title); |
|
4465 * $context['message'] = check_plain($node->title); |
|
4466 * } |
|
4467 * |
|
4468 * // More advanced example: multi-step operation - load all nodes, five by five |
|
4469 * function my_function_2(&$context) { |
|
4470 * if (empty($context['sandbox'])) { |
|
4471 * $context['sandbox']['progress'] = 0; |
|
4472 * $context['sandbox']['current_node'] = 0; |
|
4473 * $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); |
|
4474 * } |
|
4475 * $limit = 5; |
|
4476 * $result = db_select('node') |
|
4477 * ->fields('node', array('nid')) |
|
4478 * ->condition('nid', $context['sandbox']['current_node'], '>') |
|
4479 * ->orderBy('nid') |
|
4480 * ->range(0, $limit) |
|
4481 * ->execute(); |
|
4482 * foreach ($result as $row) { |
|
4483 * $node = node_load($row->nid, NULL, TRUE); |
|
4484 * $context['results'][] = $node->nid . ' : ' . check_plain($node->title); |
|
4485 * $context['sandbox']['progress']++; |
|
4486 * $context['sandbox']['current_node'] = $node->nid; |
|
4487 * $context['message'] = check_plain($node->title); |
|
4488 * } |
|
4489 * if ($context['sandbox']['progress'] != $context['sandbox']['max']) { |
|
4490 * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; |
|
4491 * } |
|
4492 * } |
|
4493 * @endcode |
|
4494 * |
|
4495 * Sample callback_batch_finished(): |
|
4496 * @code |
|
4497 * function my_finished_callback($success, $results, $operations) { |
|
4498 * // The 'success' parameter means no fatal PHP errors were detected. All |
|
4499 * // other error management should be handled using 'results'. |
|
4500 * if ($success) { |
|
4501 * $message = format_plural(count($results), 'One post processed.', '@count posts processed.'); |
|
4502 * } |
|
4503 * else { |
|
4504 * $message = t('Finished with an error.'); |
|
4505 * } |
|
4506 * drupal_set_message($message); |
|
4507 * // Providing data for the redirected page is done through $_SESSION. |
|
4508 * foreach ($results as $result) { |
|
4509 * $items[] = t('Loaded node %title.', array('%title' => $result)); |
|
4510 * } |
|
4511 * $_SESSION['my_batch_results'] = $items; |
|
4512 * } |
|
4513 * @endcode |
|
4514 */ |
|
4515 |
|
4516 /** |
|
4517 * Adds a new batch. |
|
4518 * |
|
4519 * Batch operations are added as new batch sets. Batch sets are used to spread |
|
4520 * processing (primarily, but not exclusively, forms processing) over several |
|
4521 * page requests. This helps to ensure that the processing is not interrupted |
|
4522 * due to PHP timeouts, while users are still able to receive feedback on the |
|
4523 * progress of the ongoing operations. Combining related operations into |
|
4524 * distinct batch sets provides clean code independence for each batch set, |
|
4525 * ensuring that two or more batches, submitted independently, can be processed |
|
4526 * without mutual interference. Each batch set may specify its own set of |
|
4527 * operations and results, produce its own UI messages, and trigger its own |
|
4528 * 'finished' callback. Batch sets are processed sequentially, with the progress |
|
4529 * bar starting afresh for each new set. |
|
4530 * |
|
4531 * @param $batch_definition |
|
4532 * An associative array defining the batch, with the following elements (all |
|
4533 * are optional except as noted): |
|
4534 * - operations: (required) Array of operations to be performed, where each |
|
4535 * item is an array consisting of the name of an implementation of |
|
4536 * callback_batch_operation() and an array of parameter. |
|
4537 * Example: |
|
4538 * @code |
|
4539 * array( |
|
4540 * array('callback_batch_operation_1', array($arg1)), |
|
4541 * array('callback_batch_operation_2', array($arg2_1, $arg2_2)), |
|
4542 * ) |
|
4543 * @endcode |
|
4544 * - title: A safe, translated string to use as the title for the progress |
|
4545 * page. Defaults to t('Processing'). |
|
4546 * - init_message: Message displayed while the processing is initialized. |
|
4547 * Defaults to t('Initializing.'). |
|
4548 * - progress_message: Message displayed while processing the batch. Available |
|
4549 * placeholders are @current, @remaining, @total, @percentage, @estimate and |
|
4550 * @elapsed. Defaults to t('Completed @current of @total.'). |
|
4551 * - error_message: Message displayed if an error occurred while processing |
|
4552 * the batch. Defaults to t('An error has occurred.'). |
|
4553 * - finished: Name of an implementation of callback_batch_finished(). This is |
|
4554 * executed after the batch has completed. This should be used to perform |
|
4555 * any result massaging that may be needed, and possibly save data in |
|
4556 * $_SESSION for display after final page redirection. |
|
4557 * - file: Path to the file containing the definitions of the 'operations' and |
|
4558 * 'finished' functions, for instance if they don't reside in the main |
|
4559 * .module file. The path should be relative to base_path(), and thus should |
|
4560 * be built using drupal_get_path(). |
|
4561 * - css: Array of paths to CSS files to be used on the progress page. |
|
4562 * - url_options: options passed to url() when constructing redirect URLs for |
|
4563 * the batch. |
|
4564 */ |
|
4565 function batch_set($batch_definition) { |
|
4566 if ($batch_definition) { |
|
4567 $batch =& batch_get(); |
|
4568 |
|
4569 // Initialize the batch if needed. |
|
4570 if (empty($batch)) { |
|
4571 $batch = array( |
|
4572 'sets' => array(), |
|
4573 'has_form_submits' => FALSE, |
|
4574 ); |
|
4575 } |
|
4576 |
|
4577 // Base and default properties for the batch set. |
|
4578 // Use get_t() to allow batches during installation. |
|
4579 $t = get_t(); |
|
4580 $init = array( |
|
4581 'sandbox' => array(), |
|
4582 'results' => array(), |
|
4583 'success' => FALSE, |
|
4584 'start' => 0, |
|
4585 'elapsed' => 0, |
|
4586 ); |
|
4587 $defaults = array( |
|
4588 'title' => $t('Processing'), |
|
4589 'init_message' => $t('Initializing.'), |
|
4590 'progress_message' => $t('Completed @current of @total.'), |
|
4591 'error_message' => $t('An error has occurred.'), |
|
4592 'css' => array(), |
|
4593 ); |
|
4594 $batch_set = $init + $batch_definition + $defaults; |
|
4595 |
|
4596 // Tweak init_message to avoid the bottom of the page flickering down after |
|
4597 // init phase. |
|
4598 $batch_set['init_message'] .= '<br/> '; |
|
4599 |
|
4600 // The non-concurrent workflow of batch execution allows us to save |
|
4601 // numberOfItems() queries by handling our own counter. |
|
4602 $batch_set['total'] = count($batch_set['operations']); |
|
4603 $batch_set['count'] = $batch_set['total']; |
|
4604 |
|
4605 // Add the set to the batch. |
|
4606 if (empty($batch['id'])) { |
|
4607 // The batch is not running yet. Simply add the new set. |
|
4608 $batch['sets'][] = $batch_set; |
|
4609 } |
|
4610 else { |
|
4611 // The set is being added while the batch is running. Insert the new set |
|
4612 // right after the current one to ensure execution order, and store its |
|
4613 // operations in a queue. |
|
4614 $index = $batch['current_set'] + 1; |
|
4615 $slice1 = array_slice($batch['sets'], 0, $index); |
|
4616 $slice2 = array_slice($batch['sets'], $index); |
|
4617 $batch['sets'] = array_merge($slice1, array($batch_set), $slice2); |
|
4618 _batch_populate_queue($batch, $index); |
|
4619 } |
|
4620 } |
|
4621 } |
|
4622 |
|
4623 /** |
|
4624 * Processes the batch. |
|
4625 * |
|
4626 * Unless the batch has been marked with 'progressive' = FALSE, the function |
|
4627 * issues a drupal_goto and thus ends page execution. |
|
4628 * |
|
4629 * This function is generally not needed in form submit handlers; |
|
4630 * Form API takes care of batches that were set during form submission. |
|
4631 * |
|
4632 * @param $redirect |
|
4633 * (optional) Path to redirect to when the batch has finished processing. |
|
4634 * @param $url |
|
4635 * (optional - should only be used for separate scripts like update.php) |
|
4636 * URL of the batch processing page. |
|
4637 * @param $redirect_callback |
|
4638 * (optional) Specify a function to be called to redirect to the progressive |
|
4639 * processing page. By default drupal_goto() will be used to redirect to a |
|
4640 * page which will do the progressive page. Specifying another function will |
|
4641 * allow the progressive processing to be processed differently. |
|
4642 */ |
|
4643 function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') { |
|
4644 $batch =& batch_get(); |
|
4645 |
|
4646 drupal_theme_initialize(); |
|
4647 |
|
4648 if (isset($batch)) { |
|
4649 // Add process information |
|
4650 $process_info = array( |
|
4651 'current_set' => 0, |
|
4652 'progressive' => TRUE, |
|
4653 'url' => $url, |
|
4654 'url_options' => array(), |
|
4655 'source_url' => $_GET['q'], |
|
4656 'redirect' => $redirect, |
|
4657 'theme' => $GLOBALS['theme_key'], |
|
4658 'redirect_callback' => $redirect_callback, |
|
4659 ); |
|
4660 $batch += $process_info; |
|
4661 |
|
4662 // The batch is now completely built. Allow other modules to make changes |
|
4663 // to the batch so that it is easier to reuse batch processes in other |
|
4664 // environments. |
|
4665 drupal_alter('batch', $batch); |
|
4666 |
|
4667 // Assign an arbitrary id: don't rely on a serial column in the 'batch' |
|
4668 // table, since non-progressive batches skip database storage completely. |
|
4669 $batch['id'] = db_next_id(); |
|
4670 |
|
4671 // Move operations to a job queue. Non-progressive batches will use a |
|
4672 // memory-based queue. |
|
4673 foreach ($batch['sets'] as $key => $batch_set) { |
|
4674 _batch_populate_queue($batch, $key); |
|
4675 } |
|
4676 |
|
4677 // Initiate processing. |
|
4678 if ($batch['progressive']) { |
|
4679 // Now that we have a batch id, we can generate the redirection link in |
|
4680 // the generic error message. |
|
4681 $t = get_t(); |
|
4682 $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'))))); |
|
4683 |
|
4684 // Clear the way for the drupal_goto() redirection to the batch processing |
|
4685 // page, by saving and unsetting the 'destination', if there is any. |
|
4686 if (isset($_GET['destination'])) { |
|
4687 $batch['destination'] = $_GET['destination']; |
|
4688 unset($_GET['destination']); |
|
4689 } |
|
4690 |
|
4691 // Store the batch. |
|
4692 db_insert('batch') |
|
4693 ->fields(array( |
|
4694 'bid' => $batch['id'], |
|
4695 'timestamp' => REQUEST_TIME, |
|
4696 'token' => drupal_get_token($batch['id']), |
|
4697 'batch' => serialize($batch), |
|
4698 )) |
|
4699 ->execute(); |
|
4700 |
|
4701 // Set the batch number in the session to guarantee that it will stay alive. |
|
4702 $_SESSION['batches'][$batch['id']] = TRUE; |
|
4703 |
|
4704 // Redirect for processing. |
|
4705 $function = $batch['redirect_callback']; |
|
4706 if (function_exists($function)) { |
|
4707 $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id']))); |
|
4708 } |
|
4709 } |
|
4710 else { |
|
4711 // Non-progressive execution: bypass the whole progressbar workflow |
|
4712 // and execute the batch in one pass. |
|
4713 require_once DRUPAL_ROOT . '/includes/batch.inc'; |
|
4714 _batch_process(); |
|
4715 } |
|
4716 } |
|
4717 } |
|
4718 |
|
4719 /** |
|
4720 * Retrieves the current batch. |
|
4721 */ |
|
4722 function &batch_get() { |
|
4723 // Not drupal_static(), because Batch API operates at a lower level than most |
|
4724 // use-cases for resetting static variables, and we specifically do not want a |
|
4725 // global drupal_static_reset() resetting the batch information. Functions |
|
4726 // that are part of the Batch API and need to reset the batch information may |
|
4727 // call batch_get() and manipulate the result by reference. Functions that are |
|
4728 // not part of the Batch API can also do this, but shouldn't. |
|
4729 static $batch = array(); |
|
4730 return $batch; |
|
4731 } |
|
4732 |
|
4733 /** |
|
4734 * Populates a job queue with the operations of a batch set. |
|
4735 * |
|
4736 * Depending on whether the batch is progressive or not, the BatchQueue or |
|
4737 * BatchMemoryQueue handler classes will be used. |
|
4738 * |
|
4739 * @param $batch |
|
4740 * The batch array. |
|
4741 * @param $set_id |
|
4742 * The id of the set to process. |
|
4743 * |
|
4744 * @return |
|
4745 * The name and class of the queue are added by reference to the batch set. |
|
4746 */ |
|
4747 function _batch_populate_queue(&$batch, $set_id) { |
|
4748 $batch_set = &$batch['sets'][$set_id]; |
|
4749 |
|
4750 if (isset($batch_set['operations'])) { |
|
4751 $batch_set += array( |
|
4752 'queue' => array( |
|
4753 'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id, |
|
4754 'class' => $batch['progressive'] ? 'BatchQueue' : 'BatchMemoryQueue', |
|
4755 ), |
|
4756 ); |
|
4757 |
|
4758 $queue = _batch_queue($batch_set); |
|
4759 $queue->createQueue(); |
|
4760 foreach ($batch_set['operations'] as $operation) { |
|
4761 $queue->createItem($operation); |
|
4762 } |
|
4763 |
|
4764 unset($batch_set['operations']); |
|
4765 } |
|
4766 } |
|
4767 |
|
4768 /** |
|
4769 * Returns a queue object for a batch set. |
|
4770 * |
|
4771 * @param $batch_set |
|
4772 * The batch set. |
|
4773 * |
|
4774 * @return |
|
4775 * The queue object. |
|
4776 */ |
|
4777 function _batch_queue($batch_set) { |
|
4778 static $queues; |
|
4779 |
|
4780 // The class autoloader is not available when running update.php, so make |
|
4781 // sure the files are manually included. |
|
4782 if (!isset($queues)) { |
|
4783 $queues = array(); |
|
4784 require_once DRUPAL_ROOT . '/modules/system/system.queue.inc'; |
|
4785 require_once DRUPAL_ROOT . '/includes/batch.queue.inc'; |
|
4786 } |
|
4787 |
|
4788 if (isset($batch_set['queue'])) { |
|
4789 $name = $batch_set['queue']['name']; |
|
4790 $class = $batch_set['queue']['class']; |
|
4791 |
|
4792 if (!isset($queues[$class][$name])) { |
|
4793 $queues[$class][$name] = new $class($name); |
|
4794 } |
|
4795 return $queues[$class][$name]; |
|
4796 } |
|
4797 } |
|
4798 |
|
4799 /** |
|
4800 * @} End of "defgroup batch". |
|
4801 */ |