Projet

Général

Profil

Paste
Télécharger (90,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / field_collection / field_collection.module @ a1cafe7e

1
<?php
2

    
3
/**
4
 * @file
5
 * Module implementing field collection field type.
6
 */
7

    
8
/**
9
 * Implements hook_help().
10
 */
11
function field_collection_help($path, $arg) {
12
  switch ($path) {
13
    case 'admin/help#field_collection':
14
      $output = '';
15
      $output .= '<h3>' . t('About') . '</h3>';
16
      $output .= '<p>' . t('The field collection module provides a field, to which any number of fields can be attached. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>';
17
      return $output;
18
  }
19
}
20

    
21
/**
22
 * Implements hook_form_alter().
23
 *
24
 * Checks for a value set by the embedded widget so fields are not displayed
25
 * with the 'all languages' hint incorrectly.
26
 */
27
function field_collection_form_alter(&$form, &$form_state) {
28
  if (!empty($form['#field_collection_translation_fields'])) {
29
    foreach ($form['#field_collection_translation_fields'] as $address) {
30
      drupal_array_set_nested_value($form, array_merge($address, array('#multilingual')), TRUE);
31
    }
32
  }
33
}
34

    
35
/**
36
 * Implements hook_form_FORM_ID_alter() for field_ui_field_overview_form().
37
 *
38
 * Make the names of the field collection fields into links to edit the fields
39
 * for that field collection on the host's field edit page.
40
 */
41
function field_collection_form_field_ui_field_overview_form_alter(&$form, &$form_state) {
42
  if (count($form['#fields'])) {
43
    foreach ($form['fields'] as $fieldname => $field) {
44
      if (!isset($field['type']['#title'])) {
45
        continue;
46
      }
47
      if ($field['type']['#title'] == 'Field collection') {
48
        $form['fields'][$fieldname]['field_name']['#markup'] =
49
          l($form['fields'][$fieldname]['field_name']['#markup'], 'admin/structure/field-collections/' . str_replace('_', '-', $fieldname) . '/fields');
50
      }
51
    }
52
  }
53
}
54

    
55
/**
56
 * Implements hook_ctools_plugin_directory().
57
 */
58
function field_collection_ctools_plugin_directory($module, $plugin) {
59
  if ($module == 'ctools') {
60
    return 'ctools/' . $plugin;
61
  }
62
}
63

    
64
/**
65
 * Implements hook_entity_info().
66
 */
67
function field_collection_entity_info() {
68
  $return['field_collection_item'] = array(
69
    'label' => t('Field collection item'),
70
    'label callback' => 'entity_class_label',
71
    'uri callback' => 'entity_class_uri',
72
    'entity class' => 'FieldCollectionItemEntity',
73
    'controller class' => 'EntityAPIController',
74
    'base table' => 'field_collection_item',
75
    'revision table' => 'field_collection_item_revision',
76
    'fieldable' => TRUE,
77
    // For integration with Redirect module.
78
    // @see http://drupal.org/node/1263884
79
    'redirect' => FALSE,
80
    'entity keys' => array(
81
      'id' => 'item_id',
82
      'revision' => 'revision_id',
83
      'bundle' => 'field_name',
84
    ),
85
    'module' => 'field_collection',
86
    'view modes' => array(
87
      'full' => array(
88
        'label' => t('Full content'),
89
        'custom settings' => FALSE,
90
      ),
91
    ),
92
    'access callback' => 'field_collection_item_access',
93
    'deletion callback' => 'field_collection_item_delete',
94
    'metadata controller class' => 'FieldCollectionItemMetadataController',
95
    'translation' => array(
96
      'entity_translation' => array(
97
        'class' => 'EntityTranslationFieldCollectionItemHandler',
98
      ),
99
    ),
100
  );
101

    
102
  // Add info about the bundles. We do not use field_info_fields() but directly
103
  // use field_read_fields() as field_info_fields() requires built entity info
104
  // to work.
105
  foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) {
106
    $return['field_collection_item']['bundles'][$field_name] = array(
107
      'label' => t('Field collection @field', array('@field' => $field_name)),
108
      'admin' => array(
109
        'path' => 'admin/structure/field-collections/%field_collection_field_name',
110
        'real path' => 'admin/structure/field-collections/' . strtr($field_name, array('_' => '-')),
111
        'bundle argument' => 3,
112
        'access arguments' => array('administer field collections'),
113
      ),
114
    );
115

    
116
    $path = field_collection_field_get_path($field) . '/%field_collection_item';
117
    // Enable the first available path scheme as default one.
118
    if (!isset($return['field_collection_item']['translation']['entity_translation']['base path'])) {
119
      $return['field_collection_item']['translation']['entity_translation']['base path'] = $path;
120
      $return['field_collection_item']['translation']['entity_translation']['path wildcard'] = '%field_collection_item';
121
      $return['field_collection_item']['translation']['entity_translation']['default_scheme'] = $field_name;
122
    }
123
    else {
124
      $return['field_collection_item']['translation']['entity_translation']['path schemes'][$field_name] = array(
125
        'base path' => $path,
126
      );
127
    }
128
  }
129

    
130
  if (module_exists('entitycache')) {
131
    $return['field_collection_item']['field cache'] = FALSE;
132
    $return['field_collection_item']['entity cache'] = TRUE;
133
  }
134

    
135
  return $return;
136
}
137

    
138
/**
139
 * Provide the original entity language.
140
 *
141
 * If a language property is defined for the current entity we synchronize the
142
 * field value using the entity language, otherwise we fall back to
143
 * LANGUAGE_NONE.
144
 *
145
 * @param $entity_type
146
 * @param $entity
147
 *
148
 * @return string
149
 *   A language code
150
 */
151
function field_collection_entity_language($entity_type, $entity) {
152
  if (module_exists('entity_translation') && entity_translation_enabled($entity_type)) {
153
    $handler = entity_translation_get_handler($entity_type, $entity);
154
    $langcode = $handler->getLanguage();
155
  }
156
  else {
157
    $langcode = entity_language($entity_type, $entity);
158
  }
159
  return !empty($langcode) ? $langcode : LANGUAGE_NONE;
160
}
161

    
162
/**
163
 * Menu callback for loading the bundle names.
164
 */
165
function field_collection_field_name_load($arg) {
166
  $field_name = strtr($arg, array('-' => '_'));
167
  if (($field = field_info_field($field_name)) && $field['type'] == 'field_collection') {
168
    return $field_name;
169
  }
170
}
171

    
172
/**
173
 * Loads a field collection item.
174
 *
175
 * @return
176
 *   The field collection item entity or FALSE.
177
 */
178
function field_collection_item_load($item_id, $reset = FALSE) {
179
  $result = field_collection_item_load_multiple(array($item_id), array(), $reset);
180
  return $result ? reset($result) : FALSE;
181
}
182

    
183
/**
184
 * Loads a field collection revision.
185
 *
186
 * @param $revision_id
187
 *   The id of the revision to load.
188
 *
189
 * @return
190
 *   The entity object, or FALSE if there is no entity with the given revision
191
 *   id.
192
 */
193
function field_collection_item_revision_load($revision_id) {
194
  return entity_revision_load('field_collection_item', $revision_id);
195
}
196

    
197
/**
198
 * Loads field collection items.
199
 *
200
 * @param $ids
201
 *   An array of entity IDs, or FALSE to load all entities.
202
 * @param $conditions
203
 *   (deprecated) An associative array of conditions on the base table, where
204
 *   the keys are the database fields and the values are the values those
205
 *   fields must have. Instead, it is preferable to use EntityFieldQuery to
206
 *   retrieve a list of entity IDs loadable by this function.
207
 * @param $reset
208
 *   Whether to reset the internal cache for the requested entity type.
209
 *
210
 * @return
211
 *   An array of field collection item entities.
212
 */
213
function field_collection_item_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) {
214
  return entity_load('field_collection_item', $ids, $conditions, $reset);
215
}
216

    
217
/**
218
 * Implements hook_menu().
219
 */
220
function field_collection_menu() {
221
  $items = array();
222
  if (module_exists('field_ui')) {
223
    $items['admin/structure/field-collections'] = array(
224
      'title' => 'Field collections',
225
      'description' => 'Manage fields on field collections.',
226
      'page callback' => 'field_collections_overview',
227
      'access arguments' => array('administer field collections'),
228
      'type' => MENU_NORMAL_ITEM,
229
      'file' => 'field_collection.admin.inc',
230
    );
231
  }
232

    
233
  // Add menu paths for viewing/editing/deleting field collection items.
234
  foreach (field_info_fields() as $field) {
235
    if ($field['type'] == 'field_collection') {
236
      $path = field_collection_field_get_path($field);
237
      $count = substr_count($path, '/') + 1;
238

    
239
      $items[$path . '/%field_collection_item'] = array(
240
        'page callback' => 'field_collection_item_page_view',
241
        'page arguments' => array($count),
242
        'access callback' => 'entity_access',
243
        'access arguments' => array('view', 'field_collection_item', $count),
244
        'file' => 'field_collection.pages.inc',
245
      );
246
      $items[$path . '/%field_collection_item/view'] = array(
247
        'title' => 'View',
248
        'type' => MENU_DEFAULT_LOCAL_TASK,
249
        'weight' => -10,
250
      );
251
      $items[$path . '/%field_collection_item/edit'] = array(
252
        'page callback' => 'drupal_get_form',
253
        'page arguments' => array('field_collection_item_form', $count),
254
        'access callback' => 'entity_access',
255
        'access arguments' => array('update', 'field_collection_item', $count),
256
        'title' => 'Edit',
257
        'type' => MENU_LOCAL_TASK,
258
        'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
259
        'file' => 'field_collection.pages.inc',
260
      );
261
      $items[$path . '/%field_collection_item/delete'] = array(
262
        'page callback' => 'drupal_get_form',
263
        'page arguments' => array('field_collection_item_delete_confirm', $count),
264
        'access callback' => 'entity_access',
265
        'access arguments' => array('delete', 'field_collection_item', $count),
266
        'title' => 'Delete',
267
        'type' => MENU_LOCAL_TASK,
268
        'context' => MENU_CONTEXT_INLINE,
269
        'file' => 'field_collection.pages.inc',
270
      );
271
      // Add entity type and the entity id as additional arguments.
272
      $items[$path . '/add/%/%'] = array(
273
        'page callback' => 'field_collection_item_add',
274
        'page arguments' => array($field['field_name'], $count + 1, $count + 2),
275
        // The pace callback takes care of checking access itself.
276
        'access callback' => TRUE,
277
        'file' => 'field_collection.pages.inc',
278
      );
279
      // Add menu items for dealing with revisions.
280
      $items[$path . '/%field_collection_item/revisions/%field_collection_item_revision'] = array(
281
        'page callback' => 'field_collection_item_page_view',
282
        'page arguments' => array($count + 2),
283
        'access callback' => 'entity_access',
284
        'access arguments' => array('view', 'field_collection_item', $count + 2),
285
        'file' => 'field_collection.pages.inc',
286
      );
287
    }
288
  }
289

    
290
  return $items;
291
}
292

    
293
/**
294
 * Implements hook_menu_alter() to fix the field collections admin UI tabs.
295
 */
296
function field_collection_menu_alter(&$items) {
297
  if (isset($items['admin/structure/field-collections/%field_collection_field_name/fields']) && module_exists('field_ui')) {
298
    // Make the fields task the default local task.
299
    $items['admin/structure/field-collections/%field_collection_field_name'] = $items['admin/structure/field-collections/%field_collection_field_name/fields'];
300
    $item = &$items['admin/structure/field-collections/%field_collection_field_name'];
301
    $item['type'] = MENU_NORMAL_ITEM;
302
    $item['title'] = 'Manage fields';
303
    $item['title callback'] = 'field_collection_admin_page_title';
304
    $item['title arguments'] = array(3);
305

    
306
    $items['admin/structure/field-collections/%field_collection_field_name/fields'] = array(
307
      'title' => 'Manage fields',
308
      'type' => MENU_DEFAULT_LOCAL_TASK,
309
      'weight' => 1,
310
    );
311
  }
312
}
313

    
314
/**
315
 * Menu title callback.
316
 *
317
 * return string
318
 */
319
function field_collection_admin_page_title($field_name) {
320
  return t('Field collection @field_name', array('@field_name' => $field_name));
321
}
322

    
323
/**
324
 * Implements hook_admin_paths().
325
 *
326
 * @return array
327
 */
