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
Push — master ( 8c4913...9583e9 )
by
unknown
03:53
created

MetsDocument::_getDmdSec()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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;
14
15
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
16
use TYPO3\CMS\Core\Database\ConnectionPool;
17
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
18
use TYPO3\CMS\Core\Log\LogManager;
19
use TYPO3\CMS\Core\Utility\GeneralUtility;
20
use Ubl\Iiif\Tools\IiifHelper;
21
use Ubl\Iiif\Services\AbstractImageService;
22
23
/**
24
 * MetsDocument class for the 'dlf' extension.
25
 *
26
 * @author Sebastian Meyer <[email protected]>
27
 * @author Henrik Lochmann <[email protected]>
28
 * @package TYPO3
29
 * @subpackage dlf
30
 * @access public
31
 * @property int $cPid This holds the PID for the configuration
32
 * @property-read array $mdSec Associative array of METS metadata sections indexed by their IDs.
33
 * @property-read array $dmdSec Subset of `$mdSec` storing only the dmdSec entries; kept for compatibility.
34
 * @property-read array $fileGrps This holds the file ID -> USE concordance
35
 * @property-read bool $hasFulltext Are there any fulltext files available?
36
 * @property-read array $metadataArray This holds the documents' parsed metadata array
37
 * @property-read \SimpleXMLElement $mets This holds the XML file's METS part as \SimpleXMLElement object
38
 * @property-read int $numPages The holds the total number of pages
39
 * @property-read int $parentId This holds the UID of the parent document or zero if not multi-volumed
40
 * @property-read array $physicalStructure This holds the physical structure
41
 * @property-read array $physicalStructureInfo This holds the physical structure metadata
42
 * @property-read int $pid This holds the PID of the document or zero if not in database
43
 * @property-read bool $ready Is the document instantiated successfully?
44
 * @property-read string $recordId The METS file's / IIIF manifest's record identifier
45
 * @property-read int $rootId This holds the UID of the root document or zero if not multi-volumed
46
 * @property-read array $smLinks This holds the smLinks between logical and physical structMap
47
 * @property-read array $tableOfContents This holds the logical structure
48
 * @property-read string $thumbnail This holds the document's thumbnail location
49
 * @property-read string $toplevelId This holds the toplevel structure's @ID (METS) or the manifest's @id (IIIF)
50
 * @property-read string $parentHref URL of the parent document (determined via mptr element), or empty string if none is available
51
 */
