diff --git a/field_collection.module b/field_collection.module index 5a0132f..45a51cb 100644 --- a/field_collection.module +++ b/field_collection.module @@ -202,7 +202,7 @@ class FieldCollectionItemEntity extends Entity { if (!empty($this->is_new)) { $this->hostEntityType = $entity_type;; $this->hostEntity = $entity; - $this->langcode = LANGUAGE_NONE; + $this->langcode = $langcode; list($this->hostEntityId) = entity_extract_ids($this->hostEntityType, $this->hostEntity); // If the host entity is not saved yet, set the id to FALSE. So // fetchHostDetails() does not try to load the host entity details. @@ -365,23 +365,6 @@ class FieldCollectionItemEntity extends Entity { } /** - * Export the field collection item. - * - * Since field collection entities are not directly exportable (i.e., do not - * have 'exportable' set to TRUE in hook_entity_info()) and since Features - * calls this method when exporting the field collection as a field attached - * to another entity, we return the export in the format expected by - * Features, rather than in the normal Entity::export() format. - */ - public function export($prefix = '') { - // Based on code in EntityDefaultFeaturesController::export_render(). - $export = "entity_import('" . $this->entityType() . "', '"; - $export .= addcslashes(parent::export(), '\\\''); - $export .= "')"; - return $export; - } - - /** * Magic method to only serialize what's necessary. */ public function __sleep() { @@ -562,9 +545,8 @@ function field_collection_item_access($op, FieldCollectionItemEntity $item = NUL } $op = $op == 'view' ? 'view' : 'edit'; // Access is determined by the entity and field containing the reference. - $field = field_info_field($item->field_name); $entity_access = entity_access($op == 'view' ? 'view' : 'update', $item->hostEntityType(), $item->hostEntity(), $account); - return $entity_access && field_access($op, $field, $item->hostEntityType(), $item->hostEntity(), $account); + return $entity_access && field_access($op, $item->field_name, $item->hostEntityType(), $item->hostEntity(), $account); } /** @@ -594,10 +576,7 @@ function field_collection_field_info() { 'default_widget' => 'field_collection_hidden', 'default_formatter' => 'field_collection_view', // As of now there is no UI for setting the path. - 'settings' => array( - 'path' => '', - 'hide_blank_items' => TRUE, - ), + 'settings' => array('path' => ''), // Add entity property info. 'property_type' => 'field_collection_item', 'property_callbacks' => array('field_collection_entity_metadata_property_callback'), @@ -606,33 +585,6 @@ function field_collection_field_info() { } /** - * Implements hook_field_instance_settings_form(). - */ -function field_collection_field_instance_settings_form($field, $instance) { - - $element['fieldset'] = array( - '#type' => 'fieldset', - '#title' => t('Default value'), - '#collapsible' => FALSE, - // As field_ui_default_value_widget() does, we change the #parents so that - // the value below is writing to $instance in the right location. - '#parents' => array('instance'), - ); - // Be sure to set the default value to NULL, e.g. to repair old fields - // that still have one. - $element['fieldset']['default_value'] = array( - '#type' => 'value', - '#value' => NULL, - ); - $element['fieldset']['content'] = array( - '#pre' => '

', - '#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 Manage fields screen of the field collection.', array('!url' => url('admin/structure/field-collections/' . strtr($field['field_name'], array('_' => '-')) . '/fields'))), - '#suffix' => '

', - ); - return $element; -} - -/** * Returns the base path to use for field collection items. */ function field_collection_field_get_path($field) { @@ -643,27 +595,6 @@ function field_collection_field_get_path($field) { } /** - * Implements hook_field_settings_form(). - */ -function field_collection_field_settings_form($field, $instance) { - - $form['hide_blank_items'] = array( - '#type' => 'checkbox', - '#title' => t('Hide blank items'), - '#default_value' => $field['settings']['hide_blank_items'], - '#description' => t("A blank item is always added to any multivalued field's form. If checked, any additional blank items are hidden except of the first item which is always shown."), - '#weight' => 10, - '#states' => array( - // Hide the setting if the cardinality is 1. - 'invisible' => array( - ':input[name="field[cardinality]"]' => array('value' => '1'), - ), - ), - ); - return $form; -} - -/** * Implements hook_field_presave(). * * Support saving field collection items in @code $item['entity'] @endcode. This @@ -675,7 +606,7 @@ function field_collection_field_presave($entity_type, $entity, $field, $instance // In case the entity has been loaded / created, save it and set the id. if (isset($item['entity'])) { if (!empty($item['entity']->is_new)) { - $item['entity']->setHostEntity($entity_type, $entity, LANGUAGE_NONE, FALSE); + $item['entity']->setHostEntity($entity_type, $entity, $langcode, FALSE); } $item['entity']->save(TRUE); $item = array('value' => $item['entity']->item_id); @@ -743,7 +674,6 @@ function field_collection_field_is_empty($item, $field) { */ function field_collection_item_is_empty(FieldCollectionItemEntity $item) { $instances = field_info_instances('field_collection_item', $item->field_name); - $is_empty = TRUE; foreach ($instances as $instance) { $field_name = $instance['field_name']; @@ -758,16 +688,13 @@ function field_collection_item_is_empty(FieldCollectionItemEntity $item) { // field collection item is not empty. foreach ($item->{$field_name}[$langcode] as $field_item) { if (!module_invoke($field['module'], 'field_is_empty', $field_item, $field)) { - $is_empty = FALSE; + return FALSE; } } } } } - - // Allow other modules a chance to alter the value before returning. - drupal_alter('field_collection_is_empty', $is_empty, $item); - return $is_empty; + return TRUE; } /** @@ -973,7 +900,7 @@ function field_collection_field_formatter_links(&$element, $entity_type, $entity if ($settings['add'] && ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || count($items) < $field['cardinality'])) { // Check whether the current is allowed to create a new item. $field_collection_item = entity_create('field_collection_item', array('field_name' => $field['field_name'])); - $field_collection_item->setHostEntity($entity_type, $entity, LANGUAGE_NONE, FALSE); + $field_collection_item->setHostEntity($entity_type, $entity, $langcode, FALSE); if (field_collection_item_access('create', $field_collection_item)) { $path = field_collection_field_get_path($field); @@ -1020,7 +947,7 @@ function field_collection_field_widget_info() { 'field types' => array('field_collection'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, - 'default value' => FIELD_BEHAVIOR_NONE, + 'default value' => FIELD_BEHAVIOR_CUSTOM, ), ), 'field_collection_embed' => array( @@ -1028,7 +955,7 @@ function field_collection_field_widget_info() { 'field types' => array('field_collection'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, - 'default value' => FIELD_BEHAVIOR_NONE, + 'default value' => FIELD_BEHAVIOR_DEFAULT, ), ), ); @@ -1054,7 +981,18 @@ function field_collection_field_widget_form(&$form, &$form_state, $field, $insta $field_parents = $element['#field_parents']; $field_name = $element['#field_name']; $language = $element['#language']; - + + // Set to LANGUAGE_NONE if field is non-translatable. + if (arg(2) == 'translate' && arg(4)) { + if ($language == LANGUAGE_NONE) { + $language = LANGUAGE_NONE; + } + else { + $element['#language'] = arg(4); + $language = arg(4); + } + } + // Nest the field collection item entity form in a dedicated parent space, // by appending [field_name, langcode, delta] to the current parent space. // That way the form values of the field collection item are separated. @@ -1071,36 +1009,25 @@ function field_collection_field_widget_form(&$form, &$form_state, $field, $insta $field_state = field_form_get_state($field_parents, $field_name, $language, $form_state); - if (!empty($field['settings']['hide_blank_items']) && $delta == $field_state['items_count'] && $delta > 0) { - // Do not add a blank item. Also see - // field_collection_field_attach_form() for correcting #max_delta. - $recursion--; - return FALSE; - } - elseif (!empty($field['settings']['hide_blank_items']) && $field_state['items_count'] == 0) { - // We show one item, so also specify that as item count. So when the - // add button is pressed the item count will be 2 and we show to items. - $field_state['items_count'] = 1; - } - if (isset($field_state['entity'][$delta])) { $field_collection_item = $field_state['entity'][$delta]; } else { - if (isset($items[$delta])) { - $field_collection_item = field_collection_field_get_entity($items[$delta], $field_name); - } - // Show an empty collection if we have no existing one or it does not - // load. - if (empty($field_collection_item)) { - $field_collection_item = entity_create('field_collection_item', array('field_name' => $field_name)); - } + $field_collection_item = field_collection_field_get_entity($items[$delta], $field_name); // Put our entity in the form state, so FAPI callbacks can access it. $field_state['entity'][$delta] = $field_collection_item; + field_form_set_state($field_parents, $field_name, $language, $form_state, $field_state); + } + + // If the field collection entity did not load, we need to return nothing + // for the form otherwise fatal errors with entity_extract_ids() will + // continue in validation and save. + if (empty($field_collection_item)) { + $recursion--; + return array(); } - field_form_set_state($field_parents, $field_name, $language, $form_state, $field_state); field_attach_form('field_collection_item', $field_collection_item, $element, $form_state, $language); if (empty($element['#required'])) { @@ -1130,30 +1057,6 @@ function field_collection_field_widget_form(&$form, &$form_state, $field, $insta } /** - * Implements hook_field_attach_form(). - * - * Corrects #max_delta when we hide the blank field collection item. - * - * @see field_add_more_js() - * @see field_collection_field_widget_form() - */ -function field_collection_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { - - foreach (field_info_instances($entity_type, $form['#bundle']) as $field_name => $instance) { - $field = field_info_field($field_name); - - if ($field['type'] == 'field_collection' && $field['settings']['hide_blank_items'] - && field_access('edit', $field, $entity_type) && $instance['widget']['type'] == 'field_collection_embed') { - - $element_langcode = $form[$field_name]['#language']; - if ($form[$field_name][$element_langcode]['#max_delta'] > 0) { - $form[$field_name][$element_langcode]['#max_delta']--; - } - } - } -} - -/** * Page callback to handle AJAX for removing a field collection item. * * This is a direct page callback. The actual job of deleting the item is @@ -1219,10 +1122,9 @@ function field_collection_remove_submit($form, &$form_state) { $parents = $parent_element['#field_parents']; $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); - // Go ahead and renumber everything from our delta to the last // item down one. This will overwrite the item being removed. - for ($i = $delta; $i <= $field_state['items_count']; $i++) { + for ($i = $delta; $i < $field_state['items_count']; $i++) { $old_element_address = array_merge($address, array($i + 1)); $new_element_address = array_merge($address, array($i)); @@ -1238,19 +1140,20 @@ function field_collection_remove_submit($form, &$form_state) { drupal_array_set_nested_value($form_state['input'], $moving_element['#parents'], $moving_element_input); // Move the entity in our saved state. - if (isset($field_state['entity'][$i + 1])) { - $field_state['entity'][$i] = $field_state['entity'][$i + 1]; - } - else { - unset($field_state['entity'][$i]); - } + $field_state['entity'][$i] = $field_state['entity'][$i + 1]; } + // For the final item there is nothing to move into its place, so remove it. + $i = $field_state['items_count']; + $removed_element_address = array_merge($address, array($i)); + $removed_element = drupal_array_get_nested_value($form, $removed_element_address); + form_set_value($removed_element, NULL, $form_state); + drupal_array_set_nested_value($form_state['input'], $removed_element['#parents'], NULL); + // Replace the deleted entity with an empty one. This helps to ensure that // trying to add a new entity won't ressurect a deleted entity from the // trash bin. - $count = count($field_state['entity']); - $field_state['entity'][$count] = entity_create('field_collection_item', array('field_name' => $field_name)); + $field_state['entity'][$field_state['items_count']] = entity_create('field_collection_item', array('field_name' => $field_name)); // Then remove the last item. But we must not go negative. if ($field_state['items_count'] > 0) { @@ -1279,6 +1182,7 @@ function field_collection_remove_submit($form, &$form_state) { } drupal_array_set_nested_value($form_state['input'], $address, $input); + field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); $form_state['rebuild'] = TRUE; @@ -1341,6 +1245,39 @@ function field_collection_field_widget_render_required($element) { } /** + * Implements hook_form_FORM_ID_alter() + * + * To support the entity translation + * @see Patch http://drupal.org/files/field_collection-translation-1366220-3.patch + * + * @param Array $form + * @param Array $form_state + * + * @throws Exception + */ +function field_collection_form_entity_translation_edit_form_alter(&$form, &$form_state) { + // Special handling for entity translations. Since the populated fields for + // the translation form are added after the widgets are build, the field state + // entities don't get populated past the first item, so that needs to be done + // here. + list(, , $bundle) = entity_extract_ids($form['#entity_type'], $form['#entity']); + foreach (field_info_instances($form['#entity_type'], $bundle) as $instance) { + if ($instance['widget']['module'] == 'field_collection') { + $field_name = $instance['field_name']; + $langcode = $form[$field_name]['#language']; + foreach (element_children($form[$field_name][$langcode]) as $key => $field) { + if (!isset($form_state['field'][$field_name][$langcode]['entity'][$key])) { + $field_collection_item = field_collection_field_get_entity($items[$key], $field_name); + + // Put our entity in the form state, so FAPI callbacks can access it. + $form_state['field'][$field_name][$langcode]['entity'][$key] = $field_collection_item; + } + } + } + } +} + +/** * FAPI validation of an individual field collection element. */ function field_collection_field_widget_embed_validate($element, &$form_state, $complete_form) { @@ -1351,52 +1288,51 @@ function field_collection_field_widget_embed_validate($element, &$form_state, $c $language = $element['#language']; $field_state = field_form_get_state($field_parents, $field_name, $language, $form_state); - $field_collection_item = $field_state['entity'][$element['#delta']]; - - // Attach field API validation of the embedded form. - field_attach_form_validate('field_collection_item', $field_collection_item, $element, $form_state); - - // Now validate required elements if the entity is not empty. - if (!field_collection_item_is_empty($field_collection_item) && !empty($element['#field_collection_required_elements'])) { - foreach ($element['#field_collection_required_elements'] as &$elements) { - - // Copied from _form_validate(). - if (isset($elements['#needs_validation'])) { - $is_empty_multiple = (!count($elements['#value'])); - $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); - $is_empty_value = ($elements['#value'] === 0); - if ($is_empty_multiple || $is_empty_string || $is_empty_value) { - if (isset($elements['#title'])) { - form_error($elements, t('!name field is required.', array('!name' => $elements['#title']))); - } - else { - form_error($elements); - } - } - } - } - } - - // Only if the form is being submitted, finish the collection entity and - // prepare it for saving. - if ($form_state['submitted'] && !form_get_errors()) { - - field_attach_submit('field_collection_item', $field_collection_item, $element, $form_state); - - // Load initial form values into $item, so any other form values below the - // same parents are kept. - $item = drupal_array_get_nested_value($form_state['values'], $element['#parents']); - - // Set the _weight if it is a multiple field. - if (isset($element['_weight']) && ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED)) { - $item['_weight'] = $element['_weight']['#value']; - } - - // Put the field collection item in $item['entity'], so it is saved with - // the host entity via hook_field_presave() / field API if it is not empty. - // @see field_collection_field_presave() - $item['entity'] = $field_collection_item; - form_set_value($element, $item, $form_state); + if (isset($field_state['entity'][$element['#delta']])) { + $field_collection_item = $field_state['entity'][$element['#delta']]; + + // Attach field API validation of the embedded form. + field_attach_form_validate('field_collection_item', $field_collection_item, $element, $form_state); + + // Now validate required elements if the entity is not empty. + if (!field_collection_item_is_empty($field_collection_item) && !empty($element['#field_collection_required_elements'])) { + foreach ($element['#field_collection_required_elements'] as &$elements) { + + // Copied from _form_validate(). + if (isset($elements['#needs_validation'])) { + $is_empty_multiple = (!count($elements['#value'])); + $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); + $is_empty_value = ($elements['#value'] === 0); + if ($is_empty_multiple || $is_empty_string || $is_empty_value) { + if (isset($elements['#title'])) { + form_error($elements, t('!name field is required.', array('!name' => $elements['#title']))); + } + else { + form_error($elements); + } + } + } + } + } + + + // Only if the form is being submitted, finish the collection entity and + // prepare it for saving. + if ($form_state['submitted'] && !form_get_errors()) { + + field_attach_submit('field_collection_item', $field_collection_item, $element, $form_state); + + // Set the _weight if it is a multiple field. + if (isset($element['_weight']) && ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED)) { + $item['_weight'] = $element['_weight']['#value']; + } + + // Put the field collection item in $item['entity'], so it is saved with + // the host entity via hook_field_presave() / field API if it is not empty. + // @see field_collection_field_presave() + $item['entity'] = $field_collection_item; + form_set_value($element, $item, $form_state); + } } } diff --git a/field_collection.pages.inc b/field_collection.pages.inc index 732e101..b87e6e3 100644 --- a/field_collection.pages.inc +++ b/field_collection.pages.inc @@ -114,7 +114,9 @@ function field_collection_item_add($field_name, $entity_type, $entity_id, $revis // Check field cardinality. $field = field_info_field($field_name); - $langcode = LANGUAGE_NONE; + if (empty($langcode)) { + $langcode = LANGUAGE_NONE; + } if (!($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || !isset($entity->{$field_name}[$langcode]) || count($entity->{$field_name}[$langcode]) < $field['cardinality'])) { drupal_set_message(t('Too many items.'), 'error'); return ''; @@ -125,7 +127,7 @@ function field_collection_item_add($field_name, $entity_type, $entity_id, $revis // as during the form-workflow we have multiple field collection item entity // instances, which we don't want link all with the host. // That way the link is going to be created when the item is saved. - $field_collection_item->setHostEntity($entity_type, $entity, LANGUAGE_NONE, FALSE); + $field_collection_item->setHostEntity($entity_type, $entity, $langcode, FALSE); $title = ($field['cardinality'] == 1) ? $instance['label'] : t('Add new !instance_label', array('!instance_label' => $field_collection_item->translatedInstanceLabel())); drupal_set_title($title);