diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index 7c822ed..21cf7a9 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -6,6 +6,7 @@ */ use Drupal\Component\Utility\Html; +use Drupal\Core\TypedData\TranslatableInterface; use Drupal\editor\Entity\Editor; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; @@ -364,19 +365,39 @@ function editor_entity_update(EntityInterface $entity) { // File references that existed both in the previous version of the revision // and in the new one don't need their usage to be updated. else { - $original_uuids_by_field = _editor_get_file_uuids_by_field($entity->original); - $uuids_by_field = _editor_get_file_uuids_by_field($entity); - // Detect file usages that should be incremented. - foreach ($uuids_by_field as $field => $uuids) { - $added_files = array_diff($uuids_by_field[$field], $original_uuids_by_field[$field]); - _editor_record_file_usage($added_files, $entity); + // When an entity is updated, any amount of its translations can be updated + // as well. Check them all. + $entities = [[$entity, $entity->original]]; + if ($entity instanceof TranslatableInterface) { + $entities = []; + foreach ($entity->getTranslationLanguages() as $langcode => $language) { + // If the original entity does not have a translation in the given + // language, it will be handled in editor_entity_translation_insert(). + if ($entity->original->hasTranslation($langcode)) { + $entities[] = [ + $entity->getTranslation($langcode), + $entity->original->getTranslation($langcode) + ]; + } + } } - // Detect file usages that should be decremented. - foreach ($original_uuids_by_field as $field => $uuids) { - $removed_files = array_diff($original_uuids_by_field[$field], $uuids_by_field[$field]); - _editor_delete_file_usage($removed_files, $entity, 1); + foreach ($entities as list($entity, $entity_original)) { + $original_uuids_by_field = _editor_get_file_uuids_by_field($entity_original); + $uuids_by_field = _editor_get_file_uuids_by_field($entity); + + // Detect file usages that should be incremented. + foreach ($uuids_by_field as $field => $uuids) { + $added_files = array_diff($uuids_by_field[$field], $original_uuids_by_field[$field]); + _editor_record_file_usage($added_files, $entity); + } + + // Detect file usages that should be decremented. + foreach ($original_uuids_by_field as $field => $uuids) { + $removed_files = array_diff($original_uuids_by_field[$field], $uuids_by_field[$field]); + _editor_delete_file_usage($removed_files, $entity, 1); + } } } } @@ -410,6 +431,28 @@ function editor_entity_revision_delete(EntityInterface $entity) { } /** + * Implements hook_entity_translation_insert(). + */ +function editor_entity_translation_insert(EntityInterface $translation) { + // Process like entity insert. + editor_entity_insert($translation); +} + +/** + * Implements hook_entity_translation_delete(). + */ +function editor_entity_translation_delete(EntityInterface $translation) { + // Only act on content entities. + if (!($translation instanceof FieldableEntityInterface)) { + return; + } + $referenced_files_by_field = _editor_get_file_uuids_by_field($translation); + foreach ($referenced_files_by_field as $field => $uuids) { + _editor_delete_file_usage($uuids, $translation, 1); + } +} + +/** * Records file usage of files referenced by formatted text fields. * * Every referenced file that does not yet have the FILE_STATUS_PERMANENT state, diff --git a/core/modules/editor/tests/src/Kernel/EditorFileUsageTest.php b/core/modules/editor/tests/src/Kernel/EditorFileUsageTest.php index a8379d1..f0e9ca5 100644 --- a/core/modules/editor/tests/src/Kernel/EditorFileUsageTest.php +++ b/core/modules/editor/tests/src/Kernel/EditorFileUsageTest.php @@ -4,6 +4,7 @@ use Drupal\editor\Entity\Editor; use Drupal\KernelTests\Core\Entity\EntityKernelTestBase; +use Drupal\language\Entity\ConfigurableLanguage; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; use Drupal\file\Entity\File; @@ -23,7 +24,14 @@ class EditorFileUsageTest extends EntityKernelTestBase { * * @var array */ - public static $modules = array('editor', 'editor_test', 'node', 'file'); + public static $modules = array('editor', 'editor_test', 'node', 'file', 'language'); + + /** + * Languages to enable. + * + * @var array + */ + protected $langcodes = array('fr', 'en'); protected function setUp() { parent::setUp(); @@ -41,8 +49,14 @@ protected function setUp() { )); $filtered_html_format->save(); + // Add languages. + foreach ($this->langcodes as $langcode) { + ConfigurableLanguage::createFromLangcode($langcode)->save(); + } + // Set cardinality for body field. FieldStorageConfig::loadByName('node', 'body') + ->setTranslatable(TRUE) ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) ->save(); @@ -69,6 +83,9 @@ public function testEditorEntityHooks() { 2 => 'core/misc/help.png', ); + /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */ + $file_usage = $this->container->get('file.usage'); + $image_entities = array(); foreach ($image_paths as $key => $image_path) { $image = File::create(); @@ -76,7 +93,6 @@ public function testEditorEntityHooks() { $image->setFilename(drupal_basename($image->getFileUri())); $image->save(); - $file_usage = $this->container->get('file.usage'); $this->assertIdentical(array(), $file_usage->listUsage($image), 'The image ' . $image_paths[$key] . ' has zero usages.'); $image_entities[] = $image; @@ -104,6 +120,7 @@ public function testEditorEntityHooks() { // Test editor_entity_insert(): increment. $this->createUser(); $node = $node = Node::create([ + 'language' => 'en', 'type' => 'page', 'title' => 'test', 'body' => $body, @@ -177,11 +194,68 @@ public function testEditorEntityHooks() { $this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 2 usages.'); } + // Test editor_entity_translation_insert(): increment, by adding a new + // translation. + $translation = $node->addTranslation('fr', ['title' => 'test fr', 'body' => $original_values]); + $translation->save(); + foreach ($image_entities as $key => $image_entity) { + $this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 3 usages.'); + } + + // Test editor_entity_translation_delete(): decrement, by deleting a + // translation. + $node->removeTranslation('fr'); + $node->save(); + foreach ($image_entities as $key => $image_entity) { + $this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 2 usages.'); + } + // Test editor_entity_delete(). $node->delete(); foreach ($image_entities as $key => $image_entity) { $this->assertIdentical(array(), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has zero usages again.'); } + + // Prepare a new node having only one file entity referenced in the body + // field. + $image_entity_1 = $image_entities[0]; + $body_value = ''; + $node = Node::create([ + 'language' => 'en', + 'type' => 'page', + 'title' => 'test', + 'body' => [ + 0 => [ + 'value' => $body_value, + 'format' => 'filtered_html', + ] + ], + 'uid' => 1, + ]); + $node->save(); + $nid = $node->id(); + $this->assertIdentical(array('editor' => array('node' => array($nid => '1'))), $file_usage->listUsage($image_entity_1), 'The image ' . $image_entity_1->getFileUri() . ' has 1 usage.'); + + // Add a translation. + $translation = $node->addTranslation('fr', [ + 'title' => 'test fr', + 'body' => $node->get('body')->getValue(), + ]); + $node->save(); + $this->assertIdentical(array('editor' => array('node' => array($nid => '2'))), $file_usage->listUsage($image_entity_1), 'The image ' . $image_entity_1->getFileUri() . ' has 2 usages.'); + + // Replace image in the French translation. + $image_entity_2 = $image_entities[1]; + $translation->get('body')->value = '';; + $node->save(); + $this->assertIdentical(array('editor' => array('node' => array($nid => '1'))), $file_usage->listUsage($image_entity_1), 'The image ' . $image_entity_1->getFileUri() . ' has 1 usage.'); + $this->assertIdentical(array('editor' => array('node' => array($nid => '1'))), $file_usage->listUsage($image_entity_2), 'The image ' . $image_entity_2->getFileUri() . ' has 1 usage.'); + + // Re-save translation with no image and check if usage changed + $translation->get('body')->value = ''; + $node->save(); + $this->assertIdentical(array('editor' => array('node' => array($nid => '1'))), $file_usage->listUsage($image_entity_1), 'The image ' . $image_entity_1->getFileUri() . ' has 1 usage.'); + $this->assertIdentical(array(), $file_usage->listUsage($image_entity_2), 'The image ' . $image_entity_2->getFileUri() . ' has zero usages.'); } }