Generisches Erkennen geänderter Felder in einem benutzerdefinierten Formular vor dem Speichern eines Knotens

12

Ich füge mit field_attach_form () bestimmte Felder aus einem Inhaltstyp in ein benutzerdefiniertes Formular ein. Wenn das Formular gesendet wird, verarbeite ich diese Felder, indem ich field_attach_form_validate () und field_attach_submit () von #validate- und #submit-Rückrufen aufrufe.

Zu diesem Zeitpunkt möchte ich das vorbereitete Node-Objekt nach dem Senden mit dem ursprünglichen Node vergleichen und mich nur mit node_save () befassen, wenn sich eines der Felder geändert hat. Daher beginne ich mit dem Laden des ursprünglichen Knotens mit entity_load_unchanged().

Leider stimmen die Feldarrays im ursprünglichen Knotenobjekt nicht mit den Feldarrays im vorbereiteten Knotenobjekt überein, das darauf wartet, gespeichert zu werden, auch wenn keine Änderungen an den Feldern vorgenommen wurden, also ein einfaches "$ old_field == $ new_field" "vergleich ist unmöglich. Zum Beispiel erscheint ein einfaches Textfeld im Original so:

$old_node->field_text['und'][0] = array(
  'value' => 'Test',
  'format' => NULL,
  'safe_value' => 'Test',
);

Während es im vorbereiteten Knoten so aussieht.

$node->field_text['und'][0] = array(
  'value' => 'Test',
);

Sie könnten denken, nur den 'Wert'-Schlüssel zu vergleichen, aber dann stoßen Sie auf Felder, die aus anderen Elementen bestehen, die keine' Wert'-Schlüssel haben. Schauen wir uns zum Beispiel ein Adressfeld an, in dem es keinen "Wert" -Schlüssel gibt und Schlüssel sowohl im alten als auch im vorbereiteten Knoten vorhanden sind, die keine Gegenstücke haben.

Alter Knoten

$old_node->field_address['und'][0] = array(
  'country' => 'GB',
  'administrative_area' => 'Test',
  'sub_administrative_area' => NULL,
  'locality' => 'Test',
  'dependent_locality' => NULL,
  'postal_code' => 'Test',
  'thoroughfare' => 'Test',
  'premise' => 'Test',
  'sub_premise' => NULL,
  'organisation_name' => 'Test',
  'name_line' => 'Test',
  'first_name' => NULL,
  'last_name' => NULL,
  'data' => NULL,
);

Vorbereiteter Knoten

$node->field_address['und'][0] = array(
  'element_key' => 'node|page|field_address|und|0',
  'thoroughfare' => 'Test',
  'premise' => 'Test',
  'locality' => 'Test',
  'administrative_area' => 'Test',
  'postal_code' => 'Test',
  'country' => 'GB',
  'organisation_name' => 'Test',
  'name_line' => 'Test',
);

Bei leeren Feldern gibt es noch eine Diskrepanz.

Alter Knoten

$old_node->field_text = array();

Vorbereiteter Knoten

$node->field_text = array(
  'und' => array(),
);

Kann ich den alten und den neuen Wert eines Feldes generisch vergleichen, um festzustellen, ob es sich geändert hat oder nicht?
Ist das nur eine Unmöglichkeit?

morbid
quelle
Ich denke, Sie können damit spielen _field_invoke()oder etwas im Zusammenhang mit der Vorbereitung der vollständigen Feldstruktur vom "vorbereiteten" Knoten, dem Rendern beider Felder und dem einfachen Vergleichen dieser HTML-Zeichenfolgen. Nur eine Idee.
Kalabro
@kalabro Ja, das ist definitiv der richtige Weg. Ich kann mir nicht helfen, dass das für die Leistung ziemlich schlecht ist. Um es generisch zu machen, müssten Sie jedes Feld mit Hilfe des Formulars einzeln laden. Oder Sie könnten eine aggregierte Abfrage schreiben, um die Daten abzurufen, aber wichtige Hooks werden möglicherweise nicht ausgelöst. Konzeptionell scheint es möglich, aber ich denke, eine Implementierung wäre ziemlich kompliziert
Clive
@kalabro Ich verstehe diese Idee nicht ganz. Könnten Sie einen Pseudocode posten, um zu demonstrieren, wie die Feldstruktur vorbereitet und dann wie beschrieben gerendert wird?
Morbid

