diff --git a/core/modules/image/src/ImageServiceProvider.php b/core/modules/image/src/ImageServiceProvider.php new file mode 100644 index 0000000..de34726 --- /dev/null +++ b/core/modules/image/src/ImageServiceProvider.php @@ -0,0 +1,59 @@ +getParameter('container.modules'); + if (isset($modules['serialization'])) { + // Add an ImageItem normalizer. + $service_definition = new Definition(ImageItemNormalizer::class, [ + new Reference('renderer'), + ]); + // Priority should be higher than + // serializer.normalizer.entity_reference_field_item but lower than + // serializer.normalizer.entity_reference_item.hal. + $service_definition->addTag('normalizer', ['priority' => 9]); + $container->setDefinition('image.normalizer.image_item', $service_definition); + + if (isset($modules['hal'])) { + // Add an ImageItem normalizer. + $service_definition = new Definition(ImageItemHalNormalizer::class, [ + new Reference('hal.link_manager'), + new Reference('serializer.entity_resolver'), + new Reference('renderer'), + ]); + // Priority should be higher than + // serializer.normalizer.entity_reference_item.hal which is 10. + // Priority of 20 gives the ability to have other normalizers between + // this one and serializer.normalizer.entity_reference_item.hal. + $service_definition->addTag('normalizer', ['priority' => 20]); + $container->setDefinition('image.normalizer.hal.image_item', $service_definition); + } + } + } + +} diff --git a/core/modules/image/src/Normalizer/ImageItemHalNormalizer.php b/core/modules/image/src/Normalizer/ImageItemHalNormalizer.php new file mode 100644 index 0000000..941ea94 --- /dev/null +++ b/core/modules/image/src/Normalizer/ImageItemHalNormalizer.php @@ -0,0 +1,58 @@ +renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public function normalize($field_item, $format = NULL, array $context = []) { + /* @var \Drupal\image\Plugin\Field\FieldType\ImageItem $field_item */ + $normalization = parent::normalize($field_item, $format, $context); + if (!$field_item->isEmpty()) { + $field_key = array_keys($normalization['_embedded'])[0]; + $this->decorateWithImageStyles($field_item, $normalization['_embedded'][$field_key][0], $context); + } + return $normalization; + } + +} diff --git a/core/modules/image/src/Normalizer/ImageItemNormalizer.php b/core/modules/image/src/Normalizer/ImageItemNormalizer.php new file mode 100644 index 0000000..93fc3df --- /dev/null +++ b/core/modules/image/src/Normalizer/ImageItemNormalizer.php @@ -0,0 +1,51 @@ +renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public function normalize($field_item, $format = NULL, array $context = []) { + /* @var \Drupal\image\Plugin\Field\FieldType\ImageItem $field_item */ + $normalization = parent::normalize($field_item, $format, $context); + if (!$field_item->isEmpty()) { + $this->decorateWithImageStyles($field_item, $normalization, $context); + } + + return $normalization; + } + +} diff --git a/core/modules/image/src/Normalizer/ImageItemNormalizerTrait.php b/core/modules/image/src/Normalizer/ImageItemNormalizerTrait.php new file mode 100644 index 0000000..bdf3d5e --- /dev/null +++ b/core/modules/image/src/Normalizer/ImageItemNormalizerTrait.php @@ -0,0 +1,47 @@ +target_id)) { + $uri = $image->getFileUri(); + /** @var \Drupal\image\ImageStyleInterface[] $styles */ + $styles = ImageStyle::loadMultiple(); + $normalization['image_styles'] = []; + foreach ($styles as $id => $style) { + $dimensions = ['width' => $item->width, 'height' => $item->height]; + $style->transformDimensions($dimensions, $uri); + $normalization['image_styles'][$id] = [ + 'url' => file_url_transform_relative($style->buildUrl($uri)), + 'height' => empty($dimensions['height']) ? NULL : $dimensions['height'], + 'width' => empty($dimensions['width']) ? NULL : $dimensions['width'], + ]; + if (!empty($context['cacheability'])) { + $context['cacheability']->addCacheableDependency($style); + } + } + } + } + +} diff --git a/core/modules/image/tests/src/Kernel/Normalizer/ImageItemHalNormalizerTest.php b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemHalNormalizerTest.php new file mode 100644 index 0000000..7e4cad8 --- /dev/null +++ b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemHalNormalizerTest.php @@ -0,0 +1,28 @@ +serializer = $this->container->get('serializer'); + + $this->installEntitySchema('entity_test'); + $this->installEntitySchema('user'); + $this->installEntitySchema('file'); + $this->installConfig('system'); + $this->installConfig('image'); + $this->installSchema('file', ['file_usage']); + + FieldStorageConfig::create([ + 'entity_type' => 'entity_test', + 'field_name' => 'image_test', + 'type' => 'image', + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + ])->save(); + FieldConfig::create([ + 'entity_type' => 'entity_test', + 'field_name' => 'image_test', + 'bundle' => 'entity_test', + 'settings' => [ + 'file_extensions' => 'jpg', + ], + ])->save(); + + // Set upscale to TRUE in all image style scale effects. + /** @var \Drupal\image\Entity\ImageStyle $image_style */ + foreach (ImageStyle::loadMultiple() as $image_style) { + foreach ($image_style->getEffects() as $effect) { + $config = $effect->getConfiguration(); + $config['data']['upscale'] = TRUE; + $effect->setConfiguration($config); + } + $image_style->save(); + } + + file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://example.jpg'); + $this->image = File::create([ + 'uri' => 'public://example.jpg', + ]); + $this->image->save(); + } + + /** + * Test that the decorator provides additional image style information. + * + * @see \Drupal\image\Normalizer\ImageItemNormalizerTrait::decorateWithImageStyles() + * + * @covers ::normalize + */ + public function testNormalize() { + // Create a test entity with the image field set. + $original_entity = EntityTest::create(); + $original_entity->image_test->target_id = $this->image->id(); + $original_entity->image_test->alt = $alt = $this->randomMachineName(); + $original_entity->image_test->title = $title = $this->randomMachineName(); + $original_entity->name->value = $this->randomMachineName(); + $original_entity->save(); + + $entity = clone $original_entity; + $cacheable_metadata = new CacheableMetadata(); + $normalization = $this->serializer->normalize($entity, $this->format, ['cacheability' => $cacheable_metadata]); + + $normalized_image_styles = $this->getNormalizedImageStyles($normalization); + $this->assertEquals($this->getExpectedCacheability(), $cacheable_metadata); + $expect_dimensions = [ + 'large' => [ + 'width' => '422', + 'height' => '480', + ], + 'medium' => [ + 'width' => '194', + 'height' => '220', + ], + 'thumbnail' => [ + 'width' => '88', + 'height' => '100', + ], + ]; + $this->assertEquals(array_keys($expect_dimensions), array_keys($normalized_image_styles)); + + foreach ($normalized_image_styles as $image_style_id => $image_style_dimensions) { + $this->assertContains("files/styles/$image_style_id/public/example.jpg", $image_style_dimensions['url']); + $this->assertEquals($expect_dimensions[$image_style_id]['height'], $image_style_dimensions['height'], "Style $image_style_id matches height."); + $this->assertEquals($expect_dimensions[$image_style_id]['width'], $image_style_dimensions['width'], "Style $image_style_id matches width."); + } + } + + /** + * Gets normalized image styles from normalized entity. + * + * @param array $normalization + * The normalized entity. + * + * @return array + * The normalized image styles. + */ + abstract protected function getNormalizedImageStyles(array $normalization); + + /** + * Gets the expected bubbled cacheability metadata. + * + * @return \Drupal\Core\Cache\CacheableMetadata + * The expected cacheability metadata. + */ + protected function getExpectedCacheability() { + $cacheability = new CacheableMetadata(); + $cache_tags = []; + /** @var \Drupal\image\ImageStyleInterface $image_style */ + foreach (ImageStyle::loadMultiple() as $image_style) { + $cache_tags = Cache::mergeTags($cache_tags, $image_style->getCacheTags()); + } + $cacheability->setCacheTags($cache_tags); + return $cacheability; + } + +} diff --git a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php index 5cb887b..c85ba57 100644 --- a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php +++ b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php @@ -151,14 +151,14 @@ protected function renderResponseBody(Request $request, ResourceResponseInterfac // If there is data to send, serialize and set it as the response body. if ($data !== NULL) { $context = new RenderContext(); - $output = $this->renderer - ->executeInRenderContext($context, function () use ($serializer, $data, $format) { - return $serializer->serialize($data, $format); - }); - - if ($response instanceof CacheableResponseInterface && !$context->isEmpty()) { - $response->addCacheableDependency($context->pop()); + $serialization_contexts = ['request' => $request]; + if ($response instanceof CacheableResponseInterface) { + if (!$context->isEmpty()) { + $response->addCacheableDependency($context->pop()); + } + $serialization_contexts['cacheability'] = $response->getCacheableMetadata(); } + $output = $serializer->serialize($data, $format, $serialization_contexts); $response->setContent($output); $response->headers->set('Content-Type', $request->getMimeType($format));