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 (#650)
by Alexander
03:43 queued 51s
created

MetsDocument::getRawText()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
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\Common\Document;
14
15
use Kitodo\Dlf\Common\Helper;
16
use Kitodo\Dlf\Common\IiifUrlReader;
17
use Kitodo\Dlf\Common\MetadataInterface;
18
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
19
use TYPO3\CMS\Core\Database\ConnectionPool;
20
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
21
use TYPO3\CMS\Core\Utility\GeneralUtility;
22
use TYPO3\CMS\Core\Utility\MathUtility;
23
use Ubl\Iiif\Tools\IiifHelper;
24
use Ubl\Iiif\Services\AbstractImageService;
25
26
/**
27
 * MetsDocument class for the 'dlf' extension.
28
 *
29
 * @author Sebastian Meyer <[email protected]>
30
 * @author Henrik Lochmann <[email protected]>
31
 * @package TYPO3
32
 * @subpackage dlf
33
 * @access public
34
 * @property int $cPid This holds the PID for the configuration
35
 * @property-read array $dmdSec This holds the XML file's dmdSec parts with their IDs as array key
36
 * @property-read array $fileGrps This holds the file ID -> USE concordance
37
 * @property-read string $location This holds the documents location
38
 * @property-read array $metadataArray This holds the documents' parsed metadata array
39
 * @property-read \SimpleXMLElement $mets This holds the XML file's METS part as \SimpleXMLElement object
40
 * @property-read int $numPages The holds the total number of pages
41
 * @property-read int $parentId This holds the UID of the parent document or zero if not multi-volumed
42
 * @property-read array $physicalStructure This holds the physical structure
43
 * @property-read array $physicalStructureInfo This holds the physical structure metadata
44
 * @property-read int $pid This holds the PID of the document or zero if not in database
45
 * @property-read bool $ready Is the document instantiated successfully?
46
 * @property-read string $recordId The METS file's / IIIF manifest's record identifier
47
 * @property-read int $rootId This holds the UID of the root document or zero if not multi-volumed
48
 * @property-read array $smLinks This holds the smLinks between logical and physical structMap
49
 * @property-read array $tableOfContents This holds the logical structure
50
 * @property-read string $thumbnail This holds the document's thumbnail location
51
 * @property-read string $toplevelId This holds the toplevel structure's @ID (METS) or the manifest's @id (IIIF)
52
 * @property-read mixed $uid This holds the UID or the URL of the document
53
 */
54
final class MetsDocument extends FullTextDocument
55
{
56
    /**
57
     * This holds the whole XML file as string for serialization purposes
58
     * @see __sleep() / __wakeup()
59
     *
60
     * @var string
61
     * @access protected
62
     */
63
    protected $asXML = '';
64
65
    /**
66
     * This holds the XML file's dmdSec parts with their IDs as array key
67
     *
68
     * @var array
69
     * @access protected
70
     */
71
    protected $dmdSec = [];
72
73
    /**
74
     * Are the METS file's dmdSecs loaded?
75
     * @see $dmdSec
76
     *
77
     * @var bool
78
     * @access protected
79
     */
80
    protected $dmdSecLoaded = false;
81
82
    /**
83
     * The extension key
84
     *
85
     * @var	string
86
     * @access public
87
     */
88
    public static $extKey = 'dlf';
89
90
    /**
91
     * This holds the file ID -> USE concordance
92
     * @see _getFileGrps()
93
     *
94
     * @var array
95
     * @access protected
96
     */
97
    protected $fileGrps = [];
98
99
    /**
100
     * Are the image file groups loaded?
101
     * @see $fileGrps
102
     *
103
     * @var bool
104
     * @access protected
105
     */
106
    protected $fileGrpsLoaded = false;
107
108
    /**
109
     * This holds the XML file's METS part as \SimpleXMLElement object
110
     *
111
     * @var \SimpleXMLElement
112
     * @access protected
113
     */
114
    protected $mets;
115
116
    /**
117
     * This adds metadata from METS structural map to metadata array.
118
     *
119
     * @access	public
120
     *
121
     * @param	array	&$metadata: The metadata array to extend
122
     * @param	string	$id: The @ID attribute of the logical structure node
123
     *
124
     * @return  void
125
     */
126
    public function addMetadataFromMets(&$metadata, $id)
127
    {
128
        $details = $this->getLogicalStructure($id);
129
        if (!empty($details)) {
130
            $metadata['mets_order'][0] = $details['order'];
131
            $metadata['mets_label'][0] = $details['label'];
132
            $metadata['mets_orderlabel'][0] = $details['orderlabel'];
133
        }
134
    }
135
136
    /**
137
     *
138
     * {@inheritDoc}
139
     * @see Document::establishRecordId()
140
     */
141
    protected function establishRecordId($pid)
142
    {
143
        // Check for METS object @ID.
144
        if (!empty($this->mets['OBJID'])) {
145
            $this->recordId = (string) $this->mets['OBJID'];
0 ignored issues
show
Bug introduced by
The property recordId is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
146
        }
147
        // Get hook objects.
148
        $hookObjects = Helper::getHookObjects('Classes/Common/Document/MetsDocument.php');
149
        // Apply hooks.
150
        foreach ($hookObjects as $hookObj) {
151
            if (method_exists($hookObj, 'construct_postProcessRecordId')) {
152
                $hookObj->construct_postProcessRecordId($this->xml, $this->recordId);
153
            }
154
        }
155
    }
156
157
    /**
158
     *
159
     * {@inheritDoc}
160
     * @see Document::getDownloadLocation()
161
     */
162
    public function getDownloadLocation($id)
163
    {
164
        $fileMimeType = $this->getFileMimeType($id);
165
        $fileLocation = $this->getFileLocation($id);
166
        if ($fileMimeType === 'application/vnd.kitodo.iiif') {
167
            $fileLocation = (strrpos($fileLocation, 'info.json') === strlen($fileLocation) - 9) ? $fileLocation : (strrpos($fileLocation, '/') === strlen($fileLocation) ? $fileLocation . 'info.json' : $fileLocation . '/info.json');
168
            $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
169
            IiifHelper::setUrlReader(IiifUrlReader::getInstance());
170
            IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']);
171
            IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']);
172
            $service = IiifHelper::loadIiifResource($fileLocation);
173
            if ($service !== null && $service instanceof AbstractImageService) {
174
                return $service->getImageUrl();
175
            }
176
        } elseif ($fileMimeType === 'application/vnd.netfpx') {
177
            $baseURL = $fileLocation . (strpos($fileLocation, '?') === false ? '?' : '');
178
            // TODO CVT is an optional IIP server capability; in theory, capabilities should be determined in the object request with '&obj=IIP-server'
179
            return $baseURL . '&CVT=jpeg';
180
        }
181
        return $fileLocation;
182
    }
183
184
    /**
185
     * {@inheritDoc}
186
     * @see Document::getFileLocation()
187
     */
188
    public function getFileLocation($id)
189
    {
190
        $location = $this->mets->xpath('./mets:fileSec/mets:fileGrp/mets:file[@ID="' . $id . '"]/mets:FLocat[@LOCTYPE="URL"]');
191
        if (
192
            !empty($id)
193
            && !empty($location)
194
        ) {
195
            return (string) $location[0]->attributes('http://www.w3.org/1999/xlink')->href;
196
        } else {
197
            $this->logger->warning('There is no file node with @ID "' . $id . '"');
1 ignored issue
show
Bug introduced by
The method warning() does not exist on TYPO3\CMS\Core\Log\LogManager. ( Ignorable by Annotation )

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

197
            $this->logger->/** @scrutinizer ignore-call */ 
198
                           warning('There is no file node with @ID "' . $id . '"');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
198
            return '';
199
        }
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     * @see Document::getFileMimeType()
205
     */
206
    public function getFileMimeType($id)
207
    {
208
        $mimetype = $this->mets->xpath('./mets:fileSec/mets:fileGrp/mets:file[@ID="' . $id . '"]/@MIMETYPE');
209
        if (
210
            !empty($id)
211
            && !empty($mimetype)
212
        ) {
213
            return (string) $mimetype[0];
214
        } else {
215
            $this->logger->warning('There is no file node with @ID "' . $id . '" or no MIME type specified');
216
            return '';
217
        }
218
    }
219
220
    /**
221
     * {@inheritDoc}
222
     * @see Document::getLogicalStructure()
223
     */
224
    public function getLogicalStructure($id, $recursive = false)
225
    {
226
        $details = [];
227
        // Is the requested logical unit already loaded?
228
        if (
229
            !$recursive
230
            && !empty($this->logicalUnits[$id])
231
        ) {
232
            // Yes. Return it.
233
            return $this->logicalUnits[$id];
234
        } elseif (!empty($id)) {
235
            // Get specified logical unit.
236
            $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]');
237
        } else {
238
            // Get all logical units at top level.
239
            $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]/mets:div');
240
        }
241
        if (!empty($divs)) {
242
            if (!$recursive) {
243
                // Get the details for the first xpath hit.
244
                $details = $this->getLogicalStructureInfo($divs[0]);
245
            } else {
246
                // Walk the logical structure recursively and fill the whole table of contents.
247
                foreach ($divs as $div) {
248
                    $this->tableOfContents[] = $this->getLogicalStructureInfo($div, $recursive);
0 ignored issues
show
Bug introduced by
The property tableOfContents is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
249
                }
250
            }
251
        }
252
        return $details;
253
    }
254
255
    /**
256
     * This gets details about a logical structure element
257
     *
258
     * @access protected
259
     *
260
     * @param \SimpleXMLElement $structure: The logical structure node
261
     * @param bool $recursive: Whether to include the child elements
262
     *
263
     * @return array Array of the element's id, label, type and physical page indexes/mptr link
264
     */
265
    protected function getLogicalStructureInfo(\SimpleXMLElement $structure, $recursive = false)
266
    {
267
        // Get attributes.
268
        foreach ($structure->attributes() as $attribute => $value) {
269
            $attributes[$attribute] = (string) $value;
270
        }
271
        // Load plugin configuration.
272
        $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
273
        // Extract identity information.
274
        $details = [];
275
        $details['id'] = $attributes['ID'];
276
        $details['dmdId'] = (isset($attributes['DMDID']) ? $attributes['DMDID'] : '');
277
        $details['order'] = (isset($attributes['ORDER']) ? $attributes['ORDER'] : '');
278
        $details['label'] = (isset($attributes['LABEL']) ? $attributes['LABEL'] : '');
279
        $details['orderlabel'] = (isset($attributes['ORDERLABEL']) ? $attributes['ORDERLABEL'] : '');
280
        $details['contentIds'] = (isset($attributes['CONTENTIDS']) ? $attributes['CONTENTIDS'] : '');
281
        $details['volume'] = '';
282
        // Set volume information only if no label is set and this is the toplevel structure element.
283
        if (
284
            empty($details['label'])
285
            && $details['id'] == $this->_getToplevelId()
286
        ) {
287
            $metadata = $this->getMetadata($details['id']);
288
            if (!empty($metadata['volume'][0])) {
289
                $details['volume'] = $metadata['volume'][0];
290
            }
291
        }
292
        $details['pagination'] = '';
293
        $details['type'] = $attributes['TYPE'];
294
        $details['thumbnailId'] = '';
295
        // Load smLinks.
296
        $this->_getSmLinks();
297
        // Load physical structure.
298
        $this->_getPhysicalStructure();
299
        // Get the physical page or external file this structure element is pointing at.
300
        $details['points'] = '';
301
        // Is there a mptr node?
302
        if (count($structure->children('http://www.loc.gov/METS/')->mptr)) {
303
            // Yes. Get the file reference.
304
            $details['points'] = (string) $structure->children('http://www.loc.gov/METS/')->mptr[0]->attributes('http://www.w3.org/1999/xlink')->href;
305
        } elseif (
306
            !empty($this->physicalStructure)
307
            && array_key_exists($details['id'], $this->smLinks['l2p'])
308
        ) {
309
            // Link logical structure to the first corresponding physical page/track.
310
            $details['points'] = max(intval(array_search($this->smLinks['l2p'][$details['id']][0], $this->physicalStructure, true)), 1);
311
            $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
312
            while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
313
                if (!empty($this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['files'][$fileGrpThumb])) {
314
                    $details['thumbnailId'] = $this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['files'][$fileGrpThumb];
315
                    break;
316
                }
317
            }
318
            // Get page/track number of the first page/track related to this structure element.
319
            $details['pagination'] = $this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['orderlabel'];
320
        } elseif ($details['id'] == $this->_getToplevelId()) {
321
            // Point to self if this is the toplevel structure.
322
            $details['points'] = 1;
323
            $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
324
            while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
325
                if (
326
                    !empty($this->physicalStructure)
327
                    && !empty($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb])
328
                ) {
329
                    $details['thumbnailId'] = $this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb];
330
                    break;
331
                }