Antworten:

9

Dies sollte endlich als allgemeine Lösung funktionieren. Vielen Dank an Clive und morbiD für alle Eingaben.

Übergeben Sie beide Versionen des Knotens an die folgende Funktion. Es wird:

  1. Ziehen Sie alle bearbeitbaren Felder des erkannten Inhaltstyps und ihre bearbeitbaren Spalten (dh Elemente, die möglicherweise im benutzerdefinierten Formular angezeigt werden) in einer einzigen Abfrage aus der Datenbank.

  2. Ignorieren Sie Felder und Spalten, die in beiden Versionen vollständig leer sind.

  3. Behandeln Sie ein Feld, das zwischen den beiden Versionen eine unterschiedliche Anzahl von Werten aufweist, als Änderung.

  4. Durchlaufen Sie jedes Feld, jeden Wert und jede Spalte und vergleichen Sie die beiden Versionen.

  5. Vergleichen Sie Elemente nicht identisch (! =), Wenn sie numerisch sind, und identisch (! ==), wenn es sich um etwas anderes handelt.

  6. Geben Sie bei der ersten erkannten Änderung sofort TRUE zurück (da eine Änderung ausreicht, um zu wissen, dass der Knoten erneut gespeichert werden muss).

  7. Gibt FALSE zurück, wenn nach dem Vergleich aller Werte keine Änderung festgestellt wird.

  8. Vergleichen Sie Feldsammlungen rekursiv, indem Sie sie und ihr Schema laden und die Ergebnisse an sich selbst übergeben. Dies SOLLTE sogar erlauben, verschachtelte Feldsammlungen zu vergleichen. Der Code sollte KEINE Abhängigkeit vom Field Collection-Modul haben.

Lassen Sie mich wissen, wenn dieser Code weitere Fehler oder Tippfehler enthält.

/*
 * Pass both versions of the node to this function. Returns TRUE if it detects any changes and FALSE if not.
 * Pass field collections as an array keyed by field collection ID.
 *
 * @param object $old_entity
 *   The original (stored in the database) node object.
 *   This function may also pass itself a FieldCollectionItemEntity object to compare field collections.
 * @param object $new_entity
 *   The prepared node object for comparison.
 *   This function may also pass itself a FieldCollectionItemEntity object to compare field collections.
 */
function _fields_changed($old_entity, $new_entity) {
  // Check for node or field collection.
  $entity_is_field_collection = (get_class($old_entity) == 'FieldCollectionItemEntity');

  $bundle = ($entity_is_field_collection ? $old_entity->field_name : $old_entity->type);

  // Sanity check. Exit and throw an error if the content types don't match.
  if($bundle !== ($entity_is_field_collection ? $new_entity->field_name : $new_entity->type)) {
    drupal_set_message('Content type mismatch. Unable to save changes.', 'error');
    return FALSE;
  }

  // Get field info.
  $field_read_params = array(
    'entity_type' => ($entity_is_field_collection ? 'field_collection_item' : 'node'),
    'bundle' => $bundle
  );
  $fields_info = field_read_fields($field_read_params);

  foreach($fields_info as $field_name => $field_info) {
    $old_field = $old_entity->$field_name;
    $new_field = $new_entity->$field_name;

    // Check the number of values for each field, or if they are populated at all.
    $old_field_count = (isset($old_field[LANGUAGE_NONE]) ? count($old_field[LANGUAGE_NONE]) : 0);
    $new_field_count = (isset($new_field[LANGUAGE_NONE]) ? count($new_field[LANGUAGE_NONE]) : 0);

    if ($old_field_count != $new_field_count) {
      // The two versions have a different number of values. Something has changed.
      return TRUE;
    } elseif ($old_field_count > 0 && $new_field_count > 0) {
      // Both versions have an equal number of values. Time to compare.

      // See if this field is a field collection.
      if ($field_info['type'] == 'field_collection') {

        foreach ($new_field[LANGUAGE_NONE] as $delta => $values) {
          $old_field_collection = entity_load_unchanged('field_collection_item', $values['entity']->item_id);
          $new_field_collection = $values['entity'];

          if (_fields_changed($old_field_collection, $new_field_collection)) {
            return TRUE;
          }
        }
        unset($delta, $values);

      } else {
        foreach($old_field[LANGUAGE_NONE] as $delta => $value) {
          foreach($field_info['columns'] as $field_column_name => $field_column_info) {
            $old_value = $old_field[LANGUAGE_NONE][$delta][$field_column_name];
            $new_value = $new_field[LANGUAGE_NONE][$delta][$field_column_name];
            $field_column_type = $field_column_info['type'];

            // As with the overall field, exit if one version has a value and the other doesn't.
            if (isset($old_value) != isset($new_value)) {
              return TRUE;
            } elseif (isset($old_value) && isset($new_value)) {
              // The column stores numeric data so compare values non-identically.
              if (in_array($field_column_type, array('int', 'float', 'numeric'))) {
                if ($new_value != $old_value) {
                  return TRUE;
                }
              }
              // The column stores non-numeric data so compare values identically,
              elseif ($new_value !== $old_value) {
                return TRUE;
              }
            } else {
              // Included for clarity. Both values are empty so there was obviously no change.
            }
          } 
          unset($field_column_name, $field_column_info);
        }
        unset($delta, $value);
      }
    } else {
      // Included for clarity. Both values are empty so there was obviously no change.
    }
  }
  unset($field_name, $field_info);
  // End of field comparison loop.

  // We didn't find any changes. Don't resave the node.
  return FALSE;
}

