#849420 by Roberto Gerola, rconstantine, thekevinday, ericduran, KarenS Add ability...
[project/cck.git] / includes / content.node_form.inc
CommitLineData
dc19d48f
YC
1<?php
2// $Id$
3
4/**
64cb8552 5 * @file
dc19d48f
YC
6 * Create fields' form for a content type.
7 *
8 * Each field defines its own component of the content entry form, via its
9 * chosen widget.
10 */
11function content_form(&$form, &$form_state) {
12 $type = content_types($form['type']['#value']);
13 foreach ($type['fields'] as $field_name => $field) {
14 $form['#field_info'][$field['field_name']] = $field;
15 $form += (array) content_field_form($form, $form_state, $field);
16 }
17 return $form;
18}
19
20/**
21 * Create a separate form element for each field.
22 *
3a2d35f5 23 * // TODO: $count param ? not used anymore ?
dc19d48f
YC
24 * Hook_widget() picks up two new values, $count and $delta, to help
25 * widgets know what information to return since multiple values are
26 * sometimes controlled by the content module.
27 *
28 * @param $form
29 * the form to add this field element to
30 * @param $form_state
31 * the form_state for the above form
32 * @param $field
33 * the field array to use to create the form element
34 * @param $get_delta
35 * use to get only a specific delta value of a multiple value field, otherwise
36 * function will return the entire $field form element.
37 */
38function content_field_form(&$form, &$form_state, $field, $get_delta = NULL) {
39 $form['#cache'] = FALSE;
40 $node = $form['#node'];
41 $addition = array();
42 $form_element = array();
53a93046 43 $field_name = $field['field_name'];
9fbc360b 44
d0144209 45 $items = array();
9fbc360b 46
3a2d35f5 47 // TODO: is the "if (function_exists($function)) {" needed ?
dc19d48f
YC
48 // defining the $function here makes it unclear where it is actually called
49 $function = $field['widget']['module'] .'_widget';
dc19d48f
YC
50 if (function_exists($function)) {
51 // Prepare the values to be filled in the widget.
3a2d35f5 52 // We look in the following places:
dc19d48f
YC
53 // - Form submitted values
54 // - Node values (when editing an existing node), or pre-filled values (when
55 // creating a new node translation)
56 // - Default values set for the field (when creating a new node).
dc19d48f
YC
57 if (!empty($form_state['values'][$field['field_name']])) {
58 $items = $form_state['values'][$field['field_name']];
9372a36c
YC
59 // If there was an AHAH add more button in this field, don't save it.
60 unset($items[$field['field_name'] .'_add_more']);
dc19d48f
YC
61 }
62 elseif (!empty($node->$field['field_name'])) {
63 $items = $node->$field['field_name'];
64 }
65 elseif (empty($node->nid)) {
66 if (content_callback('widget', 'default value', $field) != CONTENT_CALLBACK_NONE) {
5561e976
KS
67 // If a module wants to insert custom default values here,
68 // it should provide a hook_default_value() function to call,
69 // otherwise the content module's content_default_value() function
70 // will be used.
71 $callback = content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_CUSTOM ? $field['widget']['module'] .'_default_value' : 'content_default_value';
dc19d48f
YC
72 if (function_exists($callback)) {
73 $items = $callback($form, $form_state, $field, 0);
74 }
75 }
76 }
77
d0144209
MFM
78 // See if access to this form element is restricted,
79 // if so, skip widget processing and just set the value.
c7d4ceea 80 $access = content_access('edit', $field, NULL, $node);
d0144209
MFM
81 if (!$access) {
82 $addition[$field_name] = array(
83 '#access' => $access,
84 '#type' => 'value',
85 '#value' => $items,
86 );
87 return $addition;
88 }
89
dc19d48f
YC
90 // If content module handles multiple values for this form element,
91 // and not selecting an individual $delta, process the multiple value form.
92 if (!isset($get_delta) && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
93 $form_element = content_multiple_value_form($form, $form_state, $field, $items);
94 }
95 // If the widget is handling multiple values (e.g optionwidgets),
96 // or selecting an individual element, just get a single form
97 // element and make it the $delta value.
98 else {
99 $delta = isset($get_delta) ? $get_delta : 0;
100 if ($element = $function($form, $form_state, $field, $items, $delta)) {
9fbc360b
YC
101 $title = check_plain(t($field['widget']['label']));
102 $description = content_filter_xss(t($field['widget']['description']));
dc19d48f 103 $defaults = array(
dc19d48f 104 '#required' => $get_delta > 0 ? FALSE : $field['required'],
dc19d48f 105 '#columns' => array_keys($field['columns']),
07c92176 106 '#title' => $title,
9fbc360b 107 '#description' => $description,
dc19d48f 108 '#delta' => $delta,
07c92176
YC
109 '#field_name' => $field['field_name'],
110 '#type_name' => $field['type_name'],
dc19d48f
YC
111 );
112 // If we're processing a specific delta value for a field where the
113 // content module handles multiples, set the delta in the result.
114 // For fields that handle their own processing, we can't make assumptions
115 // about how the field is structured, just merge in the returned value.
116 if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
117 $form_element[$delta] = array_merge($element, $defaults);
118 }
119 else {
120 $form_element = array_merge($element, $defaults);
121 }
122 }
123 }
124
125 // Field name is needed at top level as well as the individual elements
126 // so the multiple values or other field level theme or processing can find it.
127 if ($form_element) {
128 $defaults = array(
129 '#field_name' => $field['field_name'],
130 '#tree' => TRUE,
131 '#weight' => $field['widget']['weight'],
53a93046 132 '#access' => $access,
3a2d35f5 133 // TODO: what's the need for #count ? does not seem to be used anywhere ?
dc19d48f
YC
134 '#count' => count($form_element),
135 );
136 $addition[$field['field_name']] = array_merge($form_element, $defaults);
137 }
138 }
139 return $addition;
140}
141
142/**
143 * Special handling to create form elements for multiple values.
144 *
3a2d35f5 145 * Handles generic features for multiple fields:
dc19d48f
YC
146 * - number of widgets
147 * - AHAH-'add more' button
148 * - drag-n-drop value reordering
149 */
150function content_multiple_value_form(&$form, &$form_state, $field, $items) {
151 $field_name = $field['field_name'];
152
153 switch ($field['multiple']) {
154 case 0:
cb68e6f6 155 $deltas = array(0);
dc19d48f
YC
156 $max = 0;
157 break;
9372a36c 158
cb68e6f6
MFM
159 case 1:
160 $deltas = array_keys($items);
0328c552 161 $current_item_count = max(1, (isset($form_state['item_count'][$field_name]) ? $form_state['item_count'][$field_name] : count($deltas)));
cb68e6f6
MFM
162 $max = (!empty($deltas) ? max($deltas) : -1);
163 while (count($deltas) < $current_item_count) {
164 $max++;
165 $deltas[] = $max;
166 }
dc19d48f 167 break;
cb68e6f6 168
dc19d48f
YC
169 default:
170 $max = $field['multiple'] - 1;
cb68e6f6 171 $deltas = range(0, $max);
dc19d48f
YC
172 break;
173 }
174
eb0954b8
YC
175 $title = check_plain(t($field['widget']['label']));
176 $description = content_filter_xss(t($field['widget']['description']));
177
dc19d48f 178 $form_element = array(
dc19d48f 179 '#theme' => 'content_multiple_values',
eb0954b8 180 '#title' => $title,
3ed89d6e 181 '#required' => $field['required'],
eb0954b8 182 '#description' => $description,
dc19d48f 183 );
6c4e453d 184 $function = $field['widget']['module'] .'_widget';
dc19d48f 185
cb68e6f6 186 foreach ($deltas as $delta) {
dc19d48f
YC
187 if ($element = $function($form, $form_state, $field, $items, $delta)) {
188 $defaults = array(
eb0954b8
YC
189 '#title' => ($field['multiple'] >= 1) ? '' : $title,
190 '#description' => ($field['multiple'] >= 1) ? '' : $description,
cb68e6f6 191 '#required' => ($field['multiple'] == 0 ? $field['required'] : FALSE),
dc19d48f
YC
192 '#weight' => $delta,
193 '#delta' => $delta,
194 '#columns' => array_keys($field['columns']),
07c92176
YC
195 '#field_name' => $field_name,
196 '#type_name' => $field['type_name'],
dc19d48f
YC
197 );
198
199 // Add an input field for the delta (drag-n-drop reordering), which will
200 // be hidden by tabledrag js behavior.
201 if ($field['multiple'] >= 1) {
202 // We name the element '_weight' to avoid clashing with column names
203 // defined by field modules.
204 $element['_weight'] = array(
169430ce
YC
205 '#type' => 'weight',
206 '#delta' => $max, // this 'delta' is the 'weight' element's property
98fd13df 207 '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta,
dc19d48f
YC
208 '#weight' => 100,
209 );
210 }
211
cb68e6f6
MFM
212 // Add a checkbox to allow users remove a single delta item.
213 // See content_set_empty() and theme_content_multiple_values().
214 if ($field['multiple'] == 1) {
215 // We name the element '_remove' to avoid clashing with column names
216 // defined by field modules.
217 $element['_remove'] = array(
218 '#type' => 'checkbox',
219 '#attributes' => array('class' => 'content-multiple-remove-checkbox'),
220 '#default_value' => isset($items[$delta]['_remove']) ? $items[$delta]['_remove'] : 0,
221 );
222 }
223
dc19d48f
YC
224 $form_element[$delta] = array_merge($element, $defaults);
225 }
226 }
227
cb68e6f6
MFM
228 // Add an #after_build callback to prevent validation of fields that are
229 // flagged for removal and enforce field requirement settings.
230 if ($field['multiple'] >= 1) {
231 $form_element['#after_build'] = array('content_multiple_value_after_build_proxy');
232 }
233
dc19d48f
YC
234 // Add AHAH add more button, if not working with a programmed form.
235 if ($field['multiple'] == 1 && empty($form['#programmed'])) {
236 // Make sure the form is cached so ahah can work.
237 $form['#cache'] = TRUE;
07c92176 238 $content_type = content_types($field['type_name']);
dc19d48f
YC
239 $field_name_css = str_replace('_', '-', $field_name);
240
241 $form_element[$field_name .'_add_more'] = array(
242 '#type' => 'submit',
9372a36c 243 '#name' => $field_name .'_add_more',
169430ce 244 '#value' => t('Add another item'),
dc19d48f 245 '#weight' => $field['widget']['weight'] + $max + 1,
9372a36c
YC
246 // Submit callback for disabled JavaScript. drupal_get_form() might get
247 // the form from the cache, so we can't rely on content_form_alter()
248 // including this file. Therefore, call a proxy function to do this.
249 '#submit' => array('content_add_more_submit_proxy'),
dc19d48f
YC
250 '#ahah' => array(
251 'path' => 'content/js_add_more/'. $content_type['url_str'] .'/'. $field_name,
252 'wrapper' => $field_name_css .'-items',
253 'method' => 'replace',
254 'effect' => 'fade',
255 ),
256 // When JS is disabled, the content_add_more_submit handler will find
257 // the relevant field using these entries.
9372a36c 258 '#field_name' => $field_name,
07c92176 259 '#type_name' => $field['type_name'],
dc19d48f
YC
260 );
261
262 // Add wrappers for the fields and 'more' button.
a95bdc23
YC
263 $form_element['#prefix'] = '<div id="'. $field_name_css .'-items">';
264 $form_element['#suffix'] = '</div>';
265 $form_element[$field_name .'_add_more']['#prefix'] = '<div class="content-add-more clear-block">';
cb68e6f6 266 $form_element[$field_name .'_add_more']['#suffix'] = '</div>';
dc19d48f 267 }
9318808e 268
dc19d48f
YC
269 return $form_element;
270}
271
272/**
cb68e6f6
MFM
273 * After build callback for multiple value fields.
274 */
275function content_multiple_value_after_build($elements, &$form_state) {
276 $items_map = array();
277
278 foreach (element_children($elements) as $delta) {
279 // Find delta items for this field when the form if being processed for validation.
280 if (isset($elements[$delta]) && $elements[$delta] && is_numeric($delta) && !empty($elements[$delta]['#needs_validation'])) {
281
282 // Find items that have been flagged for removal.
283 if (isset($elements[$delta]['_remove']) && !empty($elements[$delta]['_remove']['#value'])) {
284
285 // Update the value in the #post attribute of the elements.
286 $post = &$elements[$delta]['#post'];
287 foreach ($elements[$delta]['#parents'] as $name) {
288 $post = &$post[$name];
289 }
290 $post = array('_weight' => $elements[$delta]['_weight']['#value'], '_remove' => 1);
291
292 // Alter the value of this element and children recursively.
293 content_multiple_value_after_build_recursive($elements[$delta], $elements[$delta]['#post']);
294
295 $items_map[$delta] = TRUE;
296 }
297 else {
298 $items_map[$delta] = FALSE;
299 }
300 }
301 }
302
303 // If the multiple values field is required, then make sure there's at
304 // least one item not flagged for removal. This is necessary to point
305 // the user to the correct form element when the validation error is
306 // issued from content_multiple_value_nodeapi_validate().
307 $items_count = count($items_map);
308 if (!empty($elements['#required']) && $items_count > 0) {
309 // If the number of removed items is equal to the total number of
310 // items, then we'll reset the '_remove' flag of the first item, and
311 // that will be used to point the user when the required field error
312 // is issued by content_multiple_value_nodeapi_validate().
313 if ($items_count == count(array_filter($items_map))) {
314 $delta = key($items_map);
315 if (isset($elements[$delta]['_remove'])) {
316 $elements[$delta]['_remove']['#value'] = 0;
317 }
318 }
319 }
320
321 return $elements;
322}
323
324/**
325 * Helper function to deal with items flagged for removal recursively.
326 */
327function content_multiple_value_after_build_recursive(&$elements, $post) {
328 foreach (element_children($elements) as $key) {
329 if (isset($elements[$key]) && $elements[$key] && !in_array($key, array('_weight', '_remove', '_error_element'))) {
330 // Recurse through all children elements.
331 content_multiple_value_after_build_recursive($elements[$key], $post);
332 }
333 }
334
335 // Remove values for items flagged for removal.
336 if (isset($elements['#value'])) {
337 $elements['#value'] = NULL;
338 form_set_value($elements, NULL, $form_state);
339 $elements['#post'] = $post;
340 }
341}
342
343/**
344 * Implementation of nodeapi('validate') for multiple value fields
345 * managed by content module itself.
346 */
347function content_multiple_value_nodeapi_validate(&$node, $field, &$items, $form) {
348 $field_name = $field['field_name'];
349
350 // Getting the field structure from the form allows other modules alter
351 // field properties such as the required attribute.
352 $field = $form['#field_info'][$field_name];
353
354 // Get rid of the add more items element.
355 unset($items[$field_name .'_add_more']);
356
357 // Reorder items to account for drag-n-drop reordering.
358 $items = _content_sort_items($field, $items);
359
360 // Create a copy of the items before filtering those that are flagged
361 // for removal. We need this copy later to obtain the error element.
362 $items_copy = $items;
363
364 // Filter out items flagged for removal.
365 $items = content_set_empty($field, $items);
366
367 // Enforce field requirement settings.
ffa4a96a 368 if ($field['required'] && empty($node->_content_ignore_required_fields[$field_name]) && content_access('edit', $field, NULL, $node)) {
cb68e6f6
MFM
369 // Count non-empty items.
370 $count = 0;
371 $function = $field['module'] .'_content_is_empty';
372 foreach ($items as $item) {
373 if (!$function($item, $field)) {
374 $count++;
375 }
376 }
377 // The field is required so we expect at least one non-empty item.
378 if ($count == 0) {
379 // Try to guess the element path in the form from the first item that
380 // is not flagged for removal. Defaults to first item.
381 $error_element_index = 0;
382 foreach ($items_copy as $index => $item) {
383 if (empty($item['_remove'])) {
384 $error_element_index = $index;
385 break;
386 }
387 }
388 $error_element = isset($items_copy[$error_element_index]) && is_array($items_copy[$error_element_index]) && isset($items_copy[$error_element_index]['_error_element']) ? $items_copy[$error_element_index]['_error_element'] : '';
389 form_set_error($error_element, t('%name field is required.', array('%name' => t($field['widget']['label']))));
390 }
391 }
392}
393
394/**
dc19d48f
YC
395 * Submit handler to add more choices to a content form. This handler is used when
396 * JavaScript is not available. It makes changes to the form state and the
397 * entire form is rebuilt during the page reload.
398 */
399function content_add_more_submit($form, &$form_state) {
400 // Set the form to rebuild and run submit handlers.
401 node_form_submit_build_node($form, $form_state);
402 $field_name = $form_state['clicked_button']['#field_name'];
403 $type_name = $form_state['clicked_button']['#type_name'];
404
9372a36c
YC
405 // Make the changes we want to the form state.
406 if ($form_state['values'][$field_name][$field_name .'_add_more']) {
407 $form_state['item_count'][$field_name] = count($form_state['values'][$field_name]);
dc19d48f
YC
408 }
409}
410
dc19d48f
YC
411/**
412 * Menu callback for AHAH addition of new empty widgets.
413 */
414function content_add_more_js($type_name_url, $field_name) {
9318808e 415 $content_type = content_types($type_name_url);
dc19d48f
YC
416 $type = content_types($type_name_url);
417 $field = content_fields($field_name, $type['type']);
880fb507 418
5dd9ad1b
YC
419 if (($field['multiple'] != 1) || empty($_POST['form_build_id'])) {
420 // Invalid request.
421 drupal_json(array('data' => ''));
422 exit;
dc19d48f
YC
423 }
424
dc19d48f 425 // Retrieve the cached form.
880fb507 426 $form_state = array('submitted' => FALSE);
5dd9ad1b
YC
427 $form_build_id = $_POST['form_build_id'];
428 $form = form_get_cache($form_build_id, $form_state);
429 if (!$form) {
430 // Invalid form_build_id.
431 drupal_json(array('data' => ''));
432 exit;
433 }
dc19d48f 434
880fb507
YC
435 // We don't simply return a new empty widget to append to existing ones, because
436 // - ahah.js won't simply let us add a new row to a table
437 // - attaching the 'draggable' behavior won't be easy
438 // So we resort to rebuilding the whole table of widgets including the existing ones,
439 // which makes us jump through a few hoops.
440
441 // The form that we get from the cache is unbuilt. We need to build it so that
442 // _value callbacks can be executed and $form_state['values'] populated.
443 // We only want to affect $form_state['values'], not the $form itself
444 // (built forms aren't supposed to enter the cache) nor the rest of $form_data,
445 // so we use copies of $form and $form_data.
446 $form_copy = $form;
447 $form_state_copy = $form_state;
448 $form_copy['#post'] = array();
449 form_builder($_POST['form_id'], $form_copy, $form_state_copy);
450 // Just grab the data we need.
451 $form_state['values'] = $form_state_copy['values'];
452 // Reset cached ids, so that they don't affect the actual form we output.
453 form_clean_id(NULL, TRUE);
454
455 // Sort the $form_state['values'] we just built *and* the incoming $_POST data
456 // according to d-n-d reordering.
457 unset($form_state['values'][$field_name][$field['field_name'] .'_add_more']);
458 foreach ($_POST[$field_name] as $delta => $item) {
459 $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight'];
cb68e6f6 460 $form_state['values'][$field_name][$delta]['_remove'] = isset($item['_remove']) ? $item['_remove'] : 0;
880fb507
YC
461 }
462 $form_state['values'][$field_name] = _content_sort_items($field, $form_state['values'][$field_name]);
463 $_POST[$field_name] = _content_sort_items($field, $_POST[$field_name]);
464
465 // Build our new form element for the whole field, asking for one more element.
cb68e6f6 466 $delta = max(array_keys($_POST[$field_name])) + 1;
880fb507 467 $form_state['item_count'] = array($field_name => count($_POST[$field_name]) + 1);
dc19d48f 468 $form_element = content_field_form($form, $form_state, $field);
880fb507 469 // Let other modules alter it.
165c7a0c
YC
470 // We pass an empty array as hook_form_alter's usual 'form_state' parameter,
471 // instead of $form_atate (for reasons we may never remember).
472 // However, this argument is still expected to be passed by-reference
473 // (and PHP5.3 will throw an error if it isn't.) This leads to:
474 $data = &$form_element;
475 $empty_form_state = array();
476 $data['__drupal_alter_by_ref'] = array(&$empty_form_state);
477 drupal_alter('form', $data, 'content_add_more_js');
dc19d48f 478
880fb507 479 // Add the new element at the right place in the (original, unbuilt) form.
9318808e 480 content_set_form_element($field_name, $type['type'], $form, $form_element[$field_name]);
dc19d48f
YC
481
482 // Save the new definition of the form.
5dd9ad1b
YC
483 $form_state['values'] = array();
484 form_set_cache($form_build_id, $form, $form_state);
dc19d48f 485
880fb507
YC
486 // Build the new form against the incoming $_POST values so that we can
487 // render the new element.
880fb507 488 $_POST[$field_name][$delta]['_weight'] = $delta;
dc19d48f
YC
489 $form_state = array('submitted' => FALSE);
490 $form += array(
491 '#post' => $_POST,
492 '#programmed' => FALSE,
493 );
494 $form = form_builder($_POST['form_id'], $form, $form_state);
495
dc19d48f 496 // Render the new output.
9318808e
KS
497 $field_form = content_get_form_element($field_name, $type['type'], $form);
498
dc19d48f 499 // We add a div around the new content to receive the ahah effect.
dc19d48f
YC
500 $field_form[$delta]['#prefix'] = '<div class="ahah-new-content">'. (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : '');
501 $field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') .'</div>';
ede4e955
YC
502 // Prevent duplicate wrapper.
503 unset($field_form['#prefix'], $field_form['#suffix']);
b7ab4f3e
YC
504
505 // If a newly inserted widget contains AHAH behaviors, they normally won't
506 // work because AHAH doesn't know about those - it just attaches to the exact
507 // form elements that were initially specified in the Drupal.settings object.
508 // The new ones didn't exist then, so we need to update Drupal.settings
509 // by ourselves in order to let AHAH know about those new form elements.
510 $javascript = drupal_add_js(NULL, NULL);
9bca71e9 511 $output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) .');</script>' : '';
b7ab4f3e 512
9bca71e9 513 $output = theme('status_messages') . drupal_render($field_form) . $output_js;
4b78f6df
YC
514
515 // Using drupal_json() breaks filefield's file upload, because the jQuery
516 // Form plugin handles file uploads in a way that is not compatible with
517 // 'text/javascript' response type.
f1a5ee1d 518 $GLOBALS['devel_shutdown'] = FALSE;
4b78f6df 519 print drupal_to_js(array('status' => TRUE, 'data' => $output));
5dd9ad1b 520 exit;
dc19d48f 521}
9318808e
KS
522
523/**
524 * Store an element into a form.
525 *
526 * @param $name
527 * The field name.
528 * @param $type
529 * The content type where the field instance belongs to.
530 * @param $form
531 * The form to store this field element into.
532 * @param $element
533 * The form element to store.
534 */
535function content_set_form_element($name, $type, &$form, $element) {
536 $is_group = substr($name, 0, 6) == 'group_';
537 if (module_exists('fieldgroup') && ($parents = fieldgroup_get_parents($type, $name))) {
538 $reference = &$form;
539 if ($is_group) {
540 array_shift($parents);
541 $parents = array_reverse($parents);
542 }
543 else {
544 $parents = array_reverse($parents);
545 }
546
547 foreach (array_values($parents) as $group_name) {
548 $reference = &$reference[$group_name];
549 }
550
551 $reference[$name] = $element;
552
553 }
554 else {
555 $form[$name] = $element;
556 }
557}
558
559/**
560 * Retrieve an element from a form.
561 *
562 * @param $name
563 * The field name.
564 * @param $type
565 * The content type where the field instance belongs to.
566 * @param $form
567 * The form to retrieve this field element from.
568 */
569function content_get_form_element($name, $type, $form) {
570 $is_group = substr($name, 0, 6) == 'group_';
571 if (module_exists('fieldgroup') && ($parents = fieldgroup_get_parents($type, $name))) {
572 $reference = &$form;
573 if ($is_group) {
574 array_shift($parents);
575 $parents = array_reverse($parents);
576 }
577 else {
578 $parents = array_reverse($parents);
579 }
580
581 foreach (array_values($parents) as $group_name) {
582 $reference = &$reference[$group_name];
583 }
584
585 return $reference[$name];
586 }
587 else {
588 return $form[$name];
589 }
590}
591