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 (#394)
by Sebastian
03:47
created

OaiPmh   F

Complexity

Total Complexity 126

Size/Duplication

Total Lines 996
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 126
eloc 574
c 1
b 0
f 0
dl 0
loc 996
rs 2

17 Methods

Rating   Name   Duplication   Size   Complexity  
A deleteExpiredTokens() 0 9 2
A getUrlParams() 0 16 3
A error() 0 5 1
C main() 0 81 12
B verbListRecords() 0 31 7
A generateResumptionTokenForDocumentListSet() 0 25 3
B verbListMetadataFormats() 0 50 9
A getMetsData() 0 20 4
B verbListIdentifiers() 0 30 7
A resume() 0 26 2
F fetchDocumentUIDs() 0 106 20
A getEpicurData() 0 51 3
B verbListSets() 0 47 6
C generateOutputForDocumentList() 0 73 13
F getDcData() 0 67 15
A verbIdentify() 0 76 4
C verbGetRecord() 0 68 15

How to fix   Complexity   

Complex Class

Complex classes like OaiPmh often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OaiPmh, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Kitodo\Dlf\Plugin;
3
4
/**
5
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
6
 *
7
 * This file is part of the Kitodo and TYPO3 projects.
8
 *
9
 * @license GNU General Public License version 3 or later.
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 */
13
14
use Kitodo\Dlf\Common\DocumentList;
15
use Kitodo\Dlf\Common\Helper;
16
use Kitodo\Dlf\Common\Solr;
17
use TYPO3\CMS\Core\Database\ConnectionPool;
18
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
19
use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21
/**
22
 * 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 OaiPmh extends \Kitodo\Dlf\Common\AbstractPlugin {
30
    public $scriptRelPath = 'Classes/Plugin/OaiPmh.php';
31
32
    /**
33
     * Did an error occur?
34
     *
35
     * @var boolean
36
     * @access protected
37
     */
38
    protected $error = FALSE;
39
40
    /**
41
     * This holds the OAI DOM object
42
     *
43
     * @var \DOMDocument
44
     * @access protected
45
     */
46
    protected $oai;
47
48
    /**
49
     * This holds the configuration for all supported metadata prefixes
50
     *
51
     * @var array
52
     * @access protected
53
     */
54
    protected $formats = [
55
        'oai_dc' => [
56
            'schema' => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
57
            'namespace' => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
58
            'requiredFields' => ['record_id'],
59
        ],
60
        'epicur' => [
61
            'schema' => 'http://www.persistent-identifier.de/xepicur/version1.0/xepicur.xsd',
62
            'namespace' => 'urn:nbn:de:1111-2004033116',
63
            'requiredFields' => ['purl', 'urn'],
64
        ],
65
        'mets' => [
66
            'schema' => 'http://www.loc.gov/standards/mets/version17/mets.v1-7.xsd',
67
            'namespace' => 'http://www.loc.gov/METS/',
68
            'requiredFields' => ['location'],
69
        ]
70
    ];
71
72
    /**
73
     * Delete expired resumption tokens
74
     *
75
     * @access protected
76
     *
77
     * @return void
78
     */
79
    protected function deleteExpiredTokens() {
80
        // Delete expired resumption tokens.
81
        $GLOBALS['TYPO3_DB']->exec_DELETEquery(
82
            'tx_dlf_tokens',
83
            'tx_dlf_tokens.ident="oai" AND tx_dlf_tokens.tstamp<'.intval($GLOBALS['EXEC_TIME'] - $this->conf['expired'])
84
        );
85
        if ($GLOBALS['TYPO3_DB']->sql_affected_rows() === -1) {
86
            // Deletion failed.
87
            Helper::devLog('Could not delete expired resumption tokens', DEVLOG_SEVERITY_WARNING);
88
        }
89
    }
90
91
    /**
92
     * Process error
93
     *
94
     * @access protected
95
     *
96
     * @param string $type: Error type
97
     *
98
     * @return \DOMElement XML node to add to the OAI response
99
     */
100
    protected function error($type) {
101
        $this->error = TRUE;
102
        $error = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'error', htmlspecialchars($this->pi_getLL($type, $type, FALSE), ENT_NOQUOTES, 'UTF-8'));
103
        $error->setAttribute('code', $type);
104
        return $error;
105
    }
106
107
    /**
108
     * Load URL parameters
109
     *
110
     * @access protected
111
     *
112
     * @return void
113
     */
114
    protected function getUrlParams() {
115
        $allowedParams = [
116
            'verb',
117
            'identifier',
118
            'metadataPrefix',
119
            'from',
120
            'until',
121
            'set',
122
            'resumptionToken'
123
        ];
124
        // Clear plugin variables.
125
        $this->piVars = [];
126
        // Set only allowed parameters.
127
        foreach ($allowedParams as $param) {
128
            if (\TYPO3\CMS\Core\Utility\GeneralUtility::_GP($param)) {
129
                $this->piVars[$param] = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP($param);
130
            }
131
        }
132
    }
133
134
    /**
135
     * Get unqualified Dublin Core data.
136
     * @see http://www.openarchives.org/OAI/openarchivesprotocol.html#dublincore
137
     *
138
     * @access protected
139
     *
140
     * @param array $metadata: The metadata array
141
     *
142
     * @return \DOMElement XML node to add to the OAI response
143
     */