52
final class MetsDocument extends AbstractDocument
53
{
54
    /**
55
     * Subsections / tags that may occur within `<mets:amdSec>`.
56
     *
57
     * @link https://www.loc.gov/standards/mets/docs/mets.v1-9.html#amdSec
58
     * @link https://www.loc.gov/standards/mets/docs/mets.v1-9.html#mdSecType
59
     *
60
     * @var string[]
61
     */
62
    protected const ALLOWED_AMD_SEC = ['techMD', 'rightsMD', 'sourceMD', 'digiprovMD'];
63
64
    /**
65
     * This holds the whole XML file as string for serialization purposes
66
     * @see __sleep() / __wakeup()
67
     *
68
     * @var string
69
     * @access protected
70
     */
71
    protected $asXML = '';
72
73
    /**
74
     * This maps the ID of each amdSec to the IDs of its children (techMD etc.).
75
     * When an ADMID references an amdSec instead of techMD etc., this is used to iterate the child elements.
76
     *
77
     * @var array
78
     * @access protected
79
     */
80
    protected $amdSecChildIds = [];
81
82
    /**
83
     * Associative array of METS metadata sections indexed by their IDs.
84
     *
85
     * @var array
86
     * @access protected
87
     */
88
    protected $mdSec = [];
89
90
    /**
91
     * Are the METS file's metadata sections loaded?
92
     * @see MetsDocument::$mdSec
93
     *
94
     * @var bool
95
     * @access protected
96
     */
97
    protected $mdSecLoaded = false;
98
99
    /**
100
     * Subset of $mdSec storing only the dmdSec entries; kept for compatibility.
101
     *
102
     * @var array
103
     * @access protected
104
     */
105
    protected $dmdSec = [];
106
107
    /**
108
     * The extension key
109
     *
110
     * @var	string
111
     * @access public
112
     */
113
    public static $extKey = 'dlf';
114
115
    /**
116
     * This holds the file ID -> USE concordance
117
     * @see _getFileGrps()
118
     *
119
     * @var array
120
     * @access protected
121
     */
122
    protected $fileGrps = [];
123
124
    /**
125
     * Are the image file groups loaded?
126
     * @see $fileGrps
127
     *
128
     * @var bool
129
     * @access protected
130
     */
131
    protected $fileGrpsLoaded = false;
132
133
    /**
134
     * This holds the XML file's METS part as \SimpleXMLElement object
135
     *
136
     * @var \SimpleXMLElement
137
     * @access protected
138
     */
139
    protected $mets;
140
141
    /**
142
     * This holds the whole XML file as \SimpleXMLElement object
143
     *
144
     * @var \SimpleXMLElement
145
     * @access protected
146
     */
147
    protected $xml;
148
149
    /**
150
     * URL of the parent document (determined via mptr element),
151
     * or empty string if none is available
152
     *
153
     * @var string|null
154
     * @access protected
155
     */
156
    protected $parentHref;
157
158
    /**
159
     * This adds metadata from METS structural map to metadata array.
160
     *
161
     * @access	public
162
     *
163
     * @param	array	&$metadata: The metadata array to extend
164
     * @param	string	$id: The "@ID" attribute of the logical structure node
165
     *
166
     * @return  void
167
     */
168
    public function addMetadataFromMets(&$metadata, $id)
169
    {
170
        $details = $this->getLogicalStructure($id);
171
        if (!empty($details)) {
172
            $metadata['mets_order'][0] = $details['order'];
173
            $metadata['mets_label'][0] = $details['label'];
174
            $metadata['mets_orderlabel'][0] = $details['orderlabel'];
175
        }
176
    }
177
178
    /**
179
     *
180
     * {@inheritDoc}
181
     * @see AbstractDocument::establishRecordId()
182
     */
183
    protected function establishRecordId($pid)
184
    {
185
        // Check for METS object @ID.
186
        if (!empty($this->mets['OBJID'])) {
187
            $this->recordId = (string) $this->mets['OBJID'];
188
        }
189
        // Get hook objects.
190
        $hookObjects = Helper::getHookObjects('Classes/Common/MetsDocument.php');
191
        // Apply hooks.
192
        foreach ($hookObjects as $hookObj) {
193
            if (method_exists($hookObj, 'construct_postProcessRecordId')) {
194
                $hookObj->construct_postProcessRecordId($this->xml, $this->recordId);
195
            }
196
        }
197
    }
198
199
    /**
200
     *
201
     * {@inheritDoc}
202
     * @see AbstractDocument::getDownloadLocation()
203
     */
204
    public function getDownloadLocation($id)
205
    {
206
        $file = $this->getFileInfo($id);
207
        if ($file['mimeType'] === 'application/vnd.kitodo.iiif') {
208
            $file['location'] = (strrpos($file['location'], 'info.json') === strlen($file['location']) - 9) ? $file['location'] : (strrpos($file['location'], '/') === strlen($file['location']) ? $file['location'] . 'info.json' : $file['location'] . '/info.json');
209
            $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
210
            IiifHelper::setUrlReader(IiifUrlReader::getInstance());
211
            IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']);
212
            IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']);
213
            $service = IiifHelper::loadIiifResource($file['location']);
214
            if ($service !== null && $service instanceof AbstractImageService) {
215
                return $service->getImageUrl();
216
            }
217
        } elseif ($file['mimeType'] === 'application/vnd.netfpx') {
218
            $baseURL = $file['location'] . (strpos($file['location'], '?') === false ? '?' : '');
219
            // TODO CVT is an optional IIP server capability; in theory, capabilities should be determined in the object request with '&obj=IIP-server'
220
            return $baseURL . '&CVT=jpeg';
221
        }
222
        return $file['location'];
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     * @see AbstractDocument::getFileInfo()
228
     */
229
    public function getFileInfo($id)
230
    {
231
        $this->_getFileGrps();
232
233
        if (isset($this->fileInfos[$id]) && empty($this->fileInfos[$id]['location'])) {
234
            $this->fileInfos[$id]['location'] = $this->getFileLocation($id);
0 ignored issues
show
Bug introduced by
The property fileInfos is declared read-only in Kitodo\Dlf\Common\AbstractDocument.
Loading history...
235
        }
236
237
        if (isset($this->fileInfos[$id]) && empty($this->fileInfos[$id]['mimeType'])) {
238
            $this->fileInfos[$id]['mimeType'] = $this->getFileMimeType($id);
239
        }
240
241
        return $this->fileInfos[$id];
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     * @see AbstractDocument::getFileLocation()
247
     */
248
    public function getFileLocation($id)
249
    {
250
        $location = $this->mets->xpath('./mets:fileSec/mets:fileGrp/mets:file[@ID="' . $id . '"]/mets:FLocat[@LOCTYPE="URL"]');
251
        if (
252
            !empty($id)
253
            && !empty($location)
254
        ) {
255
            return (string) $location[0]->attributes('http://www.w3.org/1999/xlink')->href;
256
        } else {
257
            $this->logger->warning('There is no file node with @ID "' . $id . '"');
258
            return '';
259
        }
260
    }
261
262
    /**
263
     * {@inheritDoc}
264
     * @see AbstractDocument::getFileMimeType()
265
     */
266
    public function getFileMimeType($id)
267
    {
268
        $mimetype = $this->mets->xpath('./mets:fileSec/mets:fileGrp/mets:file[@ID="' . $id . '"]/@MIMETYPE');
269
        if (
270
            !empty($id)
271
            && !empty($mimetype)
272
        ) {
273
            return (string) $mimetype[0];
274
        } else {
275
            $this->logger->warning('There is no file node with @ID "' . $id . '" or no MIME type specified');
276
            return '';
277
        }
278
    }
279
280
    /**
281
     * {@inheritDoc}
282
     * @see AbstractDocument::getLogicalStructure()
283
     */
284
    public function getLogicalStructure($id, $recursive = false)
285
    {
286
        $details = [];
287
        // Is the requested logical unit already loaded?
288
        if (
289
            !$recursive
290
            && !empty($this->logicalUnits[$id])
291
        ) {
292
            // Yes. Return it.
293
            return $this->logicalUnits[$id];
294
        } elseif (!empty($id)) {
295
            // Get specified logical unit.
296
            $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]');
297
        } else {
298
            // Get all logical units at top level.
299
            $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]/mets:div');
300
        }
301
        if (!empty($divs)) {
302
            if (!$recursive) {
303
                // Get the details for the first xpath hit.
304
                $details = $this->getLogicalStructureInfo($divs[0]);
305
            } else {
306
                // Walk the logical structure recursively and fill the whole table of contents.
307
                foreach ($divs as $div) {
308
                    $this->tableOfContents[] = $this->getLogicalStructureInfo($div, $recursive);
309
                }
310
            }
311
        }
312
        return $details;
313
    }
314
315
    /**
316
     * This gets details about a logical structure element
317
     *
318
     * @access protected
319
     *
320
     * @param \SimpleXMLElement $structure: The logical structure node
321
     * @param bool $recursive: Whether to include the child elements
322
     *
323
     * @return array Array of the element's id, label, type and physical page indexes/mptr link
324
     */
325
    protected function getLogicalStructureInfo(\SimpleXMLElement $structure, $recursive = false)
326
    {
327
        // Get attributes.
328
        foreach ($structure->attributes() as $attribute => $value) {
329
            $attributes[$attribute] = (string) $value;
330
        }
331
        // Load plugin configuration.
332
        $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
333
        // Extract identity information.
334
        $details = [];
335
        $details['id'] = $attributes['ID'];
336
        $details['dmdId'] = (isset($attributes['DMDID']) ? $attributes['DMDID'] : '');
337
        $details['admId'] = (isset($attributes['ADMID']) ? $attributes['ADMID'] : '');
338
        $details['order'] = (isset($attributes['ORDER']) ? $attributes['ORDER'] : '');
339
        $details['label'] = (isset($attributes['LABEL']) ? $attributes['LABEL'] : '');
340
        $details['orderlabel'] = (isset($attributes['ORDERLABEL']) ? $attributes['ORDERLABEL'] : '');
341
        $details['contentIds'] = (isset($attributes['CONTENTIDS']) ? $attributes['CONTENTIDS'] : '');
342
        $details['volume'] = '';
343
        // Set volume and year information only if no label is set and this is the toplevel structure element.
344
        if (
345
            empty($details['label'])
346
            && empty($details['orderlabel'])
347
        ) {
348
            $metadata = $this->getMetadata($details['id']);
349
            if (!empty($metadata['volume'][0])) {
350
                $details['volume'] = $metadata['volume'][0];
351
            }
352
            if (!empty($metadata['year'][0])) {
353
                $details['year'] = $metadata['year'][0];
354
            }
355
        }
356
        $details['pagination'] = '';
357
        $details['type'] = $attributes['TYPE'];
358
        // add description for 3D objects
359
        if ($details['type'] == 'object') {
360
            $metadata = $this->getMetadata($details['id']);
361
            $details['description'] = $metadata['description'][0] ?? '';
362
        }
363
        $details['thumbnailId'] = '';
364
        // Load smLinks.
365
        $this->_getSmLinks();
366
        // Load physical structure.
367
        $this->_getPhysicalStructure();
368
        // Get the physical page or external file this structure element is pointing at.
369
        $details['points'] = '';
370
        // Is there a mptr node?
371
        if (count($structure->children('http://www.loc.gov/METS/')->mptr)) {
372
            // Yes. Get the file reference.
373
            $details['points'] = (string) $structure->children('http://www.loc.gov/METS/')->mptr[0]->attributes('http://www.w3.org/1999/xlink')->href;
374
        } elseif (
375
            !empty($this->physicalStructure)
376
            && array_key_exists($details['id'], $this->smLinks['l2p'])
377
        ) {
378
            // Link logical structure to the first corresponding physical page/track.
379
            $details['points'] = max(intval(array_search($this->smLinks['l2p'][$details['id']][0], $this->physicalStructure, true)), 1);
380
            $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
381
            while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
382
                if (!empty($this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['files'][$fileGrpThumb])) {
383
                    $details['thumbnailId'] = $this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['files'][$fileGrpThumb];
384
                    break;
385
                }
386
            }
387
            // Get page/track number of the first page/track related to this structure element.
388
            $details['pagination'] = $this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['orderlabel'];
389
        } elseif ($details['id'] == $this->_getToplevelId()) {
390
            // Point to self if this is the toplevel structure.
391
            $details['points'] = 1;
392
            $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
393
            while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
394
                if (
395
                    !empty($this->physicalStructure)
396
                    && !empty($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb])
397
                ) {
398
                    $details['thumbnailId'] = $this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb];
399
                    break;
400
                }
