Detección genérica de campos modificados en un formulario personalizado antes de guardar un nodo

12

Estoy agregando ciertos campos de un tipo de contenido a un formulario personalizado usando field_attach_form (). Cuando se envía el formulario, estoy procesando esos campos llamando a field_attach_form_validate () y field_attach_submit () desde #validate y #submit callbacks.

En ese momento, quiero comparar el objeto de nodo preparado posterior al envío con el nodo original y solo molestarme en node_save () si alguno de los campos ha cambiado. Por lo tanto, empiezo cargando el nodo original usando entity_load_unchanged().

Desafortunadamente, las matrices de campo en el objeto de nodo original no coinciden con las matrices de campo en el objeto de nodo preparado que está esperando ser guardado, incluso si no se han realizado cambios en los campos, por lo que un simple "$ old_field == $ new_field "La comparación es imposible. Por ejemplo, un campo de texto simple aparece así en el original:

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

Mientras que en el nodo preparado aparece así.

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

Puede pensar en comparar la clave de "valor", pero luego se encuentra con campos formados por otros elementos que no tienen claves de "valor". Por ejemplo, veamos un campo de dirección donde no hay una clave de 'valor' y hay claves en los nodos antiguos y preparados que no tienen contrapartes.

Nodo antiguo

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

Nodo preparado

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

Para los campos vacíos, hay otra discrepancia.

Nodo antiguo

$old_node->field_text = array();

Nodo preparado

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

¿Puedo comparar genéricamente el valor antiguo y el nuevo de cualquier campo para detectar si ha cambiado o no?
¿Es esto solo una imposibilidad?

mórbido
fuente
Creo que puedes jugar _field_invoke()o algo relacionado para preparar la estructura de campo completa del nodo "preparado", renderizar ambos campos y simplemente comparar estas cadenas HTML. Solo una idea.
kalabro
@kalabro Sí, ese es definitivamente el camino a seguir, no puedo evitar sentir que sería bastante malo para el rendimiento, sin embargo, para que sea genérico, deberías cargar cada bit de información de campo individualmente mediante el envío del formulario. O supongo que podría escribir una consulta agregada para obtener los datos, pero luego los ganchos importantes pueden no activarse. Conceptualmente parece posible, pero creo que una implementación sería bastante complicada
Clive
@kalabro No entiendo esta idea. ¿Podría publicar algún pseudocódigo para demostrar cómo preparar la estructura del campo y luego representarlo como lo describió?
morbiD

Respuestas:

9

Esto, por fin, debería funcionar como una solución genérica. Gracias a Clive y morbiD por todos los aportes.

Pase ambas versiones del nodo a la siguiente función. Va a:

  1. Extraiga todos los campos editables del tipo de contenido detectado y sus columnas editables (es decir, elementos que podrían aparecer en el formulario personalizado) de la base de datos en una sola consulta.

  2. Ignora los campos y columnas que están completamente vacíos en ambas versiones.

  3. Trate un campo que tenga un número diferente de valores entre las dos versiones como un cambio.

  4. Itere a través de cada campo, valor y columna y compare las dos versiones.

  5. Compare elementos no idénticamente (! =) Si son numéricos e idénticamente (! ==) si son algo más.

  6. Inmediatamente devuelve VERDADERO en el primer cambio que detecta (ya que un cambio es suficiente para saber que necesitamos volver a guardar el nodo).

  7. Devuelve FALSE si no se detecta ningún cambio después de comparar todos los valores.

  8. Compare recursivamente las colecciones de campo al cargarlas y su esquema y pasar los resultados a sí mismo. Esto DEBE incluso permitirle comparar colecciones de campos anidados. El código NO debería tener ninguna dependencia en el módulo Field Collection.

Avíseme si hay más errores o errores tipográficos en este código.

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

A veces le interesa saber qué campos han cambiado. Para saber eso, puede usar esta versión de la función:

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

A veces es posible que desee hacer que cambiar ciertos campos de un nodo no haga que se actualice la marca de tiempo "modificada" de ese nodo. Esto podría implementarse de la siguiente manera:

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

EDITAR ( 30/07/2013 ) Se mejoró el soporte de recolección de campo. Se agregó soporte para campos con múltiples valores.

EDITAR (31/07/2015) Se agregó una versión de la función que devuelve qué campos han cambiado, y un caso de uso de ejemplo.

Eric N
fuente
Esto es genial, creo que debería estar en algún tipo de módulo de API para que los desarrolladores lo usen.
Jelle
3

Aquí hay otro enfoque más simple que evita las complejas comparaciones de valores del lado del servidor y funcionaría con cualquier forma:

  1. Use jQuery para detectar si los valores del formulario han cambiado
  2. Establezca un valor de elemento oculto para indicar que el formulario ha cambiado.
  3. Verifique el valor del elemento oculto del lado del servidor y procese según sea necesario.

