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 (#796)
by
unknown
09:00
created

MetsDocument::processMdSec()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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

373
            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...
374
                $details['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
375
            }
376
        }
377
        // Keep for later usage.
378
        $this->logicalUnits[$details['id']] = $details;
379
        // Walk the structure recursively? And are there any children of the current element?
380
        if (
381
            $recursive
382
            && count($structure->children('http://www.loc.gov/METS/')->div)
383
        ) {
384
            $details['children'] = [];
385
            foreach ($structure->children('http://www.loc.gov/METS/')->div as $child) {
386
                // Repeat for all children.
387
                $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

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

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

919
                            $this->fileGrps[(string) $file->/** @scrutinizer ignore-call */ attributes()->ID] = (string) $fileGrp['USE'];

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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