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.
Completed
Push — master ( c23e92...610884 )
by Sebastian
19s queued 15s
created

MetsDocument::_getParentHref()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

394
            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...
395
                $details['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
396
            }
397
        }
398
        // Keep for later usage.
399
        $this->logicalUnits[$details['id']] = $details;
400
        // Walk the structure recursively? And are there any children of the current element?
401
        if (
402
            $recursive
403
            && count($structure->children('http://www.loc.gov/METS/')->div)
404
        ) {
405
            $details['children'] = [];
406
            foreach ($structure->children('http://www.loc.gov/METS/')->div as $child) {
407
                // Repeat for all children.
408
                $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

408
                $details['children'][] = $this->getLogicalStructureInfo(/** @scrutinizer ignore-type */ $child, true);
Loading history...
409
            }
410
        }
411
        return $details;
412
    }
413
414
    /**
415
     * {@inheritDoc}
416
     * @see \Kitodo\Dlf\Common\Doc::getMetadata()
417
     */
418
    public function getMetadata($id, $cPid = 0)
419
    {
420
        // Make sure $cPid is a non-negative integer.
421
        $cPid = max(intval($cPid), 0);
422
        // If $cPid is not given, try to get it elsewhere.
423
        if (
424
            !$cPid
425
            && ($this->cPid || $this->pid)
426
        ) {
427
            // Retain current PID.
428
            $cPid = ($this->cPid ? $this->cPid : $this->pid);
429
        } elseif (!$cPid) {
430
            $this->logger->warning('Invalid PID ' . $cPid . ' for metadata definitions');
431
            return [];
432
        }
433
        // Get metadata from parsed metadata array if available.
434
        if (
435
            !empty($this->metadataArray[$id])
436
            && $this->metadataArray[0] == $cPid
437
        ) {
438
            return $this->metadataArray[$id];
439
        }
440
        // Initialize metadata array with empty values.
441
        $metadata = [
442
            'title' => [],
443
            'title_sorting' => [],
444
            'author' => [],
445
            'place' => [],
446
            'year' => [],
447
            'prod_id' => [],
448
            'record_id' => [],
449
            'opac_id' => [],
450
            'union_id' => [],
451
            'urn' => [],
452
            'purl' => [],
453
            'type' => [],
454
            'volume' => [],
455
            'volume_sorting' => [],
456
            'license' => [],
457
            'terms' => [],
458
            'restrictions' => [],
459
            'out_of_print' => [],
460
            'rights_info' => [],
461
            'collection' => [],
462
            'owner' => [],
463
            'mets_label' => [],
464
            'mets_orderlabel' => [],
465
            'document_format' => ['METS'],
466
        ];
467
        $mdIds = $this->getMetadataIds($id);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $mdIds is correct as $this->getMetadataIds($id) targeting Kitodo\Dlf\Common\MetsDocument::getMetadataIds() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
468
        if (empty($mdIds)) {
469
            // There is no metadata section for this structure node.
470
            return [];
471
        }
472
        // Associative array used as set of available section types (dmdSec, techMD, ...)
473
        $hasMetadataSection = [];
474
        // Load available metadata formats and metadata sections.
475
        $this->loadFormats();
476
        $this->_getMdSec();
477
        // Get the structure's type.
478
        if (!empty($this->logicalUnits[$id])) {
479
            $metadata['type'] = [$this->logicalUnits[$id]['type']];
480
        } else {
481
            $struct = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]/@TYPE');
482
            if (!empty($struct)) {
483
                $metadata['type'] = [(string) $struct[0]];
484
            }
485
        }
