How can i determine whatever fields of "Field Collection" field were changed during saving of a node?

Inside my custom module i use hooks node_presave, node_update, node_insert but the problem is that inside this hooks i can load only new field values of "Filed Collection" field. After a little code research i have found out that inside field_collection_field_presave values are already saved to DB, so there is no way of getting previous ones... or is there?

PS: It is preferably to use node_$op hooks, but not form_alter or others, because in dependent from some of those fields i need to make another record to db.

Comments

Niremizov’s picture

Component: Documentation » Code
Category: support » bug
Priority: Normal » Major

According to the Field API "Only field modules that are storing or tracking information outside the standard field storage mechanism need to implement this hook." this applies to the hook_field_update and hook_field_insert. So probably save action's schould be made inside this 2 hooks but not inside hook_field_presave, like that for example:

/**
 * Implements hook_field_update(). 
 */
function field_collection_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($items as &$item) {
    if (isset($item['entity'])) {
      $item['entity']->save(TRUE);
      $item = array('value' => $item['entity']->item_id);
    }
  }
}

/**
 * Implements hook_field_insert().
 */
function field_collection_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
  foreach ($items as &$item) {
    if (isset($item['entity'])) {
      $item['entity']->setHostEntity($entity_type, $entity, LANGUAGE_NONE, FALSE);
      $item['entity']->save(TRUE);
      $item = array('value' => $item['entity']->item_id);
    }
  }
}

Thus inside hook_node_presave we will have current values and strored values inside $node->original.

Niremizov’s picture

Version: 7.x-1.0-beta4 » 7.x-1.x-dev

Till the save made inside field_collection_field_presave the only solution that i found is using hook_entity_presave($entity, $type).

function hook_entity_presave($entity, $type) {
  if ($type == 'field_collection_item' && $entity->field_name == 'some_field_name') {
    your_module_function($previous_field_values_of_field_collection);
  }
}

Thus, to save previous values and use them inside hook_entity_update or hook_entity_insert you will need to use user function "your_module_function($previuos_value_of_field_collection)", for example:

function ancets_m_update_project_plans_fact_update($previous_field_values_of_field_collection = NULL, $return = FALSE) {
  $stored_info =& drupal_static(__FUNCTION__, array());
  if ($previous_field_values_of_field_collection) {
    if (!array_key_exists($stored_info , $previous_field_values_of_field_collection->id)) {
      $stored_pids[$previous_field_values_of_field_collection->id] = $previous__field_values_of_field_collection;
    }
  }
  if ($return){
    return $stored_pids;
  }
}

After that you can receive previous values inside update hooks and make needed compare actions:

function hook_node_update($node){
  $prev_fcoll_values = ancets_m_update_project_plans_fact_update(NULL,TRUE);
  //... other actions
}
fago’s picture

Inside my custom module i use hooks node_presave, node_update, node_insert but the problem is that inside this hooks i can load only new field values of "Filed Collection" field. After a little code research i have found out that inside field_collection_field_presave values are already saved to DB, so there is no way of getting previous ones... or is there?

I agree, we should do our save during insert/update, so feel free to roll a patch.

Still, comparing changes will be more difficult as usually I think, but at least you can load the original collections during presave then - yep.

geek-merlin’s picture

in current dev
* fieldcollecitons are saved in field_collection_field_presave().
* old revisions are marked archived in field_collection_field_update() then.

according to "field_attach_insert":http://api.drupal.org/api/drupal/modules%21field%21field.attach.inc/func... hooks are:
* field_default_[op]()
* hook_field_[op]()
* hook_field_storage_pre_insert
* (storage)_field_storage_write

so looks like hook_field_storage_pre_insert() is a good point.

kscheirer’s picture

Assigned: Unassigned » kscheirer
Status: Active » Needs review
StatusFileSize
new1.88 KB

Based on #1 and #3, attempted a patch - this is not my area of expertise, but I did my best to combine the proposed update hook in #1 with what was already in the module, and moved some presave code to the insert hook instead.

Status: Needs review » Needs work

The last submitted patch, 1781190-hook-presave-5.patch, failed testing.

