rest_oai_pmh-8.x-1.0-beta1/rest_oai_pmh.module
rest_oai_pmh.module
<?php
/**
* @file
* Contains rest_oai_pmh.module.
*/
use Drupal\views\Views;
use Drupal\Core\Url;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Queue\SuspendQueueException;
use Drupal\Core\Utility\Error;
/**
* Implements hook_help().
*/
function rest_oai_pmh_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the rest_oai_pmh module.
case 'help.page.rest_oai_pmh':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Exposes schema.org dublin core mappings in an OAI-PMH endpoint') . '</p>';
return $output;
default:
}
}
/**
* Implements hook_rest_resource_alter().
*/
function rest_oai_pmh_rest_resource_alter(&$definitions) {
// If the repository admin set a path, override the default URI.
$path = \Drupal::config('rest_oai_pmh.settings')->get('repository_path');
if ($path) {
$definitions['oai_pmh']['uri_paths']['canonical'] = $path;
$definitions['oai_pmh']['uri_paths']['https://www.drupal.org/link-relations/create'] = $path;
}
}
/**
* Implements hook_metatag_tags_alter().
*/
function rest_oai_pmh_metatag_tags_alter(&$definitions) {
// Set some dublin core metatags to allow for multiple values.
$terms = ['dcterms_creator', 'dcterms_subject'];
foreach ($terms as $term) {
if (!empty($definitions[$term])) {
$definitions[$term]['multiple'] = TRUE;
}
}
}
/**
* Implements hook_entity_insert().
*/
function rest_oai_pmh_entity_insert(EntityInterface $entity) {
rest_oai_pmh_entity_alter($entity, 'insert');
}
/**
* Implements hook_entity_update().
*/
function rest_oai_pmh_entity_update(EntityInterface $entity) {
rest_oai_pmh_entity_alter($entity, 'update');
}
/**
* Implements hook_entity_delete().
*/
function rest_oai_pmh_entity_delete(EntityInterface $entity) {
rest_oai_pmh_entity_alter($entity, 'delete');
}
/**
* Helper function. Act when an entity is added/updated/deleted.
*/
function rest_oai_pmh_entity_alter($entity, $op = '') {
$oai_cache_plugin_manager = \Drupal::service('plugin.manager.oai_cache');
$config = \Drupal::config('rest_oai_pmh.settings');
$cache_technique = $config->get('cache_technique') ?: 'liberal_cache';
$cache_plugin = $oai_cache_plugin_manager->createInstance($cache_technique);
$cache_plugin->clearCache($entity, $op);
}
/**
* Create QueueWorker to execute all, or a specific set of Views.
*/
function rest_oai_pmh_cache_views($view_displays = FALSE, $queue_name = 'rest_oai_pmh_views_cache_cron') {
// Get the queue factory.
$queue_factory = \Drupal::service('queue');
$queue = $queue_factory->get($queue_name);
// If no view_displays were passed
// get a list of all view displays set for OAI-PMH.
if (!$view_displays) {
$config = \Drupal::config('rest_oai_pmh.settings');
$view_displays = $config->get('view_displays') ?: [];
// Flush the queue since we're rebuilding everything.
$queue->deleteQueue();
// Truncate the tables since we're caching all the views.
$tables = [
'rest_oai_pmh_set',
'rest_oai_pmh_record',
'rest_oai_pmh_member',
];
foreach ($tables as $table) {
\Drupal::database()->truncate($table)->execute();
}
}
foreach ($view_displays as $view_display) {
[$view_id, $display_id] = explode(':', $view_display);
$view = Views::getView($view_id);
$view->setDisplay($display_id);
$has_sets = FALSE;
// Go through all the contextual filters for this View display.
foreach ($view->display_handler->getHandlers('argument') as $contextual_filter) {
$definition = $contextual_filter->definition;
$set_entity_type = FALSE;
// Look at the contextual filter definition
// and see if it looks like an entity reference field.
$entity_type = empty($definition['entity_type']) ? FALSE : $definition['entity_type'];
$table = $definition['table'];
$field = $definition['field_name'];
$column = $definition['field'];
// If we know the entity type of the view is querying,
// and the field is referencing another entity
// load the referenced entity's field storage
// to find what type of entity reference is for.
if ($entity_type && $column === $field . '_target_id') {
$field_storage = \Drupal::service('entity_field.manager')
->getFieldStorageDefinitions($entity_type);
if (isset($field_storage[$field])) {
$set_entity_type = $field_storage[$field]->getSetting('target_type');
}
}
// If the contextual filter is of an entity reference field.
if ($set_entity_type) {
// See what sort of entity is exposed
// and see what table it's stored in
// e.g. $entity = 'node' if $definition['entity_type'] === 'node'.
$set_entity_storage = \Drupal::entityTypeManager()->getStorage($set_entity_type);
$set_entity_table = $set_entity_storage->getBaseTable();
// Get the table where the data for the entity reference is stored
// e.g. if the field name is "field_member" and entity_type is "node"
// $field_table = 'node__field_member';.
$field_table = $entity_type . '__' . $field;
// Get the database column that stores the entity's key property.
$id = $set_entity_storage->getEntityType()->getKey('id');
// This is what we'll perform our JOIN on
// $column is the field that the contextual reference queries on
// so for field_member $column = 'field_member_target_id';.
$condition = $column . ' = ' . $id;
// Find entities that had at least one record referencing the entity
// in the field defined on the contextual filter.
$query = \Drupal::database()->select($set_entity_table, 'entity');
$query->innerJoin($field_table, 'f', $condition);
$query->addField('entity', $id);
$query->groupBy($id);
// Make each entity found that's referenced a set in OAI.
$ids = $query->execute()->fetchCol();
foreach ($ids as $id) {
$entity = $set_entity_storage->load($id);
if ($entity) {
$has_sets = TRUE;
$data = [
'view_id' => $view_id,
'display_id' => $display_id,
'arguments' => [$entity->id()],
'set_entity_type' => $set_entity_type,
'set_id' => $set_entity_type . ':' . $entity->id(),
'set_label' => $entity->label(),
'view_display' => $view_display,
];
// Load the View and apply the display ID.
$view = Views::getView($view_id);
$view->setDisplay($display_id);
$view->get_total_rows = TRUE;
$view->getDisplay()->setOption('entity_reference_options', ['limit' => $view->getItemsPerPage()]);
// Get the first set of results from the View.
$members = $view->executeDisplay($display_id, $data['arguments']);
// After we executed the View, see how many items were returned
// use this to page through all results.
$data['limit'] = $view->getItemsPerPage();
$total = $view->total_rows;
$offset = 0;
while ($total > 0) {
$data['offset'] = $offset;
// Queue the information we found to be processed by the queue.
$queue->createItem($data);
if ($data['limit'] <= 0) {
break;
}
$total -= $data['limit'];
$offset += $data['limit'];
}
}
}
}
}
// If no contextual filter was found for this View
// use all the View results as a single set for OAI-PMH
// and make the set's name/id based off the View.
if (!$has_sets) {
$view_storage = \Drupal::entityTypeManager()->getStorage('view');
$view = $view_storage->load($view_id);
$display = $view->get('display');
$data = [
'view_id' => $view_id,
'display_id' => $display_id,
'arguments' => [],
'set_entity_type' => 'view',
'set_id' => $view_display,
'set_label' => $display[$display_id]['display_title'],
'view_display' => $view_display,
];
// Load the View and apply the display ID.
$view = Views::getView($view_id);
$view->setDisplay($display_id);
$view->getDisplay()->setOption('entity_reference_options', ['limit' => $view->getItemsPerPage()]);
$view->get_total_rows = TRUE;
// Get the first set of results from the View.
$members = $view->executeDisplay($display_id);
// After we executed the View, we'll know how many items were returned
// use this to page through all results.
$data['limit'] = $view->getItemsPerPage();
$total = $view->total_rows;
$offset = 0;
while ($total > 0) {
$data['offset'] = $offset;
// Queue the information we found to be processed by the queue.
$queue->createItem($data);
if ($data['limit'] <= 0) {
break;
}
$total -= $data['limit'];
$offset += $data['limit'];
}
}
}
}
/**
* Helper function. Remove all sets/records exposed by a specific View display.
*/
function rest_oai_pmh_remove_sets_by_display_id($view_display) {
$disabled_sets = \Drupal::database()->query('SELECT set_id FROM {rest_oai_pmh_set}
WHERE view_display = :view_display', [':view_display' => $view_display])->fetchCol();
foreach ($disabled_sets as $disabled_set) {
[$entity_type, $entity_id] = explode(':', $disabled_set);
rest_oai_pmh_remove_record($entity_type, $entity_id);
}
}
/**
* Helper function. Delete a set from OAI.
*
* @todo queue this?
*/
function rest_oai_pmh_remove_set($set_id) {
// Find all records that belong to this set.
$args = [':set_id' => $set_id];
$disabled_records = \Drupal::database()->query('SELECT entity_type, entity_id
FROM {rest_oai_pmh_member}
WHERE set_id = :set_id', $args);
foreach ($disabled_records as $disabled_record) {
rest_oai_pmh_remove_record($disabled_record->entity_type, $disabled_record->entity_id, $set_id);
}
// finally, delete the set.
\Drupal::database()->delete('rest_oai_pmh_set')
->condition('set_id', $set_id)
->execute();
}
/**
* Helper function. Delete a record from OAI.
*/
function rest_oai_pmh_remove_record($entity_type, $entity_id, $remove_from_set = FALSE) {
// Only attempt to remove a record if it exists as a set OR an OAI item.
if (!rest_oai_pmh_is_valid_entity_type($entity_type)) {
return;
}
$set_id = $entity_type . ':' . $entity_id;
rest_oai_pmh_remove_set($set_id);
// Remove this record's set associations.
$query = \Drupal::database()->delete('rest_oai_pmh_member')
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id);
if ($remove_from_set) {
$query->condition('set_id', $remove_from_set);
}
$query->execute();
// By default, remove the entity from OAI cache.
$delete_entity_from_cache = TRUE;
// If we're just removing the entity because it id in a set being removed.
if ($remove_from_set) {
// First check if sets are enabled.
$config = \Drupal::config('rest_oai_pmh.settings');
$support_sets = $config->get('support_sets') ?: FALSE;
// If sets are supported.
if ($support_sets) {
// Do not delete this entity from the OAI record cache
// if it belongs to any other sets.
$delete_entity_from_cache = !(bool) \Drupal::database()->query('SELECT entity_id
FROM {rest_oai_pmh_member}
WHERE entity_type = :entity_type
AND entity_id = :entity_id', [
':entity_type' => $entity_type,
':entity_id' => $entity_id,
])->fetchField();
}
}
if ($delete_entity_from_cache) {
\Drupal::database()->delete('rest_oai_pmh_record')
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id)
->execute();
}
}
/**
* Helper function. Rebuild {rest_oai_pmh_*} tables immediately.
*/
function rest_oai_pmh_rebuild_entries() {
rest_oai_pmh_cache_views();
$queue = \Drupal::service('queue')->get('rest_oai_pmh_views_cache_cron');
while ($item = $queue->claimItem()) {
rest_oai_pmh_process_queue($item);
}
return $item;
}
/**
* Helper function. Run through the queue to populate OAI cache table.
*/
function rest_oai_pmh_process_queue($item) {
$queue = \Drupal::service('queue')->get('rest_oai_pmh_views_cache_cron');
$queue_worker = \Drupal::service('plugin.manager.queue_worker')->createInstance('rest_oai_pmh_views_cache_cron');
try {
$queue_worker->processItem($item->data);
$queue->deleteItem($item);
}
catch (SuspendQueueException $e) {
$queue->releaseItem($item);
$logger = \Drupal::logger('rest_oai_pmh');
Error::logException($logger, $e);
}
catch (\Exception $e) {
$logger = \Drupal::logger('rest_oai_pmh');
Error::logException($logger, $e);
}
}
/**
* Helper function to display status of OAI cache batch rebuild.
*/
function rest_oai_pmh_batch_finished($success, $results, $operations) {
$messenger = \Drupal::messenger();
if ($success) {
$url_options = [
'absolute' => TRUE,
'query' => [
'verb' => 'ListRecords',
'metadataPrefix' => 'oai_dc',
],
];
$t_args = [
':link' => Url::fromRoute('rest.oai_pmh.GET', [], $url_options)->toString(),
];
$messenger->addStatus(t('Successfully rebuilt your OAI-PMH entries. You can now see your records at <a href=":link">:link</a>', $t_args));
}
else {
$url_options = [
'absolute' => TRUE,
];
$t_args = [
':link' => Url::fromRoute('dblog.overview', [], $url_options)->toString(),
];
$messenger->addError(t('Could not rebuild your OAI-PMH endpoint. Please check your <a href=":link">Recent log messages</a>', $t_args));
}
}
/**
* Helper function. Check if the entity type is exposed in OAI.
*/
function rest_oai_pmh_is_valid_entity_type($entity_type) {
$d_args = [
':entity_type' => $entity_type,
];
$exists = \Drupal::database()->query(<<<EOQ
SELECT 1
FROM {rest_oai_pmh_record} r
WHERE r.entity_type = :entity_type
UNION ALL
SELECT 1
FROM {rest_oai_pmh_set} s
WHERE s.entity_type = :entity_type
LIMIT 1
EOQ
, $d_args)->fetchField();
return $exists === '1';
}
