rest_oai_pmh-8.x-1.0-beta1/src/Plugin/rest/resource/OaiPmh.php

src/Plugin/rest/resource/OaiPmh.php
<?php

namespace Drupal\rest_oai_pmh\Plugin\rest\resource;

use Drupal\Core\Session\AccountProxyInterface;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Psr\Log\LoggerInterface;
use Drupal\Core\Config\ImmutableConfig;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Render\RenderContext;

/**
 * Provides a resource to get view modes by entity and bundle.
 *
 * @RestResource(
 *   id = "oai_pmh",
 *   label = @Translation("OAI-PMH"),
 *   uri_paths = {
 *     "canonical" = "/oai/request",
 *     "https://www.drupal.org/link-relations/create" = "/oai/request"
 *   }
 * )
 */
class OaiPmh extends ResourceBase {

  const OAI_DEFAULT_PATH = '/oai/request';
  const OAI_DATE_FORMAT = 'Y-m-d\TH:i:s\Z';

  /**
   * A current user instance.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * The request being served.
   *
   * @var \Symfony\Component\HttpFoundation\Request
   */
  protected $currentRequest;

  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The REST response.
   *
   * @var \Drupal\rest\ResourceResponse
   */
  private $response = [];

  /**
   * An array of error(s) for the response. Default false.
   *
   * @var array
   */
  private $error = FALSE;

  /**
   * An entity that is being exposed to OAI-PMH.
   *
   * @var \Drupal\Core\Entity\EntityInterface
   */
  private $entity;


  /**
   * The entity's type.
   *
   * @var string
   */
  private $bundle;


  /**
   * The OAI-PMH verb passed into the request.
   *
   * @var string
   */
  private $verb;


  /**
   * The view display IDs rendered by this OAI-PMH endpoint.
   *
   * @var array
   */
  private $viewDisplays;


  /**
   * The name of the repository this OAI-PMH endpoint is serving content for.
   *
   * @var string
   */
  private $repositoryName;

  /**
   * The name of the repository this OAI-PMH endpoint is serving content for.
   *
   * @var string
   */
  private $repositoryEmail;

  /**
   * The name of the repository this OAI-PMH endpoint is serving content for.
   *
   * @var string
   */
  private $repositoryPath;


  /**
   * Unix timestamp when the resumption token expires.
   *
   * @var int
   */
  private $expiration;

  /**
   * Object used to stored metadata about an OAI entity in current response.
   *
   * @var object
   */
  private $oaiEntity;

  /**
   * The metadataPrefix GET parameter from the current request.
   *
   * @var string
   */
  private $metadataPrefix;

  /**
   * The resumption token ID that will be returned in the current response.
   *
   * @var int
   */
  private $nextTokenId;

  /**
   * Kv store to get the current rest_oai_pmh.resumption_token.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
   */
  private $keyValueStore;

  /**
   * Module responsible for mapping Drupal fields to a metadata schema.
   *
   * @var string
   */
  private $metadataMapPlugins;

  /**
   * How OAI maps Drupal fields.
   *
   * @var string
   */
  private $mappingSource;

  /**
   * Whether this OAI endpoint support sets.
   *
   * @var bool
   */
  private $supportSets;

  /**
   * Constructs a new OaiPmh object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param array $serializer_formats
   *   The available serialization formats.
   * @param \Drupal\Core\Config\ImmutableConfig $config
   *   An immutable config object.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   A current user instance.
   * @param \Symfony\Component\HttpFoundation\Request $currentRequest
   *   The request being made to the OAI-PMH endpoint.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   A module handler instance.
   */
  public function __construct(
        array $configuration,
        $plugin_id,
        $plugin_definition,
        array $serializer_formats,
        ImmutableConfig $config,
        LoggerInterface $logger,
        AccountProxyInterface $current_user,
        Request $currentRequest,
        ModuleHandlerInterface $module_handler
    ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);

    $this->currentUser = $current_user;
    $this->currentRequest = $currentRequest;
    $this->moduleHandler = $module_handler;