Manchmal interessiert es Sie, welche Felder sich geändert haben. Um das zu wissen, können Sie diese Version der Funktion verwenden:

/*
 * Pass both versions of the node to this function. Returns an array of
 * fields that were changed or an empty array if none were changed.
 * Pass field collections as an array keyed by field collection ID.
 *
 * @param object $old_entity
 *   The original (stored in the database) node object.
 *   This function may also pass itself a FieldCollectionItemEntity object to compare field collections.
 * @param object $new_entity
 *   The prepared node object for comparison.
 *   This function may also pass itself a FieldCollectionItemEntity object to compare field collections.
 */
function _fields_changed($old_entity, $new_entity) {
  // Check for node or field collection.
  $entity_is_field_collection = (get_class($old_entity) == 'FieldCollectionItemEntity');

  $bundle = ($entity_is_field_collection ? $old_entity->field_name : $old_entity->type);

  // Sanity check. Exit and throw an error if the content types don't match.
  if ($bundle !== ($entity_is_field_collection ? $new_entity->field_name : $new_entity->type)) {
    drupal_set_message('Content type mismatch. Unable to save changes.', 'error');
    return FALSE;
  }

  // Get field info.
  $field_read_params = array(
    'entity_type' => ($entity_is_field_collection ? 'field_collection_item' : 'node'),
    'bundle' => $bundle
  );
  $fields_info = field_read_fields($field_read_params);

  $fields_changed = array();

  foreach ($fields_info as $field_name => $field_info) {
    $old_field = $old_entity->$field_name;
    $new_field = $new_entity->$field_name;

    // Check the number of values for each field, or if they are populated at all.
    $old_field_count = (isset($old_field[LANGUAGE_NONE]) ? count($old_field[LANGUAGE_NONE]) : 0);
    $new_field_count = (isset($new_field[LANGUAGE_NONE]) ? count($new_field[LANGUAGE_NONE]) : 0);

    if ($old_field_count != $new_field_count) {
      // The two versions have a different number of values. Something has changed.
      $fields_changed[] = $field_name;
    }
    elseif ($old_field_count > 0 && $new_field_count > 0) {
      // Both versions have an equal number of values. Time to compare.

      // See if this field is a field collection.
      if ($field_info['type'] == 'field_collection') {

        foreach ($new_field[LANGUAGE_NONE] as $delta => $values) {
          $old_field_collection = entity_load_unchanged('field_collection_item', $values['entity']->item_id);
          $new_field_collection = $values['entity'];

          $fields_changed = array_merge($fields_changed, _fields_changed($old_field_collection, $new_field_collection));
        }
        unset($delta, $values);

      }
      else {
        foreach ($old_field[LANGUAGE_NONE] as $delta => $value) {
          foreach ($field_info['columns'] as $field_column_name => $field_column_info) {
            $old_value = $old_field[LANGUAGE_NONE][$delta][$field_column_name];
            $new_value = $new_field[LANGUAGE_NONE][$delta][$field_column_name];
            $field_column_type = $field_column_info['type'];

            // As with the overall field, exit if one version has a value and the other doesn't.
            if (isset($old_value) != isset($new_value)) {
              $fields_changed[] = $old_field;
            }
            elseif (isset($old_value) && isset($new_value)) {
              // The column stores numeric data so compare values non-identically.
              if (in_array($field_column_type, array(
                'int',
                'float',
                'numeric'
              ))) {
                if ($new_value != $old_value) {
                  $fields_changed[] = $field_name;
                }
              }
              // The column stores non-numeric data so compare values identically,
              elseif ($new_value !== $old_value) {
                $fields_changed[] = $field_name;
              }
            }
            else {
              // Included for clarity. Both values are empty so there was obviously no change.
            }
          }
          unset($field_column_name, $field_column_info);
        }
        unset($delta, $value);
      }
    }
    else {
      // Included for clarity. Both values are empty so there was obviously no change.
    }
  }
  unset($field_name, $field_info);
  // End of field comparison loop.

  return $fields_changed;
}

