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 ( de1ea3...c23e92 )
by Sebastian
17s queued 15s
created

MetsDocument::getDownloadLocation()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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

384
            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...
385
                $details['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
386
            }
387
        }
388
        // Keep for later usage.
389
        $this->logicalUnits[$details['id']] = $details;
390
        // Walk the structure recursively? And are there any children of the current element?
391
        if (
392
            $recursive
393
            && count($structure->children('http://www.loc.gov/METS/')->div)
394
        ) {
395
            $details['children'] = [];
396
            foreach ($structure->children('http://www.loc.gov/METS/')->div as $child) {
397
                // Repeat for all children.
398
                $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

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

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

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