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
Pull Request — master (#715)
by Alexander
03:15
created

OaiPmhController::getDcData()   F

Complexity

Conditions 15
Paths 12288

Size

Total Lines 53
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 33
nc 12288
nop 1
dl 0
loc 53
rs 1.7499
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        if (!empty($record['purl'])) {
176
            $metadata[] = ['dc:identifier' => $record['purl']];
177
        }
178
        if (!empty($record['prod_id'])) {
179
            $metadata[] = ['dc:identifier' => $record['prod_id']];
180
        }
181
        if (!empty($record['urn'])) {
182
            $metadata[] = ['dc:identifier' => $record['urn']];
183
        }
184
        if (!empty($record['title'])) {
185
            $metadata[] = ['dc:title' => $record['title']];
186
        }
187
        if (!empty($record['author'])) {
188
            $metadata[] = ['dc:creator' => $record['author']];
189
        }
190
        if (!empty($record['year'])) {
191
            $metadata[] = ['dc:date' => $record['year']];
192
        }
193
        if (!empty($record['place'])) {
194
            $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
200
            $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

200
            /** @scrutinizer ignore-call */ 
201
            $document = $this->documentRepository->findOneByPartof($metadata['partof']);
Loading history...
201
202
            if ($document) {
203
                $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

203
                $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...
204
            }
205
        }
206
        if (!empty($record['license'])) {
207
            $metadata[] = ['dc:rights' => $record['license']];
208
        }
209
        if (!empty($record['terms'])) {
210
            $metadata[] = ['dc:rights' => $record['terms']];
211
        }
212
        if (!empty($record['restrictions'])) {
213
            $metadata[] = ['dc:rights' => $record['restrictions']];
214
        }
215
        if (!empty($record['out_of_print'])) {
216
            $metadata[] = ['dc:rights' => $record['out_of_print']];
217
        }
218
        if (!empty($record['rights_info'])) {
219
            $metadata[] = ['dc:rights' => $record['rights_info']];
220
        }
221
        return $metadata;
222
    }
223
224
225
    /**
226
     * Get METS data.
227
     * @see http://www.loc.gov/standards/mets/docs/mets.v1-7.html
228
     *
229
     * @access protected
230
     *
231
     * @param array $record : The full record array
232
     *
233
     * @return string: The fetched METS XML
234
     */
235
    protected function getMetsData(array $record)
236
    {
237
        $mets = null;
238
        // Load METS file.
239
        $xml = new \DOMDocument();
240
        if ($xml->load($record['location'])) {
241
            // Get root element.
242
            $root = $xml->getElementsByTagNameNS($this->formats['mets']['namespace'], 'mets');
243
            if ($root->item(0) instanceof \DOMNode) {
244
                $mets = $xml->saveXML($root->item(0));
245
            } else {
246
                $this->logger->error('No METS part found in document with location "' . $record['location'] . '"');
247
            }
248
        } else {
249
            $this->logger->error('Could not load XML file from "' . $record['location'] . '"');
250
        }
251
        return $mets;
252
    }
253
254
    /**
255
     * The main method of the plugin
256
     *
257
     * @return void
258
     */
259
    public function mainAction()
260
    {
261
        // Get allowed GET and POST variables.
262
        $this->getUrlParams();
263
264
        // Delete expired resumption tokens.
265
        $this->deleteExpiredTokens();
266
267
        switch ($this->parameters['verb']) {
268
            case 'GetRecord':
269
                $this->verbGetRecord();
270
                break;
271
            case 'Identify':
272
                $this->verbIdentify();
273
                break;
274
            case 'ListIdentifiers':
275
                $this->verbListIdentifiers();
276
                break;
277
            case 'ListMetadataFormats':
278
                $this->verbListMetadataFormats();
279
                break;
280
            case 'ListRecords':
281
                $this->verbListRecords();
282
                break;
283
            case 'ListSets':
284
                $this->verbListSets();
285
                break;
286
            default:
287
                $this->error = 'badVerb';
288
                break;
289
        }
290
291
        $this->view->assign('parameters', $this->parameters);
292
        $this->view->assign('error', $this->error);
293
294
        return;
295
    }
296
297
    /**
298
     * Continue with resumption token
299
     *
300
     * @access protected
301
     *
302
     * @return array|null list of uids
303
     */
304
    protected function resume(): ?array
305
    {
306
        $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

306
        /** @scrutinizer ignore-call */ 
307
        $token = $this->tokenRepository->findOneByToken($this->parameters['resumptionToken']);
Loading history...
307
308
        if ($token) {
309
            $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

309
            /** @scrutinizer ignore-call */ 
310
            $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...
310
        }
311
        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...
312
            return $options;
313
        } else {
314
            // No resumption token found or resumption token expired.
315
            $this->error = 'badResumptionToken';
316
            return null;
317
        }
318
    }
319
320
    /**
321
     * Process verb "GetRecord"
322
     *
323
     * @access protected
324
     *
325
     * @return void
326
     */
327
    protected function verbGetRecord()
