-
Notifications
You must be signed in to change notification settings - Fork 0
/
rest_oai_pmh.module
428 lines (385 loc) · 14.2 KB
/
rest_oai_pmh.module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
<?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';
}