Manchmal möchten Sie möglicherweise festlegen, dass das Ändern bestimmter Felder eines Knotens nicht dazu führt, dass der "geänderte" Zeitstempel dieses Knotens aktualisiert wird. Dies könnte folgendermaßen implementiert werden:

/**
 * Implements hook_node_presave().
 */
function mymodule_node_presave($node) {
  $fields_changed = _fields_changed($node->original, $node);
  $no_update_timestamp_fields = array('field_subject', 'field_keywords');
  if (!empty($fields_changed) &&
    empty(array_diff($fields_changed, $no_update_timestamp_fields))) {
    // Don't change the $node->changed timestamp if one of the fields has
    // been changed that should not affect the timestamp.
    $node->changed = $node->original->changed;
  }
}

BEARBEITEN (30.07.2013) Die Unterstützung für Feldsammlungen wurde verbessert . Unterstützung für Felder mit mehreren Werten hinzugefügt.

EDIT (31.07.2015) Neue Version der Funktion, die zurückgibt, welche Felder geändert wurden, und Beispielanwendungsfall.

Eric N
quelle
Das ist großartig, ich denke, das sollte in einer Art API-Modul sein, das Entwickler verwenden können.
Jelle
3

Hier ist ein anderer, einfacherer Ansatz, der die komplexen Vergleiche der serverseitigen Werte vermeidet und mit jeder Form funktioniert:

  1. Verwenden Sie jQuery, um festzustellen, ob sich die Formularwerte geändert haben
  2. Legen Sie einen ausgeblendeten Elementwert fest, um anzuzeigen, dass sich das Formular geändert hat.
  3. Überprüfen Sie die Serverseite für versteckte Elementwerte und führen Sie die erforderlichen Schritte aus.

Sie können ein Plugin für fehlerhafte jQuery-Formulare verwenden, z. B. https://github.com/codedance/jquery.AreYouSure

Andere, mit denen Sie sich den Status "Geändert / Dirty" anhören können, funktionieren jedoch auch.

Fügen Sie einen Listener hinzu, um den Wert eines ausgeblendeten Formularelements festzulegen:

Setzen Sie das ausgeblendete Formularelement auf den Standardwert "geändert", um es standardmäßig für Benutzer mit deaktiviertem Javascript zu speichern (~ 2%).

z.B:

// Clear initial state for js-enabled user
$('input#hidden-indicator').val('')
// Add changed listener
$('#my-form').areYouSure({
    change: function() {
      // Set hidden element value
      if ($(this).hasClass('dirty')) {
        $('input#hidden-indicator').val('changed');
      } else {
        $('input#hidden-indicator').val('');
      }
    }
 });

Sie können dann den Wert des ausgeblendeten Elements überprüfen

if ($form_state['values']['hidden_indicator'] == 'changed') { /* node_save($node) */ }

Validieren / Übermitteln Sie die Handler in Ihrem Formular.

