Projet

Général

Profil

Paste
Télécharger (22,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / field / field.form.inc @ 73a44071

1
<?php
2

    
3
/**
4
 * @file
5
 * Field forms management.
6
 */
7

    
8
/**
9
 * Creates a form element for a field and can populate it with a default value.
10
 *
11
 * If the form element is not associated with an entity (i.e., $entity is NULL)
12
 * field_get_default_value will be called to supply the default value for the
13
 * field. Also allows other modules to alter the form element by implementing
14
 * their own hooks.
15
 *
16
 * @param $entity_type
17
 *   The type of entity (for example 'node' or 'user') that the field belongs
18
 *   to.
19
 * @param $entity
20
 *   The entity object that the field belongs to. This may be NULL if creating a
21
 *   form element with a default value.
22
 * @param $field
23
 *   An array representing the field whose editing element is being created.
24
 * @param $instance
25
 *   An array representing the structure for $field in its current context.
26
 * @param $langcode
27
 *   The language associated with the field.
28
 * @param $items
29
 *   An array of the field values. When creating a new entity this may be NULL
30
 *   or an empty array to use default values.
31
 * @param $form
32
 *   An array representing the form that the editing element will be attached
33
 *   to. 
34
 * @param $form_state
35
 *   An array containing the current state of the form.
36
 * @param $get_delta
37
 *   Used to get only a specific delta value of a multiple value field.
38
 *
39
 * @return
40
 *  The form element array created for this field.
41
 */
42
function field_default_form($entity_type, $entity, $field, $instance, $langcode, $items, &$form, &$form_state, $get_delta = NULL) {
43
  // This could be called with no entity, as when a UI module creates a
44
  // dummy form to set default values.
45
  if ($entity) {
46
    list($id, , ) = entity_extract_ids($entity_type, $entity);
47
  }
48

    
49
  $parents = $form['#parents'];
50

    
51
  $addition = array();
52
  $field_name = $field['field_name'];
53
  $addition[$field_name] = array();
54

    
55
  // Populate widgets with default values when creating a new entity.
56
  if (empty($items) && empty($id)) {
57
    $items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode);
58
  }
59

    
60
  // Let modules alter the widget properties.
61
  $context = array(
62
    'entity_type' => $entity_type,
63
    'entity' => $entity,
64
    'field' => $field,
65
    'instance' => $instance,
66
  );
67
  drupal_alter(array('field_widget_properties', 'field_widget_properties_' . $entity_type), $instance['widget'], $context);
68

    
69
  // Collect widget elements.
70
  $elements = array();
71

    
72
  // Store field information in $form_state.
73
  if (!field_form_get_state($parents, $field_name, $langcode, $form_state)) {
74
    $field_state = array(
75
      'field' => $field,
76
      'instance' => $instance,
77
      'items_count' => count($items),
78
      'array_parents' => array(),
79
      'errors' => array(),
80
    );
81
    field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
82
  }
83

    
84
  // If field module handles multiple values for this form element, and we are
85
  // displaying an individual element, process the multiple value form.
86
  if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
87
    // Store the entity in the form.
88
    $form['#entity'] = $entity;
89
    $elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state);
90
  }
91
  // If the widget is handling multiple values (e.g Options), or if we are
92
  // displaying an individual element, just get a single form element and make
93
  // it the $delta value.
94
  else {
95
    $delta = isset($get_delta) ? $get_delta : 0;
96
    $function = $instance['widget']['module'] . '_field_widget_form';
97
    if (function_exists($function)) {
98
      $element = array(
99
        '#entity' => $entity,
100
        '#entity_type' => $instance['entity_type'],
101
        '#bundle' => $instance['bundle'],
102
        '#field_name' => $field_name,
103
        '#language' => $langcode,
104
        '#field_parents' => $parents,
105
        '#columns' => array_keys($field['columns']),
106
        '#title' => check_plain($instance['label']),
107
        '#description' => field_filter_xss($instance['description']),
108
        // Only the first widget should be required.
109
        '#required' => $delta == 0 && $instance['required'],
110
        '#delta' => $delta,
111
      );
112
      if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) {
113
        // Allow modules to alter the field widget form element.
114
        $context = array(
115
          'form' => $form,
116
          'field' => $field,
117
          'instance' => $instance,
118
          'langcode' => $langcode,
119
          'items' => $items,
120
          'delta' => $delta,
121
        );
122
        drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), $element, $form_state, $context);