328
function field_collection_admin_paths() {
329
  if (variable_get('node_admin_theme')) {
330
    return array(
331
      'field-collection/*/*/edit' => TRUE,
332
      'field-collection/*/*/delete' => TRUE,
333
      'field-collection/*/add/*/*' => TRUE,
334
    );
335
  }
336
}
337

    
338
/**
339
 * Implements hook_permission().
340
 */
341
function field_collection_permission() {
342
  return array(
343
    'administer field collections' => array(
344
      'title' => t('Administer field collections'),
345
      'description' => t('Create and delete fields on field collections.'),
346
    ),
347
    'edit field collections' => array(
348
      'title' => t('Edit field collections'),
349
      'description' => t('Edit field collections.'),
350
    ),
351
  );
352
}
353

    
354
/**
355
 * Determines whether the given user has access to a field collection.
356
 *
357
 * @param $op
358
 *   The operation being performed. One of 'view', 'update', 'create', 'delete'.
359
 * @param $item
360
 *   Optionally a field collection item. If nothing is given, access for all
361
 *   items is determined.
362
 * @param $account
363
 *   The user to check for. Leave it to NULL to check for the global user.
364
 *
365
 * @return boolean
366
 *   Whether access is allowed or not.
367
 */
368
function field_collection_item_access($op, FieldCollectionItemEntity $item = NULL, $account = NULL) {
369
  // We do not support editing field collection revisions that are not used at
370
  // the hosts default revision as saving the host might result in a new default
371
  // revision.
372
  if ($op != 'view' && isset($item) && !$item->isInUse()) {
373
    return FALSE;
374
  }
375
  if (user_access('edit field collections', $account)) {
376
    return TRUE;
377
  }
378
  if (user_access('administer field collections', $account)) {
379
    return TRUE;
380
  }
381
  if (!isset($item)) {
382
    return FALSE;
383
  }
384
  $op = $op == 'view' ? 'view' : 'edit';
385

    
386
  // Access is determined by the entity and field containing the reference.
387
  $field = field_info_field($item->field_name);
388
  $hostEntity = $item->hostEntity() !== FALSE ? $item->hostEntity() : NULL;
389
  $entity_access = entity_access($op == 'view' ? 'view' : 'update', $item->hostEntityType(), $hostEntity, $account);
390

    
391
  return $entity_access && field_access($op, $field, $item->hostEntityType(), $hostEntity, $account);
392
}
393

    
394
/**
395
 * Deletion callback
396
 */
397
function field_collection_item_delete($id) {
398
  $fci = field_collection_item_load($id);
399
  if (!empty($fci)) {
400
    $fci->delete();
401
  }
402
}
403

    
404
/**
405
 * Implements hook_theme().
406
 */
407
function field_collection_theme() {
408
  return array(
409
    'field_collection_item' => array(
410
      'render element' => 'elements',
411
      'template' => 'field-collection-item',
412
    ),
413
    'field_collection_view' => array(
414
      'render element' => 'element',
415
    ),
416
  );
417
}
418

    
419
/**
420
 * Implements hook_field_info().
421
 */
422
function field_collection_field_info() {
423
  return array(
424
    'field_collection' => array(
425
      'label' => t('Field collection'),
426
      'description' => t('This field stores references to embedded entities, which itself may contain any number of fields.'),
427
      'instance_settings' => array(),
428
      'default_widget' => 'field_collection_hidden',
429
      'default_formatter' => 'field_collection_view',
430
      // As of now there is no UI for setting the path.
431
      'settings' => array(
432
        'path' => '',
433
        'hide_blank_items' => TRUE,
434
        'hide_initial_item' => FALSE,
435
      ),
436
      // Add entity property info.
437
      'property_type' => 'field_collection_item',
438
      'property_callbacks' => array('field_collection_entity_metadata_property_callback'),
439
    ),
440
  );
441
}
442

    
443
/**
444
 * Implements hook_field_instance_settings_form().
445
 */
446
function field_collection_field_instance_settings_form($field, $instance) {
447

    
448
  $element['fieldset'] = array(
449
    '#type' => 'fieldset',
450
    '#title' => t('Default value'),
451
    '#collapsible' => FALSE,
452
    // As field_ui_default_value_widget() does, we change the #parents so that
453
    // the value below is writing to $instance in the right location.
454
    '#parents' => array('instance'),
455
  );
456
  // Be sure to set the default value to NULL, e.g. to repair old fields
457
  // that still have one.
458
  $element['fieldset']['default_value'] = array(
459
    '#type' => 'value',
460
    '#value' => NULL,
461
  );
462
  $element['fieldset']['content'] = array(
463
    '#pre' => '<p>',
464
    '#markup' => t('To specify a default value, configure it via the regular default value setting of each field that is part of the field collection. To do so, go to the <a href="!url">Manage fields</a> screen of the field collection.', array('!url' => url('admin/structure/field-collections/' . strtr($field['field_name'], array('_' => '-')) . '/fields'))),
465
    '#suffix' => '</p>',
466
  );
467
  return $element;
468
}
469

    
470
/**
471
 * Returns the base path to use for field collection items.
472
 */
473
function field_collection_field_get_path($field) {
474
  if (empty($field['settings']['path'])) {
475
    return 'field-collection/' . strtr($field['field_name'], array('_' => '-'));
476
  }
477
  return $field['settings']['path'];
478
}
479

    
480
/**
481
 * Implements hook_field_settings_form().
482
 */
483
function field_collection_field_settings_form($field, $instance) {
484
  $form['hide_blank_items'] = array(
485
    '#type' => 'checkbox',
486
    '#title' => t('Hide blank items'),
487
    '#default_value' => $field['settings']['hide_blank_items'],
488
    '#description' => t('Ordinarily a new blank item will be added to unlimited cardinality fields whenever they appear in a form.  Checking this will prevent the blank item from appearing if the field already contains data.'),
489
    '#weight' => 10,
490
    '#states' => array(
491
      // Show the setting if the cardinality is -1.
492
      'visible' => array(
493
        ':input[name="field[cardinality]"]' => array('value' => '-1'),
494
      ),
495
    ),
496
  );
497
  $form['hide_initial_item'] = array(
498
    '#type' => 'checkbox',
499
    '#title' => t('Hide initial item'),
500
    '#default_value' => $field['settings']['hide_initial_item'],
501
    '#description' => t('Prevent the default blank item from appearing even if the field has no data yet.  If checked, the user must explicitly add the field collection.'),
502
    '#weight' => 11,
503
    '#states' => array(
504
      // Show the setting if the cardinality is -1 and hide_blank_items is checked.
505
      'visible' => array(
506
        ':input[name="field[cardinality]"]' => array('value' => '-1'),
507
        ':input[name="field[settings][hide_blank_items]"]' => array('checked' => TRUE),
508
      ),
509
    ),
510
  );
511
  return $form;
512
}
513

    
514
/**
515
 * Implements hook_field_insert().
516
 */
517
function field_collection_field_insert($host_entity_type, $host_entity, $field, $instance, $langcode, &$items) {
518
  foreach ($items as &$item) {
519
    if ($entity = field_collection_field_get_entity($item)) {
520
      if (!empty($host_entity->is_new) && empty($entity->is_new)) {
521
        // If the host entity is new but we have a field_collection that is not
522
        // new, it means that its host is being cloned. Thus we need to clone
523
        // the field collection entity as well.
524
        $new_entity = clone $entity;
525
        $new_entity->item_id = NULL;
526
        $new_entity->revision_id = NULL;
527
        $new_entity->is_new = TRUE;
528
        $entity = $new_entity;
529
      }
530
      if (!empty($entity->is_new)) {
531
        $entity->setHostEntity($host_entity_type, $host_entity, field_collection_entity_language($host_entity_type, $host_entity), FALSE);
532
      }
533
      $entity->save(TRUE);
534
      $item = array(
535
        'value' => $entity->item_id,
536
        'revision_id' => $entity->revision_id,
537
      );
538
    }
539
  }
540
}
541

    
542
/**
543
 * Implements hook_field_update().
544
 *
545
 * Care about removed field collection items.
546
 *
547
 * Support saving field collection items in @code $item['entity'] @endcode. This
548
 * may be used to seamlessly create field collection items during host-entity
549
 * creation or to save changes to the host entity and its collections at once.
550
 */
551
function field_collection_field_update($host_entity_type, $host_entity, $field, $instance, $langcode, &$items) {
552
  // When entity language is changed field values are moved to the new language
553
  // and old values are marked as removed. We need to avoid processing them in
554
  // this case.
555
  $entity_langcode = field_collection_entity_language($host_entity_type, $host_entity);
556
  $original = isset($host_entity->original) ? $host_entity->original : $host_entity;
557
  $original_langcode = field_collection_entity_language($host_entity_type, $original);
558
  $langcode = $langcode == $original_langcode ? $entity_langcode : $langcode;
559

    
560
  $top_host = $host_entity;
561
  while (method_exists($top_host, 'hostEntity')) {
562
    $top_host = $top_host->hostEntity();
563
  }
564

    
565
  // Prevent workbench moderation from deleting field collections or paragraphs
566
  // on node_save() during workbench_moderation_store(), when
567
  // $host_entity->revision == 0.
568
  if (!empty($top_host->workbench_moderation['updating_live_revision'])) {
569
    return;
570
  }
571

    
572
  // Load items from the original entity.
573
  $items_original = !empty($original->{$field['field_name']}[$langcode]) ? $original->{$field['field_name']}[$langcode] : array();
574
  $original_by_id = array_flip(field_collection_field_item_to_ids($items_original));
575

    
576
  foreach ($items as $delta => &$item) {
577
    // In case the entity has been changed / created, save it and set the id.
578
    // If the host entity creates a new revision, save new item-revisions as
579
    // well.
580
    if (!empty($host_entity->revision) || isset($item['entity'])) {
581
      if ($entity = field_collection_field_get_entity($item)) {
582
        // If the host entity is saved as new revision, do the same for the item.
583
        if (!empty($host_entity->revision) || !empty($host_entity->is_new_revision)) {
584
          $entity->revision = TRUE;
585
          // Without this cache clear entity_revision_is_default will
586
          // incorrectly return false here when creating a new published revision
587
          if (!isset($cleared_host_entity_cache)) {
588
            list($entity_id) = entity_extract_ids($host_entity_type, $host_entity);
589
            entity_get_controller($host_entity_type)->resetCache(array($entity_id));
590
            $cleared_host_entity_cache = TRUE;
591
          }
592
          $is_default = entity_revision_is_default($host_entity_type, $host_entity);
593
          // If an entity type does not support saving non-default entities,
594
          // assume it will be saved as default.
595
          if (!isset($is_default) || $is_default) {
596
            $entity->default_revision = TRUE;
597
            $entity->archived = FALSE;
598
          }
599
          else {
600
            $entity->default_revision = FALSE;
601
          }
602
        }
603

    
604
        if (!empty($entity->is_new)) {
605
          $entity->setHostEntity($host_entity_type, $host_entity, $langcode, FALSE);
606
        }
607
        else {
608
          $entity->updateHostEntity($host_entity, $host_entity_type);
609
        }
610

    
611
        $entity->save(TRUE);
612

    
613
        $item = array(
614
          'value' => $entity->item_id,
615
          'revision_id' => $entity->revision_id,
616
        );
617
      }
618
    }
619

    
620
    unset($original_by_id[$item['value']]);
621
  }
622

    
623
  // If there are removed items, care about deleting the item entities.
624
  if ($original_by_id) {
625
    $ids = array_flip($original_by_id);
626

    
627
    // If we are creating a new revision, the old-items should be kept but get
628
    // marked as archived now.
629
    if (!empty($host_entity->revision)) {
630
      db_update('field_collection_item')
631
        ->fields(array('archived' => 1))
632
        ->condition('item_id', $ids, 'IN')
633
        ->execute();
634
    }
635
    else {
636
      // Load items from the original entity from all languages checking which
637
      // are the unused items.
638
      $current_items = array();
639
      $languages = language_list();
640
      foreach ($languages as $langcode_value) {
641
        $current_items += !empty($host_entity->{$field['field_name']}[$langcode_value->language]) ? $host_entity->{$field['field_name']}[$langcode_value->language] : array();
642
        $current_by_id = field_collection_field_item_to_ids($current_items);
643
      }
644
      $items_to_remove = array_diff($ids, $current_by_id);
645
      // Delete unused field collection items now.
646
      foreach (field_collection_item_load_multiple($items_to_remove) as $un_item) {
647
        $un_item->updateHostEntity($host_entity, $host_entity_type);
648
        $un_item->deleteRevision(TRUE);
649
      }
650
    }
651
  }
652
}
653

    
654
/**
655
 * Implements hook_field_delete().
656
 */