Niremizov’s picture

In attached patch field_collection_field_presave was deleted and all its functionallity moved to hook_field_update and hook_field_insert.

Niremizov’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, field_collection-hook_presave-1781190-7.patch, failed testing.

Niremizov’s picture

Status: Needs work » Needs review
StatusFileSize
new3.83 KB

Changed variables naming and tested locally, should be ok now.

yannickoo’s picture

+++ b/field_collection.moduleundefined
@@ -888,21 +888,40 @@ function field_collection_field_settings_form($field, $instance) {
+        $item = array('value' => $entity->item_id, 'revision_id' => $entity->revision_id);

I would put the array in two lines like

$item = array(
  'value' => $entity->item_id,
  'revision_id' => $entity->revision_id,
);
Niremizov’s picture

johnpitcairn’s picture

Patch at #12 applies cleanly and appears to work for me. It's very useful - it allows non Entity API aware modules like Workbench Moderation to set the default_revision status of the host entity before the field collection is saved.

johnpitcairn’s picture

Hmm ... moving this logic to hook_field_update causes the Workbench Moderation problem at #1807460: Field collection doesn't play nice with workbench moderation (patch) to become worse - the first revision of the field collection will be the only revision you ever save?

johnpitcairn’s picture

Isn't this code at line 930 in field_collection_field_update() redundant? Won't any new entity already have passed through field_collection_field_insert() and have had its host entity set there?

  if (!empty($entity->is_new)) {
    $entity->setHostEntity($host_entity_type, $host_entity, LANGUAGE_NONE, FALSE);
  }

For the code that follows that, it would be tremendously useful (in Drupal 7) if workflow modules could affect how entity revisions are dealt with. As it stands, the only way to do that is in hook_entity_presave(), but that has a couple of problems for modules like Workbench Moderation:

1 - field_collection_item_get_host_entity() returns a wrapped node, where the dynamic properties set by Workbench Moderation are not accessible.

2 - To save a draft revision, Workbench Moderation saves the node again in a shutdown function with $node->revision set to 0. This causes field_collection_field_update() to remove items, as noted in #1807460: Field collection doesn't play nice with workbench moderation (patch). But the patch proposed there adds workbench_moderation-specific code.

I propose the addition of an alter hook before $entity->save, and (maybe) some modification of the following item-removal logic. This would allow other modules to cleanly modify $entity, $host_entity and $original_by_id, and avoid the need to add workbench_moderation-specific checks to this module, or field_collection-specific logic to workbench_moderation. Further customization could be handled by a dedicated module - see Field Collection Workbench Moderation

Something like the attached (which also removes the redundant hostEntity check).

Status: Needs review » Needs work

The last submitted patch, field_collection-hook_presave-1781190-15.patch, failed testing.

johnpitcairn’s picture

Duh. So maybe we do need the extra hostEntity check...

johnpitcairn’s picture

Status: Needs work » Needs review

Re queueing ... OK then.

johnpitcairn’s picture

Assigned: kscheirer » Unassigned

Unassigning kscheirer. Does anyone feel the proposed alter() hook should be a separate issue? It's a harmless addition because it requires explicit implementation to do anything. I think it's within the general spirit of the issue summary, but providing some additional flexibility beyond the reach of Entity API.

johnpitcairn’s picture

Patch at #17 won't do the job, because $item['entity'] isn't always present and therefore the alter hook will not always be called. This new patch is less far-reaching, and just provides the opportunity to alter how deletions are handled.

Modules wishing to intervene in hook_entity_presave() with host entity properties fully available can still do so by caching the host entity in an earlier hook.

johnpitcairn’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new3.32 KB

Cancel my request for a new hook, as per the comment by @hass. Reattaching the patch from @Niremizov at #12, marking RTBC.

fago’s picture

Status: Reviewed & tested by the community » Fixed

Thanks, patch looks good. Committed.

yannickoo’s picture

Finally this was committed, thanks fago! :)

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

divined’s picture