123

    
124
        // If we're processing a specific delta value for a field where the
125
        // field module handles multiples, set the delta in the result.
126
        // For fields that handle their own processing, we can't make
127
        // assumptions about how the field is structured, just merge in the
128
        // returned element.
129
        if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
130
          $elements[$delta] = $element;
131
        }
132
        else {
133
          $elements = $element;
134
        }
135
      }
136
    }
137
  }
138

    
139
  // Also aid in theming of field widgets by rendering a classified container.
140
  $addition[$field_name] = array(
141
    '#type' => 'container',
142
    '#attributes' => array(
143
      'class' => array(
144
        'field-type-' . drupal_html_class($field['type']),
145
        'field-name-' . drupal_html_class($field_name),
146
        'field-widget-' . drupal_html_class($instance['widget']['type']),
147
      ),
148
    ),
149
    '#weight' => $instance['widget']['weight'],
150
  );
151

    
152
  // Populate the 'array_parents' information in $form_state['field'] after
153
  // the form is built, so that we catch changes in the form structure performed
154
  // in alter() hooks.
155
  $elements['#after_build'][] = 'field_form_element_after_build';
156
  $elements['#field_name'] = $field_name;
157
  $elements['#language'] = $langcode;
158
  $elements['#field_parents'] = $parents;
159

    
160
  $addition[$field_name] += array(
161
    '#tree' => TRUE,
162
    // The '#language' key can be used to access the field's form element
163
    // when $langcode is unknown.
164
    '#language' => $langcode,
165
    $langcode => $elements,
166
    '#access' => field_access('edit', $field, $entity_type, $entity),
167
  );
168

    
169
  return $addition;
170
}
171

    
172
/**
173
 * Special handling to create form elements for multiple values.
174
 *
175
 * Handles generic features for multiple fields:
176
 * - number of widgets
177
 * - AHAH-'add more' button
178
 * - drag-n-drop value reordering
179
 */
180
function field_multiple_value_form($field, $instance, $langcode, $items, &$form, &$form_state) {
181
  $field_name = $field['field_name'];
182
  $parents = $form['#parents'];
183

    
184
  // Determine the number of widgets to display.
185
  switch ($field['cardinality']) {
186
    case FIELD_CARDINALITY_UNLIMITED:
187
      $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
188
      $max = $field_state['items_count'];
189
      break;
190

    
191
    default:
192
      $max = $field['cardinality'] - 1;
193
      break;
194
  }
195

    
196
  $title = check_plain($instance['label']);
197
  $description = field_filter_xss($instance['description']);
198

    
199
  $id_prefix = implode('-', array_merge($parents, array($field_name)));
200
  $wrapper_id = drupal_html_id($id_prefix . '-add-more-wrapper');
201

    
202
  $field_elements = array();
203

    
204
  $function = $instance['widget']['module'] . '_field_widget_form';
205
  if (function_exists($function)) {
206
    for ($delta = 0; $delta <= $max; $delta++) {
207
      $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
208
      $element = array(
209
        '#entity_type' => $instance['entity_type'],
210
        '#entity' => $form['#entity'],
211
        '#bundle' => $instance['bundle'],
212
        '#field_name' => $field_name,
213
        '#language' => $langcode,
214
        '#field_parents' => $parents,
215
        '#columns' => array_keys($field['columns']),
216
        // For multiple fields, title and description are handled by the wrapping table.
217
        '#title' => $multiple ? '' : $title,
218
        '#description' => $multiple ? '' : $description,
219
        // Only the first widget should be required.
220
        '#required' => $delta == 0 && $instance['required'],
221
        '#delta' => $delta,
222
        '#weight' => $delta,
223
      );
224
      if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) {
225
        // Input field for the delta (drag-n-drop reordering).
226
        if ($multiple) {
227
          // We name the element '_weight' to avoid clashing with elements
228
          // defined by widget.
229
          $element['_weight'] = array(
230
            '#type' => 'weight',
231
            '#title' => t('Weight for row @number', array('@number' => $delta + 1)),
232
            '#title_display' => 'invisible',
233
             // Note: this 'delta' is the FAPI 'weight' element's property.
234
            '#delta' => $max,
235
            '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta,
236
            '#weight' => 100,
237
          );
238
        }
239

    
240
        // Allow modules to alter the field widget form element.
241
        $context = array(
242
          'form' => $form,
243
          'field' => $field,
244
          'instance' => $instance,
245
          'langcode' => $langcode,
246
          'items' => $items,
247
          'delta' => $delta,
248
        );
249
        drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), $element, $form_state, $context);
250

    
251
        $field_elements[$delta] = $element;
252
      }
