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
04:18
created

MetsDocument::_getToplevelId()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 23
rs 9.2222
cc 6
nc 4
nop 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
        foreach ($mdIds as $dmdId) {
0 ignored issues
show
Bug introduced by
The expression $mdIds of type void is not traversable.
Loading history...
457
            $mdSectionType = $this->mdSec[$dmdId]['section'];
458
459
            // Extract metadata only from first supported tag of each type
460
            if (isset($hasMetadataSection[$mdSectionType])) {
461
                continue;
462
            }
463
464
            // Is this metadata format supported?
465
            if (!empty($this->formats[$this->mdSec[$dmdId]['type']])) {
466
                if (!empty($this->formats[$this->mdSec[$dmdId]['type']]['class'])) {
467
                    $class = $this->formats[$this->mdSec[$dmdId]['type']]['class'];
468
                    // Get the metadata from class.
469
                    if (
470
                        class_exists($class)
471
                        && ($obj = GeneralUtility::makeInstance($class)) instanceof MetadataInterface
472
                    ) {
473
                        $obj->extractMetadata($this->mdSec[$dmdId]['xml'], $metadata);
474
                    } else {
475
                        $this->logger->warning('Invalid class/method "' . $class . '->extractMetadata()" for metadata format "' . $this->mdSec[$dmdId]['type'] . '"');
476
                    }
477
                }
478
            } else {
479
                $this->logger->notice('Unsupported metadata format "' . $this->mdSec[$dmdId]['type'] . '" in ' . $mdSectionType . ' with @ID "' . $dmdId . '"');
480
                // Continue searching for supported metadata with next @DMDID.
481
                continue;
482
            }
483
            // Get the structure's type.
484
            if (!empty($this->logicalUnits[$id])) {
485
                $metadata['type'] = [$this->logicalUnits[$id]['type']];
486
            } else {
487
                $struct = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]/@TYPE');
488
                if (!empty($struct)) {
489
                    $metadata['type'] = [(string) $struct[0]];
490
                }
491
            }
492
            // Get the additional metadata from database.
493
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
494
                ->getQueryBuilderForTable('tx_dlf_metadata');
495
            // Get hidden records, too.
496
            $queryBuilder
497
                ->getRestrictions()
498
                ->removeByType(HiddenRestriction::class);
499
            // Get all metadata with configured xpath and applicable format first.
500
            $resultWithFormat = $queryBuilder
501
                ->select(
502
                    'tx_dlf_metadata.index_name AS index_name',
503
                    'tx_dlf_metadataformat_joins.xpath AS xpath',
504
                    'tx_dlf_metadataformat_joins.xpath_sorting AS xpath_sorting',
505
                    'tx_dlf_metadata.is_sortable AS is_sortable',
506
                    'tx_dlf_metadata.default_value AS default_value',
507
                    'tx_dlf_metadata.format AS format'
508
                )
509
                ->from('tx_dlf_metadata')
510
                ->innerJoin(
511
                    'tx_dlf_metadata',
512
                    'tx_dlf_metadataformat',
513
                    'tx_dlf_metadataformat_joins',
514
                    $queryBuilder->expr()->eq(
515
                        'tx_dlf_metadataformat_joins.parent_id',
516
                        'tx_dlf_metadata.uid'
517
                    )
518
                )
519
                ->innerJoin(
520
                    'tx_dlf_metadataformat_joins',
521
                    'tx_dlf_formats',
522
                    'tx_dlf_formats_joins',
523
                    $queryBuilder->expr()->eq(
524
                        'tx_dlf_formats_joins.uid',
525
                        'tx_dlf_metadataformat_joins.encoded'
526
                    )
527
                )
528
                ->where(
529
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
530
                    $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
531
                    $queryBuilder->expr()->eq('tx_dlf_metadataformat_joins.pid', intval($cPid)),
532
                    $queryBuilder->expr()->eq('tx_dlf_formats_joins.type', $queryBuilder->createNamedParameter($this->mdSec[$dmdId]['type']))
533
                )
534
                ->execute();
535
            // Get all metadata without a format, but with a default value next.
536
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
537
                ->getQueryBuilderForTable('tx_dlf_metadata');
538
            // Get hidden records, too.
539
            $queryBuilder
540
                ->getRestrictions()
541
                ->removeByType(HiddenRestriction::class);
542
            $resultWithoutFormat = $queryBuilder
543
                ->select(
544
                    'tx_dlf_metadata.index_name AS index_name',
545
                    'tx_dlf_metadata.is_sortable AS is_sortable',
546
                    'tx_dlf_metadata.default_value AS default_value',
547
                    'tx_dlf_metadata.format AS format'
548
                )
549
                ->from('tx_dlf_metadata')
550
                ->where(
551
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
552
                    $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
553
                    $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0),
554
                    $queryBuilder->expr()->neq('tx_dlf_metadata.default_value', $queryBuilder->createNamedParameter(''))
555
                )
556
                ->execute();
557
            // Merge both result sets.
558
            $allResults = array_merge($resultWithFormat->fetchAll(), $resultWithoutFormat->fetchAll());
559
            // We need a \DOMDocument here, because SimpleXML doesn't support XPath functions properly.
560
            $domNode = dom_import_simplexml($this->mdSec[$dmdId]['xml']);
561
            $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

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

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