657
function field_collection_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
658
  $ids = field_collection_field_item_to_ids($items);
659
  // Also delete all embedded entities.
660
  if ($ids && field_info_field($field['field_name'])) {
661
    // We filter out entities that are still being referenced by other
662
    // host-entities. This should never be the case, but it might happened e.g.
663
    // when modules cloned a node without knowing about field-collection.
664
    $entity_info = entity_get_info($entity_type);
665
    $entity_id_name = $entity_info['entity keys']['id'];
666
    $field_column = key($field['columns']);
667

    
668
    foreach ($ids as $id_key => $id) {
669
      $query = new EntityFieldQuery();
670
      $entities = $query
671
        ->fieldCondition($field['field_name'], $field_column, $id)
672
        ->execute();
673
      unset($entities[$entity_type][$entity->$entity_id_name]);
674

    
675
      if (!empty($entities[$entity_type])) {
676
        // Filter this $id out.
677
        unset($ids[$id_key]);
678
      }
679

    
680
      // Set a flag to remember that the host entity is being deleted. See
681
      // FieldCollectionItemEntity::deleteHostEntityReference().
682
      // Doing this on $entity is not sufficient because the cache for $entity
683
      // may have been reset since it was loaded.  That would cause
684
      // hostEntity() to load it from the database later without the flag.
685
      $field_collection_item = field_collection_item_load($id);
686
      if ($field_collection_item) {
687
        $hostEntity = $field_collection_item->hostEntity();
688
        if (!empty($hostEntity)) {
689
          $hostEntity->field_collection_deleting = TRUE;
690
        }
691
      }
692
    }
693

    
694
    entity_delete_multiple('field_collection_item', $ids);
695
  }
696
}
697

    
698
/**
699
 * Implements hook_field_load().
700
 *
701
 * Invokes the load hooks for the fields contained in new, unsaved field
702
 * collection items. This allows images inside a field collection item to be
703
 * seen on a node's preview page before it's saved.
704
 */
705
function field_collection_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
706
  foreach ($items as &$each_item) {
707
    foreach ($each_item as &$each_field_collection_item) {
708
      if (!empty($each_field_collection_item['entity'])) {
709
        _field_invoke_multiple('load', 'field_collection_item', array(NULL => &$each_field_collection_item['entity']));
710
      }
711
    }
712
  }
713
}
714

    
715
/**
716
 * Implements hook_field_delete_revision().
717
 */
718
function field_collection_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) {
719
  foreach ($items as $item) {
720
    if (!empty($item['revision_id'])
721
      && $entity = field_collection_item_revision_load($item['revision_id'])) {
722
      $entity->deleteRevision(TRUE);
723
    }
724
  }
725
}
726

    
727
/**
728
 * Get an array of field collection item IDs stored in the given field items.
729
 */
730
function field_collection_field_item_to_ids($items) {
731
  $ids = array();
732
  foreach ($items as $item) {
733
    if (!empty($item['value'])) {
734
      $ids[] = $item['value'];
735
    }
736
  }
737
  return $ids;
738
}
739

    
740
/**
741
 * Implements hook_field_is_empty().
742
 */
743
function field_collection_field_is_empty($item, $field) {
744
  if (!empty($item['value'])) {
745
    return FALSE;
746
  }
747

    
748
  if (isset($item['entity'])) {
749
    return field_collection_item_is_empty($item['entity']);
750
  }
751
  return TRUE;
752
}
753

    
754
/**
755
 * Determines whether a field collection item entity is empty based on the collection-fields.
756
 */
757
function field_collection_item_is_empty(FieldCollectionItemEntity $item) {
758
  $instances = field_info_instances('field_collection_item', $item->field_name);
759
  $is_empty = TRUE;
760

    
761
  // Check whether all fields are booleans.
762
  $all_boolean = $instances && !(bool) array_filter($instances, '_field_collection_field_is_not_boolean');
763

    
764
  foreach ($instances as $instance) {
765
    $field_name = $instance['field_name'];
766
    $field = field_info_field($field_name);
767

    
768
    // Boolean fields as those are always considered non-empty, thus their
769
    // information is not useful and can be skipped by default.
770
    if (!$all_boolean && $field['type'] == 'list_boolean') {
771
      continue;
772
    }
773

    
774
    // Determine the list of languages to iterate on.
775
    $languages = field_available_languages('field_collection_item', $field);
776

    
777
    foreach ($languages as $langcode) {
778
      if (!empty($item->{$field_name}[$langcode])) {
779
        // If at least one collection-field is not empty; the
780
        // field collection item is not empty.
781
        foreach ($item->{$field_name}[$langcode] as $field_item) {
782
          if (!module_invoke($field['module'], 'field_is_empty', $field_item, $field)) {
783
            $is_empty = FALSE;
784
          }
785
        }
786
      }
787
    }
788
  }
789

    
790
  // Allow other modules a chance to alter the value before returning.
791
  drupal_alter('field_collection_is_empty', $is_empty, $item);
792
  return $is_empty;
793
}
794

    
795
/**
796
 * Callback used by array_filter in field_collection_is_empty.
797
 */
798
function _field_collection_field_is_not_boolean($instance) {
799
  $field = field_info_field($instance['field_name']);
800
  return $field['type'] != 'list_boolean';
801
}
802

    
803
/**
804
 * Implements hook_field_formatter_info().
805
 */
806
function field_collection_field_formatter_info() {
807
  return array(
808
    'field_collection_list' => array(
809
      'label' => t('Links to field collection items'),
810
      'field types' => array('field_collection'),
811
      'settings' => array(
812
        'edit' => t('Edit'),
813
        'translate' => t('Translate'),
814
        'delete' => t('Delete'),
815
        'add' => t('Add'),
816
        'description' => TRUE,
817
      ),
818
    ),
819
    'field_collection_view' => array(
820
      'label' => t('Field collection items'),
821
      'field types' => array('field_collection'),
822
      'settings' => array(
823
        'edit' => t('Edit'),
824
        'translate' => t('Translate'),
825
        'delete' => t('Delete'),
826
        'add' => t('Add'),
827
        'description' => TRUE,
828
        'view_mode' => 'full',
829
      ),
830
    ),
831
    'field_collection_fields' => array(
832
      'label' => t('Fields only'),
833
      'field types' => array('field_collection'),
834
      'settings' => array(
835
        'view_mode' => 'full',
836
      ),
837
    ),
838
  );
839
}
840

    
841
/**
842
 * Implements hook_field_formatter_settings_form().
843
 */
844
function field_collection_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
845
  $display = $instance['display'][$view_mode];
846
  $settings = $display['settings'];
847
  $elements = array();
848

    
849
  if ($display['type'] != 'field_collection_fields') {
850
    $elements['add'] = array(
851
      '#type' => 'textfield',
852
      '#title' => t('Add link title'),
853
      '#default_value' => $settings['add'],
854
      '#description' => t('Leave the title empty, to hide the link.'),
855
    );
856
    $elements['edit'] = array(
857
      '#type' => 'textfield',
858
      '#title' => t('Edit link title'),
859
      '#default_value' => $settings['edit'],
860
      '#description' => t('Leave the title empty, to hide the link.'),
861
    );
862
    $elements['translate'] = array(
863
      '#type' => 'textfield',
864
      '#title' => t('Translate link title'),
865
      '#default_value' => isset($settings['translate']) ? $settings['translate'] : '',
866
      '#description' => t('Leave the title empty, to hide the link.'),
867
      '#access' => field_collection_item_is_translatable(),
868
    );
869
    $elements['delete'] = array(
870
      '#type' => 'textfield',
871
      '#title' => t('Delete link title'),
872
      '#default_value' => $settings['delete'],
873
      '#description' => t('Leave the title empty, to hide the link.'),
874
    );
875
    $elements['description'] = array(
876
      '#type' => 'checkbox',
877
      '#title' => t('Show the field description beside the add link.'),
878
      '#default_value' => $settings['description'],
879
      '#description' => t('If enabled and the add link is shown, the field description is shown in front of the add link.'),
880
    );
881
  }
882

    
883
  // Add a select form element for view_mode if viewing the rendered field_collection.
884
  if ($display['type'] !== 'field_collection_list') {
885

    
886
    $entity_type = entity_get_info('field_collection_item');
887
    $options = array();
888
    foreach ($entity_type['view modes'] as $mode => $info) {
889
      $options[$mode] = $info['label'];
890
    }
891

    
892
    $elements['view_mode'] = array(
893
      '#type' => 'select',
894
      '#title' => t('View mode'),
895
      '#options' => $options,
896
      '#default_value' => $settings['view_mode'],
897
      '#description' => t('Select the view mode'),
898
    );
899
  }
900

    
901
  return $elements;
902
}
903

    
904
/**
905
 * Implements hook_field_formatter_settings_summary().
906
 */
907
function field_collection_field_formatter_settings_summary($field, $instance, $view_mode) {
908
  $display = $instance['display'][$view_mode];
909
  $settings = $display['settings'];
910
  $output = array();
911

    
912
  if ($display['type'] !== 'field_collection_fields') {
913
    $links = field_collection_get_operations($settings, TRUE);
914
    if ($links) {
915
      $output[] = t('Links: @links', array('@links' => check_plain(implode(', ', $links))));
916
    }
917
    else {
918
      $output[] = t('Links: none');
919
    }
920
  }
921

    
922
  if ($display['type'] !== 'field_collection_list') {
923
    $entity_type = entity_get_info('field_collection_item');
924
    if (!empty($entity_type['view modes'][$settings['view_mode']]['label'])) {
925
      $output[] = t('View mode: @mode', array('@mode' => $entity_type['view modes'][$settings['view_mode']]['label']));
926
    }
927
  }
928

    
929
  return implode('<br>', $output);
930
}
931

    
932
function field_collection_entity_preload($entities, $langcode, &$items, $fields) {
933
  static $local_entity_cache = array();
934

    
935
  $fc_ids = array();
936

    
937
  // Collect every possible term attached to any of the fieldable entities.
938
  foreach ($entities as $id => $entity) {
939
    foreach ($items[$id] as $delta => $item) {
940

    
941
      // Check if this item is in our local entity cache
942
      if (isset($local_entity_cache[$item['value']])) {
943
        $items[$id][$delta]['field_collection'] = $local_entity_cache[$item['value']];
944
        continue;
945
      }
946

    
947
      // Force the array key to prevent duplicates.
948
      $fc_ids[$item['value']] = $item['value'];
949
    }
950
  }
951

    
952
  if ($fc_ids) {
953
    $new_entities = array();
954
    $terms = field_collection_item_load_multiple($fc_ids);
955

    
956
    // Iterate through the fieldable entities again to attach the loaded
957
    // field collection data.
958
    foreach ($entities as $id => $entity) {
959
      $rekey = FALSE;
960

    
961
      foreach ($items[$id] as $delta => $item) {
962
        // Check whether the field collection field instance value could be loaded.
963
        if (isset($terms[$item['value']])) {
964
          // Replace the instance value with the term data.
965
          $e = $terms[$item['value']];
966
          $local_entity_cache[$item['value']] = $e;
967
          $items[$id][$delta]['field_collection'] = $e;
968

    
969
	  foreach ($fields as $field_name => $field) {
970
            if (isset($e->$field_name)) {
971
              $field_data = $e->$field_name;
972
              if (isset($field_data[$langcode])) {
973
                $new_entities[$e->item_id] = $e->item_id;
974
                $items[$e->item_id] = $field_data[$langcode];
975
              }
976
            }
977
          }
978
        }
979
        // Otherwise, unset the instance value, since the field colletion entity does not exist.
980
        else {
981
          unset($items[$id][$delta]);
982
          $rekey = TRUE;
983
        }
984
      }
985

    
986
      if ($rekey) {
987
        // Rekey the items array.
988
        $items[$id] = array_values($items[$id]);
989
      }
990
    }
991

    
992
    field_collection_entity_preload($new_entities, $langcode, $items, $fields);
993
  }
994
}
995

    
996
function field_collection_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
997
  $fields = field_read_fields(array('type' => 'field_collection'));
