Generalnie wykrywa zmienione pola w formie niestandardowej przed zapisaniem węzła

12

Dodam pewne pola z typu zawartości do formularza niestandardowego za pomocą field_attach_form (). Kiedy formularz jest przesyłany, przetwarzam te pola, wywołując field_attach_form_validate () i field_attach_submit () z # callidvalidate i #submit.

W tym momencie chcę porównać obiekt po przesłaniu, przygotowany obiekt węzła z pierwotnym węzłem i zawracać sobie głowę węzłem_save (), jeśli którekolwiek z pól uległo zmianie. Dlatego zaczynam od załadowania oryginalnego węzła za pomocą entity_load_unchanged().

Niestety tablice pól w oryginalnym obiekcie węzła nie pasują do tablic pól w przygotowanym obiekcie węzła, który czeka na zapisanie, nawet jeśli nie wprowadzono żadnych zmian w polach, więc proste „$ old_field == $ new_field „porównanie jest niemożliwe. Na przykład proste pole tekstowe wygląda tak jak w oryginale:

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

Natomiast w przygotowanym węźle wygląda to tak.

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

Możesz po prostu porównać klucz „wartość”, ale potem natrafisz na pola złożone z innych elementów, które nie mają kluczy „wartość”. Na przykład spójrzmy na pole adresu, w którym nie ma klucza „wartość” i są klucze zarówno w starym, jak i przygotowanym węźle, które nie mają odpowiedników.

Stary węzeł

$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,
);

Przygotowany węzeł

$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',
);

W przypadku pustych pól istnieje jeszcze jedna rozbieżność.

Stary węzeł

$old_node->field_text = array();

Przygotowany węzeł

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

Czy mogę ogólnie porównać starą i nową wartość dowolnego pola w celu wykrycia, czy uległo ono zmianie?
Czy to tylko niemożliwość?

chorobowy
źródło
Myślę, że możesz grać z _field_invoke()czymś związanym z przygotowaniem pełnej struktury pola z „przygotowanego” węzła, renderować oba pola i po prostu porównać te ciągi HTML. Po prostu pomysł.
kalabro
@kalabro Tak, zdecydowanie taka jest droga, nie mogę się oprzeć wrażeniu, że byłoby to bardzo złe z punktu widzenia wydajności - aby było ogólne, trzeba ładować każdy fragment informacji o polu indywidualnie za pomocą przesłania formularza. Albo myślę, że możesz napisać zagregowane zapytanie, aby uzyskać dane, ale wtedy ważne zaczepy mogą się nie uruchomić. Koncepcyjnie wydaje się to możliwe, ale myślę, że wdrożenie byłoby dość skomplikowane
Clive
@kalabro Nie do końca rozumiem ten pomysł. Czy możesz napisać pseudokod, aby zademonstrować, jak przygotować strukturę pola, a następnie wyrenderować ją zgodnie z opisem?
morbiD

Odpowiedzi:

9

To w końcu powinno działać jako ogólne rozwiązanie. Dzięki Clive i morbiD za cały wkład.

Przekaż obie wersje węzła do następującej funkcji. To będzie:

  1. Wyciągnij z bazy danych wszystkie edytowalne pola wykrytego typu treści i ich edytowalne kolumny (tj. Elementy, które mogą pojawić się w formularzu niestandardowym).

  2. Zignoruj ​​pola i kolumny, które są całkowicie puste w obu wersjach.

  3. Traktuj pole, które ma inną liczbę wartości między dwiema wersjami, jako zmianę.

  4. Iteruj przez każde pole, wartość i kolumnę i porównaj dwie wersje.

  5. Porównaj elementy nieidentycznie (! =), Jeśli są numeryczne i identycznie (! ==), jeśli są czymś innym.

  6. Natychmiast zwróć wartość PRAWDA przy pierwszej wykrytej zmianie (ponieważ wystarczy jedna zmiana, aby wiedzieć, że musimy ponownie zapisać węzeł).

  7. Zwróć FAŁSZ, jeśli po porównaniu wszystkich wartości nie zostanie wykryta żadna zmiana.

  8. Rekurencyjnie porównuj zbiory pól, ładując je i ich schemat oraz przekazując wyniki do siebie. POWINIEN nawet pozwolić na porównywanie zagnieżdżonych zbiorów pól. Kod NIE powinien mieć żadnej zależności od modułu Field Collection.

Daj mi znać, jeśli w tym kodzie są więcej błędów lub literówek.