401
            }
402
        }
403
        // Get the files this structure element is pointing at.
404
        $details['files'] = [];
405
        $fileUse = $this->_getFileGrps();
406
        // Get the file representations from fileSec node.
407
        foreach ($structure->children('http://www.loc.gov/METS/')->fptr as $fptr) {
408
            // Check if file has valid @USE attribute.
409
            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

409
            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...
410
                $details['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
411
            }
412
        }
413
        // Keep for later usage.
414
        $this->logicalUnits[$details['id']] = $details;
415
        // Walk the structure recursively? And are there any children of the current element?
416
        if (
417
            $recursive
418
            && count($structure->children('http://www.loc.gov/METS/')->div)
419
        ) {
420
            $details['children'] = [];
421
            foreach ($structure->children('http://www.loc.gov/METS/')->div as $child) {
422
                // Repeat for all children.
423
                $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\MetsDo...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

423
                $details['children'][] = $this->getLogicalStructureInfo(/** @scrutinizer ignore-type */ $child, true);
Loading history...
424
            }
425
        }
426
        return $details;
427
    }
428
429
    /**
430
     * {@inheritDoc}
431
     * @see AbstractDocument::getMetadata()
432
     */
433
    public function getMetadata($id, $cPid = 0)
434
    {
435
        // Make sure $cPid is a non-negative integer.
436
        $cPid = max(intval($cPid), 0);
437
        // If $cPid is not given, try to get it elsewhere.
438
        if (
439
            !$cPid
440
            && ($this->cPid || $this->pid)
441
        ) {
442
            // Retain current PID.
443
            $cPid = ($this->cPid ? $this->cPid : $this->pid);
444
        } elseif (!$cPid) {
445
            $this->logger->warning('Invalid PID ' . $cPid . ' for metadata definitions');
446
            return [];
447
        }
448
        // Get metadata from parsed metadata array if available.
449
        if (
450
            !empty($this->metadataArray[$id])
451
            && $this->metadataArray[0] == $cPid
452
        ) {
453
            return $this->metadataArray[$id];
454
        }
455
456
        $metadata = $this->initializeMetadata('METS');
457
458
        $mdIds = $this->getMetadataIds($id);
459
        if (empty($mdIds)) {
460
            // There is no metadata section for this structure node.
461
            return [];
462
        }
463
        // Associative array used as set of available section types (dmdSec, techMD, ...)
464
        $hasMetadataSection = [];
465
        // Load available metadata formats and metadata sections.
466
        $this->loadFormats();
467
        $this->_getMdSec();
468
        // Get the structure's type.
469
        if (!empty($this->logicalUnits[$id])) {
470
            $metadata['type'] = [$this->logicalUnits[$id]['type']];
471
        } else {
472
            $struct = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]/@TYPE');
473
            if (!empty($struct)) {
474
                $metadata['type'] = [(string) $struct[0]];
475
            }
476
        }