144
    protected function getDcData(array $metadata) {
145
        $oai_dc = $this->oai->createElementNS($this->formats['oai_dc']['namespace'], 'oai_dc:dc');
146
        $oai_dc->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/');
147
        $oai_dc->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
148
        $oai_dc->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', $this->formats['oai_dc']['namespace'].' '.$this->formats['oai_dc']['schema']);
149
        $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:identifier', htmlspecialchars($metadata['record_id'], ENT_NOQUOTES, 'UTF-8')));
150
        if (!empty($metadata['purl'])) {
151
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:identifier', htmlspecialchars($metadata['purl'], ENT_NOQUOTES, 'UTF-8')));
152
        }
153
        if (!empty($metadata['prod_id'])) {
154
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:identifier', 'kitodo:production:'.htmlspecialchars($metadata['prod_id'], ENT_NOQUOTES, 'UTF-8')));
155
        }
156
        if (!empty($metadata['urn'])) {
157
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:identifier', htmlspecialchars($metadata['urn'], ENT_NOQUOTES, 'UTF-8')));
158
        }
159
        if (!empty($metadata['title'])) {
160
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:title', htmlspecialchars($metadata['title'], ENT_NOQUOTES, 'UTF-8')));
161
        }
162
        if (!empty($metadata['author'])) {
163
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:creator', htmlspecialchars($metadata['author'], ENT_NOQUOTES, 'UTF-8')));
164
        }
165
        if (!empty($metadata['year'])) {
166
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:date', htmlspecialchars($metadata['year'], ENT_NOQUOTES, 'UTF-8')));
167
        }
168
        if (!empty($metadata['place'])) {
169
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:coverage', htmlspecialchars($metadata['place'], ENT_NOQUOTES, 'UTF-8')));
170
        }
171
        $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:format', 'application/mets+xml'));
172
        $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:type', 'Text'));
173
        if (!empty($metadata['partof'])) {
174
            /** @var QueryBuilder $queryBuilder */
175
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
176
                ->getQueryBuilderForTable('tx_dlf_documents');
177
178
            $result = $queryBuilder
179
                ->select('tx_dlf_documents.record_id')
180
                ->from('tx_dlf_documents')
181
                ->where(
182
                    $queryBuilder->expr()->eq('tx_dlf_documents.uid', intval($metadata['partof'])),
183
                    Helper::whereExpression('tx_dlf_documents')
184
                )
185
                ->setMaxResults(1)
186
                ->execute();
187
188
            $allResults = $result->fetchAll();
189
190
            if (count($allResults) == 1) {
191
                $partof = $allResults[0];
192
                $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:relation', htmlspecialchars($partof['record_id'], ENT_NOQUOTES, 'UTF-8')));
193
            }
194
        }
195
        if (!empty($metadata['license'])) {
196
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:rights', htmlspecialchars($metadata['license'], ENT_NOQUOTES, 'UTF-8')));
197
        }
198
        if (!empty($metadata['terms'])) {
199
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:rights', htmlspecialchars($metadata['terms'], ENT_NOQUOTES, 'UTF-8')));
200
        }
201
        if (!empty($metadata['restrictions'])) {
202
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:rights', htmlspecialchars($metadata['restrictions'], ENT_NOQUOTES, 'UTF-8')));
203
        }
204
        if (!empty($metadata['out_of_print'])) {
205
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:rights', htmlspecialchars($metadata['out_of_print'], ENT_NOQUOTES, 'UTF-8')));
206
        }
207
        if (!empty($metadata['rights_info'])) {
208
            $oai_dc->appendChild($this->oai->createElementNS('http://purl.org/dc/elements/1.1/', 'dc:rights', htmlspecialchars($metadata['rights_info'], ENT_NOQUOTES, 'UTF-8')));
209
        }
210
        return $oai_dc;
211
    }
212
213
    /**
214
     * Get epicur data.
215
     * @see http://www.persistent-identifier.de/?link=210
216
     *
217
     * @access protected
218
     *
219
     * @param array $metadata: The metadata array
220
     *
221
     * @return \DOMElement XML node to add to the OAI response
222
     */
223
    protected function getEpicurData(array $metadata) {
224
        // Define all XML elements with or without qualified namespace.
225
        if (empty($this->conf['unqualified_epicur'])) {
226
            $epicur = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:epicur');
227
            $admin = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:administrative_data');
228
            $delivery = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:delivery');
229
            $update = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:update_status');
230
            $transfer = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:transfer');
231
            $format = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:format', 'text/html');
232
            $record = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:record');
233
            $identifier = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:identifier', htmlspecialchars($metadata['urn'], ENT_NOQUOTES, 'UTF-8'));
234
            $resource = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:resource');
235
            $ident = $this->oai->createElementNS($this->formats['epicur']['namespace'], 'epicur:identifier', htmlspecialchars($metadata['purl'], ENT_NOQUOTES, 'UTF-8'));
236
        } else {
237
            $epicur = $this->oai->createElement('epicur');
238
            $epicur->setAttribute('xmlns', $this->formats['epicur']['namespace']);
239
            $admin = $this->oai->createElement('administrative_data');
240
            $delivery = $this->oai->createElement('delivery');
241
            $update = $this->oai->createElement('update_status');
242
            $transfer = $this->oai->createElement('transfer');
243
            $format = $this->oai->createElement('format', 'text/html');
244
            $record = $this->oai->createElement('record');
245
            $identifier = $this->oai->createElement('identifier', htmlspecialchars($metadata['urn'], ENT_NOQUOTES, 'UTF-8'));
246
            $resource = $this->oai->createElement('resource');
247
            $ident = $this->oai->createElement('identifier', htmlspecialchars($metadata['purl'], ENT_NOQUOTES, 'UTF-8'));
248
        }
249
        // Add attributes and build XML tree.
250
        $epicur->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
251
        $epicur->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', $this->formats['epicur']['namespace'].' '.$this->formats['epicur']['schema']);
252
        // Do we update an URN or register a new one?
253
        if ($metadata['tstamp'] == $metadata['crdate']) {
254
            $update->setAttribute('type', 'urn_new');
255
        } else {
256
            $update->setAttribute('type', 'url_update_general');
257
        }
258
        $delivery->appendChild($update);
259
        $transfer->setAttribute('type', 'http');
260
        $delivery->appendChild($transfer);
261
        $admin->appendChild($delivery);
262
        $epicur->appendChild($admin);
263
        $identifier->setAttribute('scheme', 'urn:nbn:de');
264
        $record->appendChild($identifier);
265
        $ident->setAttribute('scheme', 'url');
266
        $ident->setAttribute('type', 'frontpage');
267
        $ident->setAttribute('role', 'primary');
268
        $resource->appendChild($ident);
269
        $format->setAttribute('scheme', 'imt');
270
        $resource->appendChild($format);
271
        $record->appendChild($resource);
272
        $epicur->appendChild($record);
273
        return $epicur;
274
    }
