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.
Passed
Push — master ( 2e589b...c271b7 )
by
unknown
08:48 queued 13s
created

OaiPmhController::addDcData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 3
rs 10
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;
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
 * @author Sebastian Meyer <[email protected]>
25
 * @author Alexander Bigga <[email protected]>
26
 * @package TYPO3
27
 * @subpackage dlf
28
 * @access public
29
 */
30
class OaiPmhController extends AbstractController
31
{
32
    /**
33
     * @var TokenRepository
34
     */
35
    protected $tokenRepository;
36
37
    /**
38
     * @param TokenRepository $tokenRepository
39
     */
40
    public function injectTokenRepository(TokenRepository $tokenRepository)
41
    {
42
        $this->tokenRepository = $tokenRepository;
43
    }
44
45
    /**
46
     * @var CollectionRepository
47
     */
48
    protected $collectionRepository;
49
50
    /**
51
     * @param CollectionRepository $collectionRepository
52
     */
53
    public function injectCollectionRepository(CollectionRepository $collectionRepository)
54
    {
55
        $this->collectionRepository = $collectionRepository;
56
    }
57
58
    /**
59
     * @var LibraryRepository
60
     */
61
    protected $libraryRepository;
62
63
    /**
64
     * @param LibraryRepository $libraryRepository
65
     */
66
    public function injectLibraryRepository(LibraryRepository $libraryRepository)
67
    {
68
        $this->libraryRepository = $libraryRepository;
69
    }
70
71
    /**
72
     * Initializes the current action
73
     *
74
     * @return void
75
     */
76
    public function initializeAction()
77
    {
78
        $this->request->setFormat('xml');
79
    }
80
81
    /**
82
     * Did an error occur?
83
     *
84
     * @var string
85
     * @access protected
86
     */
87
    protected $error;
88
89
    /**
90
     * This holds the configuration for all supported metadata prefixes
91
     *
92
     * @var array
93
     * @access protected
94
     */
95
    protected $formats = [
96
        'oai_dc' => [
97
            'schema' => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
98
            'namespace' => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
99
            'requiredFields' => ['record_id'],
100
        ],
101
        'epicur' => [
102
            'schema' => 'http://www.persistent-identifier.de/xepicur/version1.0/xepicur.xsd',
103
            'namespace' => 'urn:nbn:de:1111-2004033116',
104
            'requiredFields' => ['purl', 'urn'],
105
        ],
106
        'mets' => [
107
            'schema' => 'http://www.loc.gov/standards/mets/version17/mets.v1-7.xsd',
108
            'namespace' => 'http://www.loc.gov/METS/',
109
            'requiredFields' => ['location'],
110
        ]
111
    ];
112
113
    /**
114
     * @var array
115
     */
116
    protected $parameters = [];
117
118
    /**
119
     * Delete expired resumption tokens
120
     *
121
     * @access protected
122
     *
123
     * @return void
124
     */
125
    protected function deleteExpiredTokens()
126
    {
127
        // Delete expired resumption tokens.
128
        $this->tokenRepository->deleteExpiredTokens($this->settings['expired']);
129
    }
130
131
    /**
132
     * Load URL parameters
133
     *
134
     * @access protected
135
     *
136
     * @return void
137
     */
138
    protected function getUrlParams()
139
    {
140
        $allowedParams = [
141
            'verb',
142
            'identifier',
143
            'metadataPrefix',
144
            'from',
145
            'until',
146
            'set',
147
            'resumptionToken'
148
        ];
149
        // Clear plugin variables.
150
        $this->parameters = [];
151
        // Set only allowed parameters.
152
        foreach ($allowedParams as $param) {
153
            if (GeneralUtility::_GP($param)) {
154
                $this->parameters[$param] = GeneralUtility::_GP($param);
155
            }
156
        }
157
    }
158
159
    /**
160
     * Get unqualified Dublin Core data.
161
     * @see http://www.openarchives.org/OAI/openarchivesprotocol.html#dublincore
162
     *
163
     * @access protected
164
     *
165
     * @param array $record : The full record array
166
     *
167
     * @return array $metadata: The mapped metadata array
168
     */
169
    protected function getDcData(array $record)
170
    {
171
        $metadata = [];
172
173
        $metadata[] = ['dc:identifier' => $record['record_id']];
174
175
        $this->addDcData($metadata, 'dc:identifier', $record['purl']);
176
        $this->addDcData($metadata, 'dc:identifier', $record['prod_id']);
177
        $this->addDcData($metadata, 'dc:identifier', $record['urn']);
178
        $this->addDcData($metadata, 'dc:title', $record['title']);
179
        $this->addDcData($metadata, 'dc:creator', $record['author']);
180
        $this->addDcData($metadata, 'dc:date', $record['year']);
181
        $this->addDcData($metadata, 'dc:coverage', $record['place']);
182
183
        $record[] = ['dc:format' => $record['application/mets+xml']];
184
        $record[] = ['dc:type' => $record['Text']];
185
        if (!empty($record['partof'])) {
186
            $document = $this->documentRepository->findOneByPartof($metadata['partof']);
0 ignored issues
show
Bug introduced by
The method findOneByPartof() does not exist on Kitodo\Dlf\Domain\Repository\DocumentRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
            /** @scrutinizer ignore-call */ 
187
            $document = $this->documentRepository->findOneByPartof($metadata['partof']);
Loading history...
187
188
            if ($document) {
189
                $metadata[] = ['dc:relation' => $document->getRecordId()];
0 ignored issues
show
Bug introduced by
The method getRecordId() does not exist on TYPO3\CMS\Extbase\Persistence\QueryResultInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

189
                $metadata[] = ['dc:relation' => $document->/** @scrutinizer ignore-call */ getRecordId()];

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
190
            }
191
        }
192
        $this->addDcData($metadata, 'dc:rights', $record['license']);
0 ignored issues
show
Bug introduced by
$record['license'] of type array|array<string,array> is incompatible with the type string expected by parameter $value of Kitodo\Dlf\Controller\Oa...Controller::addDcData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

192
        $this->addDcData($metadata, 'dc:rights', /** @scrutinizer ignore-type */ $record['license']);
Loading history...
193
        $this->addDcData($metadata, 'dc:rights', $record['terms']);
194
        $this->addDcData($metadata, 'dc:rights', $record['restrictions']);
195
        $this->addDcData($metadata, 'dc:rights', $record['out_of_print']);
196
        $this->addDcData($metadata, 'dc:rights', $record['rights_info']);
197
198
        return $metadata;
199
    }
200
201
    /**
202
     * @param array $metadata The mapped metadata array
203
     * @param string $key The key to which record value should be assigned
204
     * @param string $value The key from record array
205
     *
206
     * @return void
207
     */
208
    private function addDcData(&$metadata, $key, $value) {
209
        if (!empty($value)) {
210
            $metadata[] = [$key => $value];
211
        }
212
    }
213
214
    /**
215
     * Get METS data.
216
     * @see http://www.loc.gov/standards/mets/docs/mets.v1-7.html
217
     *
218
     * @access protected
219
     *
220
     * @param array $record : The full record array
221
     *
222
     * @return string: The fetched METS XML
223
     */
224
    protected function getMetsData(array $record)
225
    {
226
        $mets = null;
227
        // Load METS file.
228
        $xml = new \DOMDocument();
229
        if ($xml->load($record['location'])) {
230
            // Get root element.
231
            $root = $xml->getElementsByTagNameNS($this->formats['mets']['namespace'], 'mets');
232
            if ($root->item(0) instanceof \DOMNode) {
233
                $mets = $xml->saveXML($root->item(0));
234
            } else {
235
                $this->logger->error('No METS part found in document with location "' . $record['location'] . '"');
236
            }
237
        } else {
238
            $this->logger->error('Could not load XML file from "' . $record['location'] . '"');
239
        }
240
        return $mets;
241
    }
242
243
    /**
244
     * The main method of the plugin
245
     *
246
     * @return void
247
     */
248
    public function mainAction()
249
    {
250
        // Get allowed GET and POST variables.
251
        $this->getUrlParams();
252
253
        // Delete expired resumption tokens.
254
        $this->deleteExpiredTokens();
255
256
        switch ($this->parameters['verb']) {
257
            case 'GetRecord':
258
                $this->verbGetRecord();
259
                break;
260
            case 'Identify':
261
                $this->verbIdentify();
262
                break;
263
            case 'ListIdentifiers':
264
                $this->verbListIdentifiers();
265
                break;
266
            case 'ListMetadataFormats':
267
                $this->verbListMetadataFormats();
268
                break;
269
            case 'ListRecords':
270
                $this->verbListRecords();
271
                break;
272
            case 'ListSets':
273
                $this->verbListSets();
274
                break;
275
            default:
276
                $this->error = 'badVerb';
277
                break;
278
        }
279
280
        $this->view->assign('parameters', $this->parameters);
281
        $this->view->assign('error', $this->error);
282
283
        return;
284
    }
285
286
    /**
287
     * Continue with resumption token
288
     *
289
     * @access protected
290
     *
291
     * @return array|null list of uids
292
     */
293
    protected function resume(): ?array
294
    {
295
        $token = $this->tokenRepository->findOneByToken($this->parameters['resumptionToken']);
0 ignored issues
show
Bug introduced by
The method findOneByToken() does not exist on Kitodo\Dlf\Domain\Repository\TokenRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

295
        /** @scrutinizer ignore-call */ 
296
        $token = $this->tokenRepository->findOneByToken($this->parameters['resumptionToken']);
Loading history...
296
297
        if ($token) {
298
            $options = $token->getOptions();
0 ignored issues
show
Bug introduced by
The method getOptions() does not exist on TYPO3\CMS\Extbase\Persistence\QueryResultInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

298
            /** @scrutinizer ignore-call */ 
299
            $options = $token->getOptions();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
299
        }
300
        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...
301
            return $options;
302
        } else {
303
            // No resumption token found or resumption token expired.
304
            $this->error = 'badResumptionToken';
305
            return null;
306
        }
307
    }