477
        foreach ($mdIds as $dmdId) {
478
            $mdSectionType = $this->mdSec[$dmdId]['section'];
479
480
            // To preserve behavior of previous Kitodo versions, extract metadata only from first supported dmdSec
481
            // However, we want to extract, for example, all techMD sections (VIDEOMD, AUDIOMD)
482
            if ($mdSectionType === 'dmdSec' && isset($hasMetadataSection['dmdSec'])) {
483
                continue;
484
            }
485
486
            // Is this metadata format supported?
487
            if (!empty($this->formats[$this->mdSec[$dmdId]['type']])) {
488
                if (!empty($this->formats[$this->mdSec[$dmdId]['type']]['class'])) {
489
                    $class = $this->formats[$this->mdSec[$dmdId]['type']]['class'];
490
                    // Get the metadata from class.
491
                    if (
492
                        class_exists($class)
493
                        && ($obj = GeneralUtility::makeInstance($class)) instanceof MetadataInterface
494
                    ) {
495
                        $obj->extractMetadata($this->mdSec[$dmdId]['xml'], $metadata);
496
                    } else {
497
                        $this->logger->warning('Invalid class/method "' . $class . '->extractMetadata()" for metadata format "' . $this->mdSec[$dmdId]['type'] . '"');
498
                    }
499
                }
500
            } else {
501
                $this->logger->notice('Unsupported metadata format "' . $this->mdSec[$dmdId]['type'] . '" in ' . $mdSectionType . ' with @ID "' . $dmdId . '"');
502
                // Continue searching for supported metadata with next @DMDID.
503
                continue;
504
            }
505
            // Get the additional metadata from database.
506
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
507
                ->getQueryBuilderForTable('tx_dlf_metadata');
508
            // Get hidden records, too.
509
            $queryBuilder
510
                ->getRestrictions()
511
                ->removeByType(HiddenRestriction::class);
512
            // Get all metadata with configured xpath and applicable format first.
513
            $resultWithFormat = $queryBuilder
514
                ->select(
515
                    'tx_dlf_metadata.index_name AS index_name',
516
                    'tx_dlf_metadataformat_joins.xpath AS xpath',
517
                    'tx_dlf_metadataformat_joins.xpath_sorting AS xpath_sorting',
518
                    'tx_dlf_metadata.is_sortable AS is_sortable',
519
                    'tx_dlf_metadata.default_value AS default_value',
520
                    'tx_dlf_metadata.format AS format'
521
                )
522
                ->from('tx_dlf_metadata')
523
                ->innerJoin(
524
                    'tx_dlf_metadata',
525
                    'tx_dlf_metadataformat',
526
                    'tx_dlf_metadataformat_joins',
527
                    $queryBuilder->expr()->eq(
528
                        'tx_dlf_metadataformat_joins.parent_id',
529
                        'tx_dlf_metadata.uid'
530
                    )
531
                )
532
                ->innerJoin(
533
                    'tx_dlf_metadataformat_joins',
534
                    'tx_dlf_formats',
535
                    'tx_dlf_formats_joins',
536
                    $queryBuilder->expr()->eq(
537
                        'tx_dlf_formats_joins.uid',
538
                        'tx_dlf_metadataformat_joins.encoded'
539
                    )
540
                )
541
                ->where(
542
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
543
                    $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
544
                    $queryBuilder->expr()->eq('tx_dlf_metadataformat_joins.pid', intval($cPid)),
545
                    $queryBuilder->expr()->eq('tx_dlf_formats_joins.type', $queryBuilder->createNamedParameter($this->mdSec[$dmdId]['type']))
546
                )
547
                ->execute();
548
            // Get all metadata without a format, but with a default value next.
549
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
550
                ->getQueryBuilderForTable('tx_dlf_metadata');
551
            // Get hidden records, too.
552
            $queryBuilder
553
                ->getRestrictions()
554
                ->removeByType(HiddenRestriction::class);
555
            $resultWithoutFormat = $queryBuilder
556
                ->select(
557
                    'tx_dlf_metadata.index_name AS index_name',
558
                    'tx_dlf_metadata.is_sortable AS is_sortable',
559
                    'tx_dlf_metadata.default_value AS default_value',
560
                    'tx_dlf_metadata.format AS format'
561
                )
562
                ->from('tx_dlf_metadata')
563
                ->where(
564
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
565
                    $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
566
                    $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0),
567
                    $queryBuilder->expr()->neq('tx_dlf_metadata.default_value', $queryBuilder->createNamedParameter(''))
568
                )
569
                ->execute();
570
            // Merge both result sets.
571
            $allResults = array_merge($resultWithFormat->fetchAll(), $resultWithoutFormat->fetchAll());
572
            // We need a \DOMDocument here, because SimpleXML doesn't support XPath functions properly.
573
            $domNode = dom_import_simplexml($this->mdSec[$dmdId]['xml']);
574
            $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

574
            $domXPath = new \DOMXPath(/** @scrutinizer ignore-type */ $domNode->ownerDocument);
Loading history...
575
            $this->registerNamespaces($domXPath);
576
            // OK, now make the XPath queries.
577
            foreach ($allResults as $resArray) {
578
                // Set metadata field's value(s).
579
                if (
580
                    $resArray['format'] > 0
581
                    && !empty($resArray['xpath'])
582
                    && ($values = $domXPath->evaluate($resArray['xpath'], $domNode))
583
                ) {
584
                    if (
585
                        $values instanceof \DOMNodeList
586
                        && $values->length > 0
587
                    ) {
588
                        $metadata[$resArray['index_name']] = [];
589
                        foreach ($values as $value) {
590
                            $metadata[$resArray['index_name']][] = trim((string) $value->nodeValue);
591
                        }
592
                    } elseif (!($values instanceof \DOMNodeList)) {
593
                        $metadata[$resArray['index_name']] = [trim((string) $values)];
594
                    }
595
                }
596
                // Set default value if applicable.
597
                if (
598
                    empty($metadata[$resArray['index_name']][0])
599
                    && strlen($resArray['default_value']) > 0
600
                ) {
601
                    $metadata[$resArray['index_name']] = [$resArray['default_value']];
602
                }
603
                // Set sorting value if applicable.
604
                if (
605
                    !empty($metadata[$resArray['index_name']])
606
                    && $resArray['is_sortable']
607
                ) {
608
                    if (
609
                        $resArray['format'] > 0
610
                        && !empty($resArray['xpath_sorting'])
611
                        && ($values = $domXPath->evaluate($resArray['xpath_sorting'], $domNode))
612
                    ) {
613
                        if (
614
                            $values instanceof \DOMNodeList
615
                            && $values->length > 0
616
                        ) {
617
                            $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $values->item(0)->nodeValue);
618
                        } elseif (!($values instanceof \DOMNodeList)) {
619
                            $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $values);
620
                        }
621
                    }
622
                    if (empty($metadata[$resArray['index_name'] . '_sorting'][0])) {
623
                        $metadata[$resArray['index_name'] . '_sorting'][0] = $metadata[$resArray['index_name']][0];
624
                    }
625
                }
626
            }
627
628
            $hasMetadataSection[$mdSectionType] = true;
629
        }
630
        // Set title to empty string if not present.
631
        if (empty($metadata['title'][0])) {
632
            $metadata['title'][0] = '';
633
            $metadata['title_sorting'][0] = '';
634
        }
635
        // Set title_sorting to title as default.
636
        if (empty($metadata['title_sorting'][0])) {
637
            $metadata['title_sorting'][0] = $metadata['title'][0];
638
        }
639
        // Set date to empty string if not present.
640
        if (empty($metadata['date'][0])) {
641
            $metadata['date'][0] = '';
642
        }