275
276
    /**
277
     * Get METS data.
278
     * @see http://www.loc.gov/standards/mets/docs/mets.v1-7.html
279
     *
280
     * @access protected
281
     *
282
     * @param array $metadata: The metadata array
283
     *
284
     * @return \DOMElement XML node to add to the OAI response
285
     */
286
    protected function getMetsData(array $metadata) {
287
        $mets = NULL;
288
        // Load METS file.
289
        $xml = new \DOMDocument();
290
        if ($xml->load($metadata['location'])) {
291
            // Get root element.
292
            $root = $xml->getElementsByTagNameNS($this->formats['mets']['namespace'], 'mets');
293
            if ($root->item(0) instanceof \DOMNode) {
294
                // Import node into \DOMDocument.
295
                $mets = $this->oai->importNode($root->item(0), TRUE);
296
            } else {
297
                Helper::devLog('No METS part found in document with location "'.$metadata['location'].'"', DEVLOG_SEVERITY_ERROR);
298
            }
299
        } else {
300
            Helper::devLog('Could not load XML file from "'.$metadata['location'].'"', DEVLOG_SEVERITY_ERROR);
301
        }
302
        if ($mets === NULL) {
303
            $mets = $this->oai->createElementNS('http://kitodo.org/', 'kitodo:error', htmlspecialchars($this->pi_getLL('error', 'Error!', FALSE), ENT_NOQUOTES, 'UTF-8'));
304
        }
305
        return $mets;
306
    }
307
308
    /**
309
     * The main method of the PlugIn
310
     *
311
     * @access public
312
     *
313
     * @param string $content: The PlugIn content
314
     * @param array $conf: The PlugIn configuration
315
     *
316
     * @return void
317
     */
318
    public function main($content, $conf) {
319
        // Initialize plugin.
320
        $this->init($conf);
321
        // Turn cache off.
322
        $this->setCache(FALSE);
323
        // Get GET and POST variables.
324
        $this->getUrlParams();
325
        // Delete expired resumption tokens.
326
        $this->deleteExpiredTokens();
327
        // Create XML document.
328
        $this->oai = new \DOMDocument('1.0', 'UTF-8');
329
        // Add processing instruction (aka XSL stylesheet).
330
        if (!empty($this->conf['stylesheet'])) {
331
            // Resolve "EXT:" prefix in file path.
332
            if (substr($this->conf['stylesheet'], 0, 4) == 'EXT:') {
333
                list ($extKey, $filePath) = explode('/', substr($this->conf['stylesheet'], 4), 2);
334
                if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($extKey)) {
335
                    $this->conf['stylesheet'] = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($extKey).$filePath;
336
                }
337
            }
338
            $stylesheet = \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl($this->conf['stylesheet']);
339
        } else {
340
            // Use default stylesheet if no custom stylesheet is given.
341
            $stylesheet = \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($this->extKey).'Resources/Public/Stylesheets/OaiPmh.xsl');
342
        }
343
        $this->oai->appendChild($this->oai->createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="'.htmlspecialchars($stylesheet, ENT_NOQUOTES, 'UTF-8').'"'));
344
        // Create root element.
345
        $root = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'OAI-PMH');
346
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
347
        $root->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', 'http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd');
348
        // Add response date.
349
        $root->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'responseDate', gmdate('Y-m-d\TH:i:s\Z', $GLOBALS['EXEC_TIME'])));
350
        // Get response data.
351
        switch ($this->piVars['verb']) {
352
            case 'GetRecord':
353
                $response = $this->verbGetRecord();
354
                break;
355
            case 'Identify':
356
                $response = $this->verbIdentify();
357
                break;
358
            case 'ListIdentifiers':
359
                $response = $this->verbListIdentifiers();
360
                break;
361
            case 'ListMetadataFormats':
362
                $response = $this->verbListMetadataFormats();
363
                break;
364
            case 'ListRecords':
365
                $response = $this->verbListRecords();
366
                break;
367
            case 'ListSets':
368
                $response = $this->verbListSets();
369
                break;
370
            default:
371
                $response = $this->error('badVerb');
372
        }
373
        // Add request.
374
        $linkConf = [
375
            'parameter' => $GLOBALS['TSFE']->id,
376
            'forceAbsoluteUrl' => 1
377
        ];
378
        $request = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'request', htmlspecialchars($this->cObj->typoLink_URL($linkConf), ENT_NOQUOTES, 'UTF-8'));
379
        if (!$this->error) {
380
            foreach ($this->piVars as $key => $value) {
381
                $request->setAttribute($key, htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8'));
382
            }
383
        }
384
        $root->appendChild($request);
385
        $root->appendChild($response);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type string; however, parameter $newnode of DOMNode::appendChild() does only seem to accept DOMNode, maybe add an additional type check? ( Ignorable by Annotation )

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

385
        $root->appendChild(/** @scrutinizer ignore-type */ $response);
Loading history...
386
        $this->oai->appendChild($root);
387
        $content = $this->oai->saveXML();
388
        // Clean output buffer.
389
        ob_end_clean();
390
        // Send headers.
391
        header('HTTP/1.1 200 OK');
392
        header('Cache-Control: no-cache');
393
        header('Content-Length: '.strlen($content));
394
        header('Content-Type: text/xml; charset=utf-8');
395
        header('Date: '.date('r', $GLOBALS['EXEC_TIME']));
396
        header('Expires: '.date('r', $GLOBALS['EXEC_TIME'] + $this->conf['expired']));
397
        echo $content;
398
        exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
399
    }