332
            }
333
        }
334
        // Get the files this structure element is pointing at.
335
        $details['files'] = [];
336
        $fileUse = $this->_getFileGrps();
337
        // Get the file representations from fileSec node.
338
        foreach ($structure->children('http://www.loc.gov/METS/')->fptr as $fptr) {
339
            // Check if file has valid @USE attribute.
340
            if (!empty($fileUse[(string) $fptr->attributes()->FILEID])) {
0 ignored issues
show
Bug introduced by
The method attributes() does not exist on null. ( Ignorable by Annotation )

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

340
            if (!empty($fileUse[(string) $fptr->/** @scrutinizer ignore-call */ attributes()->FILEID])) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
341
                $details['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
342
            }
343
        }
344
        // Keep for later usage.
345
        $this->logicalUnits[$details['id']] = $details;
346
        // Walk the structure recursively? And are there any children of the current element?
347
        if (
348
            $recursive
349
            && count($structure->children('http://www.loc.gov/METS/')->div)
350
        ) {
351
            $details['children'] = [];
352
            foreach ($structure->children('http://www.loc.gov/METS/')->div as $child) {
353
                // Repeat for all children.
354
                $details['children'][] = $this->getLogicalStructureInfo($child, true);
0 ignored issues
show
Bug introduced by
It seems like $child can also be of type null; however, parameter $structure of Kitodo\Dlf\Common\Docume...tLogicalStructureInfo() does only seem to accept SimpleXMLElement, 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

354
                $details['children'][] = $this->getLogicalStructureInfo(/** @scrutinizer ignore-type */ $child, true);
Loading history...
355
            }
356
        }
357
        return $details;
358
    }