643
644
        // Files are not expected to reference a dmdSec
645
        if (isset($this->fileInfos[$id]) || isset($hasMetadataSection['dmdSec'])) {
646
            return $metadata;
647
        } else {
648
            $this->logger->warning('No supported descriptive metadata found for logical structure with @ID "' . $id . '"');
649
            return [];
650
        }
651
    }
652
653
    /**
654
     * Get IDs of (descriptive and administrative) metadata sections
655
     * referenced by node of given $id. The $id may refer to either
656
     * a logical structure node or to a file.
657
     *
658
     * @access protected
659
     *
660
     * @param string $id: The "@ID" attribute of the file node
661
     *
662
     * @return array
663
     */
664
    protected function getMetadataIds($id)
665
    {
666
        // Load amdSecChildIds concordance
667
        $this->_getMdSec();
668
        $fileInfo = $this->getFileInfo($id);
669
670
        // Get DMDID and ADMID of logical structure node
671
        if (!empty($this->logicalUnits[$id])) {
672
            $dmdIds = $this->logicalUnits[$id]['dmdId'] ?? '';
673
            $admIds = $this->logicalUnits[$id]['admId'] ?? '';
674
        } else {
675
            $mdSec = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]')[0];
676
            if ($mdSec) {
0 ignored issues
show
introduced by
$mdSec is of type SimpleXMLElement, thus it always evaluated to true.
Loading history...
677
                $dmdIds = (string) $mdSec->attributes()->DMDID;
678
                $admIds = (string) $mdSec->attributes()->ADMID;
679
            } else if (isset($fileInfo)) {
680
                $dmdIds = $fileInfo['dmdId'];
681
                $admIds = $fileInfo['admId'];
682
            } else {
683
                $dmdIds = '';
684
                $admIds = '';
685
            }
686
        }
687
688
        // Handle multiple DMDIDs/ADMIDs
689
        $allMdIds = explode(' ', $dmdIds);
690
691
        foreach (explode(' ', $admIds) as $admId) {
692
            if (isset($this->mdSec[$admId])) {
693
                // $admId references an actual metadata section such as techMD
694
                $allMdIds[] = $admId;
695
            } elseif (isset($this->amdSecChildIds[$admId])) {
696
                // $admId references a <mets:amdSec> element. Resolve child elements.
697
                foreach ($this->amdSecChildIds[$admId] as $childId) {
698
                    $allMdIds[] = $childId;
699
                }
700
            }
701
        }
702
703
        return array_filter($allMdIds, function ($element) {
704
            return !empty($element);
705
        });
706
    }
707
708
    /**
709
     * {@inheritDoc}
710
     * @see AbstractDocument::getFullText()
711
     */
712
    public function getFullText($id)
713
    {
714
        $fullText = '';
715
716
        // Load fileGrps and check for full text files.
717
        $this->_getFileGrps();
718
        if ($this->hasFulltext) {
719
            $fullText = $this->getFullTextFromXml($id);
720
        }
721
        return $fullText;
722
    }
723
724
    /**
725
     * {@inheritDoc}
726
     * @see AbstractDocument::getStructureDepth()
727
     */
728
    public function getStructureDepth($logId)
729
    {
730
        $ancestors = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $logId . '"]/ancestor::*');
731
        if (!empty($ancestors)) {
732
            return count($ancestors);
733
        } else {
734
            return 0;
735
        }
736
    }
737
738
    /**
739
     * {@inheritDoc}
740
     * @see AbstractDocument::init()
741
     */
742
    protected function init($location)
743
    {
744
        $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(get_class($this));
745
        // Get METS node from XML file.
746
        $this->registerNamespaces($this->xml);
747
        $mets = $this->xml->xpath('//mets:mets');
748
        if (!empty($mets)) {
749
            $this->mets = $mets[0];
750
            // Register namespaces.
751
            $this->registerNamespaces($this->mets);
752
        } else {
753
            if (!empty($location)) {
754
                $this->logger->error('No METS part found in document with location "' . $location . '".');
755
            } else if (!empty($this->recordId)) {
756
                $this->logger->error('No METS part found in document with recordId "' . $this->recordId . '".');
757
            } else {
758
                $this->logger->error('No METS part found in current document.');
759
            }
760
        }
761
    }
762
763
    /**
764
     * {@inheritDoc}
765
     * @see AbstractDocument::loadLocation()
766
     */
767
    protected function loadLocation($location)
768
    {
769
        $fileResource = Helper::getUrl($location);
770
        if ($fileResource !== false) {
771
            $xml = Helper::getXmlFileAsString($fileResource);
772
            // Set some basic properties.
773
            if ($xml !== false) {
774
                $this->xml = $xml;
775
                return true;
776
            }
777
        }
778
        $this->logger->error('Could not load XML file from "' . $location . '"');
779
        return false;
780
    }
781
782
    /**
783
     * {@inheritDoc}
784
     * @see AbstractDocument::ensureHasFulltextIsSet()
785
     */
786
    protected function ensureHasFulltextIsSet()
787
    {
788
        // Are the fileGrps already loaded?
789
        if (!$this->fileGrpsLoaded) {
790
            $this->_getFileGrps();
791
        }
792
    }
793
794
    /**
795
     * {@inheritDoc}
796
     * @see AbstractDocument::setPreloadedDocument()
797
     */
798
    protected function setPreloadedDocument($preloadedDocument)
799
    {
800
801
        if ($preloadedDocument instanceof \SimpleXMLElement) {
802
            $this->xml = $preloadedDocument;
803
            return true;
804
        }
805
        return false;
806
    }
807
808
    /**
809
     * {@inheritDoc}
810
     * @see AbstractDocument::getDocument()
811
     */
812
    protected function getDocument()
813
    {
814
        return $this->mets;
815
    }
816
817
    /**
818
     * This builds an array of the document's metadata sections
819
     *
820
     * @access protected
821
     *
822
     * @return array Array of metadata sections with their IDs as array key
823
     */
824
    protected function _getMdSec()