get following error when try to save node after patching module:

 PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'field_metro_target_id' cannot be null: INSERT INTO {field_data_field_metro} (entity_type, entity_id, revision_id, bundle, delta, language, field_metro_target_id) 

where "field_data_field_metro" is a collection field with not required fields.

vladimiraus’s picture

Issue summary: View changes
Status: Closed (fixed) » Needs work
Issue tags: +token, +Field collection

This patch breaks functionality for rules and tokens.
I created a rule where I'm trying to use values of multifield/multivalue field collection.
Example:

[entityform:field-form-guest-collection:0:field-fcol-guest-name]
[entityform:field-form-guest-collection:0:field-fcol-guest-company]
[entityform:field-form-guest-collection:1:field-fcol-guest-name]
[entityform:field-form-guest-collection:1:field-fcol-guest-company]

If I'll change field_collection_field_update back to field_collection_field_presave, the values are populated normally.
Otherwise they are just empty values.

vladimiraus’s picture

Status: Needs work » Needs review
StatusFileSize
new2.44 KB

Patch to restore the generation of tokens as per comment 26

Status: Needs review » Needs work

The last submitted patch, 27: field_collection-hook_presave-1781190-27.patch, failed testing.

bmunslow’s picture

Hi,

Perhaps I'm missing something, but I try to compare original FC values with new FC values in hook_node_update and I always get the new values, I can't see the old ones.

$node->my_field_collection matches exactly $node->original->my_field_collection

I use entity_metadata_wrapper to populate actual values of the field collection (instead fc ids).

What would be the proper way to access original field_collection values?

EDIT:

Ok, for some reason I wasn't getting the updated results of the applied patch, even after clearing cache.
Long story short, the new values can be found in $node->my_field_collection, whereas $node->original->my_field_collection contains fc id which can be loaded to retrieve old values.

jmuzz’s picture

Priority: Major » Normal
Issue tags: -token, -Field collection
milos.kroulik’s picture

Patch from #27 added tokens for me, but I encountered another issue:

EntityMetadataWrapperException: Unable to get the data property field_myfield as the parent data structure is not set. in EntityStructureWrapper->getPropertyValue() (line 438 of /path_to_site/sites/all/modules/entity/includes/entity.wrapper.inc).

This doesn't seem to be related to issue https://www.drupal.org/node/2186689 (which results in the same error), because I'm not deleting any field collection item in my rule.

I still have to try this on the clean site to be sure it's not environment-specific.

aaronelborg’s picture

Not sure if this is my exact issue or not so I apologize ahead of time if I'm barking up the wrong tree...

Basically, in hook_node_presave I'd like to compare node values that are about to be saved and depending on that, insert new FC values.

function mymodule_node_presave($node){
  $time = time();
    $now = array(
      'value' => $time,
      'timezone' => 'America/Los_Angeles',
      'timezone_db' => 'America/Los_Angeles',
      'date_type' => 'datestamp',
    );
 $orig_count = isset($node->original->field_thing_count['und'][0]['value']) ? $node->original->field_thing_count['und'][0]['value'] : '';
  $new_count = isset($node->field_thing_count['und'][0]['value']) ? $node->field_thing_count['und'][0]['value'] : '';
  if(!empty($orig_count) && !empty($new_count)){
    if($new_count > $orig_count){
      $change = $new_count - $orig_count;
      $already_existing_change = !empty($node->field_thing_change['und'][0]['value']) ? $node->field_thing_change['und'][0]['value'] : 0;
      $node->field_thing_change['und'][0]['value'] = $already_existing_change + $change;
    }
// Apparently, this will not work.  I'm including it so you can tell what I'd like to do.
// Perhaps hook_entity_presave??
      $field_collection_value = entity_create('field_collection_item', array('field_name' => 'field_import_info'));
      $field_collection_value->setHostEntity('node', $node);
      $field_collection_value->field_import_date['und'][] = $now
      $field_collection_value->field_change['und'][] = array('value' => $existing_change + $change);
      $field_collection_value->save();
}

Thanks for any advice.

ilyasmdgh’s picture

@AaronELBorg Even I wish to save field collection in hook_node_presave. Were you able to do that?? Thanks