David Thomas
quelle
2
Nette Lösung, obwohl es offensichtlich einige Benutzer ohne js gibt. Schauen Sie sich auch Drupal.behaviors.formUpdated in der Datei misc / form.js von Drupal Core an. Zu beachten ist auch, dass bei der Funktionsweise einiger Wysiwyg-Editoren und ihrer Drupal-Module das Erkennen eines geänderten Werts nicht immer so einfach ist, wie es sein sollte.
Rooby
Ja, wenn Sie für das ausgeblendete Element den Standardwert "geändert" festlegen, wird dies standardmäßig für die wenigen Benutzer gespeichert, für die js nicht aktiviert ist - kleiner Prozentsatz. Interessanter Hinweis zu dem, was Drupal.behaviors.formUpdatedmöglicherweise val()damit verknüpft sein könnte, obwohl es so aussieht, als würde es ausgelöst, ohne dass sich der Wert tatsächlich ändert (z. B. Klickereignis), während die dedizierten Plug-ins tatsächlich geänderte Formularwerte besser erkennen können.
David Thomas
0

Ich bin mir nicht sicher, ob das perfekt ist, aber warum nicht umgekehrt, indem ich die Formulare anstelle der Knotenobjekte vergleiche ?

Ich bin mir nicht sicher, ob Sie sich strikt in einem Knotenformular befinden, aber Sie können das Formular trotzdem mit Ihrem alten Knoten und Ihrem neuen Knoten rendern:

module_load_include('inc', 'node', 'node.pages');
node_object_prepare($new_node);
$new_form = drupal_get_form($new_node->node_type . '_node_form', $new_node);
node_object_prepare($old_node);
$old_form = drupal_get_form($old_node->node_type . '_node_form', $old_node);

Vergleichen Sie Ihre Formulare ...

Ich hoffe, es ist eine gute Strecke ... lass es mich wissen.

Gregory Kapustin
quelle
Ich hatte bereits in drupal_get_form () nachgesehen, aber ich wusste nicht, dass Sie $ node als zweiten Parameter übergeben können. Ich habe jedoch gerade Ihren obigen Beispielcode getestet und leider sind die zurückgegebenen Array-Strukturen dieselben, die Werte jedoch nicht. Schauen Sie sich dieses rekursive array_diff_assoc () für das Adressfeld an, mit dem ich teste
morbiD 24.07.13
Ich sehe das array_diff_assoc, aber hätten Sie Zeit, die dpm von beiden drupal_get_form zu geben? Es könnte einen Weg geben, dies zu umgehen.
Gregory Kapustin
0

Hier ist eine Methode mit hook_node_presave ($ node). Es ist nur ein Modell, wenn Sie denken, dass es hilft, testen Sie es und verbessern Sie es auf Ihre Bedürfnisse!

  /**
   * Implements hook_node_presave().
   *
   * Look for changes in node fields, before they are saved
   */
  function mymodule_node_presave($node) {

    $changes = array();

    $node_before = node_load($node->nid);

    $fields = field_info_instances('node', $node->type);
    foreach (array_keys($fields) as $field_name) {

      $val_before = field_get_items('node', $node_before, $field_name);
      $val = field_get_items('node', $node, $field_name);

      if ($val_before != $val) {

        //if new field values has more instances then old one, it has changed
        if (count($val) != count($val_before)) {
          $changes[] = $field_name;
        } else {
          //cycle throught 1 or multiple field value instances
          foreach ($val as $k_i => $val_i) {
            if (is_array($val_i)) {
              foreach ($val_i as $k => $v) {
                if (isset($val_before[$k_i][$k]) && $val_before[$k_i][$k] != $val[$k_i][$k]) {
                  $changes[] = $field_name;
                }
              }
            }
          }
        }
      }
    }
    dpm($changes);
  }

Ich nehme an, dass für jeden Feldwert die in $ node definierten Instanzen definiert und in $ node_before gleich sein müssen. Ich kümmere mich nicht um die Felder des Feldwerts, die sich in $ node_before und nicht in $ node befinden. Ich nehme an, sie bleiben gleich.

dxvargas
quelle
Vielleicht fehlt mir etwas, aber bedeutet hook_node_presave () nicht, dass node_save () aufgerufen wurde? Wir versuchen zu vermeiden, node_save () aufzurufen, wenn keine Felder geändert wurden.
Morbid
Richtig, dieser Hook wird in node_save () aufgerufen. Sie können das Speichern jedoch abbrechen, indem Sie drupal_goto () in mymodule_node_presave () aufrufen.
Dxvargas
2
@hiphip Das ist wirklich keine gute Idee, Sie werden den Knoten in einem inkonsistenten Zustand speichern, wenn Sie in der Mitte umleiten
Clive
0