400
401
    /**
402
     * Continue with resumption token
403
     *
404
     * @access protected
405
     *
406
     * @return string Substitution for subpart "###RESPONSE###"
407
     */
408
    protected function resume() {
409
        /** @var QueryBuilder $queryBuilder */
410
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
411
            ->getQueryBuilderForTable('tx_dlf_tokens');
412
413
        // Get resumption token.
414
        $result = $queryBuilder
415
            ->select('tx_dlf_tokens.options AS options')
416
            ->from('tx_dlf_tokens')
417
            ->where(
418
                $queryBuilder->expr()->eq('tx_dlf_tokens.ident', '"oai"'),
419
                $queryBuilder->expr()->eq('tx_dlf_tokens.token', $queryBuilder->expr()->literal($this->piVars['resumptionToken'])),
420
                Helper::whereExpression('tx_dlf_structures')
421
            )
422
            ->setMaxResults(1)
423
            ->execute();
424
425
        $allResults = $result->fetchAll();
426
427
        if (count($allResults) > 1) {
428
            // No resumption token found or resumption token expired.
429
            return $this->error('badResumptionToken');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('badResumptionToken') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
430
        }
431
        $resArray = $allResults[0];
432
        $resultSet = unserialize($resArray['options']);
433
        return $this->generateOutputForDocumentList($resultSet);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->generateOu...ocumentList($resultSet) returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
434
    }
435
436
    /**
437
     * Process verb "GetRecord"
438
     *
439
     * @access protected
440
     *
441
     * @return string Substitution for subpart "###RESPONSE###"
442
     */
443
    protected function verbGetRecord() {
444
        if (count($this->piVars) != 3 || empty($this->piVars['metadataPrefix']) || empty($this->piVars['identifier'])) {
445
            return $this->error('badArgument');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('badArgument') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
446
        }
447
        if (!in_array($this->piVars['metadataPrefix'], array_keys($this->formats))) {
448
            return $this->error('cannotDisseminateFormat');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('cannotDisseminateFormat') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
449
        }
450
        $where = '';
451
        if (!$this->conf['show_userdefined']) {
452
            $where .= ' AND tx_dlf_collections.fe_cruser_id=0';
453
        }
454
        $record = $GLOBALS['TYPO3_DB']->exec_SELECT_mm_query(
455
            'tx_dlf_documents.*,GROUP_CONCAT(DISTINCT tx_dlf_collections.oai_name ORDER BY tx_dlf_collections.oai_name SEPARATOR " ") AS collections',
456
            'tx_dlf_documents',
457
            'tx_dlf_relations',
458
            'tx_dlf_collections',
459
            'AND tx_dlf_documents.record_id='.$GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['identifier'], 'tx_dlf_documents')
460
                .' AND tx_dlf_documents.pid='.intval($this->conf['pages'])
461
                .' AND tx_dlf_collections.pid='.intval($this->conf['pages'])
462
                .' AND tx_dlf_relations.ident='.$GLOBALS['TYPO3_DB']->fullQuoteStr('docs_colls', 'tx_dlf_relations')
463
                .$where
464
                .Helper::whereClause('tx_dlf_collections'),
465
            '',
466
            '',
467
            '1'
468
        );
469
        if (!$GLOBALS['TYPO3_DB']->sql_num_rows($record)) {
470
            return $this->error('idDoesNotExist');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('idDoesNotExist') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
471
        }
472
        $resArray = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($record);
473
        // Check for required fields.
474
        foreach ($this->formats[$this->piVars['metadataPrefix']]['requiredFields'] as $required) {
475
            if (empty($resArray[$required])) {
476
                return $this->error('cannotDisseminateFormat');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('cannotDisseminateFormat') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
477
            }
478
        }
479
        $GetRecord = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'GetRecord');
480
        $recordNode = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'record');
481
        $headerNode = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'header');
482
        $headerNode->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'identifier', htmlspecialchars($resArray['record_id'], ENT_NOQUOTES, 'UTF-8')));
483
        $headerNode->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'datestamp', gmdate('Y-m-d\TH:i:s\Z', $resArray['tstamp'])));
484
        // Handle deleted documents.
485
        // TODO: Use TYPO3 API functions here!
486
        if ($resArray['deleted']
487
            || $resArray['hidden']) {
488
            $headerNode->setAttribute('status', 'deleted');
489
            $recordNode->appendChild($headerNode);
490
        } else {
491
            foreach (explode(' ', $resArray['collections']) as $spec) {
492
                $headerNode->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'setSpec', htmlspecialchars($spec, ENT_NOQUOTES, 'UTF-8')));
493
            }
494
            $recordNode->appendChild($headerNode);
495
            $metadataNode = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'metadata');
496
            switch ($this->piVars['metadataPrefix']) {
497
                case 'oai_dc':
498
                    $metadataNode->appendChild($this->getDcData($resArray));
499
                    break;
500
                case 'epicur':
501
                    $metadataNode->appendChild($this->getEpicurData($resArray));
502
                    break;
503
                case 'mets':
504
                    $metadataNode->appendChild($this->getMetsData($resArray));
505
                    break;
506
            }
507
            $recordNode->appendChild($metadataNode);
508
        }
509
        $GetRecord->appendChild($recordNode);
510
        return $GetRecord;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $GetRecord returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
511
    }
512
513
    /**
514
     * Process verb "Identify"
515
     *
516
     * @access protected
517
     *
518
     * @return \DOMElement XML node to add to the OAI response
519
     */
