diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..091b703 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,11 @@ +--- +name: Code Linting +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + Lint: + uses: discoverygarden/CodeSniffer/.github/workflows/lint.yml@v1 diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml new file mode 100644 index 0000000..7a9c768 --- /dev/null +++ b/.github/workflows/semver.yml @@ -0,0 +1,12 @@ +--- +name: Semantic Version Tagging +on: + pull_request_target: + types: closed + branches: + - main + +jobs: + tag_update: + name: Tag Update + uses: discoverygarden/auto-semver/.github/workflows/semver.yml@v1 diff --git a/README.md b/README.md index 3ab7ca0..278f23a 100644 --- a/README.md +++ b/README.md @@ -1 +1,49 @@ -# iiif_presentation_api \ No newline at end of file +# IIIF Presentation API + +## Introduction + +Provides serialization to IIIF Presentation API. + +## Requirements + +This module requires the following modules/libraries: + +* Serialization (part of Drupal core) + +## Installation + +Install as usual, see +[this](https://www.drupal.org/docs/extending-drupal/installing-modules) for +further information. + +## Configuration + +Out of the box the module provides minimal implementation and expects other +modules to either decorate or provide their own implementation by extending the +provided normalizers. + +For any content entity that should be exposed the format will need to be +configured as [documented by Drupal][1]. + +## Troubleshooting/Issues + +Having problems or solved one? contact +[discoverygarden](http://support.discoverygarden.ca). + +## Maintainers/Sponsors + +Current maintainers: + +* [discoverygarden](http://www.discoverygarden.ca) + +## Development + +If you would like to contribute to this module create an issue, pull request +and or contact +[discoverygarden](http://support.discoverygarden.ca). + +## License + +[GPLv3](http://www.gnu.org/licenses/gpl-3.0.txt) + +[1]: https://www.drupal.org/docs/drupal-apis/restful-web-services-api/restful-web-services-api-overview#s-api-features] diff --git a/iiif_presentation_api.info.yml b/iiif_presentation_api.info.yml new file mode 100644 index 0000000..e8cd4df --- /dev/null +++ b/iiif_presentation_api.info.yml @@ -0,0 +1,7 @@ +name: IIIF Presentation API +type: module +description: 'Provides a IIIF Presentation API implementation for Drupal entities.' +package: DGI +core_version_requirement: ^9 || ^10 +dependencies: + - drupal:serialization diff --git a/iiif_presentation_api.services.yml b/iiif_presentation_api.services.yml new file mode 100644 index 0000000..05b3c1e --- /dev/null +++ b/iiif_presentation_api.services.yml @@ -0,0 +1,18 @@ +services: + serializer.encoder.iiif_presentation_api.iiif_p_v3.encoder: + class: 'Drupal\iiif_presentation_api\Encoder\V3\IiifP' + tags: + - { name: encoder, priority: 20, format: 'iiif-p-v3' } + serializer.normalizer.iiif_presentation_api.iiif_p_v3.content_entity: + class: 'Drupal\iiif_presentation_api\Normalizer\V3\ContentEntityNormalizer' + tags: + - { name: normalizer, priority: 10 } + arguments: ['@current_user'] + serializer.normalizer.iiif_presentation_api.iiif_p_v3.field_item_list: + class: 'Drupal\iiif_presentation_api\Normalizer\V3\FieldItemListNormalizer' + tags: + - { name: normalizer, priority: 10 } + serializer.normalizer.iiif_presentation_api.iiif_p_v3.field_item: + class: 'Drupal\iiif_presentation_api\Normalizer\V3\FieldItemNormalizer' + tags: + - { name: normalizer, priority: 10 } diff --git a/src/Encoder/V3/IiifP.php b/src/Encoder/V3/IiifP.php new file mode 100644 index 0000000..c80a586 --- /dev/null +++ b/src/Encoder/V3/IiifP.php @@ -0,0 +1,19 @@ +has('http_middleware.negotiation') && is_a($container->getDefinition('http_middleware.negotiation')->getClass(), '\Drupal\Core\StackMiddleware\NegotiationMiddleware', TRUE)) { + $container->getDefinition('http_middleware.negotiation')->addMethodCall('registerFormat', [ + 'iiif-p-v3', + ['application/json'], + ]); + } + } + +} diff --git a/src/Normalizer/EntityUriTrait.php b/src/Normalizer/EntityUriTrait.php new file mode 100644 index 0000000..f3b3930 --- /dev/null +++ b/src/Normalizer/EntityUriTrait.php @@ -0,0 +1,53 @@ +isNew()) { + return ''; + } + + $route_name = 'rest.entity.' . $entity->getEntityTypeId() . '.GET'; + if ($entity->hasLinkTemplate('canonical')) { + $url = $entity->toUrl('canonical'); + } + elseif ($this->getRouteProvider()->getRoutesByNames([$route_name])) { + $url = Url::fromRoute('rest.entity.' . $entity->getEntityTypeId() . '.GET', [$entity->getEntityTypeId() => $entity->id()]); + } + else { + return ''; + } + + $url->setAbsolute(); + if (!$url->isExternal()) { + $url->setRouteParameter('_format', 'iiif-p-v3'); + } + $generated_url = $url->toString(TRUE); + $this->addCacheableDependency($context, $generated_url); + + return $generated_url->getGeneratedUrl(); + } + +} diff --git a/src/Normalizer/RouterProviderTrait.php b/src/Normalizer/RouterProviderTrait.php new file mode 100644 index 0000000..0024487 --- /dev/null +++ b/src/Normalizer/RouterProviderTrait.php @@ -0,0 +1,41 @@ +routeProvider)) { + $this->routeProvider = \Drupal::service('router.route_provider'); + } + return $this->routeProvider; + } + + /** + * Set the route provider service. + * + * @param \Drupal\Core\Routing\RouteProviderInterface $routeProvider + * The route provider service. + */ + protected function setRouteProvider(RouteProviderInterface $routeProvider): void { + $this->routeProvider = $routeProvider; + } + +} diff --git a/src/Normalizer/V3/ContentEntityNormalizer.php b/src/Normalizer/V3/ContentEntityNormalizer.php new file mode 100644 index 0000000..969b102 --- /dev/null +++ b/src/Normalizer/V3/ContentEntityNormalizer.php @@ -0,0 +1,115 @@ +user = $user; + } + + /** + * {@inheritDoc} + */ + public function normalize($object, $format = NULL, array $context = []) { + + $normalized = []; + if (!isset($context['base-depth'])) { + $context['base-depth'] = TRUE; + $normalized['@context'] = 'http://iiif.io/api/presentation/3/context.json'; + } + else { + $context['base-depth'] = FALSE; + } + $normalized += [ + 'id' => $this->getEntityUri($object, $context), + 'type' => $context['base-depth'] ? 'Manifest' : 'Canvas', + 'label' => [ + 'none' => [$object->label()], + ], + ]; + + $context += [ + 'account' => $this->user, + ]; + + if (isset($context[static::SERIALIZATION_CONTEXT_CACHEABILITY])) { + $context[static::SERIALIZATION_CONTEXT_CACHEABILITY]->addCacheContexts(['user.roles']); + } + + $context['parent'] = [ + 'type' => $normalized['type'], + 'id' => $normalized['id'], + ]; + return $this->normalizeEntityFields($object, $format, $context, $normalized); + } + + /** + * Normalizes all fields present on a content entity. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $object + * The entity being normalized. + * @param string $format + * The format being serialized. + * @param array $context + * An array containing any context being passed to the normalizers. + * @param array $normalized + * An array representing the normalized entity to be rendered. + * + * @return array + * The normalized representation of the entity. + */ + protected function normalizeEntityFields(ContentEntityInterface $object, string $format, array $context, array $normalized): array { + $this->addCacheableDependency($context, $object); + foreach ($object->getFields() as $field) { + $access = $field->access('view', $context['account'], TRUE); + $this->addCacheableDependency($context, $access); + if (!$access->isAllowed()) { + continue; + } + $normalized_property = $this->serializer->normalize($field, $format, $context); + if (!empty($normalized_property)) { + $normalized = NestedArray::mergeDeep($normalized, (array) $normalized_property); + } + } + return $normalized; + } + + /** + * {@inheritDoc} + */ + public function getSupportedTypes(?string $format): array { + return [ + ContentEntityInterface::class => TRUE, + ]; + } + +} diff --git a/src/Normalizer/V3/FieldItemListNormalizer.php b/src/Normalizer/V3/FieldItemListNormalizer.php new file mode 100644 index 0000000..7b8bf82 --- /dev/null +++ b/src/Normalizer/V3/FieldItemListNormalizer.php @@ -0,0 +1,26 @@ + TRUE, + ]; + } + +} diff --git a/src/Normalizer/V3/FieldItemNormalizer.php b/src/Normalizer/V3/FieldItemNormalizer.php new file mode 100644 index 0000000..78b5074 --- /dev/null +++ b/src/Normalizer/V3/FieldItemNormalizer.php @@ -0,0 +1,26 @@ + FALSE, + ]; + } + +} diff --git a/src/Normalizer/V3/NormalizerBase.php b/src/Normalizer/V3/NormalizerBase.php new file mode 100644 index 0000000..7fabc1e --- /dev/null +++ b/src/Normalizer/V3/NormalizerBase.php @@ -0,0 +1,49 @@ +format; + } + + /** + * {@inheritdoc} + */ + public function hasCacheableSupportsMethod(): bool { + return TRUE; + } + +}