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 (#506)
by Sebastian
02:52
created

OaiPmh::getDcData()   F

Complexity

Conditions 15
Paths 12288

Size

Total Lines 67
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 47
c 0
b 0
f 0
dl 0
loc 67
rs 1.7499
cc 15
nc 12288
nop 1

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

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

342
                    $this->conf['stylesheet'] = /** @scrutinizer ignore-deprecated */ \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($extKey) . $filePath;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
Bug Best Practice introduced by
The property conf does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
343
                }
344
            }
345
            $stylesheet = \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl($this->conf['stylesheet']);
346
        } else {
347
            // Use default stylesheet if no custom stylesheet is given.
348
            $stylesheet = \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($this->extKey) . 'Resources/Public/Stylesheets/OaiPmh.xsl');
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Core\Utility\E...tUtility::siteRelPath() has been deprecated: use extPath() or GeneralUtility::getFileAbsFileName() together with PathUtility::getAbsoluteWebPath() instead. ( Ignorable by Annotation )

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

348
            $stylesheet = \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl(/** @scrutinizer ignore-deprecated */ \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($this->extKey) . 'Resources/Public/Stylesheets/OaiPmh.xsl');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

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