253
    }
254

    
255
    if ($field_elements) {
256
      $field_elements += array(
257
        '#theme' => 'field_multiple_value_form',
258
        '#field_name' => $field['field_name'],
259
        '#cardinality' => $field['cardinality'],
260
        '#title' => $title,
261
        '#required' => $instance['required'],
262
        '#description' => $description,
263
        '#prefix' => '<div id="' . $wrapper_id . '">',
264
        '#suffix' => '</div>',
265
        '#max_delta' => $max,
266
      );
267
      // Add 'add more' button, if not working with a programmed form.
268
      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form_state['programmed'])) {
269
        $field_elements['add_more'] = array(
270
          '#type' => 'submit',
271
          '#name' => strtr($id_prefix, '-', '_') . '_add_more',
272
          '#value' => t('Add another item'),
273
          '#attributes' => array('class' => array('field-add-more-submit')),
274
          '#limit_validation_errors' => array(array_merge($parents, array($field_name, $langcode))),
275
          '#submit' => array('field_add_more_submit'),
276
          '#ajax' => array(
277
            'callback' => 'field_add_more_js',
278
            'wrapper' => $wrapper_id,
279
            'effect' => 'fade',
280
          ),
281
        );
282
      }
283
    }
284
  }
285

    
286
  return $field_elements;
287
}
288

    
289
/**
290
 * Returns HTML for an individual form element.
291
 *
292
 * Combine multiple values into a table with drag-n-drop reordering.
293
 * TODO : convert to a template.
294
 *
295
 * @param $variables
296
 *   An associative array containing:
297
 *   - element: A render element representing the form element.
298
 *
299
 * @ingroup themeable
300
 */
301
function theme_field_multiple_value_form($variables) {
302
  $element = $variables['element'];
303
  $output = '';
304

    
305
  if ($element['#cardinality'] > 1 || $element['#cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
306
    $table_id = drupal_html_id($element['#field_name'] . '_values');
307
    $order_class = $element['#field_name'] . '-delta-order';
308
    $required = !empty($element['#required']) ? theme('form_required_marker', $variables) : '';
309

    
310
    $header = array(
311
      array(
312
        'data' => '<label>' . t('!title !required', array('!title' => $element['#title'], '!required' => $required)) . "</label>",
313
        'colspan' => 2,
314
        'class' => array('field-label'),
315
      ),
316
      t('Order'),
317
    );
318
    $rows = array();
319

    
320
    // Sort items according to '_weight' (needed when the form comes back after
321
    // preview or failed validation)
322
    $items = array();
323
    foreach (element_children($element) as $key) {
324
      if ($key === 'add_more') {
325
        $add_more_button = &$element[$key];
326
      }
327
      else {
328
        $items[] = &$element[$key];
329
      }
330
    }
331
    usort($items, '_field_sort_items_value_helper');
332

    
333
    // Add the items as table rows.
334
    foreach ($items as $key => $item) {
335
      $item['_weight']['#attributes']['class'] = array($order_class);
336
      $delta_element = drupal_render($item['_weight']);
337
      $cells = array(
338
        array('data' => '', 'class' => array('field-multiple-drag')),
339
        drupal_render($item),
340
        array('data' => $delta_element, 'class' => array('delta-order')),
341
      );
342
      $rows[] = array(
343
        'data' => $cells,
344
        'class' => array('draggable'),
345
      );
346
    }
347

    
348
    $output = '<div class="form-item">';
349
    $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => $table_id, 'class' => array('field-multiple-table'))));
350
    $output .= $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : '';
351
    $output .= '<div class="clearfix">' . drupal_render($add_more_button) . '</div>';
352
    $output .= '</div>';
353

    
354
    drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
355
  }
356
  else {
357
    foreach (element_children($element) as $key) {
358
      $output .= drupal_render($element[$key]);
359
    }
360
  }
361

    
362
  return $output;
363
}
364

    
365
/**
366
 * #after_build callback for field elements in a form.
367
 *
368
 * This stores the final location of the field within the form structure so
369
 * that field_default_form_errors() can assign validation errors to the right
370
 * form element.
371
 *
372
 * @see field_default_form_errors()
373
 */