Puede usar un complemento de formulario sucio jQuery como https://github.com/codedance/jquery.AreYouSure

Aunque otros que le permiten escuchar la forma cambiada / estado sucio también funcionarían.

Agregue un oyente para establecer el valor de un elemento de formulario oculto:

Establezca el elemento de formulario oculto en un valor predeterminado de 'cambiado' para guardarlo de forma predeterminada para aquellos usuarios con JavaScript deshabilitado (~ 2%).

p.ej:

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

Luego puede verificar el valor del elemento oculto

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

en su formulario validar / enviar controladores.

David Thomas
fuente
2
Buena solución, aunque obviamente hay algunos usuarios sin js. Además, consulte Drupal.behaviors.formUpdated en el archivo misc / form.js de drupal core. Otra cosa a tener en cuenta es que, con la forma en que funcionan algunos editores wysiwyg y sus módulos drupal, detectar un valor modificado no siempre es tan sencillo como debería ser.
rooby
Sí, establecer un valor predeterminado de 'cambiado' para el elemento oculto ahorraría de manera predeterminada para aquellos pocos usuarios sin js habilitado: porcentaje pequeño. Una nota interesante sobre Drupal.behaviors.formUpdatedtal vez val()podría vincularse con eso, aunque parece que se disparará sin que el valor realmente cambie (por ejemplo, incluye un evento de clic), mientras que los complementos dedicados son mejores para detectar los valores reales de los formularios cambiados.
David Thomas
0

No estoy seguro de que sea perfecto, pero ¿por qué no tomarlo al revés, comparando las formas en lugar de los objetos de nodo ?

No estoy seguro de que lo esté si está estrictamente en forma de nodo, pero de todos modos puede representar el formulario con su nodo anterior y su nuevo nodo:

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

Compara tus formularios ...

Espero que sea una buena pista ... hágamelo saber.

Gregory Kapustin
fuente
Ya había examinado drupal_get_form () pero no me di cuenta de que podía pasarle $ node como segundo parámetro. Sin embargo, acabo de probar su código de ejemplo anterior y desafortunadamente, aunque las estructuras de matriz devueltas son las mismas, los valores no lo son. Eche un vistazo a este array_diff_assoc () recursivo para el campo de dirección que estoy probando con: i.imgur.com/LUDPu1R.jpg
morbiD
Veo ese array_diff_assoc, pero ¿tendrías tiempo para dar el dpm de ambos drupal_get_form? Puede haber una forma de evitar esto.
Gregory Kapustin
0

Aquí hay un método que usa hook_node_presave ($ node). Es solo una maqueta, si crees que ayuda, pruébalo y mejóralo según tus necesidades.

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

Supongo que, para cada valor de campo, las instancias que se definen en $ node deben definirse y ser iguales en $ node_before. No me interesan los campos de valor de campo que están en $ node_before y no están en $ node, supongo que permanecen igual.

dxvargas
fuente
Tal vez me falta algo, pero ¿no hook_node_presave () implica que se ha llamado a node_save ()? Intentamos evitar llamar a node_save () si no se han cambiado los campos.
morbiD
Es cierto que este gancho se llama dentro de node_save (). Pero aún puede cancelar el guardado llamando a drupal_goto () dentro de mymodule_node_presave ().
dxvargas
2
@hiphip Eso realmente no es una buena idea, dejará el nodo guardado en un estado inconsistente si redirige en el medio
Clive
0

Este es solo un código que improvisé. Todo el crédito debe ir a @eclecto para hacer todo el trabajo de piernas. Esta es solo una variación (igualmente no probada) que toma los objetos del nodo directamente, reduce un poco los golpes de la base de datos y se encarga de la negociación del lenguaje.

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
fuente
1
Me tienes pensando en la misma línea con mi nueva versión. Incluso incluí el tipo de nodo de verificación de cordura.
Eric N
0

La respuesta proporcionada es excelente y me ayudó, pero hay algo que tuve que corregir.

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

En el foreach()bucle, tuve que cambiar de $new_fielda $old_field. No sé si esta es una nueva versión de Drupal o solo mi código (podría deberse a otro código en otro lugar), pero no tengo acceso $new_field['entity'].

Doud
fuente
Acabo de probar la función _fields_changed () en una nueva instalación de Drupal 7.41 y guardar un nodo con una colección de campo me da este $ old_field y $ new_field . Me parece que podría estar llamando a _fields_changed () con los parámetros $ old_entity y $ new_entity al revés (o ha cambiado accidentalmente los nombres de las variables en su código en alguna parte).
morbiD
0

Gracias por la publicación, realmente me ahorró mucho tiempo. Arreglé un montón de advertencias y avisos que la función estaba generando:

/*
 * 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
fuente
Explique cómo este código responde a la pregunta original (solo publicar algunos códigos no cumple con las reglas aquí).
Pierre.Vriens