825
    {
826
        if (!$this->mdSecLoaded) {
827
            $this->loadFormats();
828
829
            foreach ($this->mets->xpath('./mets:dmdSec') as $dmdSecTag) {
830
                $dmdSec = $this->processMdSec($dmdSecTag);
831
832
                if ($dmdSec !== null) {
833
                    $this->mdSec[$dmdSec['id']] = $dmdSec;
0 ignored issues
show
Bug introduced by
The property mdSec is declared read-only in Kitodo\Dlf\Common\MetsDocument.
Loading history...
834
                    $this->dmdSec[$dmdSec['id']] = $dmdSec;
835
                }
836
            }
837
838
            foreach ($this->mets->xpath('./mets:amdSec') as $amdSecTag) {
839
                $childIds = [];
840
841
                foreach ($amdSecTag->children('http://www.loc.gov/METS/') as $mdSecTag) {
842
                    if (!in_array($mdSecTag->getName(), self::ALLOWED_AMD_SEC)) {
843
                        continue;
844
                    }
845
846
                    // TODO: Should we check that the format may occur within this type (e.g., to ignore VIDEOMD within rightsMD)?
847
                    $mdSec = $this->processMdSec($mdSecTag);
848
849
                    if ($mdSec !== null) {
850
                        $this->mdSec[$mdSec['id']] = $mdSec;
851
852
                        $childIds[] = $mdSec['id'];
853
                    }
854
                }
855
856
                $amdSecId = (string) $amdSecTag->attributes()->ID;
857
                if (!empty($amdSecId)) {
858
                    $this->amdSecChildIds[$amdSecId] = $childIds;
859
                }
860
            }
861
862
            $this->mdSecLoaded = true;
863
        }
864
        return $this->mdSec;
865
    }
866
867
    protected function _getDmdSec()
868
    {
869
        $this->_getMdSec();
870
        return $this->dmdSec;
871
    }
872
873
    /**
874
     * Processes an element of METS `mdSecType`.
875
     *
876
     * @access protected
877
     *
878
     * @param \SimpleXMLElement $element
879
     *
880
     * @return array|null The processed metadata section
881
     */
882
    protected function processMdSec($element)
883
    {
884
        $mdId = (string) $element->attributes()->ID;
885
        if (empty($mdId)) {
886
            return null;
887
        }
888
889
        $this->registerNamespaces($element);
890
        if ($type = $element->xpath('./mets:mdWrap[not(@MDTYPE="OTHER")]/@MDTYPE')) {
891
            if (!empty($this->formats[(string) $type[0]])) {
892
                $type = (string) $type[0];
893
                $xml = $element->xpath('./mets:mdWrap[@MDTYPE="' . $type . '"]/mets:xmlData/' . strtolower($type) . ':' . $this->formats[$type]['rootElement']);
894
            }
895
        } elseif ($type = $element->xpath('./mets:mdWrap[@MDTYPE="OTHER"]/@OTHERMDTYPE')) {
896
            if (!empty($this->formats[(string) $type[0]])) {
897
                $type = (string) $type[0];
898
                $xml = $element->xpath('./mets:mdWrap[@MDTYPE="OTHER"][@OTHERMDTYPE="' . $type . '"]/mets:xmlData/' . strtolower($type) . ':' . $this->formats[$type]['rootElement']);
899
            }
900
        }
901
902
        if (empty($xml)) {
903
            return null;
904
        }
905
906
        $this->registerNamespaces($xml[0]);
907
908
        return [
909
            'id' => $mdId,
910
            'section' => $element->getName(),
911
            'type' => $type,
912
            'xml' => $xml[0],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $xml does not seem to be defined for all execution paths leading up to this point.
Loading history...
913
        ];
914
    }
915
916
    /**
917
     * This builds the file ID -> USE concordance
918
     *
919
     * @access protected
920
     *
921
     * @return array Array of file use groups with file IDs
922
     */
923
    protected function _getFileGrps()
924
    {
925
        if (!$this->fileGrpsLoaded) {
926
            // Get configured USE attributes.
927
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
928
            $useGrps = GeneralUtility::trimExplode(',', $extConf['fileGrpImages']);
929
            if (!empty($extConf['fileGrpThumbs'])) {
930
                $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']));
931
            }
932
            if (!empty($extConf['fileGrpDownload'])) {
933
                $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpDownload']));
934
            }
935
            if (!empty($extConf['fileGrpFulltext'])) {
936
                $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']));
937
            }
938
            if (!empty($extConf['fileGrpAudio'])) {
939
                $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpAudio']));
940
            }
941
            // Get all file groups.
942
            $fileGrps = $this->mets->xpath('./mets:fileSec/mets:fileGrp');
943
            if (!empty($fileGrps)) {
944
                // Build concordance for configured USE attributes.
945
                foreach ($fileGrps as $fileGrp) {
946
                    if (in_array((string) $fileGrp['USE'], $useGrps)) {
947
                        foreach ($fileGrp->children('http://www.loc.gov/METS/')->file as $file) {
948
                            $fileId = (string) $file->attributes()->ID;
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

948
                            $fileId = (string) $file->/** @scrutinizer ignore-call */ attributes()->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...
949
                            $this->fileGrps[$fileId] = (string) $fileGrp['USE'];
950
                            $this->fileInfos[$fileId] = [
0 ignored issues
show
Bug introduced by
The property fileInfos is declared read-only in Kitodo\Dlf\Common\AbstractDocument.
Loading history...
951
                                'fileGrp' => (string) $fileGrp['USE'],
952
                                'admId' => (string) $file->attributes()->ADMID,
953
                                'dmdId' => (string) $file->attributes()->DMDID,
954
                            ];
955
                        }
956
                    }
957
                }
958
            }
959
            // Are there any fulltext files available?
960
            if (
961
                !empty($extConf['fileGrpFulltext'])
962
                && array_intersect(GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']), $this->fileGrps) !== []
963
            ) {
964
                $this->hasFulltext = true;
965
            }
966
            $this->fileGrpsLoaded = true;
967
        }
968
        return $this->fileGrps;
969
    }
970
971
    /**
972
     * {@inheritDoc}
973
     * @see AbstractDocument::prepareMetadataArray()
974
     */
975
    protected function prepareMetadataArray($cPid)
976
    {
977
        $ids = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@DMDID]/@ID');
978
        // Get all logical structure nodes with metadata.
979
        if (!empty($ids)) {
980
            foreach ($ids as $id) {
981
                $this->metadataArray[(string) $id] = $this->getMetadata((string) $id, $cPid);
982
            }
983
        }
984
        // Set current PID for metadata definitions.
985
    }
986
987
    /**
988
     * This returns $this->mets via __get()
989
     *
990
     * @access protected
991
     *
992
     * @return \SimpleXMLElement The XML's METS part as \SimpleXMLElement object
993
     */
994
    protected function _getMets()