359
360
    /**
361
     * {@inheritDoc}
362
     * @see Document::getMetadata()
363
     */
364
    public function getMetadata($id, $cPid = 0)
365
    {
366
        // Make sure $cPid is a non-negative integer.
367
        $cPid = max(intval($cPid), 0);
368
        // If $cPid is not given, try to get it elsewhere.
369
        if (
370
            !$cPid
371
            && ($this->cPid || $this->pid)
372
        ) {
373
            // Retain current PID.
374
            $cPid = ($this->cPid ? $this->cPid : $this->pid);
375
        } elseif (!$cPid) {
376
            $this->logger->warning('Invalid PID ' . $cPid . ' for metadata definitions');
377
            return [];
378
        }
379
        // Get metadata from parsed metadata array if available.
380
        if (
381
            !empty($this->metadataArray[$id])
382
            && $this->metadataArray[0] == $cPid
383
        ) {
384
            return $this->metadataArray[$id];
385
        }
386
        // Initialize metadata array with empty values.
387
        $metadata = [
388
            'title' => [],
389
            'title_sorting' => [],
390
            'author' => [],
391
            'place' => [],
392
            'year' => [],
393
            'prod_id' => [],
394
            'record_id' => [],
395
            'opac_id' => [],
396
            'union_id' => [],
397
            'urn' => [],
398
            'purl' => [],
399
            'type' => [],
400
            'volume' => [],
401
            'volume_sorting' => [],
402
            'license' => [],
403
            'terms' => [],
404
            'restrictions' => [],
405
            'out_of_print' => [],
406
            'rights_info' => [],
407
            'collection' => [],
408
            'owner' => [],
409
            'mets_label' => [],
410
            'mets_orderlabel' => [],
411
            'document_format' => ['METS'],
412
        ];
413
        // Get the logical structure node's @DMDID.
414
        if (!empty($this->logicalUnits[$id])) {
415
            $dmdIds = $this->logicalUnits[$id]['dmdId'];
416
        } else {
417
            $dmdIds = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]/@DMDID');
418
            $dmdIds = (string) $dmdIds[0];
419
        }
420
        if (!empty($dmdIds)) {
421
            // Handle multiple DMDIDs separately.
422
            $dmdIds = explode(' ', $dmdIds);
423
            $hasSupportedMetadata = false;
424
        } else {
425
            // There is no dmdSec for this structure node.
426
            return [];
427
        }
428
        // Load available metadata formats and dmdSecs.
429
        $this->loadFormats();
430
        $this->_getDmdSec();
431
        foreach ($dmdIds as $dmdId) {
432
            // Is this metadata format supported?
433
            if (!empty($this->formats[$this->dmdSec[$dmdId]['type']])) {
434
                if (!empty($this->formats[$this->dmdSec[$dmdId]['type']]['class'])) {
435
                    $class = $this->formats[$this->dmdSec[$dmdId]['type']]['class'];
436
                    // Get the metadata from class.
437
                    if (
438
                        class_exists($class)
439
                        && ($obj = GeneralUtility::makeInstance($class)) instanceof MetadataInterface
440
                    ) {
441
                        $obj->extractMetadata($this->dmdSec[$dmdId]['xml'], $metadata);
442
                    } else {
443
                        $this->logger->warning('Invalid class/method "' . $class . '->extractMetadata()" for metadata format "' . $this->dmdSec[$dmdId]['type'] . '"');
444
                    }
445
                }
446
            } else {
447
                $this->logger->notice('Unsupported metadata format "' . $this->dmdSec[$dmdId]['type'] . '" in dmdSec with @ID "' . $dmdId . '"');
1 ignored issue
show
Bug introduced by
The method notice() does not exist on TYPO3\CMS\Core\Log\LogManager. ( Ignorable by Annotation )

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

447
                $this->logger->/** @scrutinizer ignore-call */ 
448
                               notice('Unsupported metadata format "' . $this->dmdSec[$dmdId]['type'] . '" in dmdSec with @ID "' . $dmdId . '"');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
448
                // Continue searching for supported metadata with next @DMDID.
449
                continue;
450
            }
451
            // Get the structure's type.
452
            if (!empty($this->logicalUnits[$id])) {
453
                $metadata['type'] = [$this->logicalUnits[$id]['type']];
454
            } else {
455
                $struct = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]/@TYPE');
456
                if (!empty($struct)) {
457
                    $metadata['type'] = [(string) $struct[0]];
458
                }
459
            }
