Drupal modules for browsing and managing Fedora-based digital repositories.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

313 lines
8.9 KiB

<?php
namespace Drupal\islandora\EventSubscriber;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Entity\EntityFieldManager;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\islandora\IslandoraUtils;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class LinkHeaderSubscriber.
*
* @package Drupal\islandora\EventSubscriber
*/
abstract class LinkHeaderSubscriber implements EventSubscriberInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManager
*/
protected $entityFieldManager;
/**
* The access manager.
*
* @var \Drupal\Core\Access\AccessManagerInterface
*/
protected $accessManager;
/**
* Current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The route match object.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Request stack (for current request).
*
* @var Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Islandora utils.
*
* @var \Drupal\islandora\IslandoraUtils
*/
protected $utils;
/**
* Constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManager $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
* The access manager.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match object.
* @param Symfony\Component\HttpFoundation\RequestStack $request_stack
* Request stack (for current request).
* @param \Drupal\islandora\IslandoraUtils $utils
* Islandora utils.
*/
public function __construct(
EntityTypeManager $entity_type_manager,
EntityFieldManager $entity_field_manager,
AccessManagerInterface $access_manager,
AccountInterface $account,
RouteMatchInterface $route_match,
RequestStack $request_stack,
IslandoraUtils $utils
) {
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->accessManager = $access_manager;
$this->account = $account;
$this->routeMatch = $route_match;
$this->accessManager = $access_manager;
$this->account = $account;
$this->requestStack = $request_stack;
$this->utils = $utils;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Run this early so the headers get cached.
$events[KernelEvents::RESPONSE][] = ['onResponse', 129];
return $events;
}
/**
* Get the Node | Media | File.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* The current response object.
* @param string $object_type
* The type of entity to look for.
*
* @return Drupal\Core\Entity\ContentEntityBase|bool
* A node or media entity or FALSE if we should skip out.
*/
protected function getObject(Response $response, $object_type) {
if ($object_type != 'node'
&& $object_type != 'media'
) {
return FALSE;
}
// Exit early if the response is already cached.
if ($response->headers->get('X-Drupal-Dynamic-Cache') == 'HIT') {
return FALSE;
}
if (!$response->isOk()) {
return FALSE;
}
// Hack the node out of the route.
$route_object = $this->routeMatch->getRouteObject();
if (!$route_object) {
return FALSE;
}
$methods = $route_object->getMethods();
$is_get = in_array('GET', $methods);
$is_head = in_array('HEAD', $methods);
if (!($is_get || $is_head)) {
return FALSE;
}
$route_contexts = $route_object->getOption('parameters');
if (!$route_contexts) {
return FALSE;
}
if (!isset($route_contexts[$object_type])) {
return FALSE;
}
$object = $this->routeMatch->getParameter($object_type);
if (!$object) {
return FALSE;
}
return $object;
}
/**
* Generates link headers for each referenced entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Entity that has reference fields.
*
* @return string[]
* Array of link headers
*/
protected function generateEntityReferenceLinks(EntityInterface $entity) {
// Use the node to add link headers for each entity reference.
$entity_type = $entity->getEntityType()->id();
$bundle = $entity->bundle();
// Get all fields for the entity.
$fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
// Strip out everything but entity references that are not base fields.
$entity_reference_fields = array_filter($fields, function ($field) {
return $field->getFieldStorageDefinition()->isBaseField() == FALSE && $field->getType() == "entity_reference";
});
// Collect links for referenced entities.
$links = [];
foreach ($entity_reference_fields as $field_name => $field_definition) {
foreach ($entity->get($field_name)->referencedEntities() as $referencedEntity) {
// Headers are subject to an access check.
if ($referencedEntity->access('view')) {
$entity_url = $this->utils->getEntityUrl($referencedEntity);
// Taxonomy terms are written out as
// <url>; rel="tag"; title="Tag Name"
// where url is defined in field_same_as.
// If field_same_as doesn't exist or is empty,
// it becomes the taxonomy term's local uri.
if ($referencedEntity->getEntityTypeId() == 'taxonomy_term') {
$rel = "tag";
if ($referencedEntity->hasField('field_external_uri')) {
$external_uri = $referencedEntity->get('field_external_uri')->getValue();
if (!empty($external_uri) && isset($external_uri[0]['uri'])) {
$entity_url = $external_uri[0]['uri'];
}
}
$title = $referencedEntity->label();
}
else {
// If it's not a taxonomy term, referenced entity link
// headers take the form
// <url>; rel="related"; title="Field Label"
// and the url is the local uri.
$rel = "related";
$title = $field_definition->label();
}
$links[] = "<$entity_url>; rel=\"$rel\"; title=\"$title\"";
}
}
}
return $links;
}
/**
* Generates link headers for REST endpoints.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Entity that has reference fields.
*
* @return string[]
* Array of link headers
*/
protected function generateRestLinks(EntityInterface $entity) {
$rest_resource_config_storage = $this->entityTypeManager->getStorage('rest_resource_config');
$entity_type = $entity->getEntityType()->id();
$rest_resource_config = $rest_resource_config_storage->load("entity.$entity_type");
$current_format = $this->requestStack->getCurrentRequest()->query->get('_format');
$links = [];
$route_name = $this->routeMatch->getRouteName();
if ($rest_resource_config) {
$formats = $rest_resource_config->getFormats("GET");
foreach ($formats as $format) {
if ($format == $current_format) {
continue;
}
switch ($format) {
case 'json':
$mime = 'application/json';
break;
case 'jsonld':
$mime = 'application/ld+json';
break;
case 'hal_json':
$mime = 'application/hal+json';
break;
case 'xml':
$mime = 'application/xml';
break;
default:
continue;
}
// Skip route if the user doesn't have access.
$meta_route_name = "rest.entity.$entity_type.GET";
$route_params = [$entity_type => $entity->id()];
if (!$this->accessManager->checkNamedRoute($meta_route_name, $route_params, $this->account)) {
continue;
}
$meta_url = $this->utils->getRestUrl($entity, $format);
$links[] = "<$meta_url>; rel=\"alternate\"; type=\"$mime\"";
}
}
return $links;
}
/**
* Adds resource-specific link headers to appropriate responses.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* Event containing the response.
*/
abstract public function onResponse(FilterResponseEvent $event);
}