520
    protected function verbIdentify() {
521
        // Check for invalid arguments.
522
        if (count($this->piVars) > 1) {
523
            return $this->error('badArgument');
524
        }
525
        // Get repository name and administrative contact.
526
        // Use default values for an installation with incomplete plugin configuration.
527
        $adminEmail = '[email protected]';
528
        $repositoryName = 'Kitodo.Presentation OAI-PMH Interface (default configuration)';
529
530
        /** @var QueryBuilder $queryBuilder */
531
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
532
            ->getQueryBuilderForTable('tx_dlf_libraries');
533
534
        $result = $queryBuilder
535
            ->select(
536
                'tx_dlf_libraries.oai_label AS oai_label',
537
                'tx_dlf_libraries.contact AS contact'
538
            )
539
            ->from('tx_dlf_libraries')
540
            ->where(
541
                $queryBuilder->expr()->eq('tx_dlf_libraries.pid', intval($this->conf['pages'])),
542
                $queryBuilder->expr()->eq('tx_dlf_libraries.uid', intval($this->conf['library'])),
543
                Helper::whereExpression('tx_dlf_libraries')
544
            )
545
            ->setMaxResults(1)
546
            ->execute();
547
548
        $allResults = $result->fetchAll();
549
550
        if (count($allResults) == 1) {
551
            $resArray = $allResults[0];
552
            $adminEmail = htmlspecialchars(trim(str_replace('mailto:', '', $resArray['contact'])), ENT_NOQUOTES);
553
            $repositoryName = htmlspecialchars($resArray['oai_label'], ENT_NOQUOTES);
554
        } else {
555
            Helper::devLog('Incomplete plugin configuration', DEVLOG_SEVERITY_NOTICE);
556
        }
557
        // Get earliest datestamp. Use a default value if that fails.
558
        $earliestDatestamp = '0000-00-00T00:00:00Z';
559
560
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
561
            ->getQueryBuilderForTable('tx_dlf_documents');
562
563
        $result = $queryBuilder
564
            ->select('tx_dlf_documents.tstamp AS tstamp')
565
            ->from('tx_dlf_documents')
566
            ->where(
567
                $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($this->conf['pages']))
568
            )
569
            ->orderBy('tx_dlf_documents.tstamp')
570
            ->setMaxResults(1)
571
            ->execute();
572
573
        $allResults = $result->fetchAll();
0 ignored issues
show
Unused Code introduced by
The assignment to $allResults is dead and can be removed.
Loading history...
574
575
        if ($resArray = $result->fetch()) {
576
            $timestamp = $resArray['tstamp'];
577
            $earliestDatestamp = gmdate('Y-m-d\TH:i:s\Z', $timestamp);
578
        } else {
579
            Helper::devLog('No records found with PID '.$this->conf['pages'], DEVLOG_SEVERITY_NOTICE);
580
        }
581
        $linkConf = [
582
            'parameter' => $GLOBALS['TSFE']->id,
583
            'forceAbsoluteUrl' => 1
584
        ];
585
        $baseURL = htmlspecialchars($this->cObj->typoLink_URL($linkConf), ENT_NOQUOTES);
586
        // Add identification node.
587
        $Identify = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'Identify');
588
        $Identify->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'repositoryName', $repositoryName));
589
        $Identify->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'baseURL', $baseURL));
590
        $Identify->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'protocolVersion', '2.0'));
591
        $Identify->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'adminEmail', $adminEmail));
592
        $Identify->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'earliestDatestamp', $earliestDatestamp));
593
        $Identify->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'deletedRecord', 'transient'));
594
        $Identify->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'granularity', 'YYYY-MM-DDThh:mm:ssZ'));
595
        return $Identify;
596
    }
597
598
    /**
599
     * Process verb "ListIdentifiers"
600
     *
601
     * @access protected
602
     *
603
     * @return string Substitution for subpart "###RESPONSE###"
604
     */
605
    protected function verbListIdentifiers() {
606
        // If we have a resumption token we can continue our work
607
        if (!empty($this->piVars['resumptionToken'])) {
608
            // "resumptionToken" is an exclusive argument.
609
            if (count($this->piVars) > 2) {
610
                return $this->error('badArgument');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('badArgument') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
611
            } else {
612
                return $this->resume();
613
            }
614
        }
615
        // "metadataPrefix" is required and "identifier" is not allowed.
616
        if (empty($this->piVars['metadataPrefix']) || !empty($this->piVars['identifier'])) {
617
            return $this->error('badArgument');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('badArgument') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
618
        }
619
        if (!in_array($this->piVars['metadataPrefix'], array_keys($this->formats))) {
620
            return $this->error('cannotDisseminateFormat');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('cannotDisseminateFormat') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
621
        }
622
        try {
623
            $documentSet = $this->fetchDocumentUIDs();
624
        } catch (\Exception $exception) {
625
            return $this->error($exception->getMessage());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error($exception->getMessage()) returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
626
        }
627
        $resultSet = GeneralUtility::makeInstance(DocumentList::class);
628
        $resultSet->reset();
629
        $resultSet->add($documentSet);
630
        $resultSet->metadata = [
631
            'completeListSize' => count($documentSet),
632
            'metadataPrefix' => $this->piVars['metadataPrefix'],
633
        ];
634
        return $this->generateOutputForDocumentList($resultSet);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->generateOu...ocumentList($resultSet) returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
635
    }
636
637
    /**
638
     * Process verb "ListMetadataFormats"
639
     *
640
     * @access protected
641
     *
642
     * @return \DOMElement XML node to add to the OAI response
643
     */
