diff --git a/README.md b/README.md index abc790f..21854c4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A module to facilitate image discovery for Islandora repository items. Image dis * contents of a Media field, `field_representative_image` on the node * an "Islandora thumbnail", i.e., a media that is "Media of" the node (using `field_media_of`) with a Media Use (`field_media_use`) taxonomy term with External URI (`field_external_uri`) equal to "http://pcdm.org/use#ThumbnailImage" -* a first child's Islandora thumbnail media, i.e. the Islandora thumbnail of the node with lowest weight (`field_weight`) that is a Member Of (`field_member_of`) the node in question. If not found on the first direct child, it will look at the first child's first child, and so forth to a depth of 3. +* a first child's Islandora thumbnail media, i.e. the Islandora thumbnail of the node with lowest weight (`field_weight`) that is a Member Of (`field_member_of`) the node in question. If not found on the first direct child, it will look at the first child's first child, and so forth to a depth of 3. ## Requirements @@ -24,9 +24,31 @@ further information. ## Usage This module allows for image discovery on parent aggregate objects such as -collections, compounds and paged objects. +collections, compounds and paged objects in multiple context. -## Configuration +### Search API + +Search API can be made to index URLs to the discovered image in multiple ways: + +- `deferred`: Create URL to dedicated endpoint which can handle the final image lookup. Given responses here can be aware of Drupal's cache tag invalidations, we can accordingly change what is ultimately served. +- `pre_generated`: Creates URL to styled image directly. May cause stale references to stick in the index, due to changing access control constraints. + +This is configurable on the field when it is added to be indexed. Effectively this defaults to `pre_generated` to maintain existing/current behaviour; however, `deferred` should possibly be preferred without other mechanisms to perform bulk reindexing due to changes on other entities. In particular, should there be something such as [Embargo](https://github.com/discoverygarden/embargo) and [Embargo Inheritance](https://github.com/discoverygarden/embargo_inheritance), where an access control statement applied to a parent node is expected to be applied to children. That said, `pre_generated` could be more convenient/efficient when there are no complex access control requirements in play. + +#### Deferral mechanism + +There are multiple plugins to dereference deferred URLs: + +- `redirect`: Issue a redirect to the final derived image destination from our endpoint. Easily enough done; however: + - incurs another round trip + - can cause a race condition if two items being displayed in a set of results happen to reference the same image. Drupal maintains a lock/semaphore around the image derivation: If the second request occurs while the first still has the lock for deriving the image, then the second request will receive an HTTP 503 with `Retry-After` of `3` seconds, but many browsers do not make use of the `Retry-After` header. +- `subrequest`: Perform subrequest to stream the image directly from our endpoint. Can deal with the 503 with `Retry-After`. + +The plugin in use is presently controlled with the `DGI_IMAGE_DISCOVERY_DEFERRED_PLUGIN`, which defaults to `subrequest`. + +### Views + +Views referencing node content can directly make use of a virtual field. ### Adding a "Representative Image" field to your content type @@ -34,7 +56,7 @@ To override the use of the "Islandora" thumbnail, you can add a new field to eac 1. In the "Manage fields" page for your content type, choose "Create a new field". 1. In the "Add a new field" list, choose "Media" (if on Drupal < 10.2, this is "Reference > Media") -1. Set the new field's label to "Representative image" so that the machine name of this field is `field_representative_image`. This machine name must be set; you can change the label later if you wish. +1. Set the new field's label to "Representative image" so that the machine name of this field is `field_representative_image`. This machine name must be set; you can change the label later if you wish. 1. On the next page, in the "Type of item to reference" setting, choose "Media" and leave the "Allowed number of values" at 1. 1. On the next page, in the "Media type" checkboxes, choose "Image". 1. Click on "Save settings". diff --git a/composer.json b/composer.json index 0b2bd87..17f2f84 100644 --- a/composer.json +++ b/composer.json @@ -4,5 +4,8 @@ "license": "GPL-3.0-only", "require": { "drupal/search_api": "^1.19" + }, + "conflict": { + "drupal/core": "<10.2" } } diff --git a/dgi_image_discovery.module b/dgi_image_discovery.module index 723250d..a4122af 100644 --- a/dgi_image_discovery.module +++ b/dgi_image_discovery.module @@ -5,10 +5,9 @@ * General hook implementations. */ -use Drupal\dgi_image_discovery\DIDImageItemList; - use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\dgi_image_discovery\DIDImageItemList; /** * Implements hook_entity_base_field_info(). diff --git a/dgi_image_discovery.routing.yml b/dgi_image_discovery.routing.yml new file mode 100644 index 0000000..fc32815 --- /dev/null +++ b/dgi_image_discovery.routing.yml @@ -0,0 +1,13 @@ +--- +dgi_image_discovery.deferred_resolution: + path: '/node/{node}/dgi_image_discovery/{style}' + defaults: + _controller: 'dgi_image_discovery.deferred_resolution_controller:resolve' + requirements: + _entity_access: node.view + options: + parameters: + style: + type: entity:image_style + node: + type: entity:node diff --git a/dgi_image_discovery.services.yml b/dgi_image_discovery.services.yml index f027034..d6d6b30 100644 --- a/dgi_image_discovery.services.yml +++ b/dgi_image_discovery.services.yml @@ -25,3 +25,14 @@ services: class: '\Drupal\dgi_image_discovery\EventSubscriber\DiscoverRepresentativeImageSubscriber' tags: - name: event_subscriber + dgi_image_discovery.deferred_resolution_controller: + class: '\Drupal\dgi_image_discovery\Controller\DeferredResolutionController' + factory: [null, 'create'] + arguments: + - '@service_container' + plugin.manager.dgi_image_discovery.url_generator: + class: Drupal\dgi_image_discovery\UrlGeneratorPluginManager + parent: default_plugin_manager + plugin.manager.dgi_image_discovery.url_generator.deferred: + class: Drupal\dgi_image_discovery\DeferredResolutionPluginManager + parent: default_plugin_manager diff --git a/src/Attribute/DeferredResolution.php b/src/Attribute/DeferredResolution.php new file mode 100644 index 0000000..6851231 --- /dev/null +++ b/src/Attribute/DeferredResolution.php @@ -0,0 +1,38 @@ +uri = $file instanceof \SplFileInfo ? $file->getPathname() : $file; + return parent::setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + } + + /** + * {@inheritDoc} + */ + public function __sleep() { + return array_diff($this->traitSleep(), [ + 'file', + ]); + } + + /** + * {@inheritDoc} + */ + public function __wakeup() : void { + $this->traitWakeup(); + $this->setFile($this->uri); + } + + /** + * Convert a BinaryFileResponse into a CacheableBinaryFileResponse. + * + * @param \Symfony\Component\HttpFoundation\BinaryFileResponse $response + * The response to convert. + * + * @return static + * The converted response. + */ + public static function convert(BinaryFileResponse $response) : static { + return new static( + $response->getFile(), + $response->getStatusCode(), + $response->headers->all(), + /* $public, $contentDisposition, $autoEtag, $autoLastModified all accounted for in headers */ + ); + } + +} diff --git a/src/Controller/DeferredResolutionController.php b/src/Controller/DeferredResolutionController.php new file mode 100644 index 0000000..fc89bb5 --- /dev/null +++ b/src/Controller/DeferredResolutionController.php @@ -0,0 +1,78 @@ +get('plugin.manager.dgi_image_discovery.url_generator.deferred'), + $container->get('renderer'), + ); + } + + /** + * Resolve image for the given node and style. + * + * @param \Drupal\image\ImageStyleInterface $style + * The style of image to get. + * @param \Drupal\node\NodeInterface $node + * The node of which to get an image. + * + * @return \Drupal\Core\Cache\CacheableResponseInterface + * A cacheable response. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function resolve(ImageStyleInterface $style, NodeInterface $node) : CacheableResponseInterface { + $context = new RenderContext(); + /** @var \Drupal\Core\Cache\CacheableResponseInterface $response */ + $response = $this->renderer->executeInRenderContext($context, function () use ($style, $node) { + // @todo Make plugin configurable? + /** @var \Drupal\dgi_image_discovery\DeferredResolutionInterface $plugin */ + $plugin = $this->deferredResolutionPluginManager->createInstance(getenv('DGI_IMAGE_DISCOVERY_DEFERRED_PLUGIN') ?: 'subrequest'); + + try { + return $plugin->resolve($node, $style); + } + catch (CacheableHttpException $e) { + return (new CacheableResponse($e->getMessage(), $e->getStatusCode())) + ->addCacheableDependency($e); + } + }); + + if (!$context->isEmpty()) { + $metadata = $context->pop(); + $response->addCacheableDependency($metadata); + } + + return $response; + + } + +} diff --git a/src/DIDImageItemList.php b/src/DIDImageItemList.php index 2547e06..df5da8c 100644 --- a/src/DIDImageItemList.php +++ b/src/DIDImageItemList.php @@ -2,8 +2,8 @@ namespace Drupal\dgi_image_discovery; -use Drupal\Core\TypedData\ComputedItemListTrait; use Drupal\Core\Field\EntityReferenceFieldItemList; +use Drupal\Core\TypedData\ComputedItemListTrait; /** * Boiler-plate for our computed field. diff --git a/src/DeferredResolutionInterface.php b/src/DeferredResolutionInterface.php new file mode 100644 index 0000000..95560e0 --- /dev/null +++ b/src/DeferredResolutionInterface.php @@ -0,0 +1,34 @@ +entityTypeManager; + } + + /** + * {@inheritdoc} + */ + protected function getImageDiscovery(): ImageDiscoveryInterface { + return $this->imageDiscovery; + } + + /** + * {@inheritdoc} + */ + public function label(): string { + // Cast the label to a string since it is a TranslatableMarkup object. + return (string) $this->pluginDefinition['label']; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) : static { + $instance = new static($configuration, $plugin_id, $plugin_definition); + + $instance->entityTypeManager = $container->get('entity_type.manager'); + $instance->imageDiscovery = $container->get('dgi_image_discovery.service'); + + return $instance; + } + +} diff --git a/src/DeferredResolutionPluginManager.php b/src/DeferredResolutionPluginManager.php new file mode 100644 index 0000000..cf21730 --- /dev/null +++ b/src/DeferredResolutionPluginManager.php @@ -0,0 +1,34 @@ +alterInfo('dgi_image_discovery__url_generator_info__deferred'); + $this->setCacheBackend($cache_backend, 'dgi_image_discovery__url_generator_plugins__deferred'); + } + + /** + * {@inheritDoc} + */ + public function getFallbackPluginId($plugin_id, array $configuration = []) : string { + return 'redirect'; + } + +} diff --git a/src/DeferredResolutionPluginManagerInterface.php b/src/DeferredResolutionPluginManagerInterface.php new file mode 100644 index 0000000..480cbf2 --- /dev/null +++ b/src/DeferredResolutionPluginManagerInterface.php @@ -0,0 +1,12 @@ +imageDiscovery = $image_discovery; $this->nodeStorage = $entity_type_manager->getStorage('node'); diff --git a/src/EventSubscriber/DiscoverOwnedThumbnailSubscriber.php b/src/EventSubscriber/DiscoverOwnedThumbnailSubscriber.php index 1ea64a4..0eecee8 100644 --- a/src/EventSubscriber/DiscoverOwnedThumbnailSubscriber.php +++ b/src/EventSubscriber/DiscoverOwnedThumbnailSubscriber.php @@ -2,11 +2,10 @@ namespace Drupal\dgi_image_discovery\EventSubscriber; -use Drupal\dgi_image_discovery\ImageDiscoveryEvent; -use Drupal\node\NodeInterface; - use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\dgi_image_discovery\ImageDiscoveryEvent; +use Drupal\node\NodeInterface; /** * Discover thumbnails which are owned by a given object. @@ -26,7 +25,7 @@ class DiscoverOwnedThumbnailSubscriber extends AbstractImageDiscoverySubscriber * Constructor. */ public function __construct( - EntityTypeManagerInterface $entity_type_manager + EntityTypeManagerInterface $entity_type_manager, ) { $this->mediaStorage = $entity_type_manager->getStorage('media'); } diff --git a/src/EventSubscriber/ViewsFieldSubscriber.php b/src/EventSubscriber/ViewsFieldSubscriber.php index 603f9ff..b15ef28 100644 --- a/src/EventSubscriber/ViewsFieldSubscriber.php +++ b/src/EventSubscriber/ViewsFieldSubscriber.php @@ -2,9 +2,8 @@ namespace Drupal\dgi_image_discovery\EventSubscriber; -use Drupal\search_api\Event\SearchApiEvents; use Drupal\search_api\Event\MappingViewsFieldHandlersEvent; - +use Drupal\search_api\Event\SearchApiEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** diff --git a/src/ImageDiscovery.php b/src/ImageDiscovery.php index cb1c4f5..1308b05 100644 --- a/src/ImageDiscovery.php +++ b/src/ImageDiscovery.php @@ -11,20 +11,13 @@ */ class ImageDiscovery implements ImageDiscoveryInterface { - /** - * The event dispatcher service. - * - * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface - */ - protected EventDispatcherInterface $eventDispatcher; - /** * Constructor. */ public function __construct( - EventDispatcherInterface $event_dispatcher + protected EventDispatcherInterface $eventDispatcher, ) { - $this->eventDispatcher = $event_dispatcher; + // No-op, other than setting property. } /** diff --git a/src/ImageDiscoveryEvent.php b/src/ImageDiscoveryEvent.php index b3d2727..1ee66c9 100644 --- a/src/ImageDiscoveryEvent.php +++ b/src/ImageDiscoveryEvent.php @@ -2,12 +2,10 @@ namespace Drupal\dgi_image_discovery; -use Drupal\media\MediaInterface; - use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Cache\RefinableCacheableDependencyTrait; use Drupal\Core\Entity\ContentEntityInterface; - +use Drupal\media\MediaInterface; use Symfony\Contracts\EventDispatcher\Event; /** diff --git a/src/Plugin/Field/FieldType/DIDImageItem.php b/src/Plugin/Field/FieldType/DIDImageItem.php index 7d81434..08c769b 100644 --- a/src/Plugin/Field/FieldType/DIDImageItem.php +++ b/src/Plugin/Field/FieldType/DIDImageItem.php @@ -2,13 +2,12 @@ namespace Drupal\dgi_image_discovery\Plugin\Field\FieldType; -use Drupal\dgi_image_discovery\ImageDiscoveryInterface; - use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Cache\RefinableCacheableDependencyTrait; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\Core\TypedData\DataDefinitionInterface; use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\dgi_image_discovery\ImageDiscoveryInterface; /** * Find image media related to the given node. @@ -46,7 +45,7 @@ class DIDImageItem extends EntityReferenceItem implements RefinableCacheableDepe public function __construct( DataDefinitionInterface $definition, $name = NULL, - TypedDataInterface $parent = NULL + TypedDataInterface $parent = NULL, ) { parent::__construct($definition, $name, $parent); diff --git a/src/Plugin/dgi_image_discovery/url_generator/Deferred.php b/src/Plugin/dgi_image_discovery/url_generator/Deferred.php new file mode 100644 index 0000000..0a85649 --- /dev/null +++ b/src/Plugin/dgi_image_discovery/url_generator/Deferred.php @@ -0,0 +1,46 @@ + $node->id(), + 'style' => $style->id(), + ], + [ + 'absolute' => TRUE, + // XXX: Prevent use of aliased paths. + 'alias' => TRUE, + ], + ) + ->toString(TRUE) + ->addCacheableDependency($node) + ->addCacheableDependency($style); + } + +} diff --git a/src/Plugin/dgi_image_discovery/url_generator/PreGenerated.php b/src/Plugin/dgi_image_discovery/url_generator/PreGenerated.php new file mode 100644 index 0000000..bff3226 --- /dev/null +++ b/src/Plugin/dgi_image_discovery/url_generator/PreGenerated.php @@ -0,0 +1,83 @@ +getGeneratedUrl($node, $style); + } + catch (NotFoundHttpException) { + return NULL; + } + } + + /** + * {@inheritDoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = new static($configuration, $plugin_id, $plugin_definition); + + $instance->imageDiscovery = $container->get('dgi_image_discovery.service'); + $instance->entityTypeManager = $container->get('entity_type.manager'); + + return $instance; + } + + /** + * {@inheritDoc} + */ + protected function getImageDiscovery(): ImageDiscoveryInterface { + return $this->imageDiscovery; + } + + /** + * {@inheritDoc} + */ + protected function getEntityTypeManager(): EntityTypeManagerInterface { + return $this->entityTypeManager; + } + +} diff --git a/src/Plugin/dgi_image_discovery/url_generator/UrlGenerationTrait.php b/src/Plugin/dgi_image_discovery/url_generator/UrlGenerationTrait.php new file mode 100644 index 0000000..c03e7f2 --- /dev/null +++ b/src/Plugin/dgi_image_discovery/url_generator/UrlGenerationTrait.php @@ -0,0 +1,75 @@ +addCacheableDependency($node) + ->addCacheableDependency($style); + + $event = $this->getImageDiscovery()->getImage($node); + $generated_url->addCacheableDependency($event); + $media = $event->getMedia(); + if (empty($media)) { + throw new CacheableNotFoundHttpException($generated_url, "No media discovered for node ({$node->id()})."); + } + + $generated_url->addCacheableDependency($media); + + $media_source = $media->getSource(); + $file_id = $media_source->getSourceFieldValue($media); + /** @var \Drupal\file\FileInterface|null $image */ + $image = $this->getEntityTypeManager()->getStorage('file')->load($file_id); + if (empty($image)) { + throw new CacheableNotFoundHttpException($generated_url, "File ID ({$file_id}) discovered for node ({$node->id()}) could not be loaded."); + } + + $generated_url->addCacheableDependency($image); + + return $generated_url->setGeneratedUrl($style->buildUrl($image->getFileUri())); + } + +} diff --git a/src/Plugin/dgi_image_discovery/url_generator/deferred/Redirect.php b/src/Plugin/dgi_image_discovery/url_generator/deferred/Redirect.php new file mode 100644 index 0000000..e6327b3 --- /dev/null +++ b/src/Plugin/dgi_image_discovery/url_generator/deferred/Redirect.php @@ -0,0 +1,35 @@ +getGeneratedUrl($node, $style); + + return (new CacheableRedirectResponse($generated_url->getGeneratedUrl())) + ->addCacheableDependency($generated_url); + } + +} diff --git a/src/Plugin/dgi_image_discovery/url_generator/deferred/Subrequest.php b/src/Plugin/dgi_image_discovery/url_generator/deferred/Subrequest.php new file mode 100644 index 0000000..595b32a --- /dev/null +++ b/src/Plugin/dgi_image_discovery/url_generator/deferred/Subrequest.php @@ -0,0 +1,122 @@ +getGeneratedUrl($node, $style); + $response = NULL; + + $attempts = 3; + while ($attempts-- > 0) { + $current_request = $this->requestStack->getCurrentRequest(); + $request = Request::create($generated_url->getGeneratedUrl()); + $request->setSession($current_request->getSession()); + $response = $this->httpKernel->handle($request, HttpKernelInterface::SUB_REQUEST); + if ($response instanceof BinaryFileResponse) { + return CacheableBinaryFileResponse::convert($response) + ->setAutoEtag() + ->setAutoLastModified() + ->addCacheableDependency($generated_url); + } + elseif ($response->getStatusCode() === 503) { + $after = $response->headers->get('Retry-After'); + if ($after === NULL) { + // No value. + throw $this->getExceptionFromResponse($response, $generated_url); + } + $after = intval($after); + if ($after > 0 && $after <= 5) { + // If we have a requested time for which we are willing to wait, let's + // wait it out. Image derivation indicates 3 seconds, but let's allow + // up to 5 in the case of some other process requesting such, just to + // be nice. + sleep($after); + continue; + } + } + throw $this->getExceptionFromResponse($response, $generated_url); + } + + throw $this->getExceptionFromResponse($response, $generated_url); + } + + /** + * Helper; build out cachable exception from the given response for the URL. + * + * @param \Symfony\Component\HttpFoundation\Response $response + * The response for which to build an exception. + * @param \Drupal\Core\GeneratedUrl $generated_url + * The URL related to the response. + * + * @return \Drupal\Core\Http\Exception\CacheableHttpException + * The built exception. + */ + protected function getExceptionFromResponse(Response $response, GeneratedUrl $generated_url) : CacheableHttpException { + if ($after = $response->headers->get('Retry-After')) { + $generated_url->mergeCacheMaxAge($after); + } + return new CacheableHttpException( + $generated_url, + $response->getStatusCode() ?? 500, + $response->getContent() ?? '', + ); + } + + /** + * {@inheritDoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + + $instance->httpKernel = $container->get('http_kernel'); + $instance->requestStack = $container->get('request_stack'); + + return $instance; + } + +} diff --git a/src/Plugin/search_api/processor/DgiImageDiscovery.php b/src/Plugin/search_api/processor/DgiImageDiscovery.php index b0674f8..ec837c4 100644 --- a/src/Plugin/search_api/processor/DgiImageDiscovery.php +++ b/src/Plugin/search_api/processor/DgiImageDiscovery.php @@ -6,6 +6,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\dgi_image_discovery\ImageDiscoveryInterface; use Drupal\dgi_image_discovery\Plugin\search_api\processor\Property\DgiImageDiscoveryProperty; +use Drupal\dgi_image_discovery\UrlGeneratorPluginManagerInterface; use Drupal\node\NodeInterface; use Drupal\search_api\Datasource\DatasourceInterface; use Drupal\search_api\Item\ItemInterface; @@ -50,7 +51,8 @@ public function __construct( $plugin_id, $plugin_definition, ImageDiscoveryInterface $image_discovery, - EntityTypeManagerInterface $entity_type_manager + EntityTypeManagerInterface $entity_type_manager, + protected UrlGeneratorPluginManagerInterface $urlGeneratorPluginManager, ) { parent::__construct($configuration, $plugin_id, $plugin_definition); @@ -67,7 +69,8 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $container->get('dgi_image_discovery.service'), - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('plugin.manager.dgi_image_discovery.url_generator'), ); } @@ -84,6 +87,7 @@ public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) { 'type' => 'string', 'is_list' => FALSE, 'processor_id' => $this->getPluginId(), + 'dgi_image_discovery__url_generator_options' => $this->getGeneratorOptions(), ]; $properties['dgi_image_discovery'] = new DgiImageDiscoveryProperty($definition); } @@ -91,36 +95,42 @@ public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) { return $properties; } + /** + * Helper; get listing of generators for use as form options. + * + * @return string[] + * An array mapping plugin IDs to human-readable strings. + */ + protected function getGeneratorOptions() : array { + $options = []; + + foreach ($this->urlGeneratorPluginManager->getDefinitions() as $plugin_id => $definition) { + $options[$plugin_id] = $definition['label']; + } + + return $options; + } + /** * {@inheritdoc} */ public function addFieldValues(ItemInterface $item) { $entity = $item->getOriginalObject()->getValue(); - $value = NULL; // Get the image discovery URL. if (!$entity->isNew() && $entity instanceof NodeInterface) { - $event = $this->imageDiscovery->getImage($entity); - $media = $event->getMedia(); - if (empty($media)) { - return; - } - - $media_source = $media->getSource(); - $file_id = $media_source->getSourceFieldValue($media); - $image = $this->entityTypeManager->getStorage('file')->load($file_id); - if (empty($image)) { - return; - } - $fields = $item->getFields(FALSE); $fields = $this->getFieldsHelper()->filterForPropertyPath($fields, NULL, 'dgi_image_discovery'); foreach ($fields as $field) { $config = $field->getConfiguration(); - $image_style = $config['image_style']; - $value = $this->entityTypeManager->getStorage('image_style')->load($image_style) - ->buildUrl($image->getFileUri()); - $field->addValue($value); + /** @var \Drupal\image\ImageStyleInterface $image_style */ + $image_style = $this->entityTypeManager->getStorage('image_style')->load($config['image_style']); + /** @var \Drupal\dgi_image_discovery\UrlGeneratorInterface $url_generator */ + $url_generator = $this->urlGeneratorPluginManager->createInstance($config['url_generator'] ?? 'pre_generated'); + $generated_url = $url_generator->generate($entity, $image_style); + if ($generated_url) { + $field->addValue($generated_url->getGeneratedUrl()); + } } } } diff --git a/src/Plugin/search_api/processor/Property/DgiImageDiscoveryProperty.php b/src/Plugin/search_api/processor/Property/DgiImageDiscoveryProperty.php index 3a22b30..20ba26e 100644 --- a/src/Plugin/search_api/processor/Property/DgiImageDiscoveryProperty.php +++ b/src/Plugin/search_api/processor/Property/DgiImageDiscoveryProperty.php @@ -20,6 +20,7 @@ class DgiImageDiscoveryProperty extends ConfigurablePropertyBase { public function defaultConfiguration() { return [ 'image_style' => 'solr_grid_thumbnail', + 'url_generator' => 'pre_generated', ]; } @@ -36,6 +37,12 @@ public function buildConfigurationForm(FieldInterface $field, array $form, FormS '#description' => $this->t('Select the image style that should be applied to derive the DGI Image Discovery image url.'), '#default_value' => $configuration['image_style'], ]; + $form['url_generator'] = [ + '#type' => 'select', + '#options' => $this->definition['dgi_image_discovery__url_generator_options'], + '#title' => $this->t('URL Generator'), + '#default_value' => $configuration['url_generator'], + ]; return $form; } diff --git a/src/UrlGeneratorInterface.php b/src/UrlGeneratorInterface.php new file mode 100644 index 0000000..65070b1 --- /dev/null +++ b/src/UrlGeneratorInterface.php @@ -0,0 +1,34 @@ +pluginDefinition['label']; + } + +} diff --git a/src/UrlGeneratorPluginManager.php b/src/UrlGeneratorPluginManager.php new file mode 100644 index 0000000..3578fe8 --- /dev/null +++ b/src/UrlGeneratorPluginManager.php @@ -0,0 +1,34 @@ +alterInfo('dgi_image_discovery__url_generator_info'); + $this->setCacheBackend($cache_backend, 'dgi_image_discovery__url_generator_plugins'); + } + + /** + * {@inheritDoc} + */ + public function getFallbackPluginId($plugin_id, array $configuration = []) : string { + return 'pre_generated'; + } + +} diff --git a/src/UrlGeneratorPluginManagerInterface.php b/src/UrlGeneratorPluginManagerInterface.php new file mode 100644 index 0000000..fc236a0 --- /dev/null +++ b/src/UrlGeneratorPluginManagerInterface.php @@ -0,0 +1,12 @@ +