Dies ist nur ein Code, den ich zusammengeschustert habe. Der gesamte Kredit muss an @eclecto gehen, um die gesamte Beinarbeit zu erledigen. Dies ist nur eine (ebenfalls nicht getestete) Variante, die die Knotenobjekte direkt aufnimmt, die DB-Treffer ein wenig reduziert und die Sprachaushandlung übernimmt.

function _node_fields_have_changed($old_node, $new_node) {
  // @TODO Sanity checks (e.g. node types match).

  // Get the fields attached to the node type.
  $params = array('entity_type' => 'node', 'bundle' => $old_node->type);
  foreach (field_read_fields($params) as $field) {
    // Get the field data for both nodes.
    $old_field_data = field_get_items('node', $old_node, $field['field_name']);
    $new_field_data = field_get_items('node', $new_node, $field['field_name']);

    // If the field existed on the old node, but not the new, it's changed.
    if ($old_field_data && !$new_field_data) {
      return TRUE;
    }
    // Ditto but in reverse.
    elseif ($new_field_data && !$old_field_data) {
      return TRUE;
    }

    foreach ($field['columns'] as $column_name => $column) {
      // If there's data in both columns we need an equality check.
      if (isset($old_field_data[$column_name]) && isset($new_field_data[$column_name])) {
        // Equality checking based on column type.
        if (in_array($column['type'], array('int', 'float', 'numeric')) && $old_field_data[$column_name] != $new_field_data[$column_name]) {
          return TRUE;
        }
        elseif ($old_field_data[$column_name] !== $new_field_data[$column_name]) {
          return TRUE;
        }
      }
      // Otherwise, if there's data for one column but not the other,
      // something changed.
      elseif (isset($old_field_data[$column_name]) || isset($new_field_data[$column_name])) {
        return TRUE;
      }
    } 
  }

  return FALSE;
}
Clive
quelle
1
Sie haben mich veranlasst, mit meiner neuen Version in die gleiche Richtung zu denken. Ich habe sogar die Überprüfung des Knotentyps integriert.
Eric N
0

Die Antwort ist großartig und hat mir geholfen, aber es gibt etwas, das ich korrigieren musste.

// See if this field is a field collection.
if ($field_info['type'] == 'field_collection') {
  foreach ($old_field[LANGUAGE_NONE] as $delta => $values) {
    $old_field_collection = entity_load_unchanged('field_collection_item', $values['entity']->item_id);
    $new_field_collection = $values['entity'];

    $fields_changed = array_merge($fields_changed, erplain_api_fields_changed($old_field_collection, $new_field_collection));
  }
  unset($delta, $values);
}

In der foreach()Schleife, musste ich von ändern $new_fieldzu $old_field. Ich weiß nicht, ob es sich um eine neue Version von Drupal oder nur um meinen Code handelt (möglicherweise aufgrund eines anderen Codes an einer anderen Stelle), aber ich habe keinen Zugriff auf $new_field['entity'].

Doud
quelle
Ich habe gerade die Funktion _fields_changed () auf einer neuen Drupal 7.41-Installation getestet und beim Speichern eines Knotens mit einer field_collection erhalte ich dieses $ old_field und $ new_field . Für mich sieht es so aus, als würden Sie _fields_changed () mit den Parametern $ old_entity und $ new_entity falsch herum aufrufen (oder Sie haben versehentlich die Variablennamen in Ihrem Code irgendwo vertauscht).
Morbid
0

Danke für den Beitrag, hat mir echt viel Zeit gespart. Ich habe eine Reihe von Warnungen und Hinweisen behoben, die von der Funktion ausgegeben wurden:

/*
 * Pass both versions of the node to this function. Returns an array of
 * fields that were changed or an empty array if none were changed.
 * Pass field collections as an array keyed by field collection ID.
 *
 * @param object $old_entity
 *   The original (stored in the database) node object.
 *   This function may also pass itself a FieldCollectionItemEntity object to compare field collections.
 * @param object $new_entity
 *   The prepared node object for comparison.
 *   This function may also pass itself a FieldCollectionItemEntity object to compare field collections.
 */