644
    protected function verbListMetadataFormats() {
645
        $resArray = [];
646
        // Check for invalid arguments.
647
        if (count($this->piVars) > 1) {
648
            if (empty($this->piVars['identifier']) || count($this->piVars) > 2) {
649
                return $this->error('badArgument');
650
            }
651
652
            /** @var QueryBuilder $queryBuilder */
653
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
654
                ->getQueryBuilderForTable('tx_dlf_documents');
655
656
            // Check given identifier.
657
            $result = $queryBuilder
658
                ->select('tx_dlf_documents.*')
659
                ->from('tx_dlf_documents')
660
                ->where(
661
                    $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($this->conf['pages'])),
662
                    $queryBuilder->expr()->eq('tx_dlf_documents.record_id', $queryBuilder->expr()->literal($this->piVars['identifier']))
663
                )
664
                ->orderBy('tx_dlf_documents.tstamp')
665
                ->setMaxResults(1)
666
                ->execute();
667
668
            $allResults = $result->fetchAll();
669
670
            if (count($allResults) < 1) {
671
                return $this->error('idDoesNotExist');
672
            }
673
            $resArray = $allResults[0];
674
        }
675
        // Add metadata formats node.
676
        $ListMetadaFormats = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'ListMetadataFormats');
677
        foreach ($this->formats as $prefix => $details) {
678
            if (!empty($resArray)) {
679
                foreach ($details['requiredFields'] as $required) {
680
                    if (empty($resArray[$required])) {
681
                        // Skip metadata formats whose requirements are not met.
682
                        continue 2;
683
                    }
684
                }
685
            }
686
            // Add format node.
687
            $format = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'metadataFormat');
688
            $format->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'metadataPrefix', htmlspecialchars($prefix, ENT_NOQUOTES, 'UTF-8')));
689
            $format->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'schema', htmlspecialchars($details['schema'], ENT_NOQUOTES, 'UTF-8')));
690
            $format->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'metadataNamespace', htmlspecialchars($details['namespace'], ENT_NOQUOTES, 'UTF-8')));
691
            $ListMetadaFormats->appendChild($format);
692
        }
693
        return $ListMetadaFormats;
694
    }
695
696
    /**
697
     * Process verb "ListRecords"
698
     *
699
     * @access protected
700
     *
701
     * @return string Substitution for subpart "###RESPONSE###"
702
     */
703
    protected function verbListRecords() {
704
        // Check for invalid arguments.
705
        if (!empty($this->piVars['resumptionToken'])) {
706
            // "resumptionToken" is an exclusive argument.
707
            if (count($this->piVars) > 2) {
708
                return $this->error('badArgument');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('badArgument') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
709
            } else {
710
                return $this->resume();
711
            }
712
        }
713
        if (empty($this->piVars['metadataPrefix']) || !empty($this->piVars['identifier'])) {
714
            // "metadataPrefix" is required and "identifier" is not allowed.
715
            return $this->error('badArgument');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('badArgument') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
716
        }
717
        // Check "metadataPrefix" for valid value.
718
        if (!in_array($this->piVars['metadataPrefix'], array_keys($this->formats))) {
719
            return $this->error('cannotDisseminateFormat');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('cannotDisseminateFormat') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
720
        }
721
        try {
722
            $documentSet = $this->fetchDocumentUIDs();
723
        } catch (\Exception $exception) {
724
            return $this->error($exception->getMessage());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error($exception->getMessage()) returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
725
        }
726
        $resultSet = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(DocumentList::class);
727
        $resultSet->reset();
728
        $resultSet->add($documentSet);
729
        $resultSet->metadata = [
730
            'completeListSize' => count($documentSet),
731
            'metadataPrefix' => $this->piVars['metadataPrefix'],
732
        ];
733
        return $this->generateOutputForDocumentList($resultSet);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->generateOu...ocumentList($resultSet) returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
734
    }
735
736
    /**
737
     * Process verb "ListSets"
738
     *
739
     * @access protected
740
     *
741
     * @return string Substitution for subpart "###RESPONSE###"
742
     */
743
    protected function verbListSets() {
744
        /** @var QueryBuilder $queryBuilder */
745
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
746
            ->getQueryBuilderForTable('tx_dlf_collections');
747
748
        // Check for invalid arguments.
749
        if (count($this->piVars) > 1) {
750
            if (!empty($this->piVars['resumptionToken'])) {
751
                return $this->error('badResumptionToken');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('badResumptionToken') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
752
            } else {
753
                return $this->error('badArgument');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('badArgument') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
754
            }
755
        }
756
        $where = '';
757
        if (!$this->conf['show_userdefined']) {
758
            $where = $queryBuilder->expr()->eq('tx_dlf_collections.fe_cruser_id', 0);
759
        }
760
761
        $result = $queryBuilder
762
            ->select(
763
                'tx_dlf_collections.oai_name AS oai_name',
764
                'tx_dlf_collections.label AS label'
765
            )
766
            ->from('tx_dlf_collections')
767
            ->where(
768
                $queryBuilder->expr()->in('tx_dlf_collections.sys_language_uid', array(-1, 0)),
769
                $queryBuilder->expr()->eq('tx_dlf_collections.pid', intval($this->conf['pages'])),
770
                $queryBuilder->expr()->neq('tx_dlf_collections.oai_name', ''),
771
                $where,
772
                Helper::whereExpression('tx_dlf_collections')
773
            )
774
            ->orderBy('tx_dlf_collections.oai_name')
775
            ->execute();
776
777
        $allResults = $result->fetchAll();
778
779
        if (count($allResults) < 1) {
780
            return $this->error('noSetHierarchy');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->error('noSetHierarchy') returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
781
        }
782
        $ListSets = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'ListSets');
783
        while ($resArray = $result->fetch()) {
784
            $set = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'set');
785
            $set->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'setSpec', htmlspecialchars($resArray['oai_name'], ENT_NOQUOTES, 'UTF-8')));
786
            $set->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'setName', htmlspecialchars($resArray['label'], ENT_NOQUOTES, 'UTF-8')));
787
            $ListSets->appendChild($set);
788
        }
789
        return $ListSets;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ListSets returns the type DOMElement which is incompatible with the documented return type string.
Loading history...
790
    }
791
792
    /**
793
     * Fetch records
794
     *
795
     * @access protected
796
     *
797
     * @return array Array of matching records
798
     * @throws \Exception
799
     */
