diff --git a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php index 83c1834..0fc6035 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php @@ -91,7 +91,7 @@ protected function doLoadFieldItems($entities, $age) { /** * {@inheritdoc} */ - protected function doSaveFieldItems(EntityInterface $entity, $update) { + protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) { } /** diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 25d4a5a..60c36b5 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -158,6 +159,65 @@ public function purgeFieldData(FieldDefinitionInterface $field_definition, $batc public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) { } /** + * {@inheritdoc} + */ + protected function doSave($id, EntityInterface $entity) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + + $is_new = $entity->isNew(); + if (!$is_new) { + // @todo, should a different value be returned when saving an entity with + // $isDefaultRevision = FALSE? + $return = $entity->isDefaultRevision() ? SAVED_UPDATED : FALSE; + } + else { + // Ensure the entity is still seen as new after assigning it an id, while + // storing its data. + $entity->enforceIsNew(); + if ($this->entityType->isRevisionable()) { + $entity->setNewRevision(); + } + $return = SAVED_NEW; + } + + $this->doSaveFieldItems($entity); + + return $return; + } + + /** + * Writes entity field values to the storage. + * + * This method is responsible for allocating entity and revision identifiers + * and update the entity object with their values. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity object. + * @param string[] $names + * (optional) The name of the fields to be written to the storage. If an + * empty value is passed all field values are saved. + */ + abstract protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []); + + /** + * {@inheritdoc} + */ + protected function doPostSave(EntityInterface $entity, $update) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + + if ($update && $this->entityType->isTranslatable()) { + $this->invokeTranslationHooks($entity); + } + + parent::doPostSave($entity, $update); + + // The revision is stored, it should no longer be marked as new now. + if ($this->entityType->isRevisionable()) { + $entity->setNewRevision(FALSE); + } + } + + /** * Checks translation statuses and invoke the related hooks if needed. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity @@ -183,27 +243,69 @@ protected function invokeTranslationHooks(ContentEntityInterface $entity) { * {@inheritdoc} */ protected function invokeHook($hook, EntityInterface $entity) { - if ($hook == 'presave') { - $this->invokeFieldMethod('preSave', $entity); + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + + switch ($hook) { + case 'presave': + $this->invokeFieldMethod('preSave', $entity); + break; + + case 'insert': + $this->invokeFieldPostSave($entity, FALSE); + break; + + case 'update': + $this->invokeFieldPostSave($entity, TRUE); + break; } + parent::invokeHook($hook, $entity); } /** * Invokes a method on the Field objects within an entity. * + * Any argument passed will be forwarded to the invoked method. + * * @param string $method - * The method name. + * The name of the method to be invoked. * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity object. + * + * @return array + * A multidimensional associative array of results, keyed by entity + * translation language code and field name. */ protected function invokeFieldMethod($method, ContentEntityInterface $entity) { + $result = []; + $args = array_slice(func_get_args(), 2); foreach (array_keys($entity->getTranslationLanguages()) as $langcode) { $translation = $entity->getTranslation($langcode); - foreach ($translation->getFields() as $field) { - $field->$method(); + foreach ($translation->getFields() as $name => $items) { + // call_user_func_array is way slower than a direct call so we avoid + // using it if have no parameters. + $result[$langcode][$name] = $args ? call_user_func_array([$items, $method], $args) : $items->{$method}(); } } + return $result; + } + + /** + * Invokes the post save method on the Field objects within an entity. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity object. + * @param bool $update + * Specifies whether the entity is being updated or created. + */ + protected function invokeFieldPostSave(ContentEntityInterface $entity, $update) { + $resave = []; + foreach ($this->invokeFieldMethod('postSave', $entity, $update) as $langcode => $results) { + $resave += array_filter($results); + } + if ($resave) { + $this->doSaveFieldItems($entity, array_keys($resave)); + } } /** diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index 55d5317..61b6511 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -382,6 +382,34 @@ public function delete(array $entities) { * {@inheritdoc} */ public function save(EntityInterface $entity) { + // Track if this entity is new. + $is_new = $entity->isNew(); + + // Execute presave logic and invoke the related hooks. + $id = $this->doPreSave($entity); + + // Perform the save and reset the static cache for the changed entity. + $return = $this->doSave($id, $entity); + + // Execute post save logic and invoke the related hooks. + $this->doPostSave($entity, !$is_new); + + return $return; + } + + /** + * Performs presave entity processing. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The saved entity. + * + * @return int + * The processed entity identifier. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * If the entity identifier is invalid. + */ + protected function doPreSave(EntityInterface $entity) { $id = $entity->id(); // Track the original ID. @@ -389,13 +417,11 @@ public function save(EntityInterface $entity) { $id = $entity->getOriginalId(); } - // Track if this entity is new. - $is_new = $entity->isNew(); // Track if this entity exists already. $id_exists = $this->has($id, $entity); // A new entity should not already exist. - if ($id_exists && $is_new) { + if ($id_exists && $entity->isNew()) { throw new EntityStorageException(SafeMarkup::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id))); } @@ -408,25 +434,7 @@ public function save(EntityInterface $entity) { $entity->preSave($this); $this->invokeHook('presave', $entity); - // Perform the save and reset the static cache for the changed entity. - $return = $this->doSave($id, $entity); - $this->resetCache(array($id)); - - // The entity is no longer new. - $entity->enforceIsNew(FALSE); - - // Allow code to run after saving. - $entity->postSave($this, !$is_new); - $this->invokeHook($is_new ? 'insert' : 'update', $entity); - - // After saving, this is now the "original entity", and subsequent saves - // will be updates instead of inserts, and updates must always be able to - // correctly identify the original entity. - $entity->setOriginalId($entity->id()); - - unset($entity->original); - - return $return; + return $id; } /** @@ -444,6 +452,32 @@ public function save(EntityInterface $entity) { abstract protected function doSave($id, EntityInterface $entity); /** + * Performs post save entity processing. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The saved entity. + * @param $update + * Specifies whether the entity is being updated or created. + */ + protected function doPostSave(EntityInterface $entity, $update) { + $this->resetCache(array($entity->id())); + + // The entity is no longer new. + $entity->enforceIsNew(FALSE); + + // Allow code to run after saving. + $entity->postSave($this, $update); + $this->invokeHook($update ? 'update' : 'insert', $entity); + + // After saving, this is now the "original entity", and subsequent saves + // will be updates instead of inserts, and updates must always be able to + // correctly identify the original entity. + $entity->setOriginalId($entity->id()); + + unset($entity->original); + } + + /** * Builds an entity query. * * @param \Drupal\Core\Entity\Query\QueryInterface $entity_query diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index c780267..937cb5f 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -934,7 +934,24 @@ public function save(EntityInterface $entity) { /** * {@inheritdoc} */ - protected function doSave($id, EntityInterface $entity) { + protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) { + if (!$names) { + $this->doRegularSave($entity); + } + else { + $this->doResaveFieldItems($entity, $names); + } + } + + /** + * Writes a full entity to the database. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity object. + * + * @internal + */ + protected function doRegularSave(ContentEntityInterface $entity) { // Create the storage record to be saved. $record = $this->mapToStorageRecord($entity); @@ -946,15 +963,9 @@ protected function doSave($id, EntityInterface $entity) { ->fields((array) $record) ->condition($this->idKey, $record->{$this->idKey}) ->execute(); - $return = SAVED_UPDATED; - } - else { - // @todo, should a different value be returned when saving an entity - // with $isDefaultRevision = FALSE? - $return = FALSE; } if ($this->revisionTable) { - $entity->{$this->revisionKey}->value = $this->saveRevision($entity); + $entity->{$this->revisionKey} = $this->saveRevision($entity); } if ($this->dataTable) { $this->saveToSharedTables($entity); @@ -964,9 +975,6 @@ protected function doSave($id, EntityInterface $entity) { } } else { - // Ensure the entity is still seen as new after assigning it an id, - // while storing its data. - $entity->enforceIsNew(); $insert_id = $this->database ->insert($this->baseTable, array('return' => Database::RETURN_INSERT_ID)) ->fields((array) $record) @@ -977,10 +985,8 @@ protected function doSave($id, EntityInterface $entity) { if (!isset($record->{$this->idKey})) { $record->{$this->idKey} = $insert_id; } - $return = SAVED_NEW; - $entity->{$this->idKey}->value = (string) $record->{$this->idKey}; + $entity->{$this->idKey} = (string) $record->{$this->idKey}; if ($this->revisionTable) { - $entity->setNewRevision(); $record->{$this->revisionKey} = $this->saveRevision($entity); } if ($this->dataTable) { @@ -990,17 +996,70 @@ protected function doSave($id, EntityInterface $entity) { $this->saveToSharedTables($entity, $this->revisionDataTable); } } - $this->invokeFieldMethod($is_new ? 'insert' : 'update', $entity); + $this->saveToDedicatedTables($entity, !$is_new); + } + + /** + * Resaves a set of entity fields to the database. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity object. + * @param string[] $names + * The name of the fields to be written to the database. + * + * @internal + */ + protected function doResaveFieldItems(ContentEntityInterface $entity, array $names) { + $table_mapping = $this->getTableMapping(); + $storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId); + $shared_table_fields = FALSE; + $dedicated_table_fields = []; + + // Collect the name of fields to be written in dedicated tables and check + // whether shared table records need to be updated. + foreach ($names as $name) { + $storage_definition = $storage_definitions[$name]; + if ($table_mapping->allowsSharedTableStorage($storage_definition)) { + $shared_table_fields = TRUE; + } + elseif ($table_mapping->requiresDedicatedTableStorage($storage_definition)) { + $dedicated_table_fields[] = $name; + } + } - if (!$is_new && $this->dataTable) { - $this->invokeTranslationHooks($entity); + // Update shared table records if necessary. + if ($shared_table_fields) { + $default_revision = $entity->isDefaultRevision(); + if ($default_revision) { + $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->baseTable); + $this->database + ->update($this->baseTable) + ->fields((array) $record) + ->condition($this->idKey, $record->{$this->idKey}) + ->execute(); + } + if ($this->revisionTable) { + $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable); + $entity->preSaveRevision($this, $record); + $this->database + ->update($this->revisionTable) + ->fields((array) $record) + ->condition($this->revisionKey, $record->{$this->revisionKey}) + ->execute(); + } + if ($default_revision && $this->dataTable) { + $this->saveToSharedTables($entity); + } + if ($this->revisionDataTable) { + $this->saveToSharedTables($entity, $this->revisionDataTable, FALSE); + } } - $entity->enforceIsNew(FALSE); - if ($this->revisionTable) { - $entity->setNewRevision(FALSE); + + // Update dedicated table records if necessary. + if ($dedicated_table_fields) { + $this->saveToDedicatedTables($entity, TRUE, $dedicated_table_fields); } - return $return; } /** @@ -1017,14 +1076,20 @@ protected function has($id, EntityInterface $entity) { * The entity object. * @param string $table_name * (optional) The table name to save to. Defaults to the data table. + * @param bool $new_revision + * (optional) Whether we are dealing with a new revision. By default fetches + * the information from the entity object. */ - protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL) { + protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL, $new_revision = NULL) { if (!isset($table_name)) { $table_name = $this->dataTable; } + if (!isset($new_revision)) { + $new_revision = $entity->isNewRevision(); + } $revision = $table_name != $this->dataTable; - if (!$revision || !$entity->isNewRevision()) { + if (!$revision || !$new_revision) { $key = $revision ? $this->revisionKey : $this->idKey; $value = $revision ? $entity->getRevisionId() : $entity->id(); // Delete and insert to handle removed values. @@ -1301,8 +1366,11 @@ protected function loadFromDedicatedTables(array &$values, $load_from_revision) * The entity. * @param bool $update * TRUE if the entity is being updated, FALSE if it is being inserted. + * @param string[] $names + * (optional) The names of the fields to be stored. Defaults to all the + * available fields. */ - protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE) { + protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE, $names = array()) { $vid = $entity->getRevisionId(); $id = $entity->id(); $bundle = $entity->bundle(); @@ -1317,7 +1385,13 @@ protected function saveToDedicatedTables(ContentEntityInterface $entity, $update $original = !empty($entity->original) ? $entity->original: NULL; - foreach ($this->entityManager->getFieldDefinitions($entity_type, $bundle) as $field_name => $field_definition) { + // Determine which fields should be actually stored. + $definitions = $this->entityManager->getFieldDefinitions($entity_type, $bundle); + if ($names) { + $definitions = array_intersect_key($definitions, array_flip($names)); + } + + foreach ($definitions as $field_name => $field_definition) { $storage_definition = $field_definition->getFieldStorageDefinition(); if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) { continue; diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php index fd521a9..c1441bb 100644 --- a/core/lib/Drupal/Core/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Field/FieldItemBase.php @@ -201,12 +201,9 @@ public function preSave() { } /** * {@inheritdoc} */ - public function insert() { } - - /** - * {@inheritdoc} - */ - public function update() { } + public function postSave($update) { + return FALSE; + } /** * {@inheritdoc} diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index a2339ac..c6b94f0 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -183,26 +183,39 @@ public function view($display_options = array()); /** * Defines custom presave behavior for field values. * - * This method is called before insert() and update() methods, and before - * values are written into storage. + * This method is called during the process of saving an entity, just before + * values are written into storage. When storing a new entity, its identifier + * will not be available yet. This should be used to massage item property + * values or perform any other operation that needs to happen before values + * are stored. For instance this is the proper phase to auto-create a new + * entity for an entity reference field item, because this way it will be + * possible to store the referenced entity identifier. */ public function preSave(); /** - * Defines custom insert behavior for field values. + * Defines custom post-save behavior for field values. * - * This method is called during the process of inserting an entity, just - * before values are written into storage. - */ - public function insert(); - - /** - * Defines custom update behavior for field values. + * This method is called during the process of saving an entity, just after + * values are written into storage. This is useful mostly when the business + * logic to be implemented always requires the entity identifier, even when + * storing a new entity. For instance, when implementing circular entity + * references, the referenced entity will be created on pre-save with a dummy + * value for the referring entity identifier, which will be updated with the + * actual one on post-save. * - * This method is called during the process of updating an entity, just before - * values are written into storage. + * In the rare cases where item properties depend on the entity identifier, + * massaging logic will have to be implemented on post-save and returning TRUE + * will allow them to be rewritten to the storage with the updated values. + * + * @param bool $update + * Specifies whether the entity is being updated or created. + * + * @return bool + * Whether field items should be rewritten to the storage as a consequence + * of the logic implemented by the custom behavior. */ - public function update(); + public function postSave($update); /** * Defines custom delete behavior for field values. diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index aa10c6f..5605f11 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -7,14 +7,12 @@ namespace Drupal\Core\Field; -use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\Core\TypedData\DataDefinitionInterface; use Drupal\Core\TypedData\Plugin\DataType\ItemList; -use Drupal\Core\TypedData\TypedDataInterface; /** * Represents an entity field; that is, a list of field item objects. @@ -212,15 +210,9 @@ public function preSave() { /** * {@inheritdoc} */ - public function insert() { - $this->delegateMethod('insert'); - } - - /** - * {@inheritdoc} - */ - public function update() { - $this->delegateMethod('update'); + public function postSave($update) { + $result = $this->delegateMethod('postSave', $update); + return (bool) array_filter($result); } /** @@ -240,13 +232,23 @@ public function deleteRevision() { /** * Calls a method on each FieldItem. * + * Any argument passed will be forwarded to the invoked method. + * * @param string $method - * The name of the method. + * The name of the method to be invoked. + * + * @return array + * An array of results keyed by delta. */ protected function delegateMethod($method) { - foreach ($this->list as $item) { - $item->{$method}(); + $result = []; + $args = array_slice(func_get_args(), 1); + foreach ($this->list as $delta => $item) { + // call_user_func_array is way slower than a direct call so we avoid using + // it if have no parameters. + $result[$delta] = $args ? call_user_func_array([$item, $method], $args) : $item->{$method}(); } + return $result; } /** diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php index e4ea12c..df595e5 100644 --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php @@ -130,26 +130,29 @@ public function __unset($property_name); /** * Defines custom presave behavior for field values. * - * This method is called before either insert() or update() methods, and - * before values are written into storage. + * This method is called during the process of saving an entity, just before + * item values are written into storage. + * + * @see \Drupal\Core\Field\FieldItemInterface::preSave() */ public function preSave(); /** - * Defines custom insert behavior for field values. + * Defines custom post-save behavior for field values. * - * This method is called after the save() method, and before values are - * written into storage. - */ - public function insert(); - - /** - * Defines custom update behavior for field values. + * This method is called during the process of saving an entity, just after + * item values are written into storage. + * + * @param bool $update + * Specifies whether the entity is being updated or created. + * + * @return bool + * Whether field items should be rewritten to the storage as a consequence + * of the logic implemented by the custom behavior. * - * This method is called after the save() method, and before values are - * written into storage. + * @see \Drupal\Core\Field\FieldItemInterface::postSave() */ - public function update(); + public function postSave($update); /** * Defines custom delete behavior for field values. diff --git a/core/modules/block_content/tests/modules/block_content_test/block_content_test.module b/core/modules/block_content/tests/modules/block_content_test/block_content_test.module index d714cb2..3450b5f 100644 --- a/core/modules/block_content/tests/modules/block_content_test/block_content_test.module +++ b/core/modules/block_content/tests/modules/block_content_test/block_content_test.module @@ -60,6 +60,7 @@ function block_content_test_block_content_insert(BlockContent $block_content) { // Set the block_content title to the block_content ID and save. if ($block_content->label() == 'new') { $block_content->setInfo('BlockContent ' . $block_content->id()); + $block_content->setNewRevision(FALSE); $block_content->save(); } if ($block_content->label() == 'fail_creation') { diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php b/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php index f8ee03c..033a742 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php @@ -23,61 +23,58 @@ public function defaultValuesForm(array &$form, FormStateInterface $form_state) /** * {@inheritdoc} */ - public function insert() { - parent::insert(); + public function postSave($update) { $entity = $this->getEntity(); - // Add a new usage for newly uploaded files. - foreach ($this->referencedEntities() as $file) { - \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id()); - } - } - - /** - * {@inheritdoc} - */ - public function update() { - parent::update(); - $entity = $this->getEntity(); - - // Get current target file entities and file IDs. - $files = $this->referencedEntities(); - $fids = array(); - - foreach ($files as $file) { - $fids[] = $file->id(); + if (!$update) { + // Add a new usage for newly uploaded files. + foreach ($this->referencedEntities() as $file) { + \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id()); + } } + else { + // Get current target file entities and file IDs. + $files = $this->referencedEntities(); + $ids = array(); - // On new revisions, all files are considered to be a new usage and no - // deletion of previous file usages are necessary. - if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) { + /** @var \Drupal\file\Entity\File $file */ foreach ($files as $file) { - \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id()); + $ids[] = $file->id(); } - return; - } - // Get the file IDs attached to the field before this update. - $field_name = $this->getFieldDefinition()->getName(); - $original_fids = array(); - $original_items = $entity->original->getTranslation($this->getLangcode())->$field_name; - foreach ($original_items as $item) { - $original_fids[] = $item->target_id; - } + // On new revisions, all files are considered to be a new usage and no + // deletion of previous file usages are necessary. + if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) { + foreach ($files as $file) { + \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id()); + } + return; + } - // Decrement file usage by 1 for files that were removed from the field. - $removed_fids = array_filter(array_diff($original_fids, $fids)); - $removed_files = \Drupal::entityManager()->getStorage('file')->loadMultiple($removed_fids); - foreach ($removed_files as $file) { - \Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id()); - } + // Get the file IDs attached to the field before this update. + $field_name = $this->getFieldDefinition()->getName(); + $original_ids = array(); + $original_items = $entity->original->getTranslation($this->getLangcode())->$field_name; + foreach ($original_items as $item) { + $original_ids[] = $item->target_id; + } - // Add new usage entries for newly added files. - foreach ($files as $file) { - if (!in_array($file->id(), $original_fids)) { - \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id()); + // Decrement file usage by 1 for files that were removed from the field. + $removed_ids = array_filter(array_diff($original_ids, $ids)); + $removed_files = \Drupal::entityManager()->getStorage('file')->loadMultiple($removed_ids); + foreach ($removed_files as $file) { + \Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id()); + } + + // Add new usage entries for newly added files. + foreach ($files as $file) { + if (!in_array($file->id(), $original_ids)) { + \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id()); + } } } + + return FALSE; } /** diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module index ed9af44..7f2cd53 100644 --- a/core/modules/node/tests/modules/node_test/node_test.module +++ b/core/modules/node/tests/modules/node_test/node_test.module @@ -170,6 +170,7 @@ function node_test_node_insert(NodeInterface $node) { // Set the node title to the node ID and save. if ($node->getTitle() == 'new') { $node->setTitle('Node '. $node->id()); + $node->setNewRevision(FALSE); $node->save(); } } diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php index c06271d..9ad35ac 100644 --- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php @@ -55,29 +55,27 @@ public function preSave() { /** * {@inheritdoc} */ - public function insert() { - if ($this->alias) { - $entity = $this->getEntity(); - - if ($path = \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) { - $this->pid = $path['pid']; + public function postSave($update) { + if (!$update) { + if ($this->alias) { + $entity = $this->getEntity(); + if ($path = \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) { + $this->pid = $path['pid']; + } } } - } - - /** - * {@inheritdoc} - */ - public function update() { - // Delete old alias if user erased it. - if ($this->pid && !$this->alias) { - \Drupal::service('path.alias_storage')->delete(array('pid' => $this->pid)); - } - // Only save a non-empty alias. - elseif ($this->alias) { - $entity = $this->getEntity(); - \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid); + else { + // Delete old alias if user erased it. + if ($this->pid && !$this->alias) { + \Drupal::service('path.alias_storage')->delete(array('pid' => $this->pid)); + } + // Only save a non-empty alias. + elseif ($this->alias) { + $entity = $this->getEntity(); + \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid); + } } + return FALSE; } /**