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
unknown
04:20 queued 01:49
created

MetsDocument::getStructureDepth()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
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::getFullText()
666
     */
667
    public function getFullText($id)
668
    {
669
        $fullText = '';
670
671
        // Load fileGrps and check for full text files.
672
        $this->_getFileGrps();
673
        if ($this->hasFullText) {
674
            $fullText = $this->getFullTextFromXml($id);
675
        }
676
        return $fullText;
677
    }
678
679
    /**
680
     * {@inheritDoc}
681
     * @see Document::getStructureDepth()
682
     */
683
    public function getStructureDepth($logId)
684
    {
685
        $ancestors = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $logId . '"]/ancestor::*');
686
        if (!empty($ancestors)) {
687
            return count($ancestors);
688
        } else {
689
            return 0;
690
        }
691
    }
692
693
    /**
694
     * {@inheritDoc}
695
     * @see Document::init()
696
     */
697
    protected function init()
698
    {
699
        // Get METS node from XML file.
700
        $this->registerNamespaces($this->xml);
701
        $mets = $this->xml->xpath('//mets:mets');
702
        if (!empty($mets)) {
703
            $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...
704
            // Register namespaces.
705
            $this->registerNamespaces($this->mets);
706
        } else {
707
            $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

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

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