308
309
    /**
310
     * Process verb "GetRecord"
311
     *
312
     * @access protected
313
     *
314
     * @return void
315
     */
316
    protected function verbGetRecord()
317
    {
318
        if (count($this->parameters) !== 3 || empty($this->parameters['metadataPrefix']) || empty($this->parameters['identifier'])) {
319
            $this->error = 'badArgument';
320
            return;
321
        }
322
323
        if (!array_key_exists($this->parameters['metadataPrefix'], $this->formats)) {
324
            $this->error = 'cannotDisseminateFormat';
325
            return;
326
        }
327
328
        $document = $this->documentRepository->getOaiRecord($this->settings, $this->parameters);
329
330
        if (!$document['uid']) {
331
            $this->error = 'idDoesNotExist';
332
            return;
333
        }
334
335
        // Check for required fields.
336
        foreach ($this->formats[$this->parameters['metadataPrefix']]['requiredFields'] as $required) {
337
            if (empty($document[$required])) {
338
                $this->error = 'cannotDisseminateFormat';
339
                return;
340
            }
341
        }
342
343
        // we need the collections as array later
344
        $document['collections'] = explode(' ', $document['collections']);
345
346
        // Add metadata
347
        switch ($this->parameters['metadataPrefix']) {
348
            case 'oai_dc':
349
                $document['metadata'] = $this->getDcData($document);
350
                break;
351
            case 'epicur':
352
                $document['metadata'] = $document;
353
                break;
354
            case 'mets':
355
                $document['metadata'] = $this->getMetsData($document);
356
                break;
357
        }
358
359
        $this->view->assign('record', $document);
360
    }