460
            // Get the additional metadata from database.
461
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
462
                ->getQueryBuilderForTable('tx_dlf_metadata');
463
            // Get hidden records, too.
464
            $queryBuilder
465
                ->getRestrictions()
466
                ->removeByType(HiddenRestriction::class);
467
            // Get all metadata with configured xpath and applicable format first.
468
            $resultWithFormat = $queryBuilder
469
                ->select(
470
                    'tx_dlf_metadata.index_name AS index_name',
471
                    'tx_dlf_metadataformat_joins.xpath AS xpath',
472
                    'tx_dlf_metadataformat_joins.xpath_sorting AS xpath_sorting',
473
                    'tx_dlf_metadata.is_sortable AS is_sortable',
474
                    'tx_dlf_metadata.default_value AS default_value',
475
                    'tx_dlf_metadata.format AS format'
476
                )
477
                ->from('tx_dlf_metadata')
478
                ->innerJoin(
479
                    'tx_dlf_metadata',
480
                    'tx_dlf_metadataformat',
481
                    'tx_dlf_metadataformat_joins',
482
                    $queryBuilder->expr()->eq(
483
                        'tx_dlf_metadataformat_joins.parent_id',
484
                        'tx_dlf_metadata.uid'
485
                    )
486
                )
487
                ->innerJoin(
488
                    'tx_dlf_metadataformat_joins',
489
                    'tx_dlf_formats',
490
                    'tx_dlf_formats_joins',
491
                    $queryBuilder->expr()->eq(
492
                        'tx_dlf_formats_joins.uid',
493
                        'tx_dlf_metadataformat_joins.encoded'
494
                    )
495
                )
496
                ->where(
497
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
498
                    $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
499
                    $queryBuilder->expr()->eq('tx_dlf_metadataformat_joins.pid', intval($cPid)),
500
                    $queryBuilder->expr()->eq('tx_dlf_formats_joins.type', $queryBuilder->createNamedParameter($this->dmdSec[$dmdId]['type']))
501
                )
502
                ->execute();
503
            // Get all metadata without a format, but with a default value next.
504
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
505
                ->getQueryBuilderForTable('tx_dlf_metadata');
506
            // Get hidden records, too.
507
            $queryBuilder
508
                ->getRestrictions()
509
                ->removeByType(HiddenRestriction::class);
510
            $resultWithoutFormat = $queryBuilder
511
                ->select(
512
                    'tx_dlf_metadata.index_name AS index_name',
513
                    'tx_dlf_metadata.is_sortable AS is_sortable',
514
                    'tx_dlf_metadata.default_value AS default_value',
515
                    'tx_dlf_metadata.format AS format'
516
                )
517
                ->from('tx_dlf_metadata')
518
                ->where(
519
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
520
                    $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
521
                    $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0),
522
                    $queryBuilder->expr()->neq('tx_dlf_metadata.default_value', $queryBuilder->createNamedParameter(''))
523
                )
524
                ->execute();
525
            // Merge both result sets.
526
            $allResults = array_merge($resultWithFormat->fetchAll(), $resultWithoutFormat->fetchAll());
527
            // We need a \DOMDocument here, because SimpleXML doesn't support XPath functions properly.
528
            $domNode = dom_import_simplexml($this->dmdSec[$dmdId]['xml']);
529
            $domXPath = new \DOMXPath($domNode->ownerDocument);
0 ignored issues
show
Bug introduced by
It seems like $domNode->ownerDocument can also be of type null; however, parameter $document of DOMXPath::__construct() does only seem to accept DOMDocument, 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

529
            $domXPath = new \DOMXPath(/** @scrutinizer ignore-type */ $domNode->ownerDocument);
Loading history...
530
            $this->registerNamespaces($domXPath);
531
            // OK, now make the XPath queries.
532
            foreach ($allResults as $resArray) {
533
                // Set metadata field's value(s).
534
                if (
535
                    $resArray['format'] > 0
536
                    && !empty($resArray['xpath'])
537
                    && ($values = $domXPath->evaluate($resArray['xpath'], $domNode))
538
                ) {
539
                    if (
540
                        $values instanceof \DOMNodeList
541
                        && $values->length > 0
542
                    ) {
543
                        $metadata[$resArray['index_name']] = [];
544
                        foreach ($values as $value) {
545
                            $metadata[$resArray['index_name']][] = trim((string) $value->nodeValue);
546
                        }
547
                    } elseif (!($values instanceof \DOMNodeList)) {
548
                        $metadata[$resArray['index_name']] = [trim((string) $values)];
549
                    }
550
                }
551
                // Set default value if applicable.
552
                if (
553
                    empty($metadata[$resArray['index_name']][0])
554
                    && strlen($resArray['default_value']) > 0
555
                ) {
556
                    $metadata[$resArray['index_name']] = [$resArray['default_value']];
557
                }
558
                // Set sorting value if applicable.
559
                if (
560
                    !empty($metadata[$resArray['index_name']])
561
                    && $resArray['is_sortable']
562
                ) {
563
                    if (
564
                        $resArray['format'] > 0
565
                        && !empty($resArray['xpath_sorting'])
566
                        && ($values = $domXPath->evaluate($resArray['xpath_sorting'], $domNode))
567
                    ) {
568
                        if (
569
                            $values instanceof \DOMNodeList
570
                            && $values->length > 0
571
                        ) {
572
                            $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $values->item(0)->nodeValue);
573
                        } elseif (!($values instanceof \DOMNodeList)) {
574
                            $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $values);
575
                        }
576
                    }
577
                    if (empty($metadata[$resArray['index_name'] . '_sorting'][0])) {
578
                        $metadata[$resArray['index_name'] . '_sorting'][0] = $metadata[$resArray['index_name']][0];
579
                    }
580
                }
581
            }
582
            // Set title to empty string if not present.
583
            if (empty($metadata['title'][0])) {
584
                $metadata['title'][0] = '';
585
                $metadata['title_sorting'][0] = '';
586
            }
587
            // Add collections and owner from database to toplevel element if document is already saved.