998
  field_collection_entity_preload($entities, $langcode, $items, $fields);
999
}
1000

    
1001
/**
1002
 * Implements hook_field_formatter_view().
1003
 */
1004
function field_collection_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
1005
  $element = array();
1006
  $settings = $display['settings'];
1007

    
1008
  switch ($display['type']) {
1009
    case 'field_collection_list':
1010

    
1011
      foreach ($items as $delta => $item) {
1012
        if ($field_collection = field_collection_field_get_entity($item)) {
1013
          $output = l($field_collection->label(), $field_collection->path());
1014
          $links = array();
1015
          foreach (field_collection_get_operations($settings) as $op => $label) {
1016
            if ($settings[$op] && entity_access($op == 'edit' ? 'update' : $op, 'field_collection_item', $field_collection)) {
1017
              $title = entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_$op", $settings[$op]);
1018
              $links[] = l($title, $field_collection->path() . '/' . $op, array('query' => drupal_get_destination()));
1019
            }
1020
          }
1021
          if ($links) {
1022
            $output .= ' (' . implode('|', $links) . ')';
1023
          }
1024
          $element[$delta] = array('#markup' => $output);
1025
        }
1026
      }
1027
      field_collection_field_formatter_links($element, $entity_type, $entity, $field, $instance, $langcode, $items, $display);
1028
      break;
1029

    
1030
    case 'field_collection_view':
1031

    
1032
      $view_mode = !empty($display['settings']['view_mode']) ? $display['settings']['view_mode'] : 'full';
1033
      foreach ($items as $delta => $item) {
1034
        // Existing field collections should be loaded without cache so that
1035
        // node Preview will work for both existing nodes and new ones.
1036
        if (isset($item['entity']->item_id)) {
1037
          // Load without cache.
1038
          $field_collection = field_collection_item_load($item['entity']->item_id, TRUE);
1039
        }
1040
        else {
1041
          $field_collection = field_collection_field_get_entity($item);
1042
        }
1043

    
1044
        if ($field_collection) {
1045
          $element[$delta]['entity'] = $field_collection->view($view_mode);
1046
          $element[$delta]['#theme_wrappers'] = array('field_collection_view');
1047
          $element[$delta]['#attributes']['class'][] = 'field-collection-view';
1048
          $element[$delta]['#attributes']['class'][] = 'clearfix';
1049
          $element[$delta]['#attributes']['class'][] = drupal_clean_css_identifier('view-mode-' . $view_mode);
1050

    
1051
          $links = array(
1052
            '#theme' => 'links__field_collection_view',
1053
          );
1054
          $links['#attributes']['class'][] = 'field-collection-view-links';
1055
          foreach (field_collection_get_operations($settings) as $op => $label) {
1056
            if ($settings[$op] && entity_access($op == 'edit' ? 'update' : $op, 'field_collection_item', $field_collection)) {
1057
              $links['#links'][$op] = array(
1058
                'title' => entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_$op", $settings[$op]),
1059
                'href' => $field_collection->path() . '/' . $op,
1060
                'query' => drupal_get_destination(),
1061
              );
1062
            }
1063
          }
1064
          $element[$delta]['links'] = $links;
1065
        }
1066
      }
1067
      field_collection_field_formatter_links($element, $entity_type, $entity, $field, $instance, $langcode, $items, $display);
1068
      if (!empty($items) || !empty($element['#suffix'])) {
1069
        $element['#attached']['css'][] = drupal_get_path('module', 'field_collection') . '/field_collection.theme.css';
1070
      }
1071
      break;
1072

    
1073
    case 'field_collection_fields':
1074

    
1075
      $view_mode = !empty($display['settings']['view_mode']) ? $display['settings']['view_mode'] : 'full';
1076
      foreach ($items as $delta => $item) {
1077
        if ($field_collection = field_collection_field_get_entity($item)) {
1078
          $element[$delta]['entity'] = $field_collection->view($view_mode);
1079
        }
1080
      }
1081
      break;
1082
  }
1083

    
1084
  return $element;
1085
}
1086

    
1087
/**
1088
 * Returns an array of enabled operations.
1089
 */
1090
function field_collection_get_operations($settings, $add = FALSE) {
1091
  $operations = array();
1092

    
1093
  if ($add) {
1094
    $operations[] = 'add';
1095
  }
1096
  $operations[] = 'edit';
1097
  if (field_collection_item_is_translatable()) {
1098
    $operations[] = 'translate';
1099
  }
1100
  $operations[] = 'delete';
1101

    
1102
  global $field_collection_operation_keys;
1103
  $field_collection_operation_keys = array_flip($operations);
1104
  $operations = array_filter(array_intersect_key($settings, $field_collection_operation_keys));
1105
  asort($operations);
1106

    
1107
  return $operations;
1108
}
1109

    
1110
/**
1111
 * Helper function to add links to a field collection field.
1112
 */
1113
function field_collection_field_formatter_links(&$element, $entity_type, $entity, $field, $instance, $langcode, $items, $display) {
1114
  $settings = $display['settings'];
1115
  $allow_create_item = FALSE;
1116

    
1117
  if ($settings['add'] && ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || count($items) < $field['cardinality'])) {
1118
    // Check whether the current is allowed to create a new item.
1119
    $field_collection_item = entity_create('field_collection_item', array('field_name' => $field['field_name']));
1120
    $field_collection_item->setHostEntity($entity_type, $entity, $langcode, FALSE);
1121

    
1122
    if (entity_access('create', 'field_collection_item', $field_collection_item)) {
1123
      $allow_create_item = TRUE;
1124
      $path = field_collection_field_get_path($field);
1125
      list($id) = entity_extract_ids($entity_type, $entity);
1126
      $element['#suffix'] = '';
1127
      if ($entity_type != 'node' && !empty($settings['description'])) {
1128
        $element['#suffix'] .= '<div class="description field-collection-description">' . field_filter_xss($instance['description']) . '</div>';
1129
      }
1130
      $title = entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_add", $settings['add']);
1131
      $add_path = $path . '/add/' . $entity_type . '/' . $id;
1132
      $element['#suffix'] .= '<ul class="action-links action-links-field-collection-add"><li>';
1133
      $element['#suffix'] .= l($title, $add_path, array('query' => drupal_get_destination()));
1134
      $element['#suffix'] .= '</li></ul>';
1135
    }
1136
  }
1137
  // If there is no add link, add a special class to the last item.
1138
  if (!empty($items) || $allow_create_item) {
1139
    if (empty($element['#suffix'])) {
1140
      $index = count(element_children($element)) - 1;
1141
      $element[$index]['#attributes']['class'][] = 'field-collection-view-final';
1142
    }
1143

    
1144
    $element += array('#prefix' => '', '#suffix' => '');
1145
    $element['#prefix'] .= '<div class="field-collection-container clearfix">';
1146
    $element['#suffix'] .= '</div>';
1147
  }
1148

    
1149
  return $element;
1150
}
1151

    
1152
/**
1153
 * Themes field collection items printed using the field_collection_view formatter.
1154
 */
1155
function theme_field_collection_view($variables) {
1156
  $element = $variables['element'];
1157
  return '<div' . drupal_attributes($element['#attributes']) . '>' . $element['#children'] . '</div>';
1158
}
1159

    
1160
/**
1161
 * Implements hook_field_widget_info().
1162
 */
1163
function field_collection_field_widget_info() {
1164
  return array(
1165
    'field_collection_hidden' => array(
1166
      'label' => t('Hidden'),
1167
      'field types' => array('field_collection'),
1168
      'behaviors' => array(
1169
        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
1170
        'default value' => FIELD_BEHAVIOR_NONE,
1171
      ),
1172
    ),
1173
    'field_collection_embed' => array(
1174
      'label' => t('Embedded'),
1175
      'field types' => array('field_collection'),
1176
      'behaviors' => array(
1177
        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1178
        'default value' => FIELD_BEHAVIOR_NONE,
1179
      ),
1180
    ),
1181
    'field_collection_sorter' => array(
1182
      'label' => t('Sortable only'),
1183
      'field types' => array('field_collection'),
1184
      'behaviors' => array(
1185
        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
1186
        'default value' => FIELD_BEHAVIOR_DEFAULT,
1187
      ),
1188
    ),
1189
  );
1190
}
1191

    
1192
/**
1193
 * Implements hook_field_widget_form().
1194
 */
1195
function field_collection_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
1196
  static $recursion = 0;
1197

    
1198
  switch ($instance['widget']['type']) {
1199
    case 'field_collection_hidden':
1200
      return $element;
1201

    
1202
    case 'field_collection_embed':
1203
      // If the field collection item form contains another field collection,
1204
      // we might ran into a recursive loop. Prevent that.
1205
      if ($recursion++ > 3) {
1206
        drupal_set_message(t('The field collection item form has not been embedded to avoid recursive loops.'), 'error');
1207
        return $element;
1208
      }
1209
      $field_parents = $element['#field_parents'];
1210
      $field_name = $element['#field_name'];
1211
      $language = $element['#language'];
1212

    
1213
      // Nest the field collection item entity form in a dedicated parent space,
1214
      // by appending [field_name, langcode, delta] to the current parent space.
1215
      // That way the form values of the field collection item are separated.
1216
      $parents = array_merge($field_parents, array($field_name, $language, $delta));
1217

    
1218
      $element += array(
1219
        '#element_validate' => array('field_collection_field_widget_embed_validate'),
1220
        '#parents' => $parents,
1221
      );
1222

    
1223
      if ($field['cardinality'] == 1) {
1224
        $element['#type'] = 'fieldset';
1225
      }
1226

    
1227
      $field_state = field_form_get_state($field_parents, $field_name, $language, $form_state);
1228

    
1229
      if ($delta > 0 && $delta == $field_state['items_count'] && field_collection_hide_blank_items($field)) {
1230
        // Do not add a blank item. Also see
1231
        // field_collection_field_attach_form() for correcting #max_delta.
1232
        $recursion--;
1233
        return FALSE;
1234
      }
1235

    
1236
      if ($field_state['items_count'] == 0 && field_collection_hide_blank_items($field)) {
1237
        // We show one item, so also specify that as item count. So when the
1238
        // add button is pressed the item count will be 2 and we show two items.
1239
        $field_state['items_count'] = 1;
1240
      }
1241

    
1242
      if (isset($field_state['entity'][$delta])) {
1243
        $field_collection_item = $field_state['entity'][$delta];
1244
      }
1245
      else {
1246
        if (isset($items[$delta])) {
1247
          $field_collection_item = field_collection_field_get_entity($items[$delta], $field_name);
1248
        }
1249
        // Show an empty collection if we have no existing one or it does not
1250
        // load.
1251
        if (empty($field_collection_item)) {
1252
          $field_collection_item = entity_create('field_collection_item', array('field_name' => $field_name));
1253
          $field_collection_item->setHostEntity($element['#entity_type'], $element['#entity'], $langcode);
1254
        }
1255

    
1256
        // Put our entity in the form state, so FAPI callbacks can access it.
1257
        $field_state['entity'][$delta] = $field_collection_item;
1258
      }
1259

    
1260
      // Register a child entity translation handler to properly deal with the
1261
      // entity form language.
1262
      if (field_collection_item_is_translatable()) {
1263
        $element['#host_entity_type'] = $element['#entity_type'];
1264
        $element['#host_entity'] = $element['#entity'];
1265
        // Give each field collection item a unique entity translation handler
1266
        // ID, otherwise an infinite loop occurs when adding values to nested
1267
        // field collection items.
1268
        if (!isset($field_collection_item->entity_translation_handler_id)) {
1269
          list($id, $revision_id) = entity_extract_ids('field_collection_item', $field_collection_item);
1270
          $revision_id = isset($revision_id) ? $revision_id : 0;
1271
          $field_collection_item->entity_translation_handler_id = 'field_collection_item' . '-' . (!empty($id) ? 'eid-' . $id . '-' . $revision_id : 'new-' . rand());
1272
        }
1273
        $element['#field_collection_item'] = $field_collection_item;
1274
        field_collection_add_child_translation_handler($element);
1275
        // Ensure this is executed even with cached forms. This is mainly useful
1276
        // when dealing with AJAX calls.
1277
        $element['#process'][] = 'field_collection_add_child_translation_handler';
1278
        // Flag the field to be processed in field_collection_form_alter to
1279
        // avoid adding incorrect translation hints.
1280
        $address = array_slice($element['#parents'], 0, -2);
1281
        if (empty($form['#field_collection_translation_fields']) || !in_array($address, $form['#field_collection_translation_fields'])) {
1282
          $form['#field_collection_translation_fields'][] = $address;
1283
        }
1284
      }
1285

    
1286
      // Add the subform
1287
      field_form_set_state($field_parents, $field_name, $language, $form_state, $field_state);
1288
      // Set the language to to parent entity language, because
1289
      // field_content_languages() will always set $language to LANGUAGE_NONE.
1290
      if (field_collection_item_is_translatable()) {
1291
        field_attach_form('field_collection_item', $field_collection_item, $element, $form_state, entity_language($element['#host_entity_type'], $element['#host_entity']));
1292
      }
1293
      else {
1294
        field_attach_form('field_collection_item', $field_collection_item, $element, $form_state, $language);
1295
      }
1296

    
1297
      // Make sure subfields get translatable clues (like 'all languages')
1298
      if (field_collection_item_is_translatable() && variable_get('entity_translation_shared_labels', TRUE)) {
1299
        foreach (element_children($element) as $key) {
1300
          $element[$key]['#process'][] = 'entity_translation_element_translatability_clue';
1301
        }
1302
      }
1303

    
1304
      if (empty($element['#required'])) {
1305
        $element['#after_build'][] = 'field_collection_field_widget_embed_delay_required_validation';
1306
      }
1307

    
1308
      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form_state['programmed'])) {
1309
        $element['remove_button'] = array(
1310
          '#delta' => $delta,
1311
          '#name' => implode('_', $parents) . '_remove_button',
1312
          '#type' => 'submit',
1313
          '#value' => t('Remove'),
1314
          '#validate' => array(),
1315
          '#submit' => array('field_collection_remove_submit'),
1316
          '#attributes' => array('class' => array('remove-button')),
1317
          '#limit_validation_errors' => array(),
1318
          '#ajax' => array(
1319
            // 'wrapper' is filled in field_collection_field_attach_form().
1320
            'callback' => 'field_collection_remove_js',
1321
            'effect' => 'fade',
1322
          ),
1323
          '#weight' => 1000,
1324
        );
1325
      }
1326

    
1327
      $recursion--;
1328
      return $element;
1329

    
1330
    case 'field_collection_sorter':
1331
      $elements = array();
1332

    
1333
      $field_name = $field['field_name'];
1334
      $parents = $form['#parents'];
1335
      $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
1336
      $count = $field_state['items_count'];
1337

    
1338
      for ($delta = 0; $delta < $count; $delta++) {
1339
        $item_id = isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL;
1340
        $revision_id = isset($items[$delta]['revision_id']) ? $items[$delta]['revision_id'] : NULL;
1341

    
1342
        // add a label component to visually identify this field collection item
1343
        $element['label'] = array();
1344
        if ($item_id) {
1345
          $item = field_collection_item_load($item_id);
1346
          $element['label']['#markup'] = $item ? $item->label() : $item_id;
1347
        }
1348

    
1349
        // the field stored value (item_id) and revision_id values
1350
        // so we need hidden fields to represent them
1351
        $element['value'] = array(
1352
          '#type' => 'hidden',
1353
          '#default_value' => $item_id,
1354
        );
1355
        $element['revision_id'] = array(
1356
          '#type' => 'hidden',
1357
          '#default_value' => $revision_id,
1358
        );
1359

    
1360
        // Input field for the delta (drag-n-drop reordering).
1361
        // We name the element '_weight' to avoid clashing with elements
1362
        // defined by widget.
1363
        $element['_weight'] = array(
1364
          '#type' => 'weight',
1365
          '#title' => t('Weight for row @number', array('@number' => $delta + 1)),
1366
          '#title_display' => 'invisible',
1367
          // Note: this 'delta' is the FAPI 'weight' element's property.
1368
          '#delta' => $count,
1369
          '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta,
1370
          '#weight' => 100,
1371
        );
1372

    
1373
        $elements[$delta] = $element;
1374
      }
1375

    
1376
      if ($elements) {
1377
        $elements += array(
1378
          '#theme' => 'field_multiple_value_form',
1379
          '#field_name' => $field['field_name'],
1380
          '#cardinality' => $field['cardinality'],
1381
          '#title' => check_plain($instance['label']),
1382
          '#required' => FALSE,
1383
          '#description' => field_filter_xss($instance['description']),
1384
          '#max_delta' => $count-1,
1385
        );
1386
      }
1387

    
1388
      return $elements;
1389
  }
1390
}
1391

    
1392
/**
1393
 * Implements hook_entity_translation_source_field_state_alter()
1394
 */
1395
function field_collection_entity_translation_source_field_state_alter(&$field_state) {
1396
  if (isset($field_state['entity'])) {
1397
    module_load_include('inc', 'entity', 'includes/entity.ui');
1398
    foreach ($field_state['entity'] as $delta => $entity) {
1399
      if ($entity instanceof FieldCollectionItemEntity) {
1400
        $field_state['entity'][$delta] = entity_ui_clone_entity('field_collection_item', $entity);
1401
      }
1402
    }
1403
  }
1404
}
1405

    
1406
/**
1407
 * Registers a child entity translation handler for the given element.
1408
 */
1409
function field_collection_add_child_translation_handler($element) {
1410
  $handler = entity_translation_get_handler($element['#host_entity_type'], $element['#host_entity']);
1411
  $handler->addChild('field_collection_item', $element['#field_collection_item']);
1412
  return $element;
1413
}
1414

    
1415
/**
1416
 * Implements hook_field_attach_form().
1417
 *
1418
 * Corrects #max_delta when we hide the blank field collection item.
1419
 *
1420
 * @see field_add_more_js()
1421
 * @see field_collection_field_widget_form()
1422
 */
1423
function field_collection_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
1424

    
1425
  foreach (field_info_instances($entity_type, $form['#bundle']) as $field_name => $instance) {
1426
    $field = field_info_field($field_name);
1427

    
1428
    if ($field['type'] == 'field_collection' && field_collection_hide_blank_items($field)
1429
        && field_access('edit', $field, $entity_type)) {
1430

    
1431
      $element_langcode = $form[$field_name]['#language'];
1432
      if ($form[$field_name][$element_langcode]['#max_delta'] > 0) {
1433
        $form[$field_name][$element_langcode]['#max_delta']--;
1434
      }
1435
      // Remove blank form elements and force user to explicitly add a field
1436
      // collection if both 'hide_initial_item' and 'hide_blank_items' are TRUE.
1437
      if ($field['settings']['hide_initial_item']
1438
        && $field['settings']['hide_blank_items']
1439
        && $form[$field_name][$element_langcode][0]['#entity'] instanceof FieldCollectionItemEntity
1440
        && field_collection_item_is_empty($form[$field_name][$element_langcode][0]['#entity'])) {
1441

    
1442
        _field_collection_process_children_attached($form[$field_name][$element_langcode][0]);
1443
        unset($form[$field_name][$element_langcode][0]);
1444
        unset($form_state['field']['#parents'][$field_name][$element_langcode][0]);
1445
      }
1446
    }
1447

    
1448
    if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED
1449
        && empty($form_state['programmed'])
1450
        && $instance['widget']['type'] === 'field_collection_embed'
1451
        && field_access('edit', $field, $entity_type)
1452
        && $field['type'] === 'field_collection') {
1453

    
1454
      $element_langcode = $form[$field_name]['#language'];
1455
      $element_wrapper = $form[$field_name][$element_langcode]['add_more']['#ajax']['wrapper'];
1456
      for ($i = 0; $i <= $form[$field_name][$element_langcode]['#max_delta']; $i++) {
1457
        if (isset($form[$field_name][$element_langcode][$i]['remove_button'])) {
1458
          $form[$field_name][$element_langcode][$i]['remove_button']['#ajax']['wrapper'] = $element_wrapper;
1459
        }
1460
      }
1461
    }
1462
  }
1463

    
1464
  // If FCs are translatable, make sure we mark any necessary sub-fields in the
1465
  // FC widget as translatable as well.
1466
  if ($entity_type === 'field_collection_item'
1467
      && field_collection_item_is_translatable()
1468
  ) {
1469
    foreach (field_info_instances($entity_type, $form['#bundle']) as $field_name => $instance) {
1470
      $field = field_info_field($field_name);
1471
      if (isset($field['translatable'])) {
1472
        $form[$field_name]['#multilingual'] = (boolean) $field['translatable'];
1473
      }
1474
    }
1475
  }
1476
}
1477

    
1478
/**
1479
 * Recurses through field children and processes thier attachments.
1480
 */
1481
function _field_collection_process_children_attached($elements) {
1482
  if (empty($elements)) {
1483
    return;
1484
  }
1485

    
1486
  if (isset($elements['#attached'])) {
1487
    drupal_process_attached($elements);
1488
  }
1489

    
1490
  foreach (element_children($elements) as $key) {
1491
    _field_collection_process_children_attached($elements[$key]);
1492
  }
1493
}
1494

    
1495
/**
1496
 * AJAX callback for removing a field collection item.
1497
 *
1498
 * This returns the new page content to replace the page content made obsolete
1499
 * by the form submission.
1500
 *
1501
 * @see field_collection_remove_submit()
1502
 */
1503
function field_collection_remove_js($form, $form_state) {
1504
  $button = $form_state['triggering_element'];
1505

    
1506
  // Go one level up in the form, to the widgets container.
1507
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -2));
1508
  $field_name = $element['#field_name'];
1509
  $langcode = $element['#language'];
1510
  $parents = $element['#field_parents'];
1511

    
1512
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
1513

    
1514
  $field = $field_state['field'];
1515
  if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
1516
    return;
1517
  }
1518

    
1519
  return $element;
1520
}
1521

    
1522
/**
1523
 * Submit callback to remove an item from the field UI multiple wrapper.
1524
 *
1525
 * When a remove button is submitted, we need to find the item that it
1526
 * referenced and delete it. Since field UI has the deltas as a straight
1527
 * unbroken array key, we have to renumber everything down. Since we do this
1528
 * we *also* need to move all the deltas around in the $form_state['values'],
1529
 * $form_state['input'], and $form_state['field'] so that user changed values
1530
 * follow. This is a bit of a complicated process.
1531
 */