361
362
    /**
363
     * Process verb "Identify"
364
     *
365
     * @access protected
366
     *
367
     * @return void
368
     */
369
    protected function verbIdentify()
370
    {
371
        $library = $this->libraryRepository->findByUid($this->settings['library']);
372
373
        $oaiIdentifyInfo = [];
374
375
        if (!$oaiIdentifyInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $oaiIdentifyInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$oaiIdentifyInfo is an empty array, thus ! $oaiIdentifyInfo is always true.
Loading history...
376
            $this->logger->notice('Incomplete plugin configuration');
377
        }
378
379
        $oaiIdentifyInfo['oai_label'] = $library ? $library->getOaiLabel() : '';
380
        // Use default values for an installation with incomplete plugin configuration.
381
        if (empty($oaiIdentifyInfo['oai_label'])) {
382
            $oaiIdentifyInfo['oai_label'] = 'Kitodo.Presentation OAI-PMH Interface (default configuration)';
383
            $this->logger->notice('Incomplete plugin configuration (oai_label is missing)');
384
        }
385
386
        $oaiIdentifyInfo['contact'] = $library ? $library->getContact() : '';
387
        if (empty($oaiIdentifyInfo['contact'])) {
388
            $oaiIdentifyInfo['contact'] = '[email protected]';
389
            $this->logger->notice('Incomplete plugin configuration (contact is missing)');
390
        }
391
392
        $document = $this->documentRepository->findOldestDocument();
393
394
        if ($document) {
395
            $oaiIdentifyInfo['earliestDatestamp'] = gmdate('Y-m-d\TH:i:s\Z', $document->getTstamp()->getTimestamp());
396
        } else {
397
            // Provide a fallback timestamp if no document is found
398
            $oaiIdentifyInfo['earliestDatestamp'] = '0000-00-00T00:00:00Z';
399
400
            // access storagePid from TypoScript
401
            $pageSettings = $this->configurationManager->getConfiguration($this->configurationManager::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
402
            $storagePid = $pageSettings["plugin."]["tx_dlf."]["persistence."]["storagePid"];
403
            if ($storagePid > 0) {
404
                $this->logger->notice('No records found with PID ' . $storagePid);
405
            } else {
406
                $this->logger->notice('No records found');
407
            }
408
        }
409
        $this->view->assign('oaiIdentifyInfo', $oaiIdentifyInfo);
410
    }
411
412
    /**
413
     * Process verb "ListIdentifiers"
414
     *
415
     * @access protected
416
     *
417
     * @return void
418
     */
419
    protected function verbListIdentifiers()
420
    {
421
        // If we have a resumption token we can continue our work
422
        if (!empty($this->parameters['resumptionToken'])) {
423
            // "resumptionToken" is an exclusive argument.
424
            if (count($this->parameters) > 2) {
425
                $this->error = 'badArgument';
426
                return;
427
            } else {
428
                // return next chunk of documents
429
                $resultSet = $this->resume();
430
                if (is_array($resultSet)) {
431
                    $listIdentifiers = $this->generateOutputForDocumentList($resultSet);
432
                    $this->view->assign('listIdentifiers', $listIdentifiers);
433
                }
434
                return;
435
            }
436
        }
437
        // "metadataPrefix" is required and "identifier" is not allowed.
438
        if (empty($this->parameters['metadataPrefix']) || !empty($this->parameters['identifier'])) {
439
            $this->error = 'badArgument';
440
            return;
441
        }
442
        if (!in_array($this->parameters['metadataPrefix'], array_keys($this->formats))) {
443
            $this->error = 'cannotDisseminateFormat';
444
            return;
445
        }
446
        try {
447
            $documentSet = $this->fetchDocumentUIDs();
448
        } catch (\Exception $exception) {
449
            $this->error = 'idDoesNotExist';
450
            return;
451
        }
452
        // create new and empty documentlist
453
        $resultSet = [];
454
        if (is_array($documentSet)) {
455
            $resultSet['elements'] = $documentSet;
456
            $resultSet['metadata'] = [
457
                'cursor' => 0,
458
                'completeListSize' => count($documentSet),
459
                'metadataPrefix' => $this->parameters['metadataPrefix'],
460
            ];
461
        }
462
463
        $listIdentifiers = $this->generateOutputForDocumentList($resultSet);
464
        $this->view->assign('listIdentifiers', $listIdentifiers);
465
    }
466
467
    /**
468
     * Process verb "ListMetadataFormats"
469
     *
470
     * @access protected
471
     *
472
     * @return void
473
     */
474
    protected function verbListMetadataFormats()
475
    {
476
        $resArray = [];
477
        // check for the optional "identifier" parameter
478
        if (isset($this->parameters['identifier'])) {
479
            $resArray = $this->documentRepository->findOneByRecordId($this->parameters['identifier']);
0 ignored issues
show
Bug introduced by
The method findOneByRecordId() does not exist on Kitodo\Dlf\Domain\Repository\DocumentRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

479
            /** @scrutinizer ignore-call */ 
480
            $resArray = $this->documentRepository->findOneByRecordId($this->parameters['identifier']);
Loading history...
480
        }
481
482
        $resultSet = [];
483
        foreach ($this->formats as $prefix => $details) {
484
            if (!empty($resArray)) {
485
                // check, if all required fields are available for a given identifier
486
                foreach ($details['requiredFields'] as $required) {
487
                    $methodName = 'get' . GeneralUtility::underscoredToUpperCamelCase($required);
488
                    if (empty($resArray->$methodName())) {
489
                        // Skip metadata formats whose requirements are not met.
490
                        continue 2;
491
                    }
492
                }
493
            }
494
            $details['prefix'] = $prefix;
495
            $resultSet[] = $details;
496
        }
497
        $this->view->assign('metadataFormats', $resultSet);
498
    }
499
500
    /**
501
     * Process verb "ListRecords"
502
     *
503
     * @access protected
504
     *
505
     * @return void
506
     */
507
    protected function verbListRecords()
508
    {
509
        // Check for invalid arguments.
510
        if (!empty($this->parameters['resumptionToken'])) {
511
            // "resumptionToken" is an exclusive argument.
512
            if (count($this->parameters) > 2) {
513
                $this->error = 'badArgument';
514
                return;
515
            } else {
516
                // return next chunk of documents
517
                $resultSet = $this->resume();
518
                if (is_array($resultSet)) {
519
                    $listRecords = $this->generateOutputForDocumentList($resultSet);
520
                    $this->parameters['metadataPrefix'] = $resultSet['metadata']['metadataPrefix'];
521
                    $this->view->assign('listRecords', $listRecords);
522
                }
523
                return;
524
            }
525
        }
526
        if (empty($this->parameters['metadataPrefix']) || !empty($this->parameters['identifier'])) {
527
            // "metadataPrefix" is required and "identifier" is not allowed.
528
            $this->error = 'badArgument';
529
            return;
530
        }
531
        // Check "metadataPrefix" for valid value.
532
        if (!in_array($this->parameters['metadataPrefix'], array_keys($this->formats))) {
533
            $this->error = 'cannotDisseminateFormat';
534
            return;
535
        }
536
        try {
537
            $documentSet = $this->fetchDocumentUIDs();
538
        } catch (\Exception $exception) {
539
            $this->error = 'idDoesNotExist';
540
            return;
541
        }
542
        $resultSet = [];
543
        if (is_array($documentSet)) {
544
            $resultSet['elements'] = $documentSet;
545
            $resultSet['metadata'] = [
546
                'cursor' => 0,
547
                'completeListSize' => count($documentSet),
548
                'metadataPrefix' => $this->parameters['metadataPrefix'],
549
            ];
550
        }
551
552
        $resultSet = $this->generateOutputForDocumentList($resultSet);
553
        $this->view->assign('listRecords', $resultSet);
554
    }
555
556
    /**
557
     * Process verb "ListSets"
558
     *
559
     * @access protected
560
     *
561
     * @return void
562
     */
563
    protected function verbListSets()
564
    {
565
        // It is required to set a oai_name inside the collection record to be shown in oai-pmh plugin.
566
        $this->settings['hideEmptyOaiNames'] = true;
567
568
        $oaiSets = $this->collectionRepository->findCollectionsBySettings($this->settings);
569
570
        $this->view->assign('oaiSets', $oaiSets);
571
    }
572
573
    /**
574
     * Fetch records
575
     *
576
     * @access protected
577
     *
578
     * @return array|null Array of matching records
579
     */
580
    protected function fetchDocumentUIDs()
581
    {
582
        $solr_query = '';
583
        // Check "set" for valid value.
584
        if (!empty($this->parameters['set'])) {
585
            // For SOLR we need the index_name of the collection,
586
            // For DB Query we need the UID of the collection
587
588
            $result = $this->collectionRepository->getIndexNameForSolr($this->settings, $this->parameters['set']);
589
590
            if ($resArray = $result->fetch()) {
591
                if ($resArray['index_query'] != "") {
592
                    $solr_query .= '(' . $resArray['index_query'] . ')';
593
                } else {
594
                    $solr_query .= 'collection:' . '"' . $resArray['index_name'] . '"';
595
                }
596
            } else {
597
                $this->error = 'noSetHierarchy';
598
                return null;
599
            }
600
        } else {
601
            // If no set is specified we have to query for all collections
602
            $solr_query .= 'collection:* NOT collection:""';
603
        }
604
        // Check for required fields.
605
        foreach ($this->formats[$this->parameters['metadataPrefix']]['requiredFields'] as $required) {
606
            $solr_query .= ' NOT ' . $required . ':""';
607
        }
608
        // toplevel="true" is always required
609
        $solr_query .= ' AND toplevel:true';
610
        $from = "*";
611
        // Check "from" for valid value.
612
        if (!empty($this->parameters['from'])) {
613
            // Is valid format?
614
            if (
615
                is_array($date_array = strptime($this->parameters['from'], '%Y-%m-%dT%H:%M:%SZ'))
616
                || is_array($date_array = strptime($this->parameters['from'], '%Y-%m-%d'))
617
            ) {
618
                $timestamp = gmmktime($date_array['tm_hour'], $date_array['tm_min'], $date_array['tm_sec'],
619
                    $date_array['tm_mon'] + 1, $date_array['tm_mday'], $date_array['tm_year'] + 1900);
620
                $from = date("Y-m-d", $timestamp) . 'T' . date("H:i:s", $timestamp) . '.000Z';
621
            } else {
622
                $this->error = 'badArgument';
623
                return;
624
            }
625
        }
626
        $until = "*";
627
        // Check "until" for valid value.
628
        if (!empty($this->parameters['until'])) {
629
            // Is valid format?
630
            if (
631
                is_array($date_array = strptime($this->parameters['until'], '%Y-%m-%dT%H:%M:%SZ'))
632
                || is_array($date_array = strptime($this->parameters['until'], '%Y-%m-%d'))
633
            ) {
634
                $timestamp = gmmktime($date_array['tm_hour'], $date_array['tm_min'], $date_array['tm_sec'],
635
                    $date_array['tm_mon'] + 1, $date_array['tm_mday'], $date_array['tm_year'] + 1900);
636
                $until = date("Y-m-d", $timestamp) . 'T' . date("H:i:s", $timestamp) . '.999Z';
637
                if ($from != "*" && $from > $until) {
638
                    $this->error = 'badArgument';
639
                }
640
            } else {
641
                $this->error = 'badArgument';
642
            }
643
        }
644
        // Check "from" and "until" for same granularity.
645
        if (
646
            !empty($this->parameters['from'])
647
            && !empty($this->parameters['until'])
648
        ) {
649
            if (strlen($this->parameters['from']) != strlen($this->parameters['until'])) {
650
                $this->error = 'badArgument';
651
            }
652
        }
653
        $solr_query .= ' AND timestamp:[' . $from . ' TO ' . $until . ']';
654
        $documentSet = [];
655
        $solr = Solr::getInstance($this->settings['solrcore']);
656
        if (!$solr->ready) {
657
            $this->logger->error('Apache Solr not available');
658
            return $documentSet;
659
        }
660
        if (intval($this->settings['solr_limit']) > 0) {
661
            $solr->limit = intval($this->settings['solr_limit']);
662
        }
663
        // We only care about the UID in the results and want them sorted
664
        $parameters = [
665
            "fields" => "uid",
666
            "sort" => [
667
                "uid" => "asc"
668
            ]
669
        ];
670
        $parameters['query'] = $solr_query;
671
        $result = $solr->search_raw($parameters);
672
        if (empty($result)) {
673
            $this->error = 'noRecordsMatch';
674
            return;
675
        }
676
        foreach ($result as $doc) {
677
            $documentSet[] = $doc->uid;
678
        }
679
        return $documentSet;
680
    }
681
682
    /**
683
     * Fetch more information for document list
684
     * @access protected
685
     *
686
     * @param array $documentListSet
687
     *
688
     * @return array of enriched records
689
     */
690
    protected function generateOutputForDocumentList(array $documentListSet)
691
    {
692
        $documentsToProcess = array_splice($documentListSet['elements'], 0, (int) $this->settings['limit']);
693
        if (empty($documentsToProcess)) {
694
            $this->error = 'noRecordsMatch';
695
            return [];
696
        }
697
        $verb = $this->parameters['verb'];
698
699
        $documents = $this->documentRepository->getOaiDocumentList($this->settings, $documentsToProcess);
700
701
        $records = [];
702
        while ($resArray = $documents->fetch()) {
703
            // we need the collections as array later
704
            $resArray['collections'] = explode(' ', $resArray['collections']);
705
706
            if ($verb === 'ListRecords') {
707
                // Add metadata node.
708
                $metadataPrefix = $this->parameters['metadataPrefix'];
709
                if (!$metadataPrefix) {
710
                    // If we resume an action the metadataPrefix is stored with the documentSet
711
                    $metadataPrefix = $documentListSet['metadata']['metadataPrefix'];
712
                }
713
                switch ($metadataPrefix) {
714
                    case 'oai_dc':
715
                        $resArray['metadata'] = $this->getDcData($resArray);
716
                        break;
717
                    case 'epicur':
718
                        $resArray['metadata'] = $resArray;
719
                        break;
720
                    case 'mets':
721
                        $resArray['metadata'] = $this->getMetsData($resArray);
722
                        break;
723
                }
724
            }
725
726
            $records[] = $resArray;
727
        }
728
729
        $this->generateResumptionTokenForDocumentListSet($documentListSet, count($documentsToProcess));
730
731
        return $records;
732
    }
733
734
    /**
735
     * Generate resumption token
736
     *
737
     * @access protected
738
     *
739
     * @param array $documentListSet
740
     * @param int $numShownDocuments
741
     *
742
     * @return void
743
     */
744
    protected function generateResumptionTokenForDocumentListSet(array $documentListSet, int $numShownDocuments)
745
    {
746
        // The cursor specifies how many elements have already been returned in previous requests
747
        // See http://www.openarchives.org/OAI/openarchivesprotocol.html#FlowControl
748
        $currentCursor = $documentListSet['metadata']['cursor'];
749
750
        if (count($documentListSet['elements']) !== 0) {
751
            $resumptionToken = uniqid('', false);
752
753
            $documentListSet['metadata']['cursor'] += $numShownDocuments;
754
755
            // create new token
756
            $newToken = GeneralUtility::makeInstance(Token::class);
757
            $newToken->setToken($resumptionToken);
758
            $newToken->setOptions($documentListSet);
759
760
            // add to tokenRepository
761
            $this->tokenRepository->add($newToken);
762
        } else {
763
            // Result set complete. We don't need a token.
764
            $resumptionToken = '';
765
        }
766
767
        $resumptionTokenInfo = [];
768
        $resumptionTokenInfo['token'] = $resumptionToken;
769
        $resumptionTokenInfo['cursor'] = $currentCursor;
770
        $resumptionTokenInfo['completeListSize'] = $documentListSet['metadata']['completeListSize'];
771
        $expireDateTime = new \DateTime();
772
        $expireDateTime->add(new \DateInterval('PT' . $this->settings['expired'] . 'S'));
773
        $resumptionTokenInfo['expired'] = $expireDateTime;
774
775
        $omitResumptionToken = $currentCursor === 0 && $numShownDocuments >= $documentListSet['metadata']['completeListSize'];
776
        if (!$omitResumptionToken) {
777
            $this->view->assign('resumptionToken', $resumptionTokenInfo);
778
        }
779
    }
780
}
781