588
            if (
589
                MathUtility::canBeInterpretedAsInteger($this->uid)
590
                && $id == $this->_getToplevelId()
591
            ) {
592
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
593
                    ->getQueryBuilderForTable('tx_dlf_documents');
594
595
                $result = $queryBuilder
596
                    ->select(
597
                        'tx_dlf_collections_join.index_name AS index_name'
598
                    )
599
                    ->from('tx_dlf_documents')
600
                    ->innerJoin(
601
                        'tx_dlf_documents',
602
                        'tx_dlf_relations',
603
                        'tx_dlf_relations_joins',
604
                        $queryBuilder->expr()->eq(
605
                            'tx_dlf_relations_joins.uid_local',
606
                            'tx_dlf_documents.uid'
607
                        )
608
                    )
609
                    ->innerJoin(
610
                        'tx_dlf_relations_joins',
611
                        'tx_dlf_collections',
612
                        'tx_dlf_collections_join',
613
                        $queryBuilder->expr()->eq(
614
                            'tx_dlf_relations_joins.uid_foreign',
615
                            'tx_dlf_collections_join.uid'
616
                        )
617
                    )
618
                    ->where(
619
                        $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($cPid)),
620
                        $queryBuilder->expr()->eq('tx_dlf_documents.uid', intval($this->uid))
621
                    )
622
                    ->orderBy('tx_dlf_collections_join.index_name', 'ASC')
623
                    ->execute();
624
625
                $allResults = $result->fetchAll();
626
627
                foreach ($allResults as $resArray) {
628
                    if (!in_array($resArray['index_name'], $metadata['collection'])) {
629
                        $metadata['collection'][] = $resArray['index_name'];
630
                    }
631
                }
632
633
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
634
                    ->getQueryBuilderForTable('tx_dlf_documents');
635
636
                $result = $queryBuilder
637
                    ->select(
638
                        'tx_dlf_documents.owner AS owner'
639
                    )
640
                    ->from('tx_dlf_documents')
641
                    ->where(
642
                        $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($cPid)),
643
                        $queryBuilder->expr()->eq('tx_dlf_documents.uid', intval($this->uid))
644
                    )
645
                    ->execute();
646
647
                $resArray = $result->fetch();
648
649
                $metadata['owner'][0] = $resArray['owner'];
650
            }
651
            // Extract metadata only from first supported dmdSec.
652
            $hasSupportedMetadata = true;
653
            break;
654
        }
655
        if ($hasSupportedMetadata) {
656
            return $metadata;
657
        } else {
658
            $this->logger->warning('No supported metadata found for logical structure with @ID "' . $id . '"');
659
            return [];
660
        }
661
    }
662
663
    /**
664
     * {@inheritDoc}
665
     * @see FullTextDocument::getRawText()
666
     */
667
    public function getRawText($id)
668
    {
669
        $rawText = '';
670
        // Get text from raw text array if available.
671
        if (!empty($this->rawTextArray[$id])) {
672
            return $this->rawTextArray[$id];
673
        }
674
        // Load fileGrps and check for fulltext files.
675
        $this->_getFileGrps();
676
        if ($this->hasFullText) {
677
            $rawText = $this->getRawTextFromXml($id);
678
        }
679
        return $rawText;
680
    }
681
682
    /**
683
     * {@inheritDoc}
684
     * @see Document::getStructureDepth()
685
     */
686
    public function getStructureDepth($logId)
687
    {
688
        $ancestors = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $logId . '"]/ancestor::*');
689
        if (!empty($ancestors)) {
690
            return count($ancestors);
691
        } else {
692
            return 0;
693
        }
694
    }
695
696
    /**
697
     * {@inheritDoc}
698
     * @see Document::init()
699
     */
700
    protected function init()
701
    {
702
        // Get METS node from XML file.
703
        $this->registerNamespaces($this->xml);
704
        $mets = $this->xml->xpath('//mets:mets');
705
        if (!empty($mets)) {
706
            $this->mets = $mets[0];
0 ignored issues
show
Bug introduced by
The property mets is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
707
            // Register namespaces.
708
            $this->registerNamespaces($this->mets);
709
        } else {
710
            $this->logger->error('No METS part found in document with UID ' . $this->uid);
1 ignored issue
show
Bug introduced by
The method error() does not exist on TYPO3\CMS\Core\Log\LogManager. ( Ignorable by Annotation )

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

710
            $this->logger->/** @scrutinizer ignore-call */ 
711
                           error('No METS part found in document with UID ' . $this->uid);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
711
        }
712
    }
713
714
    /**
715
     * {@inheritDoc}
716
     * @see Document::loadLocation()
717
     */
718
    protected function loadLocation($location)
719
    {
720
        $fileResource = GeneralUtility::getUrl($location);
721
        if ($fileResource !== false) {
722
            // Turn off libxml's error logging.
723
            $libxmlErrors = libxml_use_internal_errors(true);
724
            // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
725
            $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
726
            // Load XML from file.
727
            $xml = simplexml_load_string($fileResource);
728
            // reset entity loader setting
729
            libxml_disable_entity_loader($previousValueOfEntityLoader);
730
            // Reset libxml's error logging.
731
            libxml_use_internal_errors($libxmlErrors);
732
            // Set some basic properties.
733
            if ($xml !== false) {
734
                $this->xml = $xml;
735
                return true;
736
            }
737
        }
738
        $this->logger->error('Could not load XML file from "' . $location . '"');
739
        return false;
740
    }
741
742
    /**
743
     * {@inheritDoc}
744
     * @see FullTextDocument::ensureHasFulltextIsSet()
745
     */
746
    protected function ensureHasFulltextIsSet()
747
    {
748
        // Are the fileGrps already loaded?
749
        if (!$this->fileGrpsLoaded) {
750
            $this->_getFileGrps();
751
        }
752
    }
753
754
    /**
755
     * {@inheritDoc}
756
     * @see Document::getParentDocumentUid()
757
     */
758
    protected function getParentDocumentUidForSaving($pid, $core, $owner)
759
    {
760
        $partof = 0;
761
        // Get the closest ancestor of the current document which has a MPTR child.
762
        $parentMptr = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $this->_getToplevelId() . '"]/ancestor::mets:div[./mets:mptr][1]/mets:mptr');
