Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#836)
by Beatrycze
04:08
created

MetsDocument::getLogicalStructureFor3D()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 8
rs 10
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 = $this->getLogicalStructureFor3D($details);
349
        $details['thumbnailId'] = '';
350
        // Load smLinks.
351
        $this->_getSmLinks();
352
        // Load physical structure.
353
        $this->_getPhysicalStructure();
354
        // Get the physical page or external file this structure element is pointing at.
355
        $details['points'] = '';
356
        // Is there a mptr node?
357
        if (count($structure->children('http://www.loc.gov/METS/')->mptr)) {
358
            // Yes. Get the file reference.
359
            $details['points'] = (string) $structure->children('http://www.loc.gov/METS/')->mptr[0]->attributes('http://www.w3.org/1999/xlink')->href;
360
        } elseif (
361
            !empty($this->physicalStructure)
362
            && array_key_exists($details['id'], $this->smLinks['l2p'])
363
        ) {
364
            // Link logical structure to the first corresponding physical page/track.
365
            $details['points'] = max(intval(array_search($this->smLinks['l2p'][$details['id']][0], $this->physicalStructure, true)), 1);
366
            $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
367
            while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
368
                if (!empty($this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['files'][$fileGrpThumb])) {
369
                    $details['thumbnailId'] = $this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['files'][$fileGrpThumb];
370
                    break;
371
                }
372
            }
373
            // Get page/track number of the first page/track related to this structure element.
374
            $details['pagination'] = $this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['orderlabel'];
375
        } elseif ($details['id'] == $this->_getToplevelId()) {
376
            // Point to self if this is the toplevel structure.
377
            $details['points'] = 1;
378
            $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
379
            while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
380
                if (
381
                    !empty($this->physicalStructure)
382
                    && !empty($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb])
383
                ) {
384
                    $details['thumbnailId'] = $this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb];
385
                    break;
386
                }
387
            }
388
        }
389
        // Get the files this structure element is pointing at.
390
        $details['files'] = [];
391
        $fileUse = $this->_getFileGrps();
392
        // Get the file representations from fileSec node.
393
        foreach ($structure->children('http://www.loc.gov/METS/')->fptr as $fptr) {
394
            // Check if file has valid @USE attribute.
395
            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

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

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

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

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