diff --git a/core/lib/Drupal/Core/DefaultContent/Importer.php b/core/lib/Drupal/Core/DefaultContent/Importer.php index 41e62e2e0b5525aa31c896711f605ddc99e9bc5c..9d6c2f331aa7a0019da2b3b008e8faf80ae05689 100644 --- a/core/lib/Drupal/Core/DefaultContent/Importer.php +++ b/core/lib/Drupal/Core/DefaultContent/Importer.php @@ -87,29 +87,45 @@ public function importContent(Finder $content): void { // If a file exists in the same folder, copy it to the designated // target URI. if ($entity instanceof FileInterface) { - /** @var string $uri */ - $uri = $entity->getFileUri(); - $file_source = $path . '/' . $entity->getFilename(); - if (file_exists($file_source)) { - $target_directory = dirname($uri); - $this->fileSystem->prepareDirectory($target_directory, FileSystemInterface::CREATE_DIRECTORY); - // @todo https://drupal.org/i/3437068 Don't copy the file if it - // already exists. - $new_uri = $this->fileSystem->copy($file_source, $uri); - $entity->setFileUri($new_uri); - } - else { - $this->logger?->warning("File entity %name was imported, but the associated file (@path) was not found.", [ - '%name' => $entity->getFilename(), - '@path' => $file_source, - ]); - } + $this->copyFileAssociatedWithEntity($path, $entity); } $entity->save(); } $this->accountSwitcher->switchBack(); } + private function copyFileAssociatedWithEntity(string $path, FileInterface $entity): void { + $destination = $entity->getFileUri(); + assert(is_string($destination)); + + // If the source file doesn't exist, there's nothing we can do. + $source = $path . '/' . basename($destination); + if (!file_exists($source)) { + $this->logger?->warning("File entity %name was imported, but the associated file (@path) was not found.", [ + '%name' => $entity->label(), + '@path' => $source, + ]); + return; + } + + if (file_exists($destination)) { + $source_hash = hash_file('sha256', $source); + assert(is_string($source_hash)); + $destination_hash = hash_file('sha256', $destination); + assert(is_string($destination_hash)); + + // If we already have the file, we don't need to do anything else. + if (hash_equals($source_hash, $destination_hash)) { + return; + } + } + + $target_directory = dirname($destination); + $this->fileSystem->prepareDirectory($target_directory, FileSystemInterface::CREATE_DIRECTORY); + $uri = $this->fileSystem->copy($source, $destination); + $entity->setFileUri($uri); + } + /** * Converts an array of content entity data to a content entity object. * diff --git a/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php b/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php index 45feff14acab1995477efd062d6514b3e089fe6e..8ee77dfa992d2fbabbf1a39ddf1308659178e6e0 100644 --- a/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php +++ b/core/tests/Drupal/FunctionalTests/DefaultContent/ContentImportTest.php @@ -163,6 +163,21 @@ private function assertContentWasImported(): void { $this->assertSame('druplicon.png', $file->getFilename()); $this->assertSame('d8404562-efcc-40e3-869e-40132d53fe0b', $file->uuid()); + // Another file entity referencing an existing file should have the same + // file URI -- in other words, it should have reused the existing file. + $duplicate_file = $entity_repository->loadEntityByUuid('file', '23a7f61f-1db3-407d-a6dd-eb4731995c9f'); + $this->assertInstanceOf(FileInterface::class, $duplicate_file); + $this->assertSame('druplicon-duplicate.png', $duplicate_file->getFilename()); + $this->assertSame($file->getFileUri(), $duplicate_file->getFileUri()); + + // Another file entity that references a file with the same name as, but + // different contents than, an existing file, should be imported and the + // file should be renamed. + $different_file = $entity_repository->loadEntityByUuid('file', 'a6b79928-838f-44bd-a8f0-44c2fff9e4cc'); + $this->assertInstanceOf(FileInterface::class, $different_file); + $this->assertSame('druplicon-different.png', $different_file->getFilename()); + $this->assertStringEndsWith('/druplicon_0.png', (string) $different_file->getFileUri()); + // Our node should have a menu link, and it should use the path alias we // included with the recipe. $menu_link = $entity_repository->loadEntityByUuid('menu_link_content', '3434bd5a-d2cd-4f26-bf79-a7f6b951a21b'); diff --git a/core/tests/fixtures/default_content/a6b79928-838f-44bd-a8f0-44c2fff9e4cc.yml b/core/tests/fixtures/default_content/a6b79928-838f-44bd-a8f0-44c2fff9e4cc.yml new file mode 100644 index 0000000000000000000000000000000000000000..f126741702a33b94e93b48bbc43a76f631178006 --- /dev/null +++ b/core/tests/fixtures/default_content/a6b79928-838f-44bd-a8f0-44c2fff9e4cc.yml @@ -0,0 +1,27 @@ +_meta: + version: '1.0' + entity_type: file + uuid: a6b79928-838f-44bd-a8f0-44c2fff9e4cc + default_langcode: en +default: + uid: + - + target_id: 1 + filename: + - + value: druplicon-different.png + uri: + - + value: 'public://2024-03/druplicon.png' + filemime: + - + value: text/plain + filesize: + - + value: 11 + status: + - + value: true + created: + - + value: 1711121742 diff --git a/core/tests/fixtures/default_content/druplicon.png b/core/tests/fixtures/default_content/druplicon.png new file mode 100644 index 0000000000000000000000000000000000000000..51bb1074d33efe7eb561a572ca148b105657fd81 --- /dev/null +++ b/core/tests/fixtures/default_content/druplicon.png @@ -0,0 +1 @@ +Not a PNG. diff --git a/core/tests/fixtures/default_content/file/23a7f61f-1db3-407d-a6dd-eb4731995c9f.yml b/core/tests/fixtures/default_content/file/23a7f61f-1db3-407d-a6dd-eb4731995c9f.yml new file mode 100644 index 0000000000000000000000000000000000000000..f64d6f0eff580399f88bc7335df07d9b9372fb23 --- /dev/null +++ b/core/tests/fixtures/default_content/file/23a7f61f-1db3-407d-a6dd-eb4731995c9f.yml @@ -0,0 +1,27 @@ +_meta: + version: '1.0' + entity_type: file + uuid: 23a7f61f-1db3-407d-a6dd-eb4731995c9f + default_langcode: en +default: + uid: + - + target_id: 1 + filename: + - + value: druplicon-duplicate.png + uri: + - + value: 'public://2024-03/druplicon.png' + filemime: + - + value: image/png + filesize: + - + value: 3905 + status: + - + value: true + created: + - + value: 1711121742