763
        if (!empty($parentMptr)) {
764
            $parentLocation = (string) $parentMptr[0]->attributes('http://www.w3.org/1999/xlink')->href;
765
            if ($parentLocation != $this->location) {
766
                $parentDoc = self::getInstance($parentLocation, $pid);
767
                if ($parentDoc->ready) {
768
                    if ($parentDoc->pid != $pid) {
769
                        $parentDoc->save($pid, $core, $owner);
770
                    }
771
                    $partof = $parentDoc->uid;
772
                }
773
            }
774
        }
775
        return $partof;
776
    }
777
778
    /**
779
     * {@inheritDoc}
780
     * @see Document::setPreloadedDocument()
781
     */
782
    protected function setPreloadedDocument($preloadedDocument)
783
    {
784
785
        if ($preloadedDocument instanceof \SimpleXMLElement) {
786
            $this->xml = $preloadedDocument;
787
            return true;
788
        }
789
        return false;
790
    }
791
792
    /**
793
     * {@inheritDoc}
794
     * @see Document::getDocument()
795
     */
796
    protected function getDocument()
797
    {
798
        return $this->mets;
799
    }
800
801
    /**
802
     * This builds an array of the document's dmdSecs
803
     *
804
     * @access protected
805
     *
806
     * @return array Array of dmdSecs with their IDs as array key
807
     */
808
    protected function _getDmdSec()
809
    {
810
        if (!$this->dmdSecLoaded) {
811
            // Get available data formats.
812
            $this->loadFormats();
813
            // Get dmdSec nodes from METS.
814
            $dmdIds = $this->mets->xpath('./mets:dmdSec/@ID');
815
            if (!empty($dmdIds)) {
816
                foreach ($dmdIds as $dmdId) {
817
                    if ($type = $this->mets->xpath('./mets:dmdSec[@ID="' . (string) $dmdId . '"]/mets:mdWrap[not(@MDTYPE="OTHER")]/@MDTYPE')) {
818
                        if (!empty($this->formats[(string) $type[0]])) {
819
                            $type = (string) $type[0];
820
                            $xml = $this->mets->xpath('./mets:dmdSec[@ID="' . (string) $dmdId . '"]/mets:mdWrap[@MDTYPE="' . $type . '"]/mets:xmlData/' . strtolower($type) . ':' . $this->formats[$type]['rootElement']);
821
                        }
822
                    } elseif ($type = $this->mets->xpath('./mets:dmdSec[@ID="' . (string) $dmdId . '"]/mets:mdWrap[@MDTYPE="OTHER"]/@OTHERMDTYPE')) {
823
                        if (!empty($this->formats[(string) $type[0]])) {
824
                            $type = (string) $type[0];
825
                            $xml = $this->mets->xpath('./mets:dmdSec[@ID="' . (string) $dmdId . '"]/mets:mdWrap[@MDTYPE="OTHER"][@OTHERMDTYPE="' . $type . '"]/mets:xmlData/' . strtolower($type) . ':' . $this->formats[$type]['rootElement']);
826
                        }
827
                    }
828
                    if (!empty($xml)) {
829
                        $this->dmdSec[(string) $dmdId]['type'] = $type;
0 ignored issues
show
Bug introduced by
The property dmdSec is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
830
                        $this->dmdSec[(string) $dmdId]['xml'] = $xml[0];
831
                        $this->registerNamespaces($this->dmdSec[(string) $dmdId]['xml']);
832
                    }
833
                }
834
            }
835
            $this->dmdSecLoaded = true;
836
        }
837
        return $this->dmdSec;
838
    }
839
840
    /**
841
     * This builds the file ID -> USE concordance
842
     *
843
     * @access protected
844
     *
845
     * @return array Array of file use groups with file IDs
846
     */
847
    protected function _getFileGrps()
848
    {
849
        if (!$this->fileGrpsLoaded) {
850
            // Get configured USE attributes.
851
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
852
            $useGrps = GeneralUtility::trimExplode(',', $extConf['fileGrpImages']);
853
            if (!empty($extConf['fileGrpThumbs'])) {
854
                $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']));
855
            }
856
            if (!empty($extConf['fileGrpDownload'])) {
857
                $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpDownload']));
858
            }
859
            if (!empty($extConf['fileGrpFulltext'])) {
860
                $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']));
861
            }
862
            if (!empty($extConf['fileGrpAudio'])) {
863
                $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpAudio']));
864
            }
865
            // Get all file groups.
866
            $fileGrps = $this->mets->xpath('./mets:fileSec/mets:fileGrp');
867
            if (!empty($fileGrps)) {
868
                // Build concordance for configured USE attributes.
869
                foreach ($fileGrps as $fileGrp) {
870
                    if (in_array((string) $fileGrp['USE'], $useGrps)) {
871
                        foreach ($fileGrp->children('http://www.loc.gov/METS/')->file as $file) {
872
                            $this->fileGrps[(string) $file->attributes()->ID] = (string) $fileGrp['USE'];
0 ignored issues
show
Bug introduced by
The method attributes() does not exist on null. ( Ignorable by Annotation )

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

872
                            $this->fileGrps[(string) $file->/** @scrutinizer ignore-call */ attributes()->ID] = (string) $fileGrp['USE'];

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The property fileGrps is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
873
                        }
874
                    }
875
                }
876
            }
877
            // Are there any full text files available?
878
            if (
879
                !empty($extConf['fileGrpFulltext'])
880
                && array_intersect(GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']), $this->fileGrps) !== []
881
            ) {
882
                $this->hasFullText = true;
0 ignored issues
show
Bug introduced by
The property hasFullText is declared read-only in Kitodo\Dlf\Common\Document\FullTextDocument.
Loading history...
883
            }
884
            $this->fileGrpsLoaded = true;
885
        }
886
        return $this->fileGrps;
887
    }
888
889
    /**
890
     * {@inheritDoc}
891
     * @see Document::prepareMetadataArray()
892
     */
893
    protected function prepareMetadataArray($cPid)
894
    {
895
        $ids = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@DMDID]/@ID');
896
        // Get all logical structure nodes with metadata.
897
        if (!empty($ids)) {
898
            foreach ($ids as $id) {
899
                $this->metadataArray[(string) $id] = $this->getMetadata((string) $id, $cPid);
0 ignored issues
show
Bug introduced by
The property metadataArray is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
900
            }
901
        }
902
        // Set current PID for metadata definitions.
903
    }