1532
function field_collection_remove_submit($form, &$form_state) {
1533
  $button = $form_state['triggering_element'];
1534
  $delta = $button['#delta'];
1535

    
1536
  // Where in the form we'll find the parent element.
1537
  $address = array_slice($button['#array_parents'], 0, -2);
1538
  $values_address = array_slice($button['#parents'], 0, -2);
1539

    
1540
  // Go one level up in the form, to the widgets container.
1541
  $parent_element = drupal_array_get_nested_value($form, $address);
1542
  $field_name = $parent_element['#field_name'];
1543
  $langcode = $parent_element['#language'];
1544
  $parents = $parent_element['#field_parents'];
1545

    
1546
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
1547

    
1548
  // Use the actual array of field collection items as the upper limit of this
1549
  // for loop rather than 'item_count'. This is because it will be creating extra
1550
  // dummy items here and the two measures go out of sync after the fist delete.
1551
  $field_collection_item_count = count($field_state['entity']) - 1;
1552

    
1553
  // Go ahead and renumber everything from our delta to the last
1554
  // item down one. This will overwrite the item being removed.
1555
  for ($i = $delta; $i <= $field_collection_item_count; $i++) {
1556
    $old_element_address = array_merge($address, array($i + 1));
1557
    $old_element_values_address = array_merge($values_address, array($i + 1));
1558
    $new_element_values_address = array_merge($values_address, array($i));
1559

    
1560
    $moving_element = drupal_array_get_nested_value($form, $old_element_address);
1561
    $moving_element_value = drupal_array_get_nested_value($form_state['values'], $old_element_values_address);
1562
    $moving_element_input = drupal_array_get_nested_value($form_state['input'], $old_element_values_address);
1563
    $moving_element_field = drupal_array_get_nested_value($form_state['field']['#parents'], $old_element_address);
1564

    
1565
    // Tell the element where it's being moved to.
1566
    $moving_element['#parents'] = $new_element_values_address;
1567

    
1568
    // Move the element around.
1569
    form_set_value($moving_element, $moving_element_value, $form_state);
1570
    drupal_array_set_nested_value($form_state['input'], $moving_element['#parents'], $moving_element_input);
1571
    drupal_array_set_nested_value($form_state['field']['#parents'], $moving_element['#parents'], $moving_element_field);
1572

    
1573
    // Move the entity in our saved state.
1574
    if (isset($field_state['entity'][$i + 1])) {
1575
      $field_state['entity'][$i] = $field_state['entity'][$i + 1];
1576
    }
1577
    else {
1578
      unset($field_state['entity'][$i]);
1579
    }
1580
  }
1581

    
1582
  // Replace the deleted entity with an empty one. This helps to ensure that
1583
  // trying to add a new entity won't ressurect a deleted entity from the
1584
  // trash bin.
1585
  $count = count($field_state['entity']);
1586
  $field_state['entity'][$count] = entity_create('field_collection_item', array('field_name' => $field_name));
1587

    
1588
  // Then remove the last item. But we must not go negative.
1589
  if ($field_state['items_count'] > 0) {
1590
    $field_state['items_count']--;
1591
  }
1592

    
1593
  // Fix the weights. Field UI lets the weights be in a range of
1594
  // (-1 * item_count) to (item_count). This means that when we remove one,
1595
  // the range shrinks; weights outside of that range then get set to
1596
  // the first item in the select by the browser, floating them to the top.
1597
  // We use a brute force method because we lost weights on both ends
1598
  // and if the user has moved things around, we have to cascade because
1599
  // if I have items weight weights 3 and 4, and I change 4 to 3 but leave
1600
  // the 3, the order of the two 3s now is undefined and may not match what
1601
  // the user had selected.
1602
  $input = drupal_array_get_nested_value($form_state['input'], $values_address);
1603
  // Sort by weight
1604
  uasort($input, '_field_sort_items_helper');
1605

    
1606
  // Reweight everything in the correct order.
1607
  $weight = -1 * $field_state['items_count'];
1608
  foreach ($input as $key => $item) {
1609
    if ($item) {
1610
      $input[$key]['_weight'] = $weight++;
1611
    }
1612
  }
1613

    
1614
  drupal_array_set_nested_value($form_state['input'], $values_address, $input);
1615
  field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
1616

    
1617
  $form_state['rebuild'] = TRUE;
1618
}
1619

    
1620
/**
1621
 * Gets a field collection item entity for a given field item.
1622
 *
1623
 * @param $field_name
1624
 *   (optional) If given and there is no entity yet, a new entity object is
1625
 *   created for the given item.
1626
 *
1627
 * @return
1628
 *   The entity object or FALSE.
1629
 */
1630
function field_collection_field_get_entity(&$item, $field_name = NULL) {
1631
  if (isset($item['entity']) && ($item['entity']->entityType() == 'field_collection_item')) {
1632
    if (count($item) > 1) {
1633
      // If $item contains more thing than 'entity', then it is sent from VBO.
1634
      // We clone the object to avoid that the same field collection item of the
1635
      // faked object is attached to multiple host objects.
1636
      return clone $item['entity'];
1637
    }
1638
    return $item['entity'];
1639
  }
1640

    
1641
  if (isset($item['value'])) {
1642
    // By default always load the default revision, so caches get used.
1643
    $entity = field_collection_item_load($item['value']);
1644
    if ($entity && $entity->revision_id != $item['revision_id']) {
1645
      // A non-default revision is a referenced, so load this one.
1646
      $entity = field_collection_item_revision_load($item['revision_id']);
1647
    }
1648
    return $entity;
1649
  }
1650

    
1651
  if (isset($field_name) && !isset($item['entity'])) {
1652
    $item['entity'] = entity_create('field_collection_item', array('field_name' => $field_name));
1653
    return $item['entity'];
1654
  }
1655
  return FALSE;
1656
}
1657

    
1658
/**
1659
 * FAPI #after_build of an individual field collection element to delay the validation of #required.
1660
 */
1661
function field_collection_field_widget_embed_delay_required_validation(&$element, &$form_state) {
1662
  // If the process_input flag is set, the form and its input is going to be
1663
  // validated. Prevent #required (sub)fields from throwing errors while
1664
  // their non-#required field collection item is empty.
1665
  if ($form_state['process_input']) {
1666
    _field_collection_collect_required_elements($element, $element['#field_collection_required_elements']);
1667
  }
1668
  return $element;
1669
}
1670

    
1671
function _field_collection_collect_required_elements(&$element, &$required_elements) {
1672
  // Recurse through all children.
1673
  foreach (element_children($element) as $key) {
1674
    if (isset($element[$key]) && $element[$key]) {
1675
      _field_collection_collect_required_elements($element[$key], $required_elements);
1676
    }
1677
  }
1678
  if (!empty($element['#required'])) {
1679
    $element['#required'] = FALSE;
1680
    $required_elements[] = &$element;
1681
    $element += array('#pre_render' => array());
1682
    array_unshift($element['#pre_render'], 'field_collection_field_widget_render_required');
1683
  }
1684
}
1685

    
1686
/**
1687
 * #pre_render callback that ensures the element is rendered as being required.
1688
 */
1689
function field_collection_field_widget_render_required($element) {
1690
  $element['#required'] = TRUE;
1691
  return $element;
1692
}
1693

    
1694
/**
1695
 * FAPI validation of an individual field collection element.
1696
 */
1697
function field_collection_field_widget_embed_validate($element, &$form_state, $complete_form) {
1698
  $field = field_widget_field($element, $form_state);
1699
  $field_parents = $element['#field_parents'];
1700
  $field_name = $element['#field_name'];
1701
  $language = $element['#language'];
1702

    
1703
  $field_state = field_form_get_state($field_parents, $field_name, $language, $form_state);
1704

    
1705
  // We have to populate the field_collection_item before we can attach it to
1706
  // the form.
1707
  if (isset($field_state['entity'][$element['#delta']])) {
1708
    $field_collection_item = $field_state['entity'][$element['#delta']];
1709
  }
1710
  else {
1711
    $field_values = drupal_array_get_nested_value($form_state['values'], $field_state['array_parents']);
1712
    if ($field_values[$element['#delta']]) {
1713
      $field_collection_item = entity_create('field_collection_item', array('field_name' => $field['field_name']));
1714
      foreach ($field_values[$element['#delta']] as $key => $value) {
1715
        if (property_exists($field_collection_item, $key)) {
1716
          $field_collection_item->{$key} = $value;
1717
        }
1718
      }
1719
    }
1720
  }
1721

    
1722
  // Attach field API validation of the embedded form.
1723
  field_attach_form_validate('field_collection_item', $field_collection_item, $element, $form_state);
1724

    
1725
  // Handle a possible language change.
1726
  if (field_collection_item_is_translatable()) {
1727
    $handler = entity_translation_get_handler('field_collection_item', $field_collection_item);
1728
    $element_values = &drupal_array_get_nested_value($form_state['values'], $field_state['array_parents']);
1729
    $element_form_state = array('values' => &$element_values[$element['#delta']]);
1730
    $handler->entityFormLanguageWidgetSubmit($element, $element_form_state);
1731
  }
1732

    
1733
  // Now validate required elements if the entity is not empty.
1734
  if (!empty($element['#field_collection_required_elements']) && !field_collection_item_is_empty($field_collection_item)) {
1735
    foreach ($element['#field_collection_required_elements'] as &$elements) {
1736

    
1737
      // Copied from _form_validate().
1738
      if (isset($elements['#needs_validation'])) {
1739
        $is_countable = is_array($elements['#value']) || $elements['#value'] instanceof Countable;
1740
        $is_empty_multiple = $is_countable && (!count($elements['#value']));
1741
        $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0);
1742
        $is_empty_value = ($elements['#value'] === 0);
1743
        $is_empty_option = (isset($elements['#options']['_none']) && $elements['#value'] == '_none');
1744

    
1745
        // Validate fields with hook_field_is_empty. This will handle cases when
1746
        // file entity passes validation when it shouldn't.
1747
        $is_empty_field = FALSE;
1748
        if (isset($elements['#field_name'])) {
1749
          $field = field_info_field($elements['#field_name']);
1750
          // Extract field values array with all columns from form_state.
1751
          // The field we're looking at is always 3 levels deeper than field
1752
          // collection field.
1753
          $field_depth = count($elements['#field_parents']) + 3;
1754
          $field_values = drupal_array_get_nested_value($form_state['values'], array_slice($elements['#array_parents'], 0, $field_depth));
1755

    
1756
          // Special case lists since we don't get the correct array_parents.
1757
          if (is_array($field_values) && count($elements['#array_parents']) < $field_depth) {
1758
            $field_values = reset($field_values);
1759
          }
1760

    
1761
          $is_empty_field = module_invoke($field['module'], 'field_is_empty', $field_values, $field);
1762
        }
1763

    
1764
        if ($is_empty_multiple || $is_empty_string || $is_empty_value || $is_empty_option || $is_empty_field) {
1765
          if (isset($elements['#title'])) {
1766
            form_error($elements, t('@name field is required in the @collection collection.', array(
1767
              '@name' => $elements['#title'],
1768
              '@collection' => $field_state['instance']['label'],
1769
            )));
1770
          }
1771
          else {
1772
            form_error($elements);
1773
          }
1774
        }
1775
      }
1776
    }
1777
  }
1778

    
1779
  // Only if the form is being submitted, finish the collection entity and
1780
  // prepare it for saving.
1781
  if ($form_state['submitted'] && !form_get_error($element)) {
1782

    
1783
    field_attach_submit('field_collection_item', $field_collection_item, $element, $form_state);
1784

    
1785
    // Load initial form values into $item, so any other form values below the
1786
    // same parents are kept.
1787
    $item = drupal_array_get_nested_value($form_state['values'], $element['#parents']);
1788

    
1789
    // Set the _weight if it is a multiple field.
1790
    if (isset($element['_weight']) && ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED)) {
1791
      $item['_weight'] = $element['_weight']['#value'];
1792
    }
1793

    
1794
    // Ensure field columns are poroperly populated.
1795
    $item['value'] = $field_collection_item->item_id;
1796
    $item['revision_id'] = $field_collection_item->revision_id;
1797

    
1798
    // Put the field collection item in $item['entity'], so it is saved with
1799
    // the host entity via hook_field_presave() / field API if it is not empty.
1800
    // @see field_collection_field_presave()
1801
    $item['entity'] = $field_collection_item;
1802
    form_set_value($element, $item, $form_state);
1803
  }
1804

    
1805
}
1806

    
1807
/**
1808
 * Implements hook_field_create_field().
1809
 */
1810
function field_collection_field_create_field($field) {
1811
  if ($field['type'] == 'field_collection') {
1812
    field_attach_create_bundle('field_collection_item', $field['field_name']);
1813

    
1814
    // Clear caches.
1815
    entity_info_cache_clear();
1816
    // Do not directly issue menu rebuilds here to avoid potentially multiple
1817
    // rebuilds. Instead, let menu_get_item() issue the rebuild on the next
1818
    // request.
1819
    variable_set('menu_rebuild_needed', TRUE);
1820
  }
1821
}
1822

    
1823
/**
1824
 * Implements hook_field_delete_field().
1825
 */
1826
function field_collection_field_delete_field($field) {
1827
  if ($field['type'] == 'field_collection') {
1828
    // Notify field.module that field collection was deleted.
1829
    field_attach_delete_bundle('field_collection_item', $field['field_name']);
1830

    
1831
    // Clear caches.
1832
    entity_info_cache_clear();
1833
    // Do not directly issue menu rebuilds here to avoid potentially multiple
1834
    // rebuilds. Instead, let menu_get_item() issue the rebuild on the next
1835
    // request.
1836
    variable_set('menu_rebuild_needed', TRUE);
1837
  }
1838
}
1839

    
1840
/**
1841
 * Implements hook_i18n_string_list_{textgroup}_alter().
1842
 */