    // Read the config settings for this endpoint
    // and set some properties for this class from the config.
    $config = \Drupal::config('rest_oai_pmh.settings');
    $fields = [
      'bundle' => 'bundle',
      'view_displays' => 'viewDisplays',
      'repository_name' => 'repositoryName',
      'repository_email' => 'repositoryEmail',
      'repository_path' => 'repositoryPath',
      'expiration' => 'expiration',
      'support_sets' => 'supportSets',
      'mapping_source' => 'mappingSource',
      'metadata_map_plugins' => 'metadataMapPlugins',
    ];
    foreach ($fields as $field => $property) {
      // Metadata map plugins are stored in
      // ['label' => label, 'value' => value] format.
      if ($field == 'metadata_map_plugins') {
        $map_plugins = [];
        if (is_array($config->get($field))) {
          foreach ($config->get($field) as $map) {
            $map_plugins[$map['label']] = $map['value'];
          }
        }
        $this->{$property} = $map_plugins;
      }
      else {
        $this->{$property} = $config->get($field);
      }
    }

    // Make sure the path is always set
    // if we don't have one, resort to default value.
    if (!$this->repositoryPath) {
      $this->repositoryPath = self::OAI_DEFAULT_PATH;
    }

    // Create a key/value store for resumption tokens.
    $this->keyValueStore = \Drupal::keyValue('rest_oai_pmh.resumption_token');
    $this->nextTokenId = $this->keyValueStore
      ->get('nextTokenId');
    if (!$this->nextTokenId) {
      $this->nextTokenId = 1;
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
          $configuration,
          $plugin_id,
          $plugin_definition,
          $container->getParameter('serializer.formats'),
          $container->get('config.factory')->get('rest_oai_pmh.settings'),
          $container->get('logger.factory')->get('rest_oai_pmh'),
          $container->get('current_user'),
          $container->get('request_stack')->getCurrentRequest(),
          $container->get('module_handler')
      );
  }

  /**
   * Responds to POST requests.
   *
   * @return \Drupal\rest\ResourceResponse
   *   The HTTP response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function post() {
    return $this->get();
  }

  /**
   * Responds to GET requests.
   *
   * @return \Drupal\rest\ResourceResponse
   *   The HTTP response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function get() {
    // Init a basic response used by all verbs.
    $base_oai_url = $this->currentRequest->getSchemeAndHttpHost() . $this->repositoryPath;
    $this->response = [
      '@xmlns' => 'http://www.openarchives.org/OAI/2.0/',
      '@xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
      '@xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd',
      'responseDate' => gmdate(self::OAI_DATE_FORMAT, \Drupal::time()->getRequestTime()),
      'request' => [
        'oai-dc-string' => $base_oai_url,
      ],
    ];

    $verb = $this->currentRequest->get('verb');
    $set_id = $this->currentRequest->get('set');
    $verbs = [
      'GetRecord',
      'Identify',
      'ListIdentifiers',
      'ListMetadataFormats',
      'ListRecords',
      'ListSets',
    ];
    // Make sure a valid verb was passed in as a GET parameter
    // if so, call the respective function implemented in this class.
    if (in_array($verb, $verbs)) {
      // If we do not have any entries in the cached table,
      // the cache needs rebuilt.
      // Do so now instead of waiting on Drupal cron to avoid empty results.
      if (\Drupal::database()->query('SELECT COUNT(*) FROM {rest_oai_pmh_record}')->fetchField() == 0) {
        $context = new RenderContext();
        \Drupal::service('renderer')->executeInRenderContext(
              $context, function () {
                  rest_oai_pmh_rebuild_entries();
              }
          );
      }
      $this->response['request']['@verb'] = $this->verb = $verb;

      // Since we're using protected functions for the verbs
      // the function names are in camelcase.
      $f = lcfirst($verb);
      $this->{$f}();
    }
    // If not a valid verb, print the error message.
    else {
      $this->setError('badVerb', 'Value of the verb argument is not a legal OAI-PMH verb, the verb argument is missing, or the verb argument is repeated.');
    }

    // resumptionToken needs to be at the bottom of the request
    // so if it exists, go take it out of the array
    // and add it back to ensure it's the last element in the array.
    if (!empty($this->response[$this->verb]['resumptionToken']) && count($this->response[$this->verb]['resumptionToken'])) {
      $resumption_token = $this->response[$this->verb]['resumptionToken'];
      unset($this->response[$this->verb]['resumptionToken']);
      $this->response[$this->verb]['resumptionToken'] = $resumption_token;
    }

    $response = new ResourceResponse($this->response, 200);

    // @todo for now disabling cache altogether until can come up with sensible method if there is one
    $response->addCacheableDependency(
          [
            '#cache' => [
              'max-age' => 0,
            ],
          ]
      );
    \Drupal::service('page_cache_kill_switch')->trigger();

    return $response;
  }

  /**
   * Functionality for the OAI-PMH verb GetRecord.
   */
  protected function getRecord() {

    $identifier = $this->currentRequest->get('identifier');
    if (empty($identifier)) {
      $this->setError('badArgument', 'Missing required argument identifier.');
    }

    $this->loadEntity($identifier);

    $components = explode(':', $identifier);
    $host = $this->currentRequest->getHttpHost();
    // Remove port from hostname.
    if (strpos($host, ':') !== FALSE) {
      $host_parts = explode(':', $host);
      $host = $host_parts[0];
    }

    // Check to ensure the identifier is valid
    // and an entity was loaded.
    if (count($components) != 3
          || $components[0] !== 'oai'
          || $components[1] !== $host
          || empty($this->entity)
      ) {
      $this->setError('idDoesNotExist', 'The value of the identifier argument is unknown or illegal in this repository.');
    }

    $this->metadataPrefix = $this->currentRequest->get('metadataPrefix');
    $this->checkMetadataPrefix();

    // Check if an error was thrown.
    if ($this->error) {
      // Per OAI specs, remove the verb from the response.
      unset($this->response['request']['@verb']);
    }
    // If no error, print the record.
    else {
      $this->response['request']['@metadataPrefix'] = $this->metadataPrefix;
      $this->response[$this->verb]['record'] = $this->getRecordById($identifier);
    }
  }

  /**
   * Functionality for the OAI-PMH verb Identify.
   */
  protected function identify() {
    // Query our table to see the oldest entity exposed to OAI.
    $earliest_date = \Drupal::database()->query(
          'SELECT MIN(created)
      FROM {rest_oai_pmh_record}'
      )->fetchField();

    $this->response[$this->verb] = [
      'repositoryName' => $this->repositoryName,
      'baseURL' => $this->currentRequest->getSchemeAndHttpHost() . $this->repositoryPath,
      'protocolVersion' => '2.0',
      'adminEmail' => $this->repositoryEmail,
      'earliestDatestamp' => gmdate(self::OAI_DATE_FORMAT, $earliest_date),
      'deletedRecord' => 'no',
      'granularity' => 'YYYY-MM-DDThh:mm:ssZ',
      'description' => [
        'oai-identifier' => [
          '@xmlns' => 'http://www.openarchives.org/OAI/2.0/oai-identifier',
          '@xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/oai-identifier http://www.openarchives.org/OAI/2.0/oai-identifier.xsd',
          'scheme' => 'oai',
          'repositoryIdentifier' => $this->currentRequest->getHttpHost(),
          'delimiter' => ':',
          'sampleIdentifier' => 'oai:' . $this->currentRequest->getHttpHost() . ':node-1',
        ],
      ],
    ];
  }

  /**
   * Functionality for the OAI-PMH verb ListMetadataFormats.
   */
  protected function listMetadataFormats() {
    // @todo support more metadata formats
    $formats = [];
    foreach ($this->getMetadataFormats() as $format) {
      $plugin = $this->getMetadataPlugin($format);
      $formats[] = $plugin->getMetadataFormat();
    }
    $this->response[$this->verb]['metadataFormat'] = $formats;
  }

  /**
   * Functionality for the OAI-PMH verb ListIdentifiers.
   */
  protected function listIdentifiers() {
    $entities = $this->getRecordIds();
    foreach ($entities as $entity) {
      $this->oaiEntity = $entity;
      $identifier = $this->buildIdentifier($entity);
      $this->loadEntity($identifier, TRUE);
      $this->response[$this->verb]['header'][] = $this->getHeaderById($identifier);
    }
    if (empty($this->response[$this->verb]['header'])) {
      $this->setError('noRecordsMatch', 'No records found.');
      unset($this->response[$this->verb]);
    }
  }

  /**
   * Functionality for the OAI-PMH verb ListRecords.
   */
  protected function listRecords() {
    $entities = $this->getRecordIds();
    foreach ($entities as $entity) {
      $this->oaiEntity = $entity;
      $identifier = $this->buildIdentifier($entity);
      $this->loadEntity($identifier, TRUE);
      $this->response[$this->verb]['record'][] = $this->getRecordById($identifier);
    }
    if (empty($this->response[$this->verb]['record'])) {
      $this->setError('noRecordsMatch', 'No records found.');
      unset($this->response[$this->verb]);
    }
  }

  /**
   * Functionality for the OAI-PMH verb ListSets.
   */
  protected function listSets() {
    // Throw an error if no Views available.
    if (count($this->viewDisplays) == 0 || empty($this->supportSets)) {
      $this->setError('noSetHierarchy', 'The repository does not support sets.');
      return;
    }

    $this->response[$this->verb] = [];

    $sets = \Drupal::database()->query('SELECT set_id, label FROM {rest_oai_pmh_set}');
    foreach ($sets as $set) {
      $this->response[$this->verb]['set'][] = [
        'setSpec' => $set->set_id,
        'setName' => $set->label,
      ];
    }
  }

  /**
   * Helper function.
   *
   * A lot of different scenarios can cause an error based on GET parameters, so
   *   have a standard convention to record these errors and print them in XML.
   */
  protected function setError($code, $string) {
    $this->response['error'][] = [
      '@code' => $code,
      'oai-dc-string' => $string,
    ];
    $this->error = TRUE;
  }

  /**
   * Helper function to get a record by identifier.
   */
  protected function getRecordById($identifier) {
    $record = [];
    $record['header'] = $this->getHeaderById($identifier);
    $record['metadata'] = $this->getRecordMetadata();

    return $record;
  }

  /**
   * Helper function to get header section of a record by identifier.
   */
  protected function getHeaderById($identifier) {
    $header = [
      'identifier' => $identifier,
    ];

    // If the entity being exposed to OAI has a changed field.
    // print that in the header.
    if ($this->entity->hasField('changed')) {
      $header['datestamp'] = gmdate(self::OAI_DATE_FORMAT, $this->entity->changed->value);
    }

    // If sets are supported
    // print the sets this record belongs to.
    if (!empty($this->oaiEntity) && !empty($this->supportSets)) {
      $sets = explode(',', $this->oaiEntity->sets);
      foreach ($sets as $set) {
        $header['setSpec'][] = $set;
      }
    }
    return $header;
  }

  /**
   * Helper function to get metadata section of a record by identifier.
   */
  protected function getRecordMetadata() {
    if (empty($this->metadataPrefix)) {
      $this->metadataPrefix = $this->currentRequest->get('metadataPrefix');
    }

    // Transform the record with the relevant plugin.
    // Process the transformation to isolate any early rendering.
    $context = new RenderContext();
    $result = \Drupal::service('renderer')->executeInRenderContext(
          $context, function () {
              $mapping_plugin = $this->getMetadataPlugin($this->metadataPrefix);
              $record = $mapping_plugin->transformRecord($this->entity);
              $metadata = $mapping_plugin->getMetadataWrapper();
              $wrapper_key = array_keys($metadata)[0];
              $metadata[$wrapper_key]['metadata-xml'] = trim($record);
              return $metadata;
          }
      );
    return $result;
  }

  /**
   * Helper function to get record IDs.
   */
  private function getRecordIds() {
    $verb = $this->response['request']['@verb'];
    $resumption_token = $this->currentRequest->get('resumptionToken');
    $this->metadataPrefix = $this->currentRequest->get('metadataPrefix');
    $set = $this->currentRequest->get('set');
    $from = $this->currentRequest->get('from');
    $until = $this->currentRequest->get('until');
    $cursor = 0;
    $completeListSize = 0;
    $views_total = [];
    // If a resumption token was passed, try to find it in the key store.
    if ($resumption_token) {
      $token = $this->keyValueStore->get($resumption_token);
      // If we found a token and it's not expired, get the values needed.
      if ($token
            && $token['expires'] > \Drupal::time()->getRequestTime()
            && $token['verb'] == $this->verb
        ) {
        $this->metadataPrefix = $token['metadata_prefix'];
        $cursor = $token['cursor'];
        $set = $token['set'];
        $from = $token['from'];
        $until = $token['until'];
        $completeListSize = $token['completeListSize'];
      }
      else {
        // If we found a token, and we're here, it means the token is expired
        // delete it from key value store.
        if ($token && $token['expires'] < \Drupal::time()->getRequestTime()) {
          $this->keyValueStore->delete($resumption_token);
        }
        $this->setError('badResumptionToken', 'The value of the resumptionToken argument is invalid or expired.');
      }
    }
    // If a set parameter was passed, but this OAI endpoint doesn't support sets
    // throw an error.
    elseif ((empty($this->supportSets) || empty($this->viewDisplays)) && $set) {
      $this->setError('noSetHierarchy', 'The repository does not support sets.');
    }

    $this->checkMetadataPrefix();

    $db_conn = \Drupal::database();

    if ($this->error) {
      return [];
    }
    else {
      // Our {rest_oai_pmh_set} table stores the pager information
      // for the Views exposed to OAI. To play it safe, make the
      // limit // max results returned be the smallest pager size
      // for all the Views exposed to OAI.
      $end = $db_conn->driver() !== 'pgsql' ?
             $db_conn->query('SELECT MIN(`pager_limit`) FROM {rest_oai_pmh_set}')->fetchField() :
             $db_conn->query('SELECT MIN(pager_limit) FROM {rest_oai_pmh_set}')->fetchField();
      $this->response['request']['@metadataPrefix'] = $this->metadataPrefix;
    }

    // Query our {rest_oai_pmh_*} tables to get records that are exposed to OAI.
    $query = $db_conn->select('rest_oai_pmh_record', 'r');
    $query->innerJoin('rest_oai_pmh_member', 'm', 'm.entity_id = r.entity_id AND m.entity_type = r.entity_type');
    $query->innerJoin('rest_oai_pmh_set', 's', 's.set_id = m.set_id');
    $query->fields('r', ['entity_id', 'entity_type']);
    if ($db_conn->driver() !== 'pgsql') {
      $query->addExpression('GROUP_CONCAT(m.set_id)', 'sets');
    }
    else {
      // XXX: GROUP_CONCAT() doesn't exist in PostgreSQL.
      $query->addExpression("STRING_AGG(m.set_id, ',')", 'sets');
    }
    $query->groupBy('r.entity_type')->groupBy('r.entity_id');

    // XXX: Result sets can be unpredictable when limiting without an ORDER BY
    // clause.
    // @see: https://www.postgresql.org/docs/current/queries-limit.html.
    $query->orderBy('r.entity_id');

    // If set ID was passed in URL, filter on that
    // otherwise filter on all sets as defined on set field.
    if ($set) {
      $query->condition('m.set_id', $set);
    }

    // If from was passed as  GET parameter, filter on that.
    if ($from) {
      $this->response['request']['@from'] = $from;
      $query->condition('changed', strtotime($from), '>=');
    }
    // If until was passed as  GET parameter, filter on that.
    if ($until) {
      $this->response['request']['@until'] = $until;
      $query->condition('changed', strtotime($until), '<=');
    }

    // If we haven't checked the complete list size yet
    // i.e. this isn't a call from a resumption token
    // get the complete list size for this request.
    if (empty($completeListSize)) {
      $completeListSize = $query->countQuery()->execute()->fetchField();
    }

    $this->response[$this->verb]['resumptionToken'] = [];

    // If the total results are more than what was returned here
    // add a resumption token.
    if ($completeListSize > ($cursor + $end) && $end > 0) {
      // Set the expiration date per the admin settings.
      $expires = \Drupal::time()->getRequestTime() + $this->expiration;

      $this->response[$this->verb]['resumptionToken'] += [
        '@completeListSize' => $completeListSize,
        '@cursor' => $cursor,
        'oai-dc-string' => $this->nextTokenId,
        '@expirationDate' => gmdate(self::OAI_DATE_FORMAT, $expires),
      ];

      // Save the settings for the resumption token
      // that will be shown in these results.
      $token = [
        'metadata_prefix' => $this->metadataPrefix,
        'set' => $set,
        'cursor' => $cursor + $end,
        'expires' => $expires,
        'verb' => $this->response['request']['@verb'],
        'from' => $from,
        'until' => $until,
        'completeListSize' => $completeListSize,
      ];
      $this->keyValueStore->set($this->nextTokenId, $token);

      // Increment the token id for the next resumption token that will show.
      // @todo should we incorporate semaphores here to avoid possible duplicates?
      $this->nextTokenId += 1;
      $this->keyValueStore->set('nextTokenId', $this->nextTokenId);
    }

    // Put a pager on the query if there's a pager on the Views exposed to OAI.
    if ($end > 0) {
      $query->range($cursor, $end);
    }

    $entities = $query->execute();

    return $entities;
  }

  /**
   * Helper function. Create an OAI identifier for the given entity.
   */
  protected function buildIdentifier($entity) {
    $identifier = 'oai:';
    $identifier .= $this->currentRequest->getHost();
    $identifier .= ':';
    $identifier .= $entity->entity_type;
    $identifier .= '-' . $entity->entity_id;

    return $identifier;
  }

  /**
   * Helper function. Load an entity to be printed in OAI endpoint.
   *
   * @param string $identifier
   *   OAI identifier for a record.
   * @param bool $skip_check
   *   Whether to ensure entity being passed in $identifier
   *   is indeed exposed to OAI. Some OAI verbs (like ListRecords)
   *   are querying only the entities that are indeed exposed to OAI.
   *   Other verbs, like GetRecord, get an identifier passed and
   *   are asked for the metadata for that record.
   *   So need to check that the entity is indeed in OAI.
   */
  protected function loadEntity($identifier, $skip_check = FALSE) {
    $entity = FALSE;
    $components = explode(':', $identifier);
    $id = empty($components[2]) ? FALSE : $components[2];
    if ($id) {
      [$entity_type, $entity_id] = explode('-', $id);

      try {
        // If we need to check whether the entity is in OAI, do so
        // we don't do this for ListRecords b/c we already know the entity
        // is in OAI since we queried it from the table we're checking against
        // but for GetRecord, the user is passing the identifier,
        // so we need to ensure it's legit
        // basically just a performance enhancement to not always check.
        if (!$skip_check) {

          // Fetch all sets the record belongs to
          // even if sets aren't supported by OAI,
          // our system still stores the set information
          // so it's a reliable method to check
          // PLUS we get all the sets the record belongs to
          // so we can print the sets in <header>.
          $d_args = [
            ':type' => $entity_type,
            ':id' => $entity_id,
          ];
          $db_conn = \Drupal::database();
          if ($db_conn->driver() !== 'pgsql') {
            $in_oai_view = $db_conn->query(
                  'SELECT GROUP_CONCAT(set_id) FROM {rest_oai_pmh_record} r
              INNER JOIN {rest_oai_pmh_member} m ON m.entity_id = r.entity_id AND m.entity_type = r.entity_type
              WHERE r.entity_id = :id
                AND r.entity_type = :type
              GROUP BY r.entity_id', $d_args
              )->fetchField();
          }
          else {
            // XXX: GROUP_CONCAT() doesn't exist in PostgreSQL.
            $in_oai_view = $db_conn->query(
                  'SELECT STRING_AGG(set_id, \',\') FROM {rest_oai_pmh_record} r
              INNER JOIN {rest_oai_pmh_member} m ON m.entity_id = r.entity_id AND m.entity_type = r.entity_type
              WHERE r.entity_id = :id
                AND r.entity_type = :type
              GROUP BY r.entity_id', $d_args
              )->fetchField();
          }

          // Store the set membership from our table
          // so we can print set membership in <header>.
          $this->oaiEntity = (object) ['sets' => $in_oai_view];
        }

        // If we're skipping the OAI check OR we didn't skip the check
        // and the record is in OAI load the entity.
        if ($skip_check || $in_oai_view) {
          $storage = \Drupal::entityTypeManager()->getStorage($entity_type);
          $entity = $storage->load($entity_id);
        }
      }
      catch (Exception $e) {
      }
    }

    // Make sure the entity was loaded properly
    // AND the person viewing has access.
    $this->entity = $entity && $entity->access('view') ? $entity : FALSE;
  }

  /**
   * Helper function to check the value passed to metadataPrefix.
   */
  protected function checkMetadataPrefix() {
    // If no metadata prefix passed into request, throw error.
    if (empty($this->metadataPrefix)) {
      $this->setError('badArgument', 'Missing required argument metadataPrefix.');
    }
    // Do we have a plugin configured for it?
    elseif (!in_array($this->metadataPrefix, $this->getMetadataFormats())) {
      $this->setError('cannotDisseminateFormat', 'The metadata format identified by the value given for the metadataPrefix argument is not supported by the item or by the repository.');
    }
  }

  /**
   * Returns the configured plugin associated with a given metadata prefix.
   */
  protected function getMetadataPlugin(string $metadata_prefix) {
    if (empty($this->metadataMapPlugins[$metadata_prefix])) {
      // Should probably throw an exception...
      return FALSE;
    }
    $mapping_plugin_manager = \Drupal::service('plugin.manager.oai_metadata_map');
    return $mapping_plugin_manager->createInstance($this->metadataMapPlugins[$metadata_prefix]);
  }

  /**
   * Returns a list of available metadata formats.
   *
   * @return array
   *   Available metadata formats.
   */
  protected function getMetadataFormats() {
    return array_keys(array_filter($this->metadataMapPlugins));
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc