Passed
Pull Request — master (#123)
by
unknown
04:25
created

OaiPmhController::fetchDocumentUIDs()   F

Complexity

Conditions 20
Paths 1015

Size

Total Lines 100
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 20
eloc 64
c 1
b 0
f 0
nc 1015
nop 0
dl 0
loc 100
rs 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
B OaiPmhController::fetchDocumentSet() 0 68 10
A OaiPmhController::getFrom() 0 14 3

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

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

205
        $this->addDublinCoreData($metadata, 'dc:rights', /** @scrutinizer ignore-type */ $record['license']);
Loading history...
206
        $this->addDublinCoreData($metadata, 'dc:rights', $record['terms']);
207
        $this->addDublinCoreData($metadata, 'dc:rights', $record['restrictions']);
208
        $this->addDublinCoreData($metadata, 'dc:rights', $record['out_of_print']);
209
        $this->addDublinCoreData($metadata, 'dc:rights', $record['rights_info']);
210
211
        return $metadata;
212
    }
213
214
    /**
215
     * Add Dublin Core data.
216
     *
217
     * @access private
218
     *
219
     * @param array $metadata The mapped metadata array passed as reference
220
     * @param string $key The key to which record value should be assigned
221
     * @param string $value The key from record array
222
     *
223
     * @return void
224
     */
225
    private function addDublinCoreData(&$metadata, $key, $value)
226
    {
227
        if (!empty($value)) {
228
            $metadata[] = [$key => $value];
229
        }
230
    }
231
232
    /**
233
     * Get METS data.
234
     * @see http://www.loc.gov/standards/mets/docs/mets.v1-7.html
235
     *
236
     * @access protected
237
     *
238
     * @param array $record The full record array
239
     *
240
     * @return string The fetched METS XML
241
     */
242
    protected function getMetsData(array $record)
243
    {
244
        $mets = null;
245
        // Load METS file.
246
        $xml = new \DOMDocument();
247
        if ($xml->load($record['location'])) {
248
            // Get root element.
249
            $root = $xml->getElementsByTagNameNS($this->formats['mets']['namespace'], 'mets');
250
            if ($root->item(0) instanceof \DOMNode) {
251
                $mets = $xml->saveXML($root->item(0));
252
            } else {
253
                $this->logger->error('No METS part found in document with location "' . $record['location'] . '"');
0 ignored issues
show
Bug introduced by
The method error() does not exist on null. ( Ignorable by Annotation )

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

253
                $this->logger->/** @scrutinizer ignore-call */ 
254
                               error('No METS part found in document with location "' . $record['location'] . '"');

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...
254
            }
255
        } else {
256
            $this->logger->error('Could not load XML file from "' . $record['location'] . '"');
257
        }
258
        return $mets;
259
    }
260
261
    /**
262
     * The main method of the plugin
263
     *
264
     * @access public
265
     *
266
     * @return void
267
     */
268
    public function mainAction()
269
    {
270
        // Get allowed GET and POST variables.
271
        $this->getUrlParams();
272
273
        // Delete expired resumption tokens.
274
        $this->deleteExpiredTokens();
275
276
        switch ($this->parameters['verb']) {
277
            case 'GetRecord':
278
                $this->verbGetRecord();
279
                break;
280
            case 'Identify':
281
                $this->verbIdentify();
282
                break;
283
            case 'ListIdentifiers':
284
                $this->verbListIdentifiers();
285
                break;
286
            case 'ListMetadataFormats':
287
                $this->verbListMetadataFormats();
288
                break;
289
            case 'ListRecords':
290
                $this->verbListRecords();
291
                break;
292
            case 'ListSets':
293
                $this->verbListSets();
294
                break;
295
            default:
296
                $this->error = 'badVerb';
297
                break;
298
        }
299
300
        $this->view->assign('parameters', $this->parameters);
301
        $this->view->assign('error', $this->error);
302
303
        return;
304
    }
305
306
    /**
307
     * Continue with resumption token
308
     *
309
     * @access protected
310
     *
311
     * @return array|null list of uids
312
     */
313
    protected function resume(): ?array
314
    {
315
        $token = $this->tokenRepository->findOneByToken($this->parameters['resumptionToken']);
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

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

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

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