995
    {
996
        return $this->mets;
997
    }
998
999
    /**
1000
     * {@inheritDoc}
1001
     * @see AbstractDocument::_getPhysicalStructure()
1002
     */
1003
    protected function _getPhysicalStructure()
1004
    {
1005
        // Is there no physical structure array yet?
1006
        if (!$this->physicalStructureLoaded) {
1007
            // Does the document have a structMap node of type "PHYSICAL"?
1008
            $elementNodes = $this->mets->xpath('./mets:structMap[@TYPE="PHYSICAL"]/mets:div[@TYPE="physSequence"]/mets:div');
1009
            if (!empty($elementNodes)) {
1010
                // Get file groups.
1011
                $fileUse = $this->_getFileGrps();
1012
                // Get the physical sequence's metadata.
1013
                $physNode = $this->mets->xpath('./mets:structMap[@TYPE="PHYSICAL"]/mets:div[@TYPE="physSequence"]');
1014
                $physSeq[0] = (string) $physNode[0]['ID'];
1015
                $this->physicalStructureInfo[$physSeq[0]]['id'] = (string) $physNode[0]['ID'];
1016
                $this->physicalStructureInfo[$physSeq[0]]['dmdId'] = (isset($physNode[0]['DMDID']) ? (string) $physNode[0]['DMDID'] : '');
1017
                $this->physicalStructureInfo[$physSeq[0]]['admId'] = (isset($physNode[0]['ADMID']) ? (string) $physNode[0]['ADMID'] : '');
1018
                $this->physicalStructureInfo[$physSeq[0]]['order'] = (isset($physNode[0]['ORDER']) ? (string) $physNode[0]['ORDER'] : '');
1019
                $this->physicalStructureInfo[$physSeq[0]]['label'] = (isset($physNode[0]['LABEL']) ? (string) $physNode[0]['LABEL'] : '');
1020
                $this->physicalStructureInfo[$physSeq[0]]['orderlabel'] = (isset($physNode[0]['ORDERLABEL']) ? (string) $physNode[0]['ORDERLABEL'] : '');
1021
                $this->physicalStructureInfo[$physSeq[0]]['type'] = (string) $physNode[0]['TYPE'];
1022
                $this->physicalStructureInfo[$physSeq[0]]['contentIds'] = (isset($physNode[0]['CONTENTIDS']) ? (string) $physNode[0]['CONTENTIDS'] : '');
1023
                // Get the file representations from fileSec node.
1024
                foreach ($physNode[0]->children('http://www.loc.gov/METS/')->fptr as $fptr) {
1025
                    // Check if file has valid @USE attribute.
1026
                    if (!empty($fileUse[(string) $fptr->attributes()->FILEID])) {
1027
                        $this->physicalStructureInfo[$physSeq[0]]['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
1028
                    }
1029
                }
1030
                // Build the physical elements' array from the physical structMap node.
1031
                foreach ($elementNodes as $elementNode) {
1032
                    $elements[(int) $elementNode['ORDER']] = (string) $elementNode['ID'];
1033
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['id'] = (string) $elementNode['ID'];
1034
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['dmdId'] = (isset($elementNode['DMDID']) ? (string) $elementNode['DMDID'] : '');
1035
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['admId'] = (isset($elementNode['ADMID']) ? (string) $elementNode['ADMID'] : '');
1036
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['order'] = (isset($elementNode['ORDER']) ? (string) $elementNode['ORDER'] : '');
1037
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['label'] = (isset($elementNode['LABEL']) ? (string) $elementNode['LABEL'] : '');
1038
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['orderlabel'] = (isset($elementNode['ORDERLABEL']) ? (string) $elementNode['ORDERLABEL'] : '');
1039
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['type'] = (string) $elementNode['TYPE'];
1040
                    $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['contentIds'] = (isset($elementNode['CONTENTIDS']) ? (string) $elementNode['CONTENTIDS'] : '');
1041
                    // Get the file representations from fileSec node.
1042
                    foreach ($elementNode->children('http://www.loc.gov/METS/')->fptr as $fptr) {
1043
                        // Check if file has valid @USE attribute.
1044
                        if (!empty($fileUse[(string) $fptr->attributes()->FILEID])) {
1045
                            $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
1046
                        }
1047
                    }
1048
                }
1049
                // Sort array by keys (= @ORDER).
1050
                if (ksort($elements)) {
1051
                    // Set total number of pages/tracks.
1052
                    $this->numPages = count($elements);
1053
                    // Merge and re-index the array to get nice numeric indexes.
1054
                    $this->physicalStructure = array_merge($physSeq, $elements);
1055
                }
1056
            }
1057
            $this->physicalStructureLoaded = true;
1058
        }
1059
        return $this->physicalStructure;
1060
    }
1061
1062
    /**
1063
     * {@inheritDoc}
1064
     * @see AbstractDocument::_getSmLinks()
1065
     */
1066
    protected function _getSmLinks()
1067
    {
1068
        if (!$this->smLinksLoaded) {
1069
            $smLinks = $this->mets->xpath('./mets:structLink/mets:smLink');
1070
            if (!empty($smLinks)) {
1071
                foreach ($smLinks as $smLink) {
1072
                    $this->smLinks['l2p'][(string) $smLink->attributes('http://www.w3.org/1999/xlink')->from][] = (string) $smLink->attributes('http://www.w3.org/1999/xlink')->to;
1073
                    $this->smLinks['p2l'][(string) $smLink->attributes('http://www.w3.org/1999/xlink')->to][] = (string) $smLink->attributes('http://www.w3.org/1999/xlink')->from;
1074
                }
1075
            }
1076
            $this->smLinksLoaded = true;
1077
        }
1078
        return $this->smLinks;
1079
    }
1080
1081
    /**
1082
     * {@inheritDoc}
1083
     * @see AbstractDocument::_getThumbnail()
1084
     */
1085
    protected function _getThumbnail($forceReload = false)
1086
    {
1087
        if (
1088
            !$this->thumbnailLoaded
1089
            || $forceReload
1090
        ) {
1091
            // Retain current PID.
1092
            $cPid = ($this->cPid ? $this->cPid : $this->pid);
1093
            if (!$cPid) {
1094
                $this->logger->error('Invalid PID ' . $cPid . ' for structure definitions');
1095
                $this->thumbnailLoaded = true;
1096
                return $this->thumbnail;
1097
            }
1098
            // Load extension configuration.
1099
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
1100
            if (empty($extConf['fileGrpThumbs'])) {
1101
                $this->logger->warning('No fileGrp for thumbnails specified');
1102
                $this->thumbnailLoaded = true;
1103
                return $this->thumbnail;
1104
            }
1105
            $strctId = $this->_getToplevelId();
1106
            $metadata = $this->getTitledata($cPid);
1107
1108
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1109
                ->getQueryBuilderForTable('tx_dlf_structures');
1110
1111
            // Get structure element to get thumbnail from.
1112
            $result = $queryBuilder
1113
                ->select('tx_dlf_structures.thumbnail AS thumbnail')
1114
                ->from('tx_dlf_structures')
1115
                ->where(
1116
                    $queryBuilder->expr()->eq('tx_dlf_structures.pid', intval($cPid)),
1117
                    $queryBuilder->expr()->eq('tx_dlf_structures.index_name', $queryBuilder->expr()->literal($metadata['type'][0])),
1118
                    Helper::whereExpression('tx_dlf_structures')
1119
                )
1120
                ->setMaxResults(1)
1121
                ->execute();
1122
1123
            $allResults = $result->fetchAll();
1124
1125
            if (count($allResults) == 1) {
1126
                $resArray = $allResults[0];
1127
                // Get desired thumbnail structure if not the toplevel structure itself.
1128
                if (!empty($resArray['thumbnail'])) {
1129
                    $strctType = Helper::getIndexNameFromUid($resArray['thumbnail'], 'tx_dlf_structures', $cPid);
1130
                    // Check if this document has a structure element of the desired type.
1131
                    $strctIds = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@TYPE="' . $strctType . '"]/@ID');
1132
                    if (!empty($strctIds)) {
1133
                        $strctId = (string) $strctIds[0];
1134
                    }
1135
                }
1136
                // Load smLinks.
1137
                $this->_getSmLinks();
1138
                // Get thumbnail location.
1139
                $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
1140
                while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
1141
                    if (
1142
                        $this->_getPhysicalStructure()
1143
                        && !empty($this->smLinks['l2p'][$strctId])
1144
                        && !empty($this->physicalStructureInfo[$this->smLinks['l2p'][$strctId][0]]['files'][$fileGrpThumb])
1145
                    ) {
1146
                        $this->thumbnail = $this->getFileLocation($this->physicalStructureInfo[$this->smLinks['l2p'][$strctId][0]]['files'][$fileGrpThumb]);
1147
                        break;
1148
                    } elseif (!empty($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb])) {
1149
                        $this->thumbnail = $this->getFileLocation($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb]);
1150
                        break;
1151
                    }
1152
                }