904
905
    /**
906
     * This returns $this->mets via __get()
907
     *
908
     * @access protected
909
     *
910
     * @return \SimpleXMLElement The XML's METS part as \SimpleXMLElement object
911
     */
912
    protected function _getMets()
913
    {
914
        return $this->mets;
915
    }
916
917
    /**
918
     * {@inheritDoc}
919
     * @see Document::_getPhysicalStructure()
920
     */
921
    protected function _getPhysicalStructure()
922
    {
923
        // Is there no physical structure array yet?
924
        if (!$this->physicalStructureLoaded) {
925
            // Does the document have a structMap node of type "PHYSICAL"?
926
            $elementNodes = $this->mets->xpath('./mets:structMap[@TYPE="PHYSICAL"]/mets:div[@TYPE="physSequence"]/mets:div');
927
            if (!empty($elementNodes)) {
928
                // Get file groups.
929
                $fileUse = $this->_getFileGrps();
930
                // Get the physical sequence's metadata.
931
                $physNode = $this->mets->xpath('./mets:structMap[@TYPE="PHYSICAL"]/mets:div[@TYPE="physSequence"]');
932
                $physSeq[0] = (string) $physNode[0]['ID'];
933
                $this->physicalStructureInfo[$physSeq[0]]['id'] = (string) $physNode[0]['ID'];
0 ignored issues
show
Bug introduced by
The property physicalStructureInfo is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
934
                $this->physicalStructureInfo[$physSeq[0]]['dmdId'] = (isset($physNode[0]['DMDID']) ? (string) $physNode[0]['DMDID'] : '');
935
                $this->physicalStructureInfo[$physSeq[0]]['order'] = (isset($physNode[0]['ORDER']) ? (string) $physNode[0]['ORDER'] : '');
936
                $this->physicalStructureInfo[$physSeq[0]]['label'] = (isset($physNode[0]['LABEL']) ? (string) $physNode[0]['LABEL'] : '');
937
                $this->physicalStructureInfo[$physSeq[0]]['orderlabel'] = (isset($physNode[0]['ORDERLABEL']) ? (string) $physNode[0]['ORDERLABEL'] : '');
938
                $this->physicalStructureInfo[$physSeq[0]]['type'] = (string) $physNode[0]['TYPE'];
939
                $this->physicalStructureInfo[$physSeq[0]]['contentIds'] = (isset($physNode[0]['CONTENTIDS']) ? (string) $physNode[0]['CONTENTIDS'] : '');
940
                // Get the file representations from fileSec node.
941
                foreach ($physNode[0]->children('http://www.loc.gov/METS/')->fptr as $fptr) {
942
                    // Check if file has valid @USE attribute.
943
                    if (!empty($fileUse[(string) $fptr->attributes()->FILEID])) {
944
                        $this->physicalStructureInfo[$physSeq[0]]['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
945
                    }
946
                }
947
                // Build the physical elements' array from the physical structMap node.
948
                foreach ($elementNodes as $elementNode) {
949
                    $elements[(int) $elementNode['ORDER']] = (string) $elementNode['ID'];
950
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['id'] = (string) $elementNode['ID'];
951
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['dmdId'] = (isset($elementNode['DMDID']) ? (string) $elementNode['DMDID'] : '');
952
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['order'] = (isset($elementNode['ORDER']) ? (string) $elementNode['ORDER'] : '');
953
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['label'] = (isset($elementNode['LABEL']) ? (string) $elementNode['LABEL'] : '');
954
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['orderlabel'] = (isset($elementNode['ORDERLABEL']) ? (string) $elementNode['ORDERLABEL'] : '');
955
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['type'] = (string) $elementNode['TYPE'];
956
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['contentIds'] = (isset($elementNode['CONTENTIDS']) ? (string) $elementNode['CONTENTIDS'] : '');
957
                    // Get the file representations from fileSec node.
958
                    foreach ($elementNode->children('http://www.loc.gov/METS/')->fptr as $fptr) {
959
                        // Check if file has valid @USE attribute.
960
                        if (!empty($fileUse[(string) $fptr->attributes()->FILEID])) {
961
                            $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
962
                        }
963
                    }
964
                }
965
                // Sort array by keys (= @ORDER).
966
                if (ksort($elements)) {
967
                    // Set total number of pages/tracks.
968
                    $this->numPages = count($elements);
0 ignored issues
show
Bug introduced by
The property numPages is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
969
                    // Merge and re-index the array to get nice numeric indexes.
970
                    $this->physicalStructure = array_merge($physSeq, $elements);
0 ignored issues
show
Bug introduced by
The property physicalStructure is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
971
                }
972
            }
973
            $this->physicalStructureLoaded = true;
974
        }
975
        return $this->physicalStructure;
976
    }
977
978
    /**
979
     * {@inheritDoc}
980
     * @see Document::_getSmLinks()
981
     */
982
    protected function _getSmLinks()
983
    {
984
        if (!$this->smLinksLoaded) {
985
            $smLinks = $this->mets->xpath('./mets:structLink/mets:smLink');
986
            if (!empty($smLinks)) {
987
                foreach ($smLinks as $smLink) {
988
                    $this->smLinks['l2p'][(string) $smLink->attributes('http://www.w3.org/1999/xlink')->from][] = (string) $smLink->attributes('http://www.w3.org/1999/xlink')->to;
0 ignored issues
show
Bug introduced by
The property smLinks is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
989
                    $this->smLinks['p2l'][(string) $smLink->attributes('http://www.w3.org/1999/xlink')->to][] = (string) $smLink->attributes('http://www.w3.org/1999/xlink')->from;
990
                }
991
            }
992
            $this->smLinksLoaded = true;
993
        }
994
        return $this->smLinks;
995
    }
996
997
    /**
998
     * {@inheritDoc}
999
     * @see Document::_getThumbnail()
1000
     */
1001
    protected function _getThumbnail($forceReload = false)
1002
    {
1003
        if (
1004
            !$this->thumbnailLoaded
1005
            || $forceReload
1006
        ) {
1007
            // Retain current PID.
1008
            $cPid = ($this->cPid ? $this->cPid : $this->pid);
1009
            if (!$cPid) {
1010
                $this->logger->error('Invalid PID ' . $cPid . ' for structure definitions');
1011
                $this->thumbnailLoaded = true;
1012
                return $this->thumbnail;
1013
            }
1014
            // Load extension configuration.
1015
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
1016
            if (empty($extConf['fileGrpThumbs'])) {
1017
                $this->logger->warning('No fileGrp for thumbnails specified');
1018
                $this->thumbnailLoaded = true;
1019
                return $this->thumbnail;
1020
            }
1021
            $strctId = $this->_getToplevelId();
1022
            $metadata = $this->getTitleData($cPid);
1023
1024
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1025
                ->getQueryBuilderForTable('tx_dlf_structures');
1026
1027
            // Get structure element to get thumbnail from.
1028
            $result = $queryBuilder
1029
                ->select('tx_dlf_structures.thumbnail AS thumbnail')
1030
                ->from('tx_dlf_structures')
1031
                ->where(
1032
                    $queryBuilder->expr()->eq('tx_dlf_structures.pid', intval($cPid)),
1033
                    $queryBuilder->expr()->eq('tx_dlf_structures.index_name', $queryBuilder->expr()->literal($metadata['type'][0])),
1034
                    Helper::whereExpression('tx_dlf_structures')
1035
                )
1036
                ->setMaxResults(1)
1037
                ->execute();
1038
1039
            $allResults = $result->fetchAll();
1040
1041
            if (count($allResults) == 1) {
1042
                $resArray = $allResults[0];
1043
                // Get desired thumbnail structure if not the toplevel structure itself.
1044
                if (!empty($resArray['thumbnail'])) {
1045
                    $strctType = Helper::getIndexNameFromUid($resArray['thumbnail'], 'tx_dlf_structures', $cPid);
1046
                    // Check if this document has a structure element of the desired type.
1047
                    $strctIds = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@TYPE="' . $strctType . '"]/@ID');
1048
                    if (!empty($strctIds)) {
1049
                        $strctId = (string) $strctIds[0];
1050
                    }
1051
                }
1052
                // Load smLinks.
1053
                $this->_getSmLinks();
1054
                // Get thumbnail location.
1055
                $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
1056
                while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
1057
                    if (
1058
                        $this->_getPhysicalStructure()
1059
                        && !empty($this->smLinks['l2p'][$strctId])
1060
                        && !empty($this->physicalStructureInfo[$this->smLinks['l2p'][$strctId][0]]['files'][$fileGrpThumb])
1061
                    ) {
1062
                        $this->thumbnail = $this->getFileLocation($this->physicalStructureInfo[$this->smLinks['l2p'][$strctId][0]]['files'][$fileGrpThumb]);
0 ignored issues
show
Bug introduced by
The property thumbnail is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
1063
                        break;
1064
                    } elseif (!empty($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb])) {
1065
                        $this->thumbnail = $this->getFileLocation($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb]);
1066
                        break;
1067
                    }
1068
                }