1843
function field_collection_i18n_string_list_field_alter(&$properties, $type, $instance) {
1844
  if ($type == 'field_instance') {
1845
    $field = field_info_field($instance['field_name']);
1846

    
1847
    if ($field['type'] == 'field_collection' && !empty($instance['display'])) {
1848

    
1849
      foreach ($instance['display'] as $view_mode => $display) {
1850
        if ($display['type'] != 'field_collection_fields') {
1851
          $display['settings'] += array('edit' => 'edit', 'translate' => 'translate', 'delete' => 'delete', 'add' => 'add');
1852

    
1853
          $properties['field'][$instance['field_name']][$instance['bundle']]['setting_edit'] = array(
1854
            'title' => t('Edit link title'),
1855
            'string' => $display['settings']['edit'],
1856
          );
1857
          $properties['field'][$instance['field_name']][$instance['bundle']]['setting_translate'] = array(
1858
            'title' => t('Edit translate title'),
1859
            'string' => $display['settings']['translate'],
1860
          );
1861
          $properties['field'][$instance['field_name']][$instance['bundle']]['setting_delete'] = array(
1862
            'title' => t('Delete link title'),
1863
            'string' => $display['settings']['delete'],
1864
          );
1865
          $properties['field'][$instance['field_name']][$instance['bundle']]['setting_add'] = array(
1866
            'title' => t('Add link title'),
1867
            'string' => $display['settings']['add'],
1868
          );
1869
        }
1870
      }
1871
    }
1872
  }
1873
}
1874

    
1875
/**
1876
 * Implements hook_views_api().
1877
 */
1878
function field_collection_views_api() {
1879
  return array(
1880
    'api' => '3.0-alpha1',
1881
    'path' => drupal_get_path('module', 'field_collection') . '/views',
1882
  );
1883
}
1884

    
1885
/**
1886
 * Implements hook_features_pipe_COMPONENT_alter() for field objects.
1887
 *
1888
 * This is used with Features v1.0 and v2.0 prior to beta2, newer releases
1889
 * separated the field_base from the field_instance so this won't be used.
1890
 *
1891
 * @see field_collection_features_pipe_field_instance_alter().
1892
 */
1893
function field_collection_features_pipe_field_alter(&$pipe, $data, $export) {
1894
  // Skip this if Features has been updated to v2.0-beta2 or newer as it will
1895
  // use the separate field_instance integration instead.
1896
  if (!function_exists('field_instance_features_export_options')) {
1897
    // Add the fields of the field collection entity to the pipe.
1898
    foreach ($data as $identifier) {
1899
      if (($field = features_field_load($identifier)) && $field['field_config']['type'] == 'field_collection') {
1900
        $fields = field_info_instances('field_collection_item', $field['field_config']['field_name']);
1901
        foreach ($fields as $name => $field) {
1902
          $pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}";
1903
        }
1904
      }
1905
    }
1906
  }
1907
}
1908

    
1909
/**
1910
 * Implements hook_features_pipe_COMPONENT_alter() for field_instance objects.
1911
 *
1912
 * This is used with Features v2.0-beta2 and newer.
1913
 */
1914
function field_collection_features_pipe_field_instance_alter(&$pipe, $data, $export) {
1915
  // Add the fields of the field collection entity to the pipe.
1916
  foreach ($data as $identifier) {
1917
    if (($field = features_field_load($identifier)) && $field['field_config']['type'] == 'field_collection') {
1918
      $fields = field_info_instances('field_collection_item', $field['field_config']['field_name']);
1919
      foreach ($fields as $name => $field) {
1920
        $pipe['field_instance'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}";
1921
      }
1922
    }
1923
  }
1924
}
1925

    
1926
/**
1927
 * Callback for generating entity metadata property info for our field instances.
1928
 *
1929
 * @see field_collection_field_info()
1930
 */
1931
function field_collection_entity_metadata_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
1932
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
1933
  // Set the bundle as we know it is the name of the field.
1934
  $property['bundle'] = $field['field_name'];
1935
  $property['getter callback'] = 'field_collection_field_property_get';
1936
  $property['setter callback'] = 'field_collection_field_property_set';
1937
}
1938

    
1939
/**
1940
 * Entity property info setter callback for the host entity property.
1941
 *
1942
 * As the property is of type entity, the value will be passed as a wrapped
1943
 * entity.
1944
 */
1945
function field_collection_item_set_host_entity($item, $property_name, $wrapper) {
1946
  if (empty($item->is_new)) {
1947
    throw new EntityMetadataWrapperException('The host entity may be set only during creation of a field collection item.');
1948
  }
1949
  if (!isset($wrapper->{$item->field_name})) {
1950
    throw new EntityMetadataWrapperException('The specified entity has no such field collection field.');
1951
  }
1952
  $entity_type = $wrapper->type();
1953
  $field = field_info_field($item->field_name);
1954
  $langcode = field_is_translatable($entity_type, $field) ? field_collection_entity_language($entity_type, $wrapper->value()) : LANGUAGE_NONE;
1955
  $item->setHostEntity($wrapper->type(), $wrapper->value(), $langcode);
1956
}
1957

    
1958
/**
1959
 * Entity property info getter callback for the host entity property.
1960
 */
1961
function field_collection_item_get_host_entity($item) {
1962
  // As the property is defined as 'entity', we have to return a wrapped entity.
1963
  return entity_metadata_wrapper($item->hostEntityType(), $item->hostEntity());
1964
}
1965

    
1966
/**
1967
 * Entity property info getter callback for the host entity property.
1968
 */
1969
function field_collection_item_get_specific_type_host_entity($item, array $options, $name) {
1970
  // These properties' machine names have the form "host_entity_TYPE".
1971
  if (substr($name, 0, 12) !== 'host_entity_') {
1972
    return NULL;
1973
  }
1974
  return $item->hostEntityType() === substr($name, 12) ? $item->hostEntity() : NULL;
1975
}
1976

    
1977
/**
1978
 * Entity property info getter callback for the field collection items.
1979
 *
1980
 * Like entity_metadata_field_property_get(), but additionally supports getting
1981
 * not-yet saved collection items from @code $item['entity'] @endcode.
1982
 */
1983
function field_collection_field_property_get($entity, array $options, $name, $entity_type, $info) {
1984
  $field = field_info_field($name);
1985
  $langcode = field_language($entity_type, $entity, $name, isset($options['language']) ? $options['language']->language : NULL);
1986
  $values = array();
1987
  if (isset($entity->{$name}[$langcode])) {
1988
    foreach ($entity->{$name}[$langcode] as $delta => $data) {
1989
      // Wrappers do not support multiple entity references being revisions or
1990
      // not yet saved entities. In the case of a single reference we can return
1991
      // the entity object though.
1992
      if ($field['cardinality'] == 1) {
1993
        $values[$delta] = field_collection_field_get_entity($data);
1994
      }
1995
      elseif (isset($data['value'])) {
1996
        $values[$delta] = $data['value'];
1997
      }
1998
    }
1999
  }
2000
  // For an empty single-valued field, we have to return NULL.
2001
  return $field['cardinality'] == 1 ? ($values ? reset($values) : NULL) : $values;
2002
}
2003

    
2004
/**
2005
 * Entity property info setter callback for the field collection items.
2006
 *
2007
 * Like entity_metadata_field_property_set(), but additionally supports
2008
 * saving the revision id.
2009
 */
2010
function field_collection_field_property_set($entity, $name, $value, $langcode, $entity_type) {
2011
  $field = field_info_field($name);
2012
  $columns = array_keys($field['columns']);
2013
  $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode);
2014
  $values = $field['cardinality'] == 1 ? array($value) : (array) $value;
2015

    
2016
  $items = array();
2017
  foreach ($values as $delta => $value) {
2018
    if (isset($value)) {
2019
      if ($value instanceof FieldCollectionItemEntity) {
2020
        $items[$delta][$columns[0]] = $value->item_id;
2021
        $items[$delta][$columns[1]] = $value->revision_id;
2022
      }
2023
      elseif (is_array($value) && isset($value['value'], $value['revision_id'])) {
2024
        $items[$delta][$columns[0]] = $value['value'];
2025
        $items[$delta][$columns[1]] = $value['revision_id'];
2026
      }
2027
      else {
2028
        $item = field_collection_item_load($value);
2029
        $items[$delta][$columns[0]] = $item->item_id;
2030
        $items[$delta][$columns[1]] = $item->revision_id;
2031
      }
2032
    }
2033
  }
2034
  $entity->{$name}[$langcode] = $items;
2035
  // Empty the static field language cache, so the field system picks up any
2036
  // possible new languages.
2037
  drupal_static_reset('field_language');
2038
}
2039

    
2040
/**
2041
 * Implements hook_devel_generate().
2042
 */
2043
function field_collection_devel_generate($object, $field, $instance, $bundle) {
2044
  if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) {
2045
    return devel_generate_multiple('_field_collection_devel_generate', $object, $field, $instance, $bundle);
2046
  }
2047

    
2048
  return _field_collection_devel_generate($object, $field, $instance, $bundle);
2049
}
2050

    
2051
function _field_collection_devel_generate($object, $field, $instance, $bundle) {
2052
  // Create a new field collection object and add fake data to its fields.
2053
  $field_collection = entity_create('field_collection_item', array('field_name' => $field['field_name']));
2054
  $field_collection->language = $object->language;
2055
  $field_collection->setHostEntity($instance['entity_type'], $object, $object->language, FALSE);
2056

    
2057
  devel_generate_fields($field_collection, 'field_collection_item', $field['field_name']);
2058

    
2059
  $field_collection->save(TRUE);
2060

    
2061
  return array(
2062
    'value' => $field_collection->item_id,
2063
    'revision_id' => $field_collection->revision_id,
2064
  );
2065
}
2066

    
2067
/**
2068
 * Determine if field collection items can be translated.
2069
 *
2070
 * @return
2071
 *   Boolean indicating whether field collection items can be translated.
2072
 */
2073
function field_collection_item_is_translatable() {
2074
  return (bool) module_invoke('entity_translation', 'enabled', 'field_collection_item');
2075
}
2076

    
2077
/**
2078
 * Implements hook_entity_translation_delete().
2079
 */
2080
function field_collection_entity_translation_delete($entity_type, $entity, $langcode) {
2081
  if (field_collection_item_is_translatable()) {
2082
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2083

    
2084
    foreach (field_info_instances($entity_type, $bundle) as $instance) {
2085
      $field_name = $instance['field_name'];
2086
      $field = field_info_field($field_name);
2087

    
2088
      if ($field['type'] == 'field_collection') {
2089
        $field_langcode = field_is_translatable($entity_type, $field) ? $langcode : LANGUAGE_NONE;
2090

    
2091
        if (!empty($entity->{$field_name}[$field_langcode])) {
2092
          foreach ($entity->{$field_name}[$field_langcode] as $delta => $item) {
2093
            $field_collection_item = field_collection_field_get_entity($item);
2094
            $handler = entity_translation_get_handler('field_collection_item', $field_collection_item);
2095
            $translations = $handler->getTranslations();
2096

    
2097
            if (isset($translations->data[$langcode])) {
2098
              $handler->removeTranslation($langcode);
2099
              $field_collection_item->save(TRUE);
2100
            }
2101
          }
2102
        }
2103
      }
2104
    }
2105
  }
2106
}
2107

    
2108
/**
2109
 * Determines if the additional blank items should be displayed or not.
2110
 *
2111
 * @param array $field
2112
 *   The field info array.
2113
 *
2114
 * @return bool
2115
 *   TRUE if the additional blank items should be hidden, and FALSE if not.
2116
 */
2117
function field_collection_hide_blank_items($field) {
2118
  return !empty($field['settings']['hide_blank_items']) && $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
2119
}
2120

    
2121
/**
2122
 * Implements hook_admin_menu_map().
2123
 */
2124
function field_collection_admin_menu_map() {
2125
  if (user_access('administer field collections')) {
2126
    $map['admin/structure/field-collections/%field_collection_field_name'] = array(
2127
      'parent' => 'admin/structure/field-collections',
2128
      'arguments' => array(
2129
        array('%field_collection_field_name' => array_keys(field_read_fields(array('type' => 'field_collection')))),
2130
      ),
2131
    );
2132
    return $map;
2133
  }
2134
}
2135

    
2136
/**
2137
 * implements hook_entity_translation_insert
2138
 */
