Simpletest Coverage - modules/field/field.form.inc

1 <?php
2 // $Id: field.form.inc,v 1.13 2009/08/13 01:50:00 webchick Exp $
3
4 /**
5 * @file
6 * Field forms management.
7 */
8
9 /**
10 * Create a separate form element for each field.
11 */
12 function field_default_form($obj_type, $object, $field, $instance, $items, &$form, &$form_state, $get_delta = NULL) {
13 // This could be called with no object, as when a UI module creates a
14 // dummy form to set default values.
15 if ($object) {
16 list($id, , ) = field_attach_extract_ids($obj_type, $object);
17 }
18 $addition = array();
19
20 $field_name = $field['field_name'];
21
22 // If the field is not accessible, don't add anything. The field value will
23 // be left unchanged on update, or considered empty on insert (default value
24 // will be inserted if applicable).
25 if (!field_access('edit', $field)) {
26 return $addition;
27 }
28
29 // Put field information at the top of the form, so that it can be easily
30 // retrieved.
31 // Note : widgets and other form handling code should *always* fetch
32 // field and instance information from $form['#fields'] rather than from
33 // field_info_field(). This lets us build forms for 'variants' of a field,
34 // for instance on admin screens.
35 $form['#fields'][$field_name] = array(
36 'field' => $field,
37 'instance' => $instance,
38 );
39
40 // Populate widgets with default values if we're creating a new object.
41 if (empty($items) && empty($id) && !empty($instance['default_value_function'])) {
42 $items = array();
43 $function = $instance['default_value_function'];
44 if (drupal_function_exists($function)) {
45 $items = $function($obj_type, $object, $field, $instance);
46 }
47 }
48
49 $form_element = array();
50
51 // If field module handles multiple values for this form element,
52 // and we are displaying an individual element, process the multiple value
53 // form.
54 if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
55 $form_element = field_multiple_value_form($field, $instance, $items, $form, $form_state);
56 }
57 // If the widget is handling multiple values (e.g Options),
58 // or if we are displaying an individual element, just get a single form
59 // element and make it the $delta value.
60 else {
61 $delta = isset($get_delta) ? $get_delta : 0;
62 $function = $instance['widget']['module'] . '_field_widget';
63 if (drupal_function_exists($function)) {
64 if ($element = $function($form, $form_state, $field, $instance, $items, $delta)) {
65 $defaults = array(
66 '#required' => $get_delta > 0 ? FALSE : $instance['required'],
67 '#columns' => array_keys($field['columns']),
68 '#title' => check_plain(t($instance['label'])),
69 '#description' => field_filter_xss($instance['description']),
70 '#delta' => $delta,
71 '#field_name' => $field['field_name'],
72 '#bundle' => $instance['bundle'],
73 );
74 $element = array_merge($element, $defaults);
75 // If we're processing a specific delta value for a field where the
76 // field module handles multiples, set the delta in the result.
77 // For fields that handle their own processing, we can't make assumptions
78 // about how the field is structured, just merge in the returned value.
79 if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
80 $form_element[$delta] = $element;
81 }
82 else {
83 $form_element = $element;
84 }
85 }
86 }
87 }
88
89 if ($form_element) {
90 $defaults = array(
91 '#field_name' => $field['field_name'],
92 '#tree' => TRUE,
93 '#weight' => $instance['widget']['weight'],
94 );
95
96 $addition[$field['field_name']] = array_merge($form_element, $defaults);
97 $form['#fields'][$field['field_name']]['form_path'] = array($field['field_name']);
98 }
99
100 return $addition;
101 }
102
103 /**
104 * Special handling to create form elements for multiple values.
105 *
106 * Handles generic features for multiple fields:
107 * - number of widgets
108 * - AHAH-'add more' button
109 * - drag-n-drop value reordering
110 */
111 function field_multiple_value_form($field, $instance, $items, &$form, &$form_state) {
112 $field = field_info_field($instance['field_name']);
113 $field_name = $field['field_name'];
114
115 switch ($field['cardinality']) {
116 case FIELD_CARDINALITY_UNLIMITED:
117 $filled_items = field_set_empty($field, $items);
118 $current_item_count = isset($form_state['field_item_count'][$field_name])
119 ? $form_state['field_item_count'][$field_name]
120 : count($items);
121 // We always want at least one empty icon for the user to fill in.
122 $max = ($current_item_count > count($filled_items))
123 ? $current_item_count - 1
124 : $current_item_count;
125
126 break;
127 default:
128 $max = $field['cardinality'] - 1;
129 break;
130 }
131
132 $title = check_plain(t($instance['label']));
133 $description = field_filter_xss(t($instance['description']));
134
135 $bundle_name_url_css = str_replace('_', '-', $instance['bundle']);
136 $field_name_url_css = str_replace('_', '-', $field_name);
137
138 $form_element = array(
139 '#theme' => 'field_multiple_value_form',
140 '#multiple' => $field['cardinality'],
141 '#title' => $title,
142 '#required' => $instance['required'],
143 '#description' => $description,
144 '#prefix' => '<div id="' . $field_name_url_css . '-wrapper">',
145 '#suffix' => '</div>',
146 );
147
148 $function = $instance['widget']['module'] . '_field_widget';
149 if (drupal_function_exists($function)) {
150 for ($delta = 0; $delta <= $max; $delta++) {
151 if ($element = $function($form, $form_state, $field, $instance, $items, $delta)) {
152 $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
153 $defaults = array(
154 // For multiple fields, title and description are handled by the wrapping table.
155 '#title' => $multiple ? '' : $title,
156 '#description' => $multiple ? '' : $description,
157 '#required' => $delta == 0 && $instance['required'],
158 '#weight' => $delta,
159 '#delta' => $delta,
160 '#columns' => array_keys($field['columns']),
161 '#field_name' => $field_name,
162 '#bundle' => $instance['bundle'],
163 );
164
165 // Input field for the delta (drag-n-drop reordering).
166 if ($multiple) {
167 // We name the element '_weight' to avoid clashing with elements
168 // defined by widget.
169 $element['_weight'] = array(
170 '#type' => 'weight',
171 // Note: this 'delta' is the FAPI 'weight' element's property.
172 '#delta' => $max,
173 '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta,
174 '#weight' => 100,
175 );
176 }
177
178 $form_element[$delta] = array_merge($element, $defaults);
179 }
180 }
181
182 // Add AHAH add more button, if not working with a programmed form.
183 if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form_state['programmed'])) {
184 $bundle_name_url_str = str_replace('_', '-', $instance['bundle']);
185 $field_name_url_str = str_replace('_', '-', $field_name);
186
187 $form_element[$field_name . '_add_more'] = array(
188 '#type' => 'submit',
189 '#name' => $field_name . '_add_more',
190 '#value' => t('Add another item'),
191 // Submit callback for disabled JavaScript.
192 '#submit' => array('field_add_more_submit'),
193 '#ahah' => array(
194 'path' => 'field/js_add_more/' . $bundle_name_url_css . '/' . $field_name_url_css,
195 'wrapper' => $field_name_url_css . '-wrapper',
196 'method' => 'replace',
197 'effect' => 'fade',
198 ),
199 // When JS is disabled, the field_add_more_submit handler will find
200 // the relevant field using these entries.
201 '#field_name' => $field_name,
202 '#bundle' => $instance['bundle'],
203 '#attributes' => array('class' => 'field-add-more-submit'),
204 );
205 }
206 }
207 return $form_element;
208 }
209
210 /**
211 * Theme an individual form element.
212 *
213 * Combine multiple values into a table with drag-n-drop reordering.
214 * TODO : convert to a template.
215 */
216 function theme_field_multiple_value_form($element) {
217 $output = '';
218
219 if ($element['#multiple'] > 1 || $element['#multiple'] == FIELD_CARDINALITY_UNLIMITED) {
220 $table_id = $element['#field_name'] . '_values';
221 $order_class = $element['#field_name'] . '-delta-order';
222 $required = !empty($element['#required']) ? '<span class="form-required" title="' . t('This field is required. ') . '">*</span>' : '';
223
224 $header = array(
225 array(
226 'data' => '<label>' . t('!title: !required', array('!title' => $element['#title'], '!required' => $required)) . "</label>",
227 'colspan' => 2,
228 'class' => 'field-label',
229 ),
230 t('Order'),
231 );
232 $rows = array();
233
234 // Sort items according to '_weight' (needed when the form comes back after
235 // preview or failed validation)
236 $items = array();
237 foreach (element_children($element) as $key) {
238 if ($key === $element['#field_name'] . '_add_more') {
239 $add_more_button = &$element[$key];
240 }
241 else {
242 $items[] = &$element[$key];
243 }
244 }
245 usort($items, '_field_sort_items_value_helper');
246
247 // Add the items as table rows.
248 foreach ($items as $key => $item) {
249 $item['_weight']['#attributes']['class'] = $order_class;
250 $delta_element = drupal_render($item['_weight']);
251 $cells = array(
252 array('data' => '', 'class' => 'field-multiple-drag'),
253 drupal_render($item),
254 array('data' => $delta_element, 'class' => 'delta-order'),
255 );
256 $rows[] = array(
257 'data' => $cells,
258 'class' => 'draggable',
259 );
260 }
261
262 $output = '<div class="form-item">';
263 $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'field-multiple-table'));
264 $output .= $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : '';
265 $output .= '<div class="clearfix">' . drupal_render($add_more_button) . '</div>';
266 $output .= '</div>';
267
268 drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
269 }
270 else {
271 foreach (element_children($element) as $key) {
272 $output .= drupal_render($element[$key]);
273 }
274 }
275
276 return $output;
277 }
278
279
280 /**
281 * Transfer field-level validation errors to widgets.
282 */
283 function field_default_form_errors($obj_type, $object, $field, $instance, $items, $form, $errors) {
284 $field_name = $field['field_name'];
285 if (!empty($errors[$field_name])) {
286 $function = $instance['widget']['module'] . '_field_widget_error';
287 $function_exists = drupal_function_exists($function);
288
289 // Walk the form down to where the widget lives.
290 $form_path = $form['#fields'][$field_name]['form_path'];
291 $element = $form;
292 foreach ($form_path as $key) {
293 $element = $element[$key];
294 }
295
296 $multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT;
297 foreach ($errors[$field_name] as $delta => $delta_errors) {
298 // For multiple single-value widgets, pass errors by delta.
299 // For a multiple-value widget, all errors are passed to the main widget.
300 $error_element = $multiple_widget ? $element : $element[$delta];
301 foreach ($delta_errors as $error) {
302 if ($function_exists) {
303 $function($error_element, $error);
304 }
305 else {
306 // Make sure that errors are reported (even incorrectly flagged) if
307 // the widget module fails to implement hook_field_widget_error().
308 form_error($error_element, $error['error']);
309 }
310 }
311 }
312 }
313 }
314
315 /**
316 * Submit handler to add more choices to a field form. This handler is used when
317 * JavaScript is not available. It makes changes to the form state and the
318 * entire form is rebuilt during the page reload.
319 */
320 function field_add_more_submit($form, &$form_state) {
321 // Set the form to rebuild and run submit handlers.
322 if (isset($form['#builder_function']) && drupal_function_exists($form['#builder_function'])) {
323 $entity = $form['#builder_function']($form, $form_state);
324
325 // Make the changes we want to the form state.
326 $field_name = $form_state['clicked_button']['#field_name'];
327 if ($form_state['values'][$field_name . '_add_more']) {
328 $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name]);
329 }
330 }
331 }
332
333 /**
334 * Menu callback for AHAH addition of new empty widgets.
335 */
336 function field_add_more_js($bundle_name, $field_name) {
337 // Arguments are coming from the url, so we translate back dashes.
338 $field_name = str_replace('-', '_', $field_name);
339
340 $invalid = FALSE;
341 if (empty($_POST['form_build_id'])) {
342 // Invalid request.
343 $invalid = TRUE;
344 }
345
346 // Retrieve the cached form.
347 $form_state = form_state_defaults();
348 $form_build_id = $_POST['form_build_id'];
349 $form = form_get_cache($form_build_id, $form_state);
350 if (!$form) {
351 // Invalid form_build_id.
352 $invalid = TRUE;
353 }
354
355 // Retrieve field information.
356 $field = $form['#fields'][$field_name]['field'];
357 $instance = $form['#fields'][$field_name]['instance'];
358 $form_path = $form['#fields'][$field_name]['form_path'];
359 if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
360 // Ivnalid
361 $invalid = TRUE;
362 }
363
364 if ($invalid) {
365 drupal_json(array('data' => ''));
366 exit;
367 }
368
369 // We don't simply return a new empty widget row to append to existing ones,
370 // because:
371 // - ahah.js won't simply let us add a new row to a table
372 // - attaching the 'draggable' behavior won't be easy
373 // So we resort to rebuilding the whole table of widgets including the
374 // existing ones, which makes us jump through a few hoops.
375
376 // The form that we get from the cache is unbuilt. We need to build it so
377 // that _value callbacks can be executed and $form_state['values'] populated.
378 // We only want to affect $form_state['values'], not the $form itself
379 // (built forms aren't supposed to enter the cache) nor the rest of
380 // $form_state, so we use copies of $form and $form_state.
381 $form_copy = $form;
382 $form_state_copy = $form_state;
383 $form_state_copy['input'] = array();
384 form_builder($_POST['form_id'], $form_copy, $form_state_copy);
385 // Just grab the data we need.
386 $form_state['values'] = $form_state_copy['values'];
387 // Reset cached ids, so that they don't affect the actual form we output.
388 drupal_static_reset('form_clean_id');
389
390 // Sort the $form_state['values'] we just built *and* the incoming $_POST data
391 // according to d-n-d reordering.
392 unset($form_state['values'][$field_name][$field['field_name'] . '_add_more']);
393 foreach ($_POST[$field_name] as $delta => $item) {
394 $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight'];
395 }
396 $form_state['values'][$field_name] = _field_sort_items($field, $form_state['values'][$field_name]);
397 $_POST[$field_name] = _field_sort_items($field, $_POST[$field_name]);
398
399 // Build our new form element for the whole field, asking for one more element.
400 $form_state['field_item_count'] = array($field_name => count($_POST[$field_name]) + 1);
401 $items = $form_state['values'][$field_name];
402 $form_element = field_default_form(NULL, NULL, $field, $instance, $items, $form, $form_state);
403 // Let other modules alter it.
404 drupal_alter('form', $form_element, array(), 'field_add_more_js');
405
406 // Add the new element at the right location in the (original, unbuilt) form.
407 $target = &$form;
408 foreach ($form_path as $key) {
409 $target = &$target[$key];
410 }
411 $target = $form_element[$field_name];
412
413 // Save the new definition of the form.
414 $form_state['values'] = array();
415 form_set_cache($form_build_id, $form, $form_state);
416
417 // Build the new form against the incoming $_POST values so that we can
418 // render the new element.
419 $delta = max(array_keys($_POST[$field_name])) + 1;
420 $_POST[$field_name][$delta]['_weight'] = $delta;
421 $form_state = form_state_defaults();
422 $form_state['input'] = $_POST;
423 $form = form_builder($_POST['form_id'], $form, $form_state);
424
425 // Render the new output.
426 // We fetch the form element from the built $form.
427 $field_form = $form;
428 foreach ($form_path as $key) {
429 $field_form = $field_form[$key];
430 }
431 // Add a div around the new field to receive the ahah effect.
432 $field_form[$delta]['#prefix'] = '<div class="ahah-new-content">' . (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : '');
433 $field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') . '</div>';
434 // Prevent duplicate wrapper.
435 unset($field_form['#prefix'], $field_form['#suffix']);
436
437 // If a newly inserted widget contains AHAH behaviors, they normally won't
438 // work because AHAH doesn't know about those - it just attaches to the exact
439 // form elements that were initially specified in the Drupal.settings object.
440 // The new ones didn't exist then, so we need to update Drupal.settings
441 // by ourselves in order to let AHAH know about those new form elements.
442 $javascript = drupal_add_js(NULL, NULL);
443 $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>' : '';
444
445 $output = theme('status_messages') . drupal_render($field_form) . $output_js;
446 drupal_json(array('status' => TRUE, 'data' => $output));
447 exit;
448 }
449

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.