800
    protected function fetchDocumentUIDs() {
801
        /** @var QueryBuilder $queryBuilder */
802
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
803
            ->getQueryBuilderForTable('tx_dlf_collections');
804
805
        $solr_query = '';
806
        $where = '';
807
        if (!$this->conf['show_userdefined']) {
808
            $where = $queryBuilder->expr()->eq('tx_dlf_collections.fe_cruser_id', 0);
809
        }
810
        // Check "set" for valid value.
811
        if (!empty($this->piVars['set'])) {
812
            // For SOLR we need the index_name of the collection,
813
            // For DB Query we need the UID of the collection
814
            $result = $queryBuilder
815
                ->select(
816
                    'tx_dlf_collections.index_name AS index_name',
817
                    'tx_dlf_collections.uid AS uid',
818
                    'tx_dlf_collections.index_search as index_query'
819
                )
820
                ->from('tx_dlf_collections')
821
                ->where(
822
                    $queryBuilder->expr()->eq('tx_dlf_collections.pid', intval($this->conf['pages'])),
823
                    $queryBuilder->expr()->eq('tx_dlf_collections.oai_name', $queryBuilder->expr()->literal($this->piVars['set'])),
824
                    $where,
825
                    Helper::whereExpression('tx_dlf_collections')
826
                )
827
                ->setMaxResults(1)
828
                ->execute();
829
830
            $allResults = $result->fetchAll();
831
832
            if (count($allResults) < 1) {
833
                throw new \Exception('noSetHierarchy');
834
            }
835
            $resArray = $allResults[0];
836
            if ($resArray['index_query'] != "") {
837
                $solr_query .= '('.$resArray['index_query'].')';
838
            } else {
839
                $solr_query .= 'collection:'.'"'.$resArray['index_name'].'"';
840
            }
841
        } else {
842
            // If no set is specified we have to query for all collections
843
            $solr_query .= 'collection:* NOT collection:""';
844
        }
845
        // Check for required fields.
846
        foreach ($this->formats[$this->piVars['metadataPrefix']]['requiredFields'] as $required) {
847
            $solr_query .= ' NOT '.$required.':""';
848
        }
849
        // toplevel="true" is always required
850
        $solr_query .= ' AND toplevel:"true"';
851
        $from = "*";
852
        // Check "from" for valid value.
853
        if (!empty($this->piVars['from'])) {
854
            // Is valid format?
855
            if (is_array($date_array = strptime($this->piVars['from'], '%Y-%m-%dT%H:%M:%SZ'))
856
                || is_array($date_array = strptime($this->piVars['from'], '%Y-%m-%d'))) {
857
                $timestamp = gmmktime($date_array['tm_hour'], $date_array['tm_min'], $date_array['tm_sec'], $date_array['tm_mon'] + 1, $date_array['tm_mday'], $date_array['tm_year'] + 1900);
858
                $from = date("Y-m-d", $timestamp).'T'.date("H:i:s", $timestamp).'.000Z';
859
            } else {
860
                throw new \Exception('badArgument');
861
            }
862
        }
863
        $until = "*";
864
        // Check "until" for valid value.
865
        if (!empty($this->piVars['until'])) {
866
            // Is valid format?
867
            if (is_array($date_array = strptime($this->piVars['until'], '%Y-%m-%dT%H:%M:%SZ'))
868
                || is_array($date_array = strptime($this->piVars['until'], '%Y-%m-%d'))) {
869
                $timestamp = gmmktime($date_array['tm_hour'], $date_array['tm_min'], $date_array['tm_sec'], $date_array['tm_mon'] + 1, $date_array['tm_mday'], $date_array['tm_year'] + 1900);
870
                $until = date("Y-m-d", $timestamp).'T'.date("H:i:s", $timestamp).'.999Z';
871
                if ($from != "*" && $from > $until) {
872
                    throw new \Exception('badArgument');
873
                }
874
            } else {
875
                throw new \Exception('badArgument');
876
            }
877
        }
878
        // Check "from" and "until" for same granularity.
879
        if (!empty($this->piVars['from'])
880
            && !empty($this->piVars['until'])) {
881
            if (strlen($this->piVars['from']) != strlen($this->piVars['until'])) {
882
                throw new \Exception('badArgument');
883
            }
884
        }
885
        $solr_query .= ' AND timestamp:['.$from.' TO '.$until.']';
886
        $documentSet = [];
887
        $solr = Solr::getInstance($this->conf['solrcore']);
888
        if (intval($this->conf['solr_limit']) > 0) {
889
            $solr->limit = intval($this->conf['solr_limit']);
0 ignored issues
show
Bug Best Practice introduced by
The property $limit is declared protected in Kitodo\Dlf\Common\Solr. Since you implement __set, consider adding a @property or @property-write.
Loading history...
890
        }
891
        // We only care about the UID in the results and want them sorted
892
        $parameters = [
893
            "fields" => "uid",
894
            "sort" => [
895
                "uid" => "asc"
896
            ]
897
        ];
898
        $result = $solr->search_raw($solr_query, $parameters);
899
        if (empty($result)) {
900
            throw new \Exception('noRecordsMatch');
901
        }
902
        foreach ($result as $doc) {
903
            $documentSet[] = $doc->uid;
904
        }
905
        return $documentSet;
906
    }
907
908
    /**
909
     * Fetch more information for document list
910
     * @access protected
911
     *
912
     * @param \Kitodo\Dlf\Common\DocumentList $documentListSet
913
     *
914
     * @return \DOMElement XML of enriched records
915
     */