function _fields_changed($old_entity, $new_entity) {
  $fields_changed = array();

  // Check for node or field collection.
  if (is_object($old_entity)) {
    $entity_is_field_collection = (get_class($old_entity) == 'FieldCollectionItemEntity');
    $bundle = !empty($entity_is_field_collection) ? $old_entity->field_name : $old_entity->type;
  }

  // Sanity check. Exit and throw an error if the content types don't match.
  if (is_object($new_entity)) {
    if ($bundle !== (!empty($entity_is_field_collection) ? $new_entity->field_name : $new_entity->type)) {
      drupal_set_message('Content type mismatch. Unable to save changes.', 'error');
      return FALSE;
    }
  }

  // Get field info.
  $field_read_params = array(
    'entity_type' => !empty($entity_is_field_collection) ? 'field_collection_item' : 'node',
  );

  if (!empty($bundle)) {
    $field_read_params['bundle'] = $bundle;
  }

  $fields_info = field_read_fields($field_read_params);

  foreach ($fields_info as $field_name => $field_info) {
    $old_field = isset($old_entity->$field_name) ? $old_entity->$field_name : NULL;
    $new_field = isset($new_entity->$field_name) ? $new_entity->$field_name : NULL;

    // Check the number of values for each field, or if they are populated at all.
    $old_field_count = (isset($old_field[LANGUAGE_NONE]) ? count($old_field[LANGUAGE_NONE]) : 0);
    $new_field_count = (isset($new_field[LANGUAGE_NONE]) ? count($new_field[LANGUAGE_NONE]) : 0);

    if ($old_field_count != $new_field_count) {
      // The two versions have a different number of values. Something has changed.
      $fields_changed[] = $field_name;
    }
    elseif ($old_field_count > 0 && $new_field_count > 0) {
      // Both versions have an equal number of values. Time to compare.

      // See if this field is a field collection.
      if ($field_info['type'] == 'field_collection') {

        foreach ($new_field[LANGUAGE_NONE] as $delta => $values) {
          $old_field_collection = NULL;
          if (!empty($values['entity']->item_id)) {
            $old_field_collection = entity_load_unchanged('field_collection_item', $values['entity']->item_id);
          }

          $new_field_collection = NULL;
          if (isset($values['entity'])) {
            $new_field_collection = $values['entity'];
          }

          $fields_changed = array_merge($fields_changed, _fields_changed($old_field_collection, $new_field_collection));
        }
        unset($delta, $values);

      }
      else {
        foreach ($old_field[LANGUAGE_NONE] as $delta => $value) {
          foreach ($field_info['columns'] as $field_column_name => $field_column_info) {
            $old_value = isset($old_field[LANGUAGE_NONE][$delta][$field_column_name]) ? $old_field[LANGUAGE_NONE][$delta][$field_column_name] : NULL;
            $new_value = isset($new_field[LANGUAGE_NONE][$delta][$field_column_name]) ? $new_field[LANGUAGE_NONE][$delta][$field_column_name] : NULL;
            $field_column_type = $field_column_info['type'];

            // As with the overall field, exit if one version has a value and the other doesn't.
            if (isset($old_value) != isset($new_value)) {
              $fields_changed[] = $old_field;
            }
            elseif (isset($old_value) && isset($new_value)) {
              // The column stores numeric data so compare values non-identically.
              if (in_array($field_column_type, array(
                'int',
                'float',
                'numeric'
              ))) {
                if ($new_value != $old_value) {
                  $fields_changed[] = $field_name;
                }
              }
              // The column stores non-numeric data so compare values identically,
              elseif ($new_value !== $old_value) {
                $fields_changed[] = $field_name;
              }
            }
            else {
              // Included for clarity. Both values are empty so there was obviously no change.
            }
          }
          unset($field_column_name, $field_column_info);
        }
        unset($delta, $value);
      }
    }
    else {
      // Included for clarity. Both values are empty so there was obviously no change.
    }
  }
  unset($field_name, $field_info);
  // End of field comparison loop.

  return $fields_changed;
}
pwaterz
quelle
Bitte erläutern Sie, wie dieser Code die ursprüngliche Frage beantwortet.
Pierre.Vriens