1069
            } else {
1070
                $this->logger->error('No structure of type "' . $metadata['type'][0] . '" found in database');
1071
            }
1072
            $this->thumbnailLoaded = true;
1073
        }
1074
        return $this->thumbnail;
1075
    }
1076
1077
    /**
1078
     * {@inheritDoc}
1079
     * @see Document::_getToplevelId()
1080
     */
1081
    protected function _getToplevelId()
1082
    {
1083
        if (empty($this->toplevelId)) {
1084
            // Get all logical structure nodes with metadata, but without associated METS-Pointers.
1085
            $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@DMDID and not(./mets:mptr)]');
1086
            if (!empty($divs)) {
1087
                // Load smLinks.
1088
                $this->_getSmLinks();
1089
                foreach ($divs as $div) {
1090
                    $id = (string) $div['ID'];
1091
                    // Are there physical structure nodes for this logical structure?
1092
                    if (array_key_exists($id, $this->smLinks['l2p'])) {
1093
                        // Yes. That's what we're looking for.
1094
                        $this->toplevelId = $id;
0 ignored issues
show
Bug introduced by
The property toplevelId is declared read-only in Kitodo\Dlf\Common\Document\MetsDocument.
Loading history...
1095
                        break;
1096
                    } elseif (empty($this->toplevelId)) {
1097
                        // No. Remember this anyway, but keep looking for a better one.
1098
                        $this->toplevelId = $id;
1099
                    }
1100
                }
1101
            }
1102
        }
1103
        return $this->toplevelId;
1104
    }
1105
1106
    /**
1107
     * This magic method is executed prior to any serialization of the object
1108
     * @see __wakeup()
1109
     *
1110
     * @access public
1111
     *
1112
     * @return array Properties to be serialized
1113
     */
1114
    public function __sleep()
1115
    {
1116
        // \SimpleXMLElement objects can't be serialized, thus save the XML as string for serialization
1117
        $this->asXML = $this->xml->asXML();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->xml->asXML() can also be of type true. However, the property $asXML is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1118
        return ['uid', 'pid', 'recordId', 'parentId', 'asXML'];
1119
    }
1120
1121
    /**
1122
     * This magic method is used for setting a string value for the object
1123
     *
1124
     * @access public
1125
     *
1126
     * @return string String representing the METS object
1127
     */
1128
    public function __toString()
1129
    {
1130
        $xml = new \DOMDocument('1.0', 'utf-8');
1131
        $xml->appendChild($xml->importNode(dom_import_simplexml($this->mets), true));
1132
        $xml->formatOutput = true;
1133
        return $xml->saveXML();
1134
    }
1135
1136
    /**
1137
     * This magic method is executed after the object is deserialized
1138
     * @see __sleep()
1139
     *
1140
     * @access public
1141
     *
1142
     * @return void
1143
     */
1144
    public function __wakeup()
1145
    {
1146
        // Turn off libxml's error logging.
1147
        $libxmlErrors = libxml_use_internal_errors(true);
1148
        // Reload XML from string.
1149
        $xml = @simplexml_load_string($this->asXML);
1150
        // Reset libxml's error logging.
1151
        libxml_use_internal_errors($libxmlErrors);
1152
        if ($xml !== false) {
1153
            $this->asXML = '';
1154
            $this->xml = $xml;
1155
            // Rebuild the unserializable properties.
1156
            $this->init();
1157
        } else {
1158
            $this->logger->error('Could not load XML after deserialization');
1159
        }
1160
    }
1161
}
1162