diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 679e1c4..2a160da 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -206,7 +206,6 @@ protected function invokeFieldMethod($method, ContentEntityInterface $entity) { } } - /** * Checks whether the field values changed compared to the original entity. * @@ -225,12 +224,10 @@ protected function invokeFieldMethod($method, ContentEntityInterface $entity) { * True if the field value changed from the original entity. */ protected function hasFieldValueChanged(FieldDefinitionInterface $field_definition, ContentEntityInterface $entity, ContentEntityInterface $original) { - $field_name = $field_definition->getName(); $default_langcode = $entity->getUntranslated()->language()->getId(); $translation_langcodes = array_keys($entity->getTranslationLanguages()); $langcodes = $field_definition->isTranslatable() ? $translation_langcodes : array($default_langcode); - $columns = $field_definition->getFieldStorageDefinition()->getColumns(); foreach ($langcodes as $langcode) { // If the original entity doesn't have this translation, we need to save. @@ -239,34 +236,9 @@ protected function hasFieldValueChanged(FieldDefinitionInterface $field_definiti } $items = $entity->getTranslation($langcode)->get($field_name); - $items->filterEmptyItems(); $originalItems = $original->getTranslation($langcode)->get($field_name); - $originalItems->filterEmptyItems(); - - $count1 = count($items); - $count2 = count($originalItems); - if ($count1 === 0 && $count2 === 0) { - // Both are empty we can safely assume that it did not change. - continue; - } - if ($count1 !== $count2) { - // One of them is empty but not the other one so the value changed. - return TRUE; - } - // We have to clean things up a bit before comparing because order of - // properties sadly may vary. - $callback = function (&$value) use ($columns) { - if (is_array($value)) { - $value = array_intersect_key($value, $columns); - ksort($value); - } - }; - $value1 = $items->getValue(); - $value2 = $originalItems->getValue(); - array_walk($value1, $callback); - array_walk($value2, $callback); - - if ($value1 !== $value2) { + // If the field items are not equal, we need to save. + if (!$items->equals($originalItems)) { return TRUE; } } diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index 930b4e1..7e127dc 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -370,4 +370,37 @@ protected function defaultValueWidget(FormStateInterface $form_state) { return $form_state->get('default_value_widget'); } + /** + * {@inheritdoc} + */ + public function equals(FieldItemListInterface $field_item_to_compare) { + $columns = $this->getFieldDefinition()->getFieldStorageDefinition()->getColumns(); + $this->filterEmptyItems(); + $field_item_to_compare->filterEmptyItems(); + $count1 = count($this); + $count2 = count($field_item_to_compare); + if ($count1 === 0 && $count2 === 0) { + // Both are empty we can safely assume that it did not change. + return TRUE; + } + if ($count1 !== $count2) { + // One of them is empty but not the other one so the value changed. + return FALSE; + } + // We have to clean things up a bit before comparing because order of + // properties sadly may vary. + $callback = function (&$value) use ($columns) { + if (is_array($value)) { + $value = array_intersect_key($value, $columns); + ksort($value); + } + }; + $value1 = $this->getValue(); + $value2 = $field_item_to_compare->getValue(); + array_walk($value1, $callback); + array_walk($value2, $callback); + + return $value1 === $value2; + } + } diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php index aeb6005..38ffdf3 100644 --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php @@ -258,4 +258,14 @@ public function defaultValuesFormSubmit(array $element, array &$form, FormStateI */ public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition); + /** + * Determines equality to another object implementing FieldItemListInterface. + * + * @param \Drupal\Core\Field\FieldItemListInterface $field_item_to_compare + * + * @return bool + * TRUE if the field items are equal, FALSE if not. + */ + public function equals(FieldItemListInterface $field_item_to_compare); + } diff --git a/core/tests/Drupal/Tests/Core/Field/FieldItemListTest.php b/core/tests/Drupal/Tests/Core/Field/FieldItemListTest.php new file mode 100644 index 0000000..84024b8 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Field/FieldItemListTest.php @@ -0,0 +1,103 @@ +getMock('Drupal\Core\Field\FieldTypePluginManagerInterface'); + $container = new ContainerBuilder(); + $container->set('plugin.manager.field.field_type', $field_type_manager); + \Drupal::setContainer($container); + + $fsd = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $fsd->expects($this->any()) + ->method('getColumns') + ->willReturn([0 => '0', 1 => '1']); + $fd = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $fd->expects($this->any()) + ->method('getFieldStorageDefinition') + ->willReturn($fsd); + + $field_list_a = new FieldItemList($fd); + $field_list_b = new FieldItemList($fd); + + // Set up the mocking necessary for creating field items. + $at = 0; + if ($a instanceof FieldItemInterface) { + $field_type_manager->expects($this->at($at)) + ->method('createFieldItem') + ->willReturn($a); + $at++; + } + if ($b instanceof FieldItemInterface) { + $field_type_manager->expects($this->at($at)) + ->method('createFieldItem') + ->willReturn($b); + } + + // Set the field item values. + if ($a instanceof FieldItemInterface) { + $field_list_a->setValue($a); + } + if ($b instanceof FieldItemInterface) { + $field_list_b->setValue($b); + } + + $this->assertEquals($expected, $field_list_a->equals($field_list_b)); + } + + /** + * Data provider for testEquals. + */ + public function providerTestEquals() { + // Tests field item lists with no values. + $datasets[] = [TRUE]; + + /** @var \Drupal\Core\Field\FieldItemBase $fv_a */ + $fv_a = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE); + $fv_a->setValue([1]); + // Tests field item lists where one has a value and one does not. + $datasets[] = [FALSE, $fv_a]; + + // Tests field item lists where both have the same value. + $datasets[] = [TRUE, $fv_a, $fv_a]; + + /** @var \Drupal\Core\Field\FieldItemBase $fv */ + $fv_b = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE); + $fv_b->setValue([2]); + // Tests field item lists where both have the different values. + $datasets[] = [FALSE, $fv_a, $fv_b]; + + /** @var \Drupal\Core\Field\FieldItemBase $fv */ + $fv_c = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE); + $fv_c->setValue(['0' => 1, '1' => 2]); + $fv_d = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE); + $fv_d->setValue(['1' => 2, '0' => 1]); + + // Tests field item lists where both have the differently ordered values. + $datasets[] = [TRUE, $fv_c, $fv_d]; + + return $datasets; + } +}