diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 1b55fe0e74..b6d024befb 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -48,6 +48,18 @@ function file_help($route_name, RouteMatchInterface $route_match) { } } +/** + * File URI callback. + * + * @param \Drupal\file\FileInterface $file + * The file entity. + * @return \Drupal\Core\Url + * A Url instance. + */ +function file_uri(FileInterface $file) { + return Url::fromUri($file->getFileUri()); +} + /** * Loads file entities from the database. * diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php index a9ade9e7d6..51da0ba0be 100644 --- a/core/modules/file/src/Entity/File.php +++ b/core/modules/file/src/Entity/File.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\file\FileInterface; +use Drupal\file\FileUriItem; use Drupal\user\UserInterface; /** @@ -30,7 +31,8 @@ * "label" = "filename", * "langcode" = "langcode", * "uuid" = "uuid" - * } + * }, + * uri_callback = "file_uri" * ) */ class File extends ContentEntityBase implements FileInterface { @@ -250,6 +252,11 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setSetting('case_sensitive', TRUE) ->addConstraint('FileUriUnique'); + // Override the default item class with a file specific implementation that + // provides an additional computed URL property. + // @see \Drupal\file\FileUriItem::propertyDefinitions() + $fields['uri']->getItemDefinition()->setClass(FileUriItem::class); + $fields['filemime'] = BaseFieldDefinition::create('string') ->setLabel(t('File MIME type')) ->setSetting('is_ascii', TRUE) diff --git a/core/modules/file/src/FileUriItem.php b/core/modules/file/src/FileUriItem.php new file mode 100644 index 0000000000..cbd0f3ccbf --- /dev/null +++ b/core/modules/file/src/FileUriItem.php @@ -0,0 +1,30 @@ +setLabel(t('Full URL')) + ->setSetting('case_sensitive', $field_definition->getSetting('case_sensitive')) + ->setComputed(TRUE) + ->setInternal(FALSE) + ->setClass(FileUrl::class); + + return $properties; + } + +} diff --git a/core/modules/file/src/FileUrl.php b/core/modules/file/src/FileUrl.php new file mode 100644 index 0000000000..65a31f69ce --- /dev/null +++ b/core/modules/file/src/FileUrl.php @@ -0,0 +1,48 @@ +url !== NULL) { + return $this->url; + } + + $this->url = $this->getParent() + ->getEntity() + ->toUrl() + ->setAbsolute() + ->toString(); + + return $this->url; + } + + /** + * {@inheritdoc} + */ + public function setValue($value, $notify = TRUE) { + $this->url = $value; + + // Notify the parent of any changes. + if ($notify && isset($this->parent)) { + $this->parent->onChange($this->name); + } + } + +} diff --git a/core/modules/file/tests/src/Kernel/FileUriItemTest.php b/core/modules/file/tests/src/Kernel/FileUriItemTest.php new file mode 100644 index 0000000000..c42591bfd3 --- /dev/null +++ b/core/modules/file/tests/src/Kernel/FileUriItemTest.php @@ -0,0 +1,35 @@ + 1, + 'filename' => 'druplicon.txt', + 'uri' => $uri, + 'filemime' => 'text/plain', + 'status' => FILE_STATUS_PERMANENT, + ]); + file_put_contents($file->getFileUri(), 'hello world'); + + $file->save(); + + $this->assertSame($uri, $file->uri->value); + $this->assertSame(file_create_url($uri), $file->uri->url); + } + +} diff --git a/core/modules/file/tests/src/Unit/FileUrlTest.php b/core/modules/file/tests/src/Unit/FileUrlTest.php new file mode 100644 index 0000000000..b89109d493 --- /dev/null +++ b/core/modules/file/tests/src/Unit/FileUrlTest.php @@ -0,0 +1,92 @@ +prophesize(Url::class); + $url->setAbsolute() + ->willReturn($url->reveal()); + $url->toString() + ->willReturn($this->testUrl); + + $entity = $this->prophesize(EntityInterface::class); + $entity->toUrl() + ->willReturn($url->reveal()); + + $parent = $this->prophesize(FieldItemInterface::class); + $parent->getEntity() + ->shouldBeCalledTimes(1) + ->willReturn($entity->reveal()); + + $definition = $this->prophesize(DataDefinitionInterface::class); + + $typed_data = new FileUrl($definition->reveal(), 'test', $parent->reveal()); + + $this->assertSame($this->testUrl, $typed_data->getValue()); + // Do this a second time to confirm the same value is returned but the value + // isn't retrieved from the parent entity again. + $this->assertSame($this->testUrl, $typed_data->getValue()); + } + + /** + * @covers ::setValue + */ + public function testSetValue() { + $parent = $this->prophesize(FieldItemInterface::class); + $parent->onChange('test') + ->shouldBeCalled(); + + $definition = $this->prophesize(DataDefinitionInterface::class); + $typed_data = new FileUrl($definition->reveal(), 'test', $parent->reveal()); + + // Setting the value should explicitly should mean the parent entity is + // never called into. + $typed_data->setValue($this->testUrl); + + $this->assertSame($this->testUrl, $typed_data->getValue()); + // Do this a second time to confirm the same value is returned but the value + // isn't retrieved from the parent entity again. + $this->assertSame($this->testUrl, $typed_data->getValue()); + } + + /** + * @covers ::setValue + */ + public function testSetValueNoNotify() { + $parent = $this->prophesize(FieldItemInterface::class); + $parent->onChange('test') + ->shouldNotBeCalled(); + + $definition = $this->prophesize(DataDefinitionInterface::class); + $typed_data = new FileUrl($definition->reveal(), 'test', $parent->reveal()); + + // Setting the value should explicitly should mean the parent entity is + // never called into. + $typed_data->setValue($this->testUrl, FALSE); + } + +}