916
    protected function generateOutputForDocumentList(DocumentList $documentListSet) {
917
        $documentsToProcess = $documentListSet->removeRange(0, intval($this->conf['limit']));
918
        $verb = $this->piVars['verb'];
919
        $documents = $GLOBALS['TYPO3_DB']->exec_SELECT_mm_query(
920
            'tx_dlf_documents.*,GROUP_CONCAT(DISTINCT tx_dlf_collections.oai_name ORDER BY tx_dlf_collections.oai_name SEPARATOR " ") AS collections',
921
            'tx_dlf_documents',
922
            'tx_dlf_relations',
923
            'tx_dlf_collections',
924
            'AND tx_dlf_documents.uid IN ('.implode(',', $GLOBALS['TYPO3_DB']->cleanIntArray($documentsToProcess)).')'
925
                .' AND tx_dlf_documents.pid='.intval($this->conf['pages'])
926
                .' AND tx_dlf_collections.pid='.intval($this->conf['pages'])
927
                .' AND tx_dlf_relations.ident='.$GLOBALS['TYPO3_DB']->fullQuoteStr('docs_colls', 'tx_dlf_relations')
928
                .Helper::whereClause('tx_dlf_collections'),
929
            'tx_dlf_documents.uid',
930
            'tx_dlf_documents.tstamp',
931
            $this->conf['limit']
932
        );
933
        $output = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', $verb);
934
        while ($resArray = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($documents)) {
935
            // Add header node.
936
            $header = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'header');
937
            $header->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'identifier', htmlspecialchars($resArray['record_id'], ENT_NOQUOTES, 'UTF-8')));
938
            $header->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'datestamp', gmdate('Y-m-d\TH:i:s\Z', $resArray['tstamp'])));
939
            // Check if document is deleted or hidden.
940
            // TODO: Use TYPO3 API functions here!
941
            if ($resArray['deleted']
942
                || $resArray['hidden']) {
943
                // Add "deleted" status.
944
                $header->setAttribute('status', 'deleted');
945
                if ($verb == 'ListRecords') {
946
                    // Add record node.
947
                    $record = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'record');
948
                    $record->appendChild($header);
949
                    $output->appendChild($record);
950
                } elseif ($verb == 'ListIdentifiers') {
951
                    $output->appendChild($header);
952
                }
953
            } else {
954
                // Add sets.
955
                foreach (explode(' ', $resArray['collections']) as $spec) {
956
                    $header->appendChild($this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'setSpec', htmlspecialchars($spec, ENT_NOQUOTES, 'UTF-8')));
957
                }
958
                if ($verb == 'ListRecords') {
959
                    // Add record node.
960
                    $record = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'record');
961
                    $record->appendChild($header);
962
                    // Add metadata node.
963
                    $metadata = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'metadata');
964
                    $metadataPrefix = $this->piVars['metadataPrefix'];
965
                    if (!$metadataPrefix) {
966
                        // If we resume an action the metadataPrefix is stored with the documentSet
967
                        $metadataPrefix = $documentListSet->metadata['metadataPrefix'];
0 ignored issues
show
Bug Best Practice introduced by
The property $metadata is declared protected in Kitodo\Dlf\Common\DocumentList. Since you implement __get, consider adding a @property or @property-read.
Loading history...
968
                    }
969
                    switch ($metadataPrefix) {
970
                        case 'oai_dc':
971
                            $metadata->appendChild($this->getDcData($resArray));
972
                            break;
973
                        case 'epicur':
974
                            $metadata->appendChild($this->getEpicurData($resArray));
975
                            break;
976
                        case 'mets':
977
                            $metadata->appendChild($this->getMetsData($resArray));
978
                            break;
979
                    }
980
                    $record->appendChild($metadata);
981
                    $output->appendChild($record);
982
                } elseif ($verb == 'ListIdentifiers') {
983
                    $output->appendChild($header);
984
                }
985
            }
986
        }
987
        $output->appendChild($this->generateResumptionTokenForDocumentListSet($documentListSet));
988
        return $output;
989
    }
990
991
    /**
992
     * Generate resumption token
993
     *
994
     * @access protected
995
     *
996
     * @param \Kitodo\Dlf\Common\DocumentList $documentListSet
997
     *
998
     * @return \DOMElement XML for resumption token
999
     */
1000
    protected function generateResumptionTokenForDocumentListSet(DocumentList $documentListSet) {
1001
        if ($documentListSet->count() != 0) {
1002
            $token = uniqid();
1003
            $GLOBALS['TYPO3_DB']->exec_INSERTquery(
1004
                'tx_dlf_tokens',
1005
                [
1006
                    'tstamp' => $GLOBALS['EXEC_TIME'],
1007
                    'token' => $token,
1008
                    'options' => serialize($documentListSet),
1009
                    'ident' => 'oai',
1010
                ]
1011
            );
1012
            if ($GLOBALS['TYPO3_DB']->sql_affected_rows() == 1) {
1013
                $resumptionToken = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'resumptionToken', htmlspecialchars($token, ENT_NOQUOTES, 'UTF-8'));
1014
            } else {
1015
                Helper::devLog('Could not create resumption token', DEVLOG_SEVERITY_ERROR);
1016
            }
1017
        } else {
1018
            // Result set complete. We don't need a token.
1019
            $resumptionToken = $this->oai->createElementNS('http://www.openarchives.org/OAI/2.0/', 'resumptionToken');
1020
        }
1021
        $resumptionToken->setAttribute('cursor', intval($documentListSet->metadata['completeListSize']) - count($documentListSet));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $resumptionToken does not seem to be defined for all execution paths leading up to this point.
Loading history...
Bug Best Practice introduced by
The property $metadata is declared protected in Kitodo\Dlf\Common\DocumentList. Since you implement __get, consider adding a @property or @property-read.
Loading history...
1022
        $resumptionToken->setAttribute('completeListSize', $documentListSet->metadata['completeListSize']);
1023
        $resumptionToken->setAttribute('expirationDate', gmdate('Y-m-d\TH:i:s\Z', $GLOBALS['EXEC_TIME'] + $this->conf['expired']));
1024
        return $resumptionToken;
1025
    }
1026
}
1027