2139
function field_collection_entity_translation_insert($entity_type, $entity, $translation, $values = array()) {
2140
  // Check if some of the values inserted are of a field_collection field
2141
  if (!empty($values)) {
2142
    foreach ($values as $field_name => $value) {
2143
      $field = field_info_field($field_name);
2144

    
2145
      if ($field['type'] == 'field_collection') {
2146
        // We have found a field collection
2147
        $language = $translation['language'];
2148
        $source_language = $translation['source'];
2149

    
2150
        if (!empty($value[$language])) {
2151
          $source_items = !empty($entity->{$field_name}[$source_language]) ? field_collection_field_item_to_ids($entity->{$field_name}[$source_language]) : array();
2152
          foreach ($value[$language] as $delta => $field_value) {
2153
            // Check if this field collection item belongs to the source language
2154
            if (!isset($field_value['entity'])
2155
              && ($fc_entity = field_collection_field_get_entity($field_value))
2156
              && in_array($fc_entity->item_id, $source_items)) {
2157
              // Clone the field collection item
2158
              $new_fc_entity = clone $fc_entity;
2159
              $new_fc_entity->item_id = NULL;
2160
              $new_fc_entity->revision_id = NULL;
2161
              $new_fc_entity->is_new = TRUE;
2162

    
2163
              // Set the new entity for saving it later
2164
              $entity->{$field_name}[$language][$delta]['entity'] = $new_fc_entity;
2165
            }
2166
          }
2167
        }
2168
      }
2169
    }
2170
  }
2171
}
2172

    
2173

    
2174
/**
2175
 * Implements hook_feeds_processor_targets_alter()
2176
 */
2177
function field_collection_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
2178

    
2179
  // Load up.
2180
  $loaded = &drupal_static(__FUNCTION__, FALSE);
2181

    
2182
  if (!$loaded) {
2183
    $loaded = TRUE;
2184
    $path = drupal_get_path('module', 'feeds') . '/mappers';
2185
    $files = drupal_system_listing('/.*\.inc$/', $path, 'name', 0);
2186
    foreach ($files as $file) {
2187
      if (strpos($file->uri, '/mappers/') !== FALSE) {
2188
        require_once DRUPAL_ROOT . '/' . $file->uri;
2189
      }
2190
    }
2191
  }
2192

    
2193
  foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
2194
    $info = field_info_field($name);
2195
    if ($info['type'] == 'field_collection') {
2196

    
2197
      $sub_type = 'field_collection_item';
2198
      $new_targets = module_invoke_all('feeds_processor_targets', $sub_type, $info['field_name']);
2199
      drupal_alter('feeds_processor_targets', $new_targets, $sub_type, $info['field_name']);
2200

    
2201
      foreach ($new_targets as $sub_name => $target) {
2202
        $new_name = t($info['field_name']) . ':' . t($sub_name);
2203
        $targets[$new_name] = $target;
2204
        if (isset($target['name'])) {
2205
          $targets[$new_name]['name'] = $instance['label'] . ':' . $target['name'];
2206
        }
2207
        // We override callback for now and retrieve original later.
2208
        $targets[$new_name]['callback'] = 'field_collection_feeds_set_target';
2209
      }
2210
    }
2211
  }
2212

    
2213
}
2214

    
2215

    
2216
/**
2217
 * Process Field Collection items.
2218
 *
2219
 * @param  [type] $source       [description]
2220
 * @param  [type] $entity       [description]
2221
 * @param  [type] $target       [description]
2222
 * @param  [type] $value        [description]
2223
 * @param  [type] $main_mapping [description]
2224
 * @return [type]               [description]
2225
 */
2226
function field_collection_feeds_set_target($source, $entity, $target, $value, $main_mapping) {
2227
  $sub_targets = &drupal_static(__FUNCTION__, array());
2228

    
2229
  $args = explode(':', $target);
2230
  $target = array_shift($args);
2231
  $sub_target = implode(':', $args);
2232

    
2233
  $sub_type = 'field_collection_item';
2234
  $new_targets = module_invoke_all('feeds_processor_targets', $sub_type, $target);
2235
  drupal_alter('feeds_processor_targets', $new_targets, $sub_type, $target);
2236

    
2237
  // Now we retrieve old callbacks and keep then on a static cache.
2238
  if (!isset($sub_targets[$target])) {
2239
    $sub_targets[$target] = array();
2240
    drupal_alter('feeds_processor_targets', $sub_targets[$target], $sub_type, $target);
2241
  }
2242

    
2243
  $_sub_targets = $new_targets;
2244

    
2245
  $value = is_array($value) ? $value : array($value);
2246
  $info = field_info_field($target);
2247

    
2248
  // Iterate over all values.
2249
  $delta = 0;
2250
  $field = isset($entity->$target) ? $entity->$target : array();
2251
  try {
2252

    
2253
    // $delta = which FC instance.
2254
    // We iterate by subfield, not by FC.
2255
    while (isset($value[$delta])) {
2256

    
2257
      // Zero out.
2258
      $field_collection_item = null;
2259

    
2260
      // FC is already set on host entity.
2261
      if (isset($field[LANGUAGE_NONE][$delta]['entity'])) {
2262
        $field_collection_item = $field[LANGUAGE_NONE][$delta]['entity'];
2263
      }
2264
      // FC is on host entity, we have FC item_id, but FC is not loaded.
2265
      elseif (isset($field[LANGUAGE_NONE][$delta]['value'])) {
2266
        $field_collection_item = field_collection_item_load($field[LANGUAGE_NONE][$delta]['value']);
2267

    
2268
        // Zero out field so that we don't just keep accumulating values.
2269
        unset($field_collection_item->{$sub_target}[LANGUAGE_NONE] );
2270
      }
2271

    
2272
      // Host entity does not have any attached FCs yet.
2273
      if (empty($field_collection_item)) {
2274
        $field_collection_item = entity_create('field_collection_item', array('field_name' => $target));
2275
        $field_collection_item->setHostEntity($entity->feeds_item->entity_type, $entity);
2276
      }
2277

    
2278
      $sub_mapping = array();
2279
      $config = $source->importer()->getConfig();
2280
      if (!empty($config['processor']['config']['mappings'])) {
2281
        foreach ($config['processor']['config']['mappings'] as $mapping) {
2282
          if ($mapping['target'] == $target . ':' . $sub_target) {
2283
            $sub_mapping = $mapping;
2284
            $sub_mapping['target'] = $sub_target;
2285
            // Needs language or feeds mappers shout php notices.
2286
            $sub_mapping['language'] = !empty($config['processor']['config']['language']) ? $config['processor']['config']['language'] : LANGUAGE_NONE;
2287
            break;
2288
          }
2289
        }
2290
      }
2291

    
2292
      if (isset($_sub_targets[$sub_target]['callback']) && function_exists($_sub_targets[$sub_target]['callback'])) {
2293
        $callback = $_sub_targets[$sub_target]['callback'];
2294

    
2295
        // Normalize
2296
        if (!is_array($value[$delta])) {
2297
          $value[$delta] = array($value[$delta]);
2298
        }
2299

    
2300
        // Check for a limit and force that limit.
2301
        if ($info['cardinality'] !== '-1') {
2302
          $value[$delta] = array_slice($value[$delta], 0, $info['cardinality']);
2303
        }
2304

    
2305
        // HiJack the file callback so we can make it work.
2306
        if ($callback == 'file_feeds_set_target') {
2307
          $callback = 'field_collection_file_feeds_set_target';
2308
        }
2309

    
2310
        // Allow altering with many parameters. Cannot use drupal_alter here.
2311
        $implements = module_implements('sub_target_pre_callback_parse');
2312
        foreach ($implements as $module_name) {
2313
          $hook = $module_name . '_' . 'sub_target_pre_callback_parse';
2314
          $hook($target, $sub_target, $entity, $field, $field_collection_item, $value[$delta]);
2315
        }
2316

    
2317
        $callback($source, $field_collection_item, $sub_target, $value[$delta], $sub_mapping);
2318
      }
2319

    
2320
      // No need to save the field collection here. Just wait until the node is saved.
2321
      // If we save the FC here we get a huge performance degregation.
2322
      $field[LANGUAGE_NONE][$delta]['entity'] = $field_collection_item;
2323

    
2324
      // Break when only hitting the max delta.
2325
      if ($info['cardinality'] == $delta) {
2326
        break;
2327
      }
2328

    
2329
      $delta++;
2330
    }
2331

    
2332
  }
2333
  catch (Exception $e) {
2334
    drupal_set_message($e->getMessage(), 'error');
2335
    watchdog_exception('field_collection', $e, $e->getMessage());
2336
    throw $e;
2337
  }
2338

    
2339
  $entity->{$target} = $field;
2340
}
2341

    
2342
/**
2343
 * Wrapper function for file_feeds_set_target as it doesn't do the nested stuff.
2344
 */
2345
function field_collection_file_feeds_set_target($source, $entity, $target, $value) {
2346

    
2347
  if (empty($value)) {
2348
    return;
2349
  }
2350
  module_load_include('inc', 'file');
2351

    
2352
  // Make sure $value is an array of objects of type FeedsEnclosure.
2353
  if (!is_array($value)) {
2354
    $value = array($value);
2355
  }
2356
  foreach ($value as $k => $v) {
2357
    if (!($v instanceof FeedsEnclosure)) {
2358
      if (is_string($v)) {
2359
        $value[$k] = new FeedsEnclosure($v, file_get_mimetype($v));
2360
      }
2361
      else {
2362
        unset($value[$k]);
2363
      }
2364
    }
2365
  }
2366
  if (empty($value)) {
2367
    return;
2368
  }
2369

    
2370
  $entity_type = 'field_collection_item';
2371
  $bundle = $entity->field_name;
2372

    
2373
  // Determine file destination.
2374
  // @todo This needs review and debugging.
2375
  $instance_info = field_info_instance($entity_type, $target, $bundle);
2376
  $info = field_info_field($target);
2377
  $data = array();
2378
  if (!empty($entity->uid)) {
2379
    $data[$entity_type] = $entity;
2380
  }
2381
  $destination = file_field_widget_uri($info, $instance_info, $data);
2382

    
2383
  // Populate entity.
2384
  $i = 0;
2385
  $field = isset($entity->$target) ? $entity->$target : array();
2386
  foreach ($value as $v) {
2387
    $file = FALSE;
2388
    try {
2389
      $v->setAllowedExtensions($instance_info['settings']['file_extensions']);
2390
      $file = $v->getFile($destination);
2391
    }
2392
    catch (Exception $e) {
2393
      watchdog('feeds', check_plain($e->getMessage()));
2394
    }
2395
    if ($file) {
2396
      $field[LANGUAGE_NONE][$i] = (array)$file;
2397
      $field[LANGUAGE_NONE][$i]['display'] = 1; // @todo: Figure out how to properly populate this field.
2398
      if ($info['cardinality'] == 1) {
2399
        break;
2400
      }
2401
      $i++;
2402
    }
2403
  }
2404
  $entity->{$target} = $field;
2405

    
2406
}
2407

    
2408

    
2409
/**
2410
 * Implementation of hook_feeds_presave().
2411
 * Invoked before a feed item is saved.
2412
 *
2413
 * @param FeedsSource $source
2414
 *   FeedsSource object that describes the source that is being imported.
2415
 * @param $entity
2416
 *   The entity object.
2417
 * @param array $item
2418
 *   The parser result for this entity.
2419
 * @param int|null $entity_id
2420
 *   The id of the current item which is going to be updated. If this is a new
2421
 *   item, then NULL is passed.
2422
 */
2423
function field_collection_feeds_presave(FeedsSource $source, $entity, $item, $entity_id) {
2424
  $feed_config = $source->importer()->getConfig();
2425
  $mappings = $feed_config['processor']['config']['mappings'];
2426

    
2427
  // Only hook in when updating the entity.
2428
  if (empty($entity_id)) {
2429

    
2430
    // Go through $mappings and find the FC fields.
2431
    foreach ($mappings as $mapping) {
2432

    
2433
      if (strpos($mapping['target'], ':') !== FALSE) {
2434
        $parts = explode(':', $mapping['target']);
2435
        $fc_name = array_shift($parts);
2436
        $info = field_info_field($fc_name);
2437

    
2438
        // If the field is not a field collection, skip it.
2439
        if ($info['type'] != 'field_collection') {
2440
          continue;
2441
        }
2442

    
2443
        if (isset($entity->{$fc_name})) {
2444
          foreach ($entity->{$fc_name}[LANGUAGE_NONE] as $delta => $fc_unloaded) {
2445

    
2446
            // If an FC is not loaded, we don't want it. Unset it so it won't save.
2447
            if (!isset($entity->{$fc_name}[LANGUAGE_NONE][$delta]['entity'])) {
2448
              unset($entity->{$fc_name}[LANGUAGE_NONE][$delta]);
2449
            }
2450
          }
2451

    
2452
        }
2453
      }
2454
    }
2455
  }
2456
}
2457