328
    {
329
        if (count($this->parameters) !== 3 || empty($this->parameters['metadataPrefix']) || empty($this->parameters['identifier'])) {
330
            $this->error = 'badArgument';
331
            return;
332
        }
333
334
        if (!array_key_exists($this->parameters['metadataPrefix'], $this->formats)) {
335
            $this->error = 'cannotDisseminateFormat';
336
            return;
337
        }
338
339
        $document = $this->documentRepository->getOaiRecord($this->settings, $this->parameters);
340
341
        if (!$document['uid']) {
342
            $this->error = 'idDoesNotExist';
343
            return;
344
        }
345
346
        // Check for required fields.
347
        foreach ($this->formats[$this->parameters['metadataPrefix']]['requiredFields'] as $required) {
348
            if (empty($document[$required])) {
349
                $this->error = 'cannotDisseminateFormat';
350
                return;
351
            }
352
        }
353
354
        // we need the collections as array later
355
        $document['collections'] = explode(' ', $document['collections']);
356
357
        // Add metadata
358
        switch ($this->parameters['metadataPrefix']) {
359
            case 'oai_dc':
360
                $document['metadata'] = $this->getDcData($document);
361
                break;
362
            case 'epicur':
363
                $document['metadata'] = $document;
364
                break;
365
            case 'mets':
366
                $document['metadata'] = $this->getMetsData($document);
367
                break;
368
        }
369
370
        $this->view->assign('record', $document);
371
    }
372
373
    /**
374
     * Process verb "Identify"
375
     *
376
     * @access protected
377
     *
378
     * @return void
379
     */
380
    protected function verbIdentify()
381
    {
382
        $library = $this->libraryRepository->findByUid($this->settings['library']);
383
384
        $oaiIdentifyInfo = [];
385
386
        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...
387
            $this->logger->notice('Incomplete plugin configuration');
388
        }
389
390
        $oaiIdentifyInfo['oai_label'] = $library->getOaiLabel();
391
        // Use default values for an installation with incomplete plugin configuration.
392
        if (empty($oaiIdentifyInfo['oai_label'])) {
393
            $oaiIdentifyInfo['oai_label'] = 'Kitodo.Presentation OAI-PMH Interface (default configuration)';
394
            $this->logger->notice('Incomplete plugin configuration (oai_label is missing)');
395
        }
396
397
        $oaiIdentifyInfo['contact'] = $library->getContact();
398
        if (empty($oaiIdentifyInfo['contact'])) {
399
            $oaiIdentifyInfo['contact'] = '[email protected]';
400
            $this->logger->notice('Incomplete plugin configuration (contact is missing)');
401
        }
402
403
        $document = $this->documentRepository->findOldestDocument();
404
405
        if ($document) {
406
            $oaiIdentifyInfo['earliestDatestamp'] = gmdate('Y-m-d\TH:i:s\Z', $document->getTstamp()->getTimestamp());
407
        } else {
408
            // Provide a fallback timestamp if no document is found
409
            $oaiIdentifyInfo['earliestDatestamp'] = '0000-00-00T00:00:00Z';
410
411
            // access storagePid from TypoScript
412
            $pageSettings = $this->configurationManager->getConfiguration($this->configurationManager::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
413
            $storagePid = $pageSettings["plugin."]["tx_dlf."]["persistence."]["storagePid"];
414
            if ($storagePid > 0) {
415
                $this->logger->notice('No records found with PID ' . $storagePid);
416
            } else {
417
                $this->logger->notice('No records found');
418
            }
419
        }
420
        $this->view->assign('oaiIdentifyInfo', $oaiIdentifyInfo);
421
    }
422
423
    /**
424
     * Process verb "ListIdentifiers"
425
     *
426
     * @access protected
427
     *
428
     * @return void
429
     */
430
    protected function verbListIdentifiers()
431
    {
432
        // If we have a resumption token we can continue our work
433
        if (!empty($this->parameters['resumptionToken'])) {
434
            // "resumptionToken" is an exclusive argument.
435
            if (count($this->parameters) > 2) {
436
                $this->error = 'badArgument';
437
                return;
438
            } else {
439
                // return next chunk of documents
440
                $resultSet = $this->resume();
441
                if (is_array($resultSet)) {
442
                    $listIdentifiers = $this->generateOutputForDocumentList($resultSet);
443
                    $this->view->assign('listIdentifiers', $listIdentifiers);
444
                }
445
                return;
446
            }
447
        }
448
        // "metadataPrefix" is required and "identifier" is not allowed.
449
        if (empty($this->parameters['metadataPrefix']) || !empty($this->parameters['identifier'])) {
450
            $this->error = 'badArgument';
451
            return;
452
        }
453
        if (!in_array($this->parameters['metadataPrefix'], array_keys($this->formats))) {
454
            $this->error = 'cannotDisseminateFormat';
455
            return;
456
        }
457
        try {
458
            $documentSet = $this->fetchDocumentUIDs();
459
        } catch (\Exception $exception) {
460
            $this->error = 'idDoesNotExist';
461
            return;
462
        }
463
        // create new and empty documentlist
464
        $resultSet = [];
465
        if (is_array($documentSet)) {
466
            $resultSet['elements'] = $documentSet;
467
            $resultSet['metadata'] = [
468
                'cursor' => 0,
469
                'completeListSize' => count($documentSet),
470
                'metadataPrefix' => $this->parameters['metadataPrefix'],
471
            ];
472
        }
473
474
        $listIdentifiers = $this->generateOutputForDocumentList($resultSet);
475
        $this->view->assign('listIdentifiers', $listIdentifiers);
476
    }
477
478
    /**
479
     * Process verb "ListMetadataFormats"
480
     *
481
     * @access protected
482
     *
483
     * @return void
484
     */
485
    protected function verbListMetadataFormats()
486
    {
487
        $resArray = [];
488
        // check for the optional "identifier" parameter
489
        if (isset($this->parameters['identifier'])) {
490
            $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

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