1153
            } else {
1154
                $this->logger->error('No structure of type "' . $metadata['type'][0] . '" found in database');
1155
            }
1156
            $this->thumbnailLoaded = true;
1157
        }
1158
        return $this->thumbnail;
1159
    }
1160
1161
    /**
1162
     * {@inheritDoc}
1163
     * @see AbstractDocument::_getToplevelId()
1164
     */
1165
    protected function _getToplevelId()
1166
    {
1167
        if (empty($this->toplevelId)) {
1168
            // Get all logical structure nodes with metadata, but without associated METS-Pointers.
1169
            $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@DMDID and not(./mets:mptr)]');
1170
            if (!empty($divs)) {
1171
                // Load smLinks.
1172
                $this->_getSmLinks();
1173
                foreach ($divs as $div) {
1174
                    $id = (string) $div['ID'];
1175
                    // Are there physical structure nodes for this logical structure?
1176
                    if (array_key_exists($id, $this->smLinks['l2p'])) {
1177
                        // Yes. That's what we're looking for.
1178
                        $this->toplevelId = $id;
1179
                        break;
1180
                    } elseif (empty($this->toplevelId)) {
1181
                        // No. Remember this anyway, but keep looking for a better one.
1182
                        $this->toplevelId = $id;
1183
                    }
1184
                }
1185
            }
1186
        }
1187
        return $this->toplevelId;
1188
    }
1189
1190
    /**
1191
     * Try to determine URL of parent document.
1192
     *
1193
     * @return string
1194
     */
1195
    public function _getParentHref()
1196
    {
1197
        if ($this->parentHref === null) {
1198
            $this->parentHref = '';
0 ignored issues
show
Bug introduced by
The property parentHref is declared read-only in Kitodo\Dlf\Common\MetsDocument.
Loading history...
1199
1200
            // Get the closest ancestor of the current document which has a MPTR child.
1201
            $parentMptr = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $this->toplevelId . '"]/ancestor::mets:div[./mets:mptr][1]/mets:mptr');
1202
            if (!empty($parentMptr)) {
1203
                $this->parentHref = (string) $parentMptr[0]->attributes('http://www.w3.org/1999/xlink')->href;
1204
            }
1205
        }
1206
1207
        return $this->parentHref;
1208
    }
1209
1210
    /**
1211
     * This magic method is executed prior to any serialization of the object
1212
     * @see __wakeup()
1213
     *
1214
     * @access public
1215
     *
1216
     * @return array Properties to be serialized
1217
     */
1218
    public function __sleep()
1219
    {
1220
        // \SimpleXMLElement objects can't be serialized, thus save the XML as string for serialization
1221
        $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...
1222
        return ['uid', 'pid', 'recordId', 'parentId', 'asXML'];
1223
    }
1224
1225
    /**
1226
     * This magic method is used for setting a string value for the object
1227
     *
1228
     * @access public
1229
     *
1230
     * @return string String representing the METS object
1231
     */
1232
    public function __toString()
1233
    {
1234
        $xml = new \DOMDocument('1.0', 'utf-8');
1235
        $xml->appendChild($xml->importNode(dom_import_simplexml($this->mets), true));
1236
        $xml->formatOutput = true;
1237
        return $xml->saveXML();
1238
    }
1239
1240
    /**
1241
     * This magic method is executed after the object is deserialized
1242
     * @see __sleep()
1243
     *
1244
     * @access public
1245
     *
1246
     * @return void
1247
     */
1248
    public function __wakeup()
1249
    {
1250
        $xml = Helper::getXmlFileAsString($this->asXML);
1251
        if ($xml !== false) {
1252
            $this->asXML = '';
1253
            $this->xml = $xml;
1254
            // Rebuild the unserializable properties.
1255
            $this->init('');
1256
        } else {
1257
            $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
1258
            $this->logger->error('Could not load XML after deserialization');
1259
        }
1260
    }
1261
}
1262