Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (188)

Classes/Controller/OaiPmhController.php (1 issue)

1
<?php
2
/**
3
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
4
 *
5
 * This file is part of the Kitodo and TYPO3 projects.
6
 *
7
 * @license GNU General Public License version 3 or later.
8
 * For the full copyright and license information, please read the
9
 * LICENSE.txt file that was distributed with this source code.
10
 */
11
12
namespace Kitodo\Dlf\Controller;
13
14
use TYPO3\CMS\Core\Utility\GeneralUtility;
15
use Kitodo\Dlf\Common\Solr\Solr;
16
use Kitodo\Dlf\Domain\Model\Token;
17
use Kitodo\Dlf\Domain\Repository\CollectionRepository;
18
use Kitodo\Dlf\Domain\Repository\LibraryRepository;
19
use Kitodo\Dlf\Domain\Repository\TokenRepository;
20
21
/**
22
 * Controller class for the plugin 'OAI-PMH Interface'.
23
 *
24
 * @package TYPO3
25
 * @subpackage dlf
26
 *
27
 * @access public
28
 */
29
class OaiPmhController extends AbstractController
30
{
31
    /**
32
     * @access protected
33
     * @var TokenRepository
34
     */
35
    protected $tokenRepository;
36
37
    /**
38
     * @access public
39
     *
40
     * @param TokenRepository $tokenRepository
41
     *
42
     * @return void
43
     */
44
    public function injectTokenRepository(TokenRepository $tokenRepository)
45
    {
46
        $this->tokenRepository = $tokenRepository;
47
    }
48
49
    /**
50
     * @access protected
51
     * @var CollectionRepository
52
     */
53
    protected $collectionRepository;
54
55
    /**
56
     * @access public
57
     *
58
     * @param CollectionRepository $collectionRepository
59
     *
60
     * @return void
61
     */
62
    public function injectCollectionRepository(CollectionRepository $collectionRepository)
63
    {
64
        $this->collectionRepository = $collectionRepository;
65
    }
66
67
    /**
68
     * @access protected
69
     * @var LibraryRepository
70
     */
71
    protected $libraryRepository;
72
73
    /**
74
     * @access public
75
     *
76
     * @param LibraryRepository $libraryRepository
77
     *
78
     * @return void
79
     */
80
    public function injectLibraryRepository(LibraryRepository $libraryRepository)
81
    {
82
        $this->libraryRepository = $libraryRepository;
83
    }
84
85
    /**
86
     * Initializes the current action
87
     *
88
     * @access public
89
     *
90
     * @return void
91
     */
92
    public function initializeAction()
93
    {
94
        $this->request->setFormat('xml');
95
    }
96
97
    /**
98
     * @access protected
99
     * @var string Did an error occur?
100
     */
101
    protected $error;
102
103
    /**
104
     * @access protected
105
     * @var array This holds the configuration for all supported metadata prefixes
106
     */
107
    protected $formats = [
108
        'oai_dc' => [
109
            'schema' => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
110
            'namespace' => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
111
            'requiredFields' => ['record_id'],
112
        ],
113
        'epicur' => [
114
            'schema' => 'http://www.persistent-identifier.de/xepicur/version1.0/xepicur.xsd',
115
            'namespace' => 'urn:nbn:de:1111-2004033116',
116
            'requiredFields' => ['purl', 'urn'],
117
        ],
118
        'mets' => [
119
            'schema' => 'http://www.loc.gov/standards/mets/version17/mets.v1-7.xsd',
120
            'namespace' => 'http://www.loc.gov/METS/',
121
            'requiredFields' => ['location'],
122
        ]
123
    ];
124
125
    /**
126
     * @access protected
127
     * @var array
128
     */
129
    protected $parameters = [];
130
131
    /**
132
     * Delete expired resumption tokens
133
     *
134
     * @access protected
135
     *
136
     * @return void
137
     */
138
    protected function deleteExpiredTokens()
139
    {
140
        // Delete expired resumption tokens.
141
        $this->tokenRepository->deleteExpiredTokens($this->settings['expired']);
142
    }
143
144
    /**
145
     * Load URL parameters
146
     *
147
     * @access protected
148
     *
149
     * @return void
150
     */
151
    protected function getUrlParams()
152
    {
153
        $allowedParams = [
154
            'verb',
155
            'identifier',
156
            'metadataPrefix',
157
            'from',
158
            'until',
159
            'set',
160
            'resumptionToken'
161
        ];
162
        // Clear plugin variables.
163
        $this->parameters = [];
164
        // Set only allowed parameters.
165
        foreach ($allowedParams as $param) {
166
            if (GeneralUtility::_GP($param)) {
167
                $this->parameters[$param] = GeneralUtility::_GP($param);
168
            }
169
        }
170
    }
171
172
    /**
173
     * Get unqualified Dublin Core data.
174
     * @see http://www.openarchives.org/OAI/openarchivesprotocol.html#dublincore
175
     *
176
     * @access private
177
     *
178
     * @param array $record The full record array
179
     *
180
     * @return array The mapped metadata array
181
     */
182
    private function getDublinCoreData(array $record)
183
    {
184
        $metadata = [];
185
186
        $metadata[] = ['dc:identifier' => $record['record_id']];
187
188
        $this->addDublinCoreData($metadata, 'dc:identifier', $record['purl']);
189
        $this->addDublinCoreData($metadata, 'dc:identifier', $record['prod_id']);
190
        $this->addDublinCoreData($metadata, 'dc:identifier', $record['urn']);
191
        $this->addDublinCoreData($metadata, 'dc:title', $record['title']);
192
        $this->addDublinCoreData($metadata, 'dc:creator', $record['author']);
193
        $this->addDublinCoreData($metadata, 'dc:date', $record['year']);
194
        $this->addDublinCoreData($metadata, 'dc:coverage', $record['place']);
195
196
        $record[] = ['dc:format' => $record['application/mets+xml']];
197
        $record[] = ['dc:type' => $record['Text']];
198
        if (!empty($record['partof'])) {
199
            $document = $this->documentRepository->findOneByPartof($metadata['partof']);
200
201
            if ($document) {
202
                $metadata[] = ['dc:relation' => $document->getRecordId()];
203
            }
204
        }
205
        $this->addDublinCoreData($metadata, 'dc:rights', $record['license']);
206
        $this->addDublinCoreData($metadata, 'dc:rights', $record['terms']);
207
        $this->addDublinCoreData($metadata, 'dc:rights', $record['restrictions']);
208
        $this->addDublinCoreData($metadata, 'dc:rights', $record['out_of_print']);
209
        $this->addDublinCoreData($metadata, 'dc:rights', $record['rights_info']);
210
211
        return $metadata;
212
    }
213
214
    /**
215
     * Add Dublin Core data.
216
     *
217
     * @access private
218
     *
219
     * @param array $metadata The mapped metadata array passed as reference
220
     * @param string $key The key to which record value should be assigned
221
     * @param string $value The key from record array
222
     *
223
     * @return void
224
     */
225
    private function addDublinCoreData(&$metadata, $key, $value)
226
    {
227
        if (!empty($value)) {
228
            $metadata[] = [$key => $value];
229
        }
230
    }
231
232
    /**
233
     * Get METS data.
234
     * @see http://www.loc.gov/standards/mets/docs/mets.v1-7.html
235
     *
236
     * @access protected
237
     *
238
     * @param array $record The full record array
239
     *
240
     * @return string The fetched METS XML
241
     */
242
    protected function getMetsData(array $record)
243
    {
244
        $mets = null;
245
        // Load METS file.
246
        $xml = new \DOMDocument();
247
        if ($xml->load($record['location'])) {
248
            // Get root element.
249
            $root = $xml->getElementsByTagNameNS($this->formats['mets']['namespace'], 'mets');
250
            if ($root->item(0) instanceof \DOMNode) {
251
                $mets = $xml->saveXML($root->item(0));
252
            } else {
253
                $this->logger->error('No METS part found in document with location "' . $record['location'] . '"');
254
            }
255
        } else {
256
            $this->logger->error('Could not load XML file from "' . $record['location'] . '"');
257
        }
258
        return $mets;
259
    }
260
261
    /**
262
     * The main method of the plugin
263
     *
264
     * @access public
265
     *
266
     * @return void
267
     */
268
    public function mainAction()
269
    {
270
        // Get allowed GET and POST variables.
271
        $this->getUrlParams();
272
273
        // Delete expired resumption tokens.
274
        $this->deleteExpiredTokens();
275
276
        switch ($this->parameters['verb']) {
277
            case 'GetRecord':
278
                $this->verbGetRecord();
279
                break;
280
            case 'Identify':
281
                $this->verbIdentify();
282
                break;
283
            case 'ListIdentifiers':
284
                $this->verbListIdentifiers();
285
                break;
286
            case 'ListMetadataFormats':
287
                $this->verbListMetadataFormats();
288
                break;
289
            case 'ListRecords':
290
                $this->verbListRecords();
291
                break;
292
            case 'ListSets':
293
                $this->verbListSets();
294
                break;
295
            default:
296
                $this->error = 'badVerb';
297
                break;
298
        }
299
300
        $this->view->assign('parameters', $this->parameters);
301
        $this->view->assign('error', $this->error);
302
303
        return;
304
    }
305
306
    /**
307
     * Continue with resumption token
308
     *
309
     * @access protected
310
     *
311
     * @return array|null list of uids
312
     */
313
    protected function resume(): ?array
314
    {
315
        $token = $this->tokenRepository->findOneByToken($this->parameters['resumptionToken']);
316
317
        if ($token) {
318
            $options = $token->getOptions();
319
        }
320
        if (is_array($options)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $options does not seem to be defined for all execution paths leading up to this point.
Loading history...
321
            return $options;
322
        } else {
323
            // No resumption token found or resumption token expired.
324
            $this->error = 'badResumptionToken';
325
            return null;
326
        }
327
    }
328
329
    /**
330
     * Process verb "GetRecord"
331
     *
332
     * @access protected
333
     *
334
     * @return void
335
     */
336
    protected function verbGetRecord()
337
    {
338
        if (count($this->parameters) !== 3 || empty($this->parameters['metadataPrefix']) || empty($this->parameters['identifier'])) {
339
            $this->error = 'badArgument';
340
            return;
341
        }
342
343
        if (!array_key_exists($this->parameters['metadataPrefix'], $this->formats)) {
344
            $this->error = 'cannotDisseminateFormat';
345
            return;
346
        }
347
348
        $document = $this->documentRepository->getOaiRecord($this->settings, $this->parameters);
349
350
        if (!$document['uid']) {
351
            $this->error = 'idDoesNotExist';
352
            return;
353
        }
354
355
        // Check for required fields.
356
        foreach ($this->formats[$this->parameters['metadataPrefix']]['requiredFields'] as $required) {
357
            if (empty($document[$required])) {
358
                $this->error = 'cannotDisseminateFormat';
359
                return;
360
            }
361
        }
362
363
        // we need the collections as array later
364
        $document['collections'] = explode(' ', $document['collections']);
365
366
        // Add metadata
367
        switch ($this->parameters['metadataPrefix']) {
368
            case 'oai_dc':
369
                $document['metadata'] = $this->getDublinCoreData($document);
370
                break;
371
            case 'epicur':
372
                $document['metadata'] = $document;
373
                break;
374
            case 'mets':
375
                $document['metadata'] = $this->getMetsData($document);
376
                break;
377
        }
378
379
        $this->view->assign('record', $document);
380
    }
381
382
    /**
383
     * Process verb "Identify"
384
     *
385
     * @access protected
386
     *
387
     * @return void
388
     */
389
    protected function verbIdentify()
390
    {
391
        $library = $this->libraryRepository->findByUid($this->settings['library']);
392
393
        $oaiIdentifyInfo = [];
394
395
        if (!$oaiIdentifyInfo) {
396
            $this->logger->notice('Incomplete plugin configuration');
397
        }
398
399
        $oaiIdentifyInfo['oai_label'] = $library ? $library->getOaiLabel() : '';
400
        // Use default values for an installation with incomplete plugin configuration.
401
        if (empty($oaiIdentifyInfo['oai_label'])) {
402
            $oaiIdentifyInfo['oai_label'] = 'Kitodo.Presentation OAI-PMH Interface (default configuration)';
403
            $this->logger->notice('Incomplete plugin configuration (oai_label is missing)');
404
        }
405
406
        $oaiIdentifyInfo['contact'] = $library ? $library->getContact() : '';
407
        if (empty($oaiIdentifyInfo['contact'])) {
408
            $oaiIdentifyInfo['contact'] = '[email protected]';
409
            $this->logger->notice('Incomplete plugin configuration (contact is missing)');
410
        }
411
412
        $document = $this->documentRepository->findOldestDocument();
413
414
        if ($document) {
415
            $oaiIdentifyInfo['earliestDatestamp'] = gmdate('Y-m-d\TH:i:s\Z', $document->getTstamp()->getTimestamp());
416
        } else {
417
            // Provide a fallback timestamp if no document is found
418
            $oaiIdentifyInfo['earliestDatestamp'] = '0000-00-00T00:00:00Z';
419
420
            // access storagePid from TypoScript
421
            $pageSettings = $this->configurationManager->getConfiguration($this->configurationManager::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
422
            $storagePid = $pageSettings["plugin."]["tx_dlf."]["persistence."]["storagePid"];
423
            if ($storagePid > 0) {
424
                $this->logger->notice('No records found with PID ' . $storagePid);
425
            } else {
426
                $this->logger->notice('No records found');
427
            }
428
        }
429
        $this->view->assign('oaiIdentifyInfo', $oaiIdentifyInfo);
430
    }
431
432
    /**
433
     * Process verb "ListIdentifiers"
434
     *
435
     * @access protected
436
     *
437
     * @return void
438
     */
439
    protected function verbListIdentifiers()
440
    {
441
        // If we have a resumption token we can continue our work
442
        if (!empty($this->parameters['resumptionToken'])) {
443
            // "resumptionToken" is an exclusive argument.
444
            if (count($this->parameters) > 2) {
445
                $this->error = 'badArgument';
446
                return;
447
            } else {
448
                // return next chunk of documents
449
                $resultSet = $this->resume();
450
                if (is_array($resultSet)) {
451
                    $listIdentifiers = $this->generateOutputForDocumentList($resultSet);
452
                    $this->view->assign('listIdentifiers', $listIdentifiers);
453
                }
454
                return;
455
            }
456
        }
457
        // "metadataPrefix" is required and "identifier" is not allowed.
458
        if (empty($this->parameters['metadataPrefix']) || !empty($this->parameters['identifier'])) {
459
            $this->error = 'badArgument';
460
            return;
461
        }
462
        if (!in_array($this->parameters['metadataPrefix'], array_keys($this->formats))) {
463
            $this->error = 'cannotDisseminateFormat';
464
            return;
465
        }
466
        try {
467
            $documentSet = $this->fetchDocumentSet();
468
        } catch (\Exception $exception) {
469
            $this->error = 'idDoesNotExist';
470
            return;
471
        }
472
        // create new and empty document list
473
        $resultSet = [];
474
        if (is_array($documentSet)) {
475
            $resultSet['elements'] = $documentSet;
476
            $resultSet['metadata'] = [
477
                'cursor' => 0,
478
                'completeListSize' => count($documentSet),
479
                'metadataPrefix' => $this->parameters['metadataPrefix'],
480
            ];
481
        }
482
483
        $listIdentifiers = $this->generateOutputForDocumentList($resultSet);
484
        $this->view->assign('listIdentifiers', $listIdentifiers);
485
    }
486
487
    /**
488
     * Process verb "ListMetadataFormats"
489
     *
490
     * @access protected
491
     *
492
     * @return void
493
     */
494
    protected function verbListMetadataFormats()
495
    {
496
        $resArray = [];
497
        // check for the optional "identifier" parameter
498
        if (isset($this->parameters['identifier'])) {
499
            $resArray = $this->documentRepository->findOneByRecordId($this->parameters['identifier']);
500
        }
501
502
        $resultSet = [];
503
        foreach ($this->formats as $prefix => $details) {
504
            if (!empty($resArray)) {
505
                // check, if all required fields are available for a given identifier
506
                foreach ($details['requiredFields'] as $required) {
507
                    $methodName = 'get' . GeneralUtility::underscoredToUpperCamelCase($required);
508
                    if (empty($resArray->$methodName())) {
509
                        // Skip metadata formats whose requirements are not met.
510
                        continue 2;
511
                    }
512
                }
513
            }
514
            $details['prefix'] = $prefix;
515
            $resultSet[] = $details;
516
        }
517
        $this->view->assign('metadataFormats', $resultSet);
518
    }
519
520
    /**
521
     * Process verb "ListRecords"
522
     *
523
     * @access protected
524
     *
525
     * @return void
526
     */
527
    protected function verbListRecords()
528
    {
529
        // Check for invalid arguments.
530
        if (!empty($this->parameters['resumptionToken'])) {
531
            // "resumptionToken" is an exclusive argument.
532
            if (count($this->parameters) > 2) {
533
                $this->error = 'badArgument';
534
                return;
535
            } else {
536
                // return next chunk of documents
537
                $resultSet = $this->resume();
538
                if (is_array($resultSet)) {
539
                    $listRecords = $this->generateOutputForDocumentList($resultSet);
540
                    $this->parameters['metadataPrefix'] = $resultSet['metadata']['metadataPrefix'];
541
                    $this->view->assign('listRecords', $listRecords);
542
                }
543
                return;
544
            }
545
        }
546
        if (empty($this->parameters['metadataPrefix']) || !empty($this->parameters['identifier'])) {
547
            // "metadataPrefix" is required and "identifier" is not allowed.
548
            $this->error = 'badArgument';
549
            return;
550
        }
551
        // Check "metadataPrefix" for valid value.
552
        if (!in_array($this->parameters['metadataPrefix'], array_keys($this->formats))) {
553
            $this->error = 'cannotDisseminateFormat';
554
            return;
555
        }
556
        try {
557
            $documentSet = $this->fetchDocumentSet();
558
        } catch (\Exception $exception) {
559
            $this->error = 'idDoesNotExist';
560
            return;
561
        }
562
        $resultSet = [];
563
        if (count($documentSet) > 0) {
564
            $resultSet['elements'] = $documentSet;
565
            $resultSet['metadata'] = [
566
                'cursor' => 0,
567
                'completeListSize' => count($documentSet),
568
                'metadataPrefix' => $this->parameters['metadataPrefix'],
569
            ];
570
        }
571
572
        $resultSet = $this->generateOutputForDocumentList($resultSet);
573
        $this->view->assign('listRecords', $resultSet);
574
    }
575
576
    /**
577
     * Process verb "ListSets"
578
     *
579
     * @access protected
580
     *
581
     * @return void
582
     */
583
    protected function verbListSets()
584
    {
585
        // It is required to set a oai_name inside the collection record to be shown in oai-pmh plugin.
586
        $this->settings['hideEmptyOaiNames'] = true;
587
588
        $oaiSets = $this->collectionRepository->findCollectionsBySettings($this->settings);
589
590
        $this->view->assign('oaiSets', $oaiSets);
591
    }
592
593
    /**
594
     * Fetch records
595
     *
596
     * @access protected
597
     *
598
     * @return array matching records or empty array if there were some errors
599
     */
600
    protected function fetchDocumentSet(): array
601
    {
602
        $documentSet = [];
603
        $solrQuery = '';
604
        // Check "set" for valid value.
605
        if (!empty($this->parameters['set'])) {
606
            // For SOLR we need the index_name of the collection,
607
            // For DB Query we need the UID of the collection
608
609
            $result = $this->collectionRepository->getIndexNameForSolr($this->settings, $this->parameters['set']);
610
            $resArray = $result->fetchAssociative();
611
            if ($resArray) {
612
                if ($resArray['index_query'] != "") {
613
                    $solrQuery .= '(' . $resArray['index_query'] . ')';
614
                } else {
615
                    $solrQuery .= 'collection:' . '"' . $resArray['index_name'] . '"';
616
                }
617
            } else {
618
                $this->error = 'noSetHierarchy';
619
                return $documentSet;
620
            }
621
        } else {
622
            // If no set is specified we have to query for all collections
623
            $solrQuery .= 'collection:* NOT collection:""';
624
        }
625
        // Check for required fields.
626
        foreach ($this->formats[$this->parameters['metadataPrefix']]['requiredFields'] as $required) {
627
            $solrQuery .= ' NOT ' . $required . ':""';
628
        }
629
        // toplevel="true" is always required
630
        $solrQuery .= ' AND toplevel:true';
631
632
        $from = $this->getFrom();
633
        $until = $this->getUntil($from);
634
635
        $this->checkGranularity();
636
637
        if ($this->error === 'badArgument') {
638
            return $documentSet;
639
        }
640
641
        $solrQuery .= ' AND timestamp:[' . $from . ' TO ' . $until . ']';
642
643
        $solr = Solr::getInstance($this->settings['solrcore']);
644
        if (!$solr->ready) {
645
            $this->logger->error('Apache Solr not available');
646
            return $documentSet;
647
        }
648
        if ((int) $this->settings['solr_limit'] > 0) {
649
            $solr->limit = (int) $this->settings['solr_limit'];
650
        }
651
        // We only care about the UID in the results and want them sorted
652
        $parameters = [
653
            "fields" => "uid",
654
            "sort" => [
655
                "uid" => "asc"
656
            ]
657
        ];
658
        $parameters['query'] = $solrQuery;
659
        $result = $solr->searchRaw($parameters);
660
        if (empty($result)) {
661
            $this->error = 'noRecordsMatch';
662
            return $documentSet;
663
        }
664
        foreach ($result as $doc) {
665
            $documentSet[] = $doc->uid;
666
        }
667
        return $documentSet;
668
    }
669
670
    /**
671
     * Get 'from' query parameter.
672
     *
673
     * @access private
674
     *
675
     * @return string
676
     */
677
    private function getFrom(): string
678
    {
679
        $from = "*";
680
        // Check "from" for valid value.
681
        if (!empty($this->parameters['from'])) {
682
            // Is valid format?
683
            $date = $this->getDate('from');
684
            if (is_array($date)) {
685
                $from = $this->getDateFromTimestamp($date, '.000Z');
686
            } else {
687
                $this->error = 'badArgument';
688
            }
689
        }
690
        return $from;
691
    }
692
693
    /**
694
     * Get 'until' query parameter.
695
     *
696
     * @access private
697
     *
698
     * @param string $from start date
699
     *
700
     * @return string
701
     */
702
    private function getUntil(string $from): string
703
    {
704
        $until = "*";
705
        // Check "until" for valid value.
706
        if (!empty($this->parameters['until'])) {
707
            // Is valid format?
708
            $date = $this->getDate('until');
709
            if (is_array($date)) {
710
                $until = $this->getDateFromTimestamp($date, '.999Z');
711
                if ($from != "*" && $from > $until) {
712
                    $this->error = 'badArgument';
713
                }
714
            } else {
715
                $this->error = 'badArgument';
716
            }
717
        }
718
        return $until;
719
    }
720
721
    /**
722
     * Get date from parameter string.
723
     *
724
     * @access private
725
     *
726
     * @param string $dateType
727
     *
728
     * @return array|false
729
     */
730
    private function getDate(string $dateType)
731
    {
732
        return strptime($this->parameters[$dateType], '%Y-%m-%dT%H:%M:%SZ') ?: strptime($this->parameters[$dateType], '%Y-%m-%d');
733
    }
734
735
    /**
736
     * Get date from timestamp.
737
     *
738
     * @access private
739
     *
740
     * @param array $date
741
     * @param string $end
742
     *
743
     * @return string
744
     */
745
    private function getDateFromTimestamp(array $date, string $end): string
746
    {
747
        $timestamp = gmmktime(
748
            $date['tm_hour'],
749
            $date['tm_min'],
750
            $date['tm_sec'],
751
            $date['tm_mon'] + 1,
752
            $date['tm_mday'],
753
            $date['tm_year'] + 1900
754
        );
755
        return date("Y-m-d", $timestamp) . 'T' . date("H:i:s", $timestamp) . $end;
756
    }
757
758
    /**
759
     * Check "from" and "until" for same granularity.
760
     *
761
     * @access private
762
     *
763
     * @return void
764
     */
765
    private function checkGranularity(): void
766
    {
767
        if (
768
            !empty($this->parameters['from'])
769
            && !empty($this->parameters['until'])
770
        ) {
771
            if (strlen($this->parameters['from']) != strlen($this->parameters['until'])) {
772
                $this->error = 'badArgument';
773
            }
774
        }
775
    }
776
777
    /**
778
     * Fetch more information for document list
779
     *
780
     * @access protected
781
     *
782
     * @param array $documentListSet
783
     *
784
     * @return array of enriched records
785
     */
786
    protected function generateOutputForDocumentList(array $documentListSet)
787
    {
788
        $documentsToProcess = array_splice($documentListSet['elements'], 0, (int) $this->settings['limit']);
789
        if (empty($documentsToProcess)) {
790
            $this->error = 'noRecordsMatch';
791
            return [];
792
        }
793
        $verb = $this->parameters['verb'];
794
795
        $documents = $this->documentRepository->getOaiDocumentList($documentsToProcess);
796
797
        $records = [];
798
        while ($resArray = $documents->fetchAssociative()) {
799
            // we need the collections as array later
800
            $resArray['collections'] = explode(' ', $resArray['collections']);
801
802
            if ($verb === 'ListRecords') {
803
                // Add metadata node.
804
                $metadataPrefix = $this->parameters['metadataPrefix'];
805
                if (!$metadataPrefix) {
806
                    // If we resume an action the metadataPrefix is stored with the documentSet
807
                    $metadataPrefix = $documentListSet['metadata']['metadataPrefix'];
808
                }
809
                switch ($metadataPrefix) {
810
                    case 'oai_dc':
811
                        $resArray['metadata'] = $this->getDublinCoreData($resArray);
812
                        break;
813
                    case 'epicur':
814
                        $resArray['metadata'] = $resArray;
815
                        break;
816
                    case 'mets':
817
                        $resArray['metadata'] = $this->getMetsData($resArray);
818
                        break;
819
                }
820
            }
821
822
            $records[] = $resArray;
823
        }
824
825
        $this->generateResumptionTokenForDocumentListSet($documentListSet, count($documentsToProcess));
826
827
        return $records;
828
    }
829
830
    /**
831
     * Generate resumption token
832
     *
833
     * @access protected
834
     *
835
     * @param array $documentListSet
836
     * @param int $numShownDocuments
837
     *
838
     * @return void
839
     */
840
    protected function generateResumptionTokenForDocumentListSet(array $documentListSet, int $numShownDocuments)
841
    {
842
        // The cursor specifies how many elements have already been returned in previous requests
843
        // See http://www.openarchives.org/OAI/openarchivesprotocol.html#FlowControl
844
        $currentCursor = $documentListSet['metadata']['cursor'];
845
846
        if (count($documentListSet['elements']) !== 0) {
847
            $resumptionToken = uniqid('', false);
848
849
            $documentListSet['metadata']['cursor'] += $numShownDocuments;
850
851
            // create new token
852
            $newToken = GeneralUtility::makeInstance(Token::class);
853
            $newToken->setToken($resumptionToken);
854
            $newToken->setOptions($documentListSet);
855
856
            // add to tokenRepository
857
            $this->tokenRepository->add($newToken);
858
        } else {
859
            // Result set complete. We don't need a token.
860
            $resumptionToken = '';
861
        }
862
863
        $resumptionTokenInfo = [];
864
        $resumptionTokenInfo['token'] = $resumptionToken;
865
        $resumptionTokenInfo['cursor'] = $currentCursor;
866
        $resumptionTokenInfo['completeListSize'] = $documentListSet['metadata']['completeListSize'];
867
        $expireDateTime = new \DateTime();
868
        $expireDateTime->add(new \DateInterval('PT' . $this->settings['expired'] . 'S'));
869
        $resumptionTokenInfo['expired'] = $expireDateTime;
870
871
        $omitResumptionToken = $currentCursor === 0 && $numShownDocuments >= $documentListSet['metadata']['completeListSize'];
872
        if (!$omitResumptionToken) {
873
            $this->view->assign('resumptionToken', $resumptionTokenInfo);
874
        }
875
    }
876
}
877