374
function field_form_element_after_build($element, &$form_state) {
375
  $parents = $element['#field_parents'];
376
  $field_name = $element['#field_name'];
377
  $langcode = $element['#language'];
378

    
379
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
380
  $field_state['array_parents'] = $element['#array_parents'];
381
  field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
382

    
383
  return $element;
384
}
385

    
386
/**
387
 * Transfer field-level validation errors to widgets.
388
 */
389
function field_default_form_errors($entity_type, $entity, $field, $instance, $langcode, $items, $form, &$form_state) {
390
  $field_state = field_form_get_state($form['#parents'], $field['field_name'], $langcode, $form_state);
391

    
392
  if (!empty($field_state['errors'])) {
393
    // Locate the correct element in the form.
394
    $element = drupal_array_get_nested_value($form_state['complete form'], $field_state['array_parents']);
395
    // Only set errors if the element is accessible.
396
    if (!isset($element['#access']) || $element['#access']) {
397
      $function = $instance['widget']['module'] . '_field_widget_error';
398
      $function_exists = function_exists($function);
399

    
400
      $multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT;
401
      foreach ($field_state['errors'] as $delta => $delta_errors) {
402
        // For multiple single-value widgets, pass errors by delta.
403
        // For a multiple-value widget, pass all errors to the main widget.
404
        $error_element = $multiple_widget ? $element : $element[$delta];
405
        foreach ($delta_errors as $error) {
406
          if ($function_exists) {
407
            $function($error_element, $error, $form, $form_state);
408
          }
409
          else {
410
            // Make sure that errors are reported (even incorrectly flagged) if
411
            // the widget module fails to implement hook_field_widget_error().
412
            form_error($error_element, $error['message']);
413
          }
414
        }
415
      }
416
      // Reinitialize the errors list for the next submit.
417
      $field_state['errors'] = array();
418
      field_form_set_state($form['#parents'], $field['field_name'], $langcode, $form_state, $field_state);
419
    }
420
  }
421
}
422

    
423
/**
424
 * Submit handler for the "Add another item" button of a field form.
425
 *
426
 * This handler is run regardless of whether JS is enabled or not. It makes
427
 * changes to the form state. If the button was clicked with JS disabled, then
428
 * the page is reloaded with the complete rebuilt form. If the button was
429
 * clicked with JS enabled, then ajax_form_callback() calls field_add_more_js()
430
 * to return just the changed part of the form.
431
 */
432
function field_add_more_submit($form, &$form_state) {
433
  $button = $form_state['triggering_element'];
434

    
435
  // Go one level up in the form, to the widgets container.
436
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
437
  $field_name = $element['#field_name'];
438
  $langcode = $element['#language'];
439
  $parents = $element['#field_parents'];
440

    
441
  // Increment the items count.
442
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
443
  $field_state['items_count']++;
444
  field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
445

    
446
  $form_state['rebuild'] = TRUE;
447
}
448

    
449
/**
450
 * Ajax callback in response to a new empty widget being added to the form.
451
 *
452
 * This returns the new page content to replace the page content made obsolete
453
 * by the form submission.
454
 *
455
 * @see field_add_more_submit()
456
 */
457
function field_add_more_js($form, $form_state) {
458
  $button = $form_state['triggering_element'];
459

    
460
  // Go one level up in the form, to the widgets container.
461
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
462
  $field_name = $element['#field_name'];
463
  $langcode = $element['#language'];
464
  $parents = $element['#field_parents'];
465

    
466
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
467

    
468
  $field = $field_state['field'];
469
  if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
470
    return;
471
  }
472

    
473
  // Add a DIV around the delta receiving the Ajax effect.
474
  $delta = $element['#max_delta'];
475
  $element[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($element[$delta]['#prefix']) ? $element[$delta]['#prefix'] : '');
476
  $element[$delta]['#suffix'] = (isset($element[$delta]['#suffix']) ? $element[$delta]['#suffix'] : '') . '</div>';
477

    
478
  return $element;
479
}
480

    
481
/**
482
 * Retrieves processing information about a field from $form_state.
483
 *
484
 * @param $parents
485
 *   The array of #parents where the field lives in the form.
486
 * @param $field_name
487
 *   The field name.
488
 * @param $langcode
489
 *   The language in which the field values are entered.
490
 * @param $form_state
491
 *   The form state.
492
 *
493
 * @return
494
 *   An array with the following key/data pairs:
495
 *   - field: the field definition array,
496
 *   - instance: the field instance definition array,
497
 *   - items_count: the number of widgets to display for the field,
498
 *   - array_parents: the location of the field's widgets within the $form
499
 *     structure. This entry is populated at '#after_build' time.
500
 *   - errors: the array of field validation errors reported on the field. This
501
 *     entry is populated at field_attach_form_validate() time.
502
 *
503
 * @see field_form_set_state()
504
 */