/*
 * 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;
}

Czasami chcesz wiedzieć, które pola się zmieniły. Aby to wiedzieć, możesz użyć tej wersji funkcji:

/*
 * 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;
}

Czasami możesz chcieć to zrobić, aby zmiana niektórych pól węzła nie powodowała aktualizacji znacznika czasu „zmienionego” tego węzła. Można to zaimplementować w następujący sposób:

/**
 * 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;
  }
}

EDYCJA (7/30/2013) Zaostrzona obsługa zbierania pól. Dodano obsługę pól z wieloma wartościami.

EDIT (31.07.2015) Dodany wersja funkcji, która powraca których pola zostały zmienione, a sprawa przykładem użycia.

Eric N.
źródło
To wspaniale, wydaje mi się, że powinien być w jakimś module API, z którego mogą korzystać programiści.
Jelle
3

Oto inne, prostsze podejście, które pozwala uniknąć skomplikowanych porównań wartości po stronie serwera i działałoby w dowolnej formie:

  1. Użyj jQuery do wykrycia, czy zmieniły się wartości formularza
  2. Ustaw wartość ukrytego elementu, aby wskazać, że formularz się zmienił.
  3. Sprawdź po stronie serwera wartość elementu ukrytego i postępuj zgodnie z wymaganiami.

Możesz użyć pluginu plugin jQuery, takiego jak https://github.com/codedance/jquery.AreYouSure

Chociaż inne, które pozwalają wysłuchać formularza zmienionego / brudnego, również by działały.

Dodaj detektor, aby ustawić wartość ukrytego elementu formularza:

Ustaw ukryty element formularza na domyślną wartość „zmieniono”, aby zapisać domyślnie dla użytkowników z wyłączonym javascript (~ 2%).

na przykład:

// 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('');
      }
    }
 });

Następnie możesz sprawdzić wartość ukrytego elementu

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

w swoim formularzu zweryfikuj / prześlij programy obsługi.

David Thomas
źródło
2
Ładne rozwiązanie, choć oczywiście niektórzy użytkownicy nie mają js. Sprawdź także plik Drupal.behaviors.formUpdated w pliku misc / form.js programu drupal core. Inną rzeczą wartą odnotowania jest to, że przy sposobie działania niektórych edytorów wysiwyg i ich modułów drupal, wykrywanie zmienionej wartości nie zawsze jest tak proste, jak powinno być.
rooby
Tak, ustawienie domyślnej wartości „zmienionej” dla ukrytego elementu zapisałoby się domyślnie dla tych kilku użytkowników bez włączonej js - mały procent. Interesująca uwaga dotycząca Drupal.behaviors.formUpdatedbyć może val()może być z tym powiązana, chociaż wygląda na to, że uruchomi się bez rzeczywistej zmiany wartości (np. Obejmuje zdarzenie kliknięcia), podczas gdy dedykowane wtyczki lepiej wykrywają rzeczywiste zmienione wartości formularza.
David Thomas
0

Nie jestem pewien, czy to jest idealne, ale dlaczego nie odwrócić tego, porównując formy zamiast obiektów węzłów ?

Nie jestem pewien, czy jesteś ściśle w formie węzła, ale w każdym razie możesz renderować formularz ze starym i nowym węzłem:

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);

Porównaj swoje formularze ...

Mam nadzieję, że to dobry utwór ... daj mi znać.

Gregory Kapustin
źródło
Zajrzałem już do drupal_get_form (), ale nie zdawałem sobie sprawy, że możesz przekazać $ node jako drugi parametr. Jednak właśnie przetestowałem twój przykładowy kod powyżej i niestety, chociaż zwrócone struktury tablic są takie same, wartości nie są. Spójrz na tę rekursywną array_diff_assoc () dla pola adresu, które testuję
morbiD
Widzę to array_diff_assoc, ale czy miałbyś czas dać dpm obu drupal_get_form? Może być na to sposób.
Gregory Kapustin
0

Oto metoda wykorzystująca hook_node_presave ($ node). To tylko makieta, jeśli uważasz, że to pomaga, przetestuj ją i popraw do swoich potrzeb!

  /**
   * 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);
  }

Przypuszczam, że dla każdej wartości pola instancje zdefiniowane w $ node muszą być zdefiniowane i równe w $ node_before. Nie dbam o pola wartości, które są w $ node_before i nie są w $ node, chyba pozostaną takie same.

dxvargas
źródło
Może coś mi brakuje, ale czy hook_node_presave () nie sugeruje, że został wywołany node_save ()? Staramy się unikać wywoływania node_save (), jeśli żadne pola nie zostały zmienione.
morbiD
To prawda, że ​​ten hak jest nazywany wewnątrz node_save (). Ale nadal możesz anulować zapisywanie, wywołując funkcję drupal_goto () wewnątrz mymodule_node_presave ().
dxvargas
2
@hiphip To naprawdę nie jest dobry pomysł, opuścisz węzeł zapisz w niespójnym stanie, jeśli przekierujesz go w środku
Clive
0

To tylko część kodu, który skompletowałem. Wszelkie uznanie należy przekazać @eclecto za wykonanie całej pracy nóg. Jest to tylko (podobnie niesprawdzona) odmiana, która bezpośrednio pobiera obiekty węzła, nieco zmniejsza trafienia DB i zajmuje się negocjacjami językowymi.

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
źródło
1
Sprawiłeś, że myślałem w ten sam sposób w mojej nowej wersji. Włączyłem nawet sprawdzanie poprawności typu węzła.
Eric N
0

Podana odpowiedź jest świetna i pomogła mi, ale muszę coś poprawić.

// 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);
}

W foreach()pętli musiałem zmienić z $new_fieldna $old_field. Nie wiem, czy jest to nowa wersja Drupala, czy tylko mój kod (może wynikać z innego kodu gdzie indziej), ale nie mam do niego dostępu $new_field['entity'].

Doud
źródło
Właśnie przetestowałem funkcję _fields_changed () na świeżej instalacji Drupal 7.41 i zapisanie węzła z kolekcją field_coll daje mi $ old_field i $ new_field . Wydaje mi się, że możesz wywoływać _fields_changed () z parametrami $ old_entity i $ new_entity w niewłaściwy sposób (lub przypadkowo zamieniłeś gdzieś nazwy zmiennych w kodzie).
morDD
0

Dzięki za wpis, naprawdę zaoszczędziłem dużo czasu. Naprawiłem kilka ostrzeżeń i powiadomień, które wyświetlała ta funkcja:

/*
 * 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
źródło
Wyjaśnij, w jaki sposób ten kod odpowiada na pierwotne pytanie (tylko opublikowanie jakiegoś kodu jest niezgodne z obowiązującymi tutaj zasadami).
Pierre.Vriens