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:31
created

generateResumptionTokenForDocumentListSet()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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