505
function field_form_get_state($parents, $field_name, $langcode, &$form_state) {
506
  $form_state_parents = _field_form_state_parents($parents, $field_name, $langcode);
507
  return drupal_array_get_nested_value($form_state, $form_state_parents);
508
}
509

    
510
/**
511
 * Stores processing information about a field in $form_state.
512
 *
513
 * @param $parents
514
 *   The array of #parents where the field lives in the form.
515
 * @param $field_name
516
 *   The field name.
517
 * @param $langcode
518
 *   The language in which the field values are entered.
519
 * @param $form_state
520
 *   The form state.
521
 * @param $field_state
522
 *   The array of data to store. See field_form_get_state() for the structure
523
 *   and content of the array.
524
 *
525
 * @see field_form_get_state()
526
 */
527
function field_form_set_state($parents, $field_name, $langcode, &$form_state, $field_state) {
528
  $form_state_parents = _field_form_state_parents($parents, $field_name, $langcode);
529
  drupal_array_set_nested_value($form_state, $form_state_parents, $field_state);
530
}
531

    
532
/**
533
 * Returns the location of processing information within $form_state.
534
 */
535
function _field_form_state_parents($parents, $field_name, $langcode) {
536
  // To ensure backwards compatibility on regular entity forms for widgets that
537
  // still access $form_state['field'][$field_name] directly,
538
  // - top-level fields (empty $parents) are placed directly under
539
  //   $form_state['fields'][$field_name].
540
  // - Other fields are placed under
541
  //   $form_state['field']['#parents'][...$parents...]['#fields'][$field_name]
542
  //   to avoid clashes between field names and $parents parts.
543
  // @todo Remove backwards compatibility in Drupal 8, and use a unique
544
  // $form_state['field'][...$parents...]['#fields'][$field_name] structure.
545
  if (!empty($parents)) {
546
    $form_state_parents = array_merge(array('#parents'), $parents, array('#fields'));
547
  }
548
  else {
549
    $form_state_parents = array();
550
  }
551
  $form_state_parents = array_merge(array('field'), $form_state_parents, array($field_name, $langcode));
552

    
553
  return $form_state_parents;
554
}
555

    
556
/**
557
 * Retrieves the field definition for a widget's helper callbacks.
558
 *
559
 * Widgets helper element callbacks (such as #process, #element_validate,
560
 * #value_callback, ...) should use field_widget_field() and
561
 * field_widget_instance() instead of field_info_field() and
562
 * field_info_instance() when they need to access field or instance properties.
563
 * See hook_field_widget_form() for more details.
564
 *
565
 * @param $element
566
 *   The structured array for the widget.
567
 * @param $form_state
568
 *   The form state.
569
 *
570
 * @return
571
 *   The $field definition array for the current widget.
572
 *
573
 * @see field_widget_instance()
574
 * @see hook_field_widget_form()
575
 */
576
function field_widget_field($element, $form_state) {
577
  $field_state = field_form_get_state($element['#field_parents'], $element['#field_name'], $element['#language'], $form_state);
578
  return $field_state['field'];
579
}
580

    
581
/**
582
 * Retrieves the instance definition array for a widget's helper callbacks.
583
 *
584
 * Widgets helper element callbacks (such as #process, #element_validate,
585
 * #value_callback, ...) should use field_widget_field() and
586
 * field_widget_instance() instead of field_info_field() and
587
 * field_info_instance() when they need to access field or instance properties.
588
 * See hook_field_widget_form() for more details.
589
 *
590
 * @param $element
591
 *   The structured array for the widget.
592
 * @param $form_state
593
 *   The form state.
594
 *
595
 * @return
596
 *   The $instance definition array for the current widget.
597
 *
598
 * @see field_widget_field()
599
 * @see hook_field_widget_form()
600
 */
601
function field_widget_instance($element, $form_state) {
602
  $field_state = field_form_get_state($element['#field_parents'], $element['#field_name'], $element['#language'], $form_state);
603
  return $field_state['instance'];
604
}