486
        foreach ($mdIds as $dmdId) {
0 ignored issues
show
Bug introduced by
The expression $mdIds of type void is not traversable.
Loading history...
487
            $mdSectionType = $this->mdSec[$dmdId]['section'];
488
489
            // To preserve behavior of previous Kitodo versions, extract metadata only from first supported dmdSec
490
            // However, we want to extract, for example, all techMD sections (VIDEOMD, AUDIOMD)
491
            if ($mdSectionType === 'dmdSec' && isset($hasMetadataSection['dmdSec'])) {
492
                continue;
493
            }
494
495
            // Is this metadata format supported?
496
            if (!empty($this->formats[$this->mdSec[$dmdId]['type']])) {
497
                if (!empty($this->formats[$this->mdSec[$dmdId]['type']]['class'])) {
498
                    $class = $this->formats[$this->mdSec[$dmdId]['type']]['class'];
499
                    // Get the metadata from class.
500
                    if (
501
                        class_exists($class)
502
                        && ($obj = GeneralUtility::makeInstance($class)) instanceof MetadataInterface
503
                    ) {
504
                        $obj->extractMetadata($this->mdSec[$dmdId]['xml'], $metadata);
505
                    } else {
506
                        $this->logger->warning('Invalid class/method "' . $class . '->extractMetadata()" for metadata format "' . $this->mdSec[$dmdId]['type'] . '"');
507
                    }
508
                }
509
            } else {
510
                $this->logger->notice('Unsupported metadata format "' . $this->mdSec[$dmdId]['type'] . '" in ' . $mdSectionType . ' with @ID "' . $dmdId . '"');
511
                // Continue searching for supported metadata with next @DMDID.
512
                continue;
513
            }
514
            // Get the additional metadata from database.
515
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
516
                ->getQueryBuilderForTable('tx_dlf_metadata');
517
            // Get hidden records, too.
518
            $queryBuilder
519
                ->getRestrictions()
520
                ->removeByType(HiddenRestriction::class);
521
            // Get all metadata with configured xpath and applicable format first.
522
            $resultWithFormat = $queryBuilder
523
                ->select(
524
                    'tx_dlf_metadata.index_name AS index_name',
525
                    'tx_dlf_metadataformat_joins.xpath AS xpath',
526
                    'tx_dlf_metadataformat_joins.xpath_sorting AS xpath_sorting',
527
                    'tx_dlf_metadata.is_sortable AS is_sortable',
528
                    'tx_dlf_metadata.default_value AS default_value',
529
                    'tx_dlf_metadata.format AS format'
530
                )
531
                ->from('tx_dlf_metadata')
532
                ->innerJoin(
533
                    'tx_dlf_metadata',
534
                    'tx_dlf_metadataformat',
535
                    'tx_dlf_metadataformat_joins',
536
                    $queryBuilder->expr()->eq(
537
                        'tx_dlf_metadataformat_joins.parent_id',
538
                        'tx_dlf_metadata.uid'
539
                    )
540
                )
541
                ->innerJoin(
542
                    'tx_dlf_metadataformat_joins',
543
                    'tx_dlf_formats',
544
                    'tx_dlf_formats_joins',
545
                    $queryBuilder->expr()->eq(
546
                        'tx_dlf_formats_joins.uid',
547
                        'tx_dlf_metadataformat_joins.encoded'
548
                    )
549
                )
550
                ->where(
551
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
552
                    $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
553
                    $queryBuilder->expr()->eq('tx_dlf_metadataformat_joins.pid', intval($cPid)),
554
                    $queryBuilder->expr()->eq('tx_dlf_formats_joins.type', $queryBuilder->createNamedParameter($this->mdSec[$dmdId]['type']))
555
                )
556
                ->execute();
557
            // Get all metadata without a format, but with a default value next.
558
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
559
                ->getQueryBuilderForTable('tx_dlf_metadata');
560
            // Get hidden records, too.
561
            $queryBuilder
562
                ->getRestrictions()
563
                ->removeByType(HiddenRestriction::class);
564
            $resultWithoutFormat = $queryBuilder
565
                ->select(
566
                    'tx_dlf_metadata.index_name AS index_name',
567
                    'tx_dlf_metadata.is_sortable AS is_sortable',
568
                    'tx_dlf_metadata.default_value AS default_value',
569
                    'tx_dlf_metadata.format AS format'
570
                )
571
                ->from('tx_dlf_metadata')
572
                ->where(
573
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
574
                    $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
575
                    $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0),
576
                    $queryBuilder->expr()->neq('tx_dlf_metadata.default_value', $queryBuilder->createNamedParameter(''))
577
                )
578
                ->execute();
579
            // Merge both result sets.
580
            $allResults = array_merge($resultWithFormat->fetchAll(), $resultWithoutFormat->fetchAll());
581
            // We need a \DOMDocument here, because SimpleXML doesn't support XPath functions properly.
582
            $domNode = dom_import_simplexml($this->mdSec[$dmdId]['xml']);
583
            $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

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

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