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 (#650)
by
unknown
04:20 queued 01:49
created

Document::_getReady()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
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\Document;
14
15
use Kitodo\Dlf\Common\Helper;
16
use Kitodo\Dlf\Common\Indexer;
17
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
18
use TYPO3\CMS\Core\Database\ConnectionPool;
19
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
20
use TYPO3\CMS\Core\Log\LogManager;
21
use TYPO3\CMS\Core\Utility\GeneralUtility;
22
use TYPO3\CMS\Core\Utility\MathUtility;
23
use Ubl\Iiif\Presentation\Common\Model\Resources\IiifResourceInterface;
24
use Ubl\Iiif\Tools\IiifHelper;
25
26
/**
27
 * Document class for the 'dlf' extension
28
 *
29
 * @author Sebastian Meyer <[email protected]>
30
 * @author Henrik Lochmann <[email protected]>
31
 * @package TYPO3
32
 * @subpackage dlf
33
 * @access public
34
 * @property int $cPid This holds the PID for the configuration
35
 * @property-read string $location This holds the documents location
36
 * @property-read array $metadataArray This holds the documents' parsed metadata array
37
 * @property-read int $numPages The holds the total number of pages
38
 * @property-read int $parentId This holds the UID of the parent document or zero if not multi-volumed
39
 * @property-read array $physicalStructure This holds the physical structure
40
 * @property-read array $physicalStructureInfo This holds the physical structure metadata
41
 * @property-read int $pid This holds the PID of the document or zero if not in database
42
 * @property-read bool $ready Is the document instantiated successfully?
43
 * @property-read string $recordId The METS file's / IIIF manifest's record identifier
44
 * @property-read int $rootId This holds the UID of the root document or zero if not multi-volumed
45
 * @property-read array $smLinks This holds the smLinks between logical and physical structMap
46
 * @property-read array $tableOfContents This holds the logical structure
47
 * @property-read string $thumbnail This holds the document's thumbnail location
48
 * @property-read string $toplevelId This holds the toplevel structure's @ID (METS) or the manifest's @id (IIIF)
49
 * @property-read mixed $uid This holds the UID or the URL of the document
50
 * @abstract
51
 */
52
abstract class Document
53
{
54
    /**
55
     * This holds the logger
56
     *
57
     * @var LogManager
58
     * @access protected
59
     */
60
    protected $logger;
61
62
    /**
63
     * This holds the PID for the configuration
64
     *
65
     * @var int
66
     * @access protected
67
     */
68
    protected $cPid = 0;
69
70
    /**
71
     * The extension key
72
     *
73
     * @var string
74
     * @access public
75
     */
76
    public static $extKey = 'dlf';
77
78
    /**
79
     * This holds the configuration for all supported metadata encodings
80
     * @see loadFormats()
81
     *
82
     * @var array
83
     * @access protected
84
     */
85
    protected $formats = [
86
        'OAI' => [
87
            'rootElement' => 'OAI-PMH',
88
            'namespaceURI' => 'http://www.openarchives.org/OAI/2.0/',
89
        ],
90
        'METS' => [
91
            'rootElement' => 'mets',
92
            'namespaceURI' => 'http://www.loc.gov/METS/',
93
        ],
94
        'XLINK' => [
95
            'rootElement' => 'xlink',
96
            'namespaceURI' => 'http://www.w3.org/1999/xlink',
97
        ]
98
    ];
99
100
    /**
101
     * Are the available metadata formats loaded?
102
     * @see $formats
103
     *
104
     * @var bool
105
     * @access protected
106
     */
107
    protected $formatsLoaded = false;
108
109
    /**
110
     * Last searched logical and physical page
111
     *
112
     * @var array
113
     * @access protected
114
     */
115
    protected $lastSearchedPhysicalPage = ['logicalPage' => null, 'physicalPage' => null];
116
117
    /**
118
     * This holds the documents location
119
     *
120
     * @var string
121
     * @access protected
122
     */
123
    protected $location = '';
124
125
    /**
126
     * This holds the logical units
127
     *
128
     * @var array
129
     * @access protected
130
     */
131
    protected $logicalUnits = [];
132
133
    /**
134
     * This holds the documents' parsed metadata array with their corresponding
135
     * structMap//div's ID (METS) or Range / Manifest / Sequence ID (IIIF) as array key
136
     *
137
     * @var array
138
     * @access protected
139
     */
140
    protected $metadataArray = [];
141
142
    /**
143
     * Is the metadata array loaded?
144
     * @see $metadataArray
145
     *
146
     * @var bool
147
     * @access protected
148
     */
149
    protected $metadataArrayLoaded = false;
150
151
    /**
152
     * The holds the total number of pages
153
     *
154
     * @var int
155
     * @access protected
156
     */
157
    protected $numPages = 0;
158
159
    /**
160
     * This holds the UID of the parent document or zero if not multi-volumed
161
     *
162
     * @var int
163
     * @access protected
164
     */
165
    protected $parentId = 0;
166
167
    /**
168
     * This holds the physical structure
169
     *
170
     * @var array
171
     * @access protected
172
     */
173
    protected $physicalStructure = [];
174
175
    /**
176
     * This holds the physical structure metadata
177
     *
178
     * @var array
179
     * @access protected
180
     */
181
    protected $physicalStructureInfo = [];
182
183
    /**
184
     * Is the physical structure loaded?
185
     * @see $physicalStructure
186
     *
187
     * @var bool
188
     * @access protected
189
     */
190
    protected $physicalStructureLoaded = false;
191
192
    /**
193
     * This holds the PID of the document or zero if not in database
194
     *
195
     * @var int
196
     * @access protected
197
     */
198
    protected $pid = 0;
199
200
    /**
201
     * Is the document instantiated successfully?
202
     *
203
     * @var bool
204
     * @access protected
205
     */
206
    protected $ready = false;
207
208
    /**
209
     * The METS file's / IIIF manifest's record identifier
210
     *
211
     * @var string
212
     * @access protected
213
     */
214
    protected $recordId;
215
216
    /**
217
     * This holds the singleton object of the document
218
     *
219
     * @var array (\Kitodo\Dlf\Common\Document\Document)
220
     * @static
221
     * @access protected
222
     */
223
    protected static $registry = [];
224
225
    /**
226
     * This holds the UID of the root document or zero if not multi-volumed
227
     *
228
     * @var int
229
     * @access protected
230
     */
231
    protected $rootId = 0;
232
233
    /**
234
     * Is the root id loaded?
235
     * @see $rootId
236
     *
237
     * @var bool
238
     * @access protected
239
     */
240
    protected $rootIdLoaded = false;
241
242
    /**
243
     * This holds the smLinks between logical and physical structMap
244
     *
245
     * @var array
246
     * @access protected
247
     */
248
    protected $smLinks = ['l2p' => [], 'p2l' => []];
249
250
    /**
251
     * Are the smLinks loaded?
252
     * @see $smLinks
253
     *
254
     * @var bool
255
     * @access protected
256
     */
257
    protected $smLinksLoaded = false;
258
259
    /**
260
     * This holds the logical structure
261
     *
262
     * @var array
263
     * @access protected
264
     */
265
    protected $tableOfContents = [];
266
267
    /**
268
     * Is the table of contents loaded?
269
     * @see $tableOfContents
270
     *
271
     * @var bool
272
     * @access protected
273
     */
274
    protected $tableOfContentsLoaded = false;
275
276
    /**
277
     * This holds the document's thumbnail location
278
     *
279
     * @var string
280
     * @access protected
281
     */
282
    protected $thumbnail = '';
283
284
    /**
285
     * Is the document's thumbnail location loaded?
286
     * @see $thumbnail
287
     *
288
     * @var bool
289
     * @access protected
290
     */
291
    protected $thumbnailLoaded = false;
292
293
    /**
294
     * This holds the toplevel structure's @ID (METS) or the manifest's @id (IIIF)
295
     *
296
     * @var string
297
     * @access protected
298
     */
299
    protected $toplevelId = '';
300
301
    /**
302
     * This holds the UID or the URL of the document
303
     *
304
     * @var mixed
305
     * @access protected
306
     */
307
    protected $uid = 0;
308
309
    /**
310
     * This holds the whole XML file as \SimpleXMLElement object
311
     *
312
     * @var \SimpleXMLElement
313
     * @access protected
314
     */
315
    protected $xml;
316
317
    /**
318
     * This clears the static registry to prevent memory exhaustion
319
     *
320
     * @access public
321
     *
322
     * @static
323
     *
324
     * @return void
325
     */
326
    public static function clearRegistry()
327
    {
328
        // Reset registry array.
329
        self::$registry = [];
330
    }
331
332
    /**
333
     * This is a singleton class, thus an instance must be created by this method
334
     *
335
     * @access public
336
     *
337
     * @static
338
     *
339
     * @param mixed $uid: The unique identifier of the document to parse, the URL of XML file or the IRI of the IIIF resource
340
     * @param int $pid: If > 0, then only document with this PID gets loaded
341
     * @param bool $forceReload: Force reloading the document instead of returning the cached instance
342
     *
343
     * @return \Kitodo\Dlf\Common\Document\Document Instance of this class, either MetsDocument or IiifManifest
344
     */
345
    public static function &getInstance($uid, $pid = 0, $forceReload = false)
346
    {
347
        // Sanitize input.
348
        $pid = max(intval($pid), 0);
349
        if (!$forceReload) {
350
            $regObj = Helper::digest($uid);
351
            if (
352
                is_object(self::$registry[$regObj])
353
                && self::$registry[$regObj] instanceof self
354
            ) {
355
                // Check if instance has given PID.
356
                if (
357
                    !$pid
358
                    || !self::$registry[$regObj]->pid
359
                    || $pid == self::$registry[$regObj]->pid
360
                ) {
361
                    // Return singleton instance if available.
362
                    return self::$registry[$regObj];
363
                }
364
            } else {
365
                // Check the user's session...
366
                $sessionData = Helper::loadFromSession(get_called_class());
367
                if (
368
                    is_object($sessionData[$regObj])
369
                    && $sessionData[$regObj] instanceof self
370
                ) {
371
                    // Check if instance has given PID.
372
                    if (
373
                        !$pid
374
                        || !$sessionData[$regObj]->pid
375
                        || $pid == $sessionData[$regObj]->pid
376
                    ) {
377
                        // ...and restore registry.
378
                        self::$registry[$regObj] = $sessionData[$regObj];
379
                        return self::$registry[$regObj];
380
                    }
381
                }
382
            }
383
        }
384
        // Create new instance depending on format (METS or IIIF) ...
385
        $instance = null;
386
        $documentFormat = null;
387
        $xml = null;
388
        $iiif = null;
389
        // Try to get document format from database
390
        if (MathUtility::canBeInterpretedAsInteger($uid)) {
391
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
392
                ->getQueryBuilderForTable('tx_dlf_documents');
393
394
            $queryBuilder
395
                ->select(
396
                    'tx_dlf_documents.location AS location',
397
                    'tx_dlf_documents.document_format AS document_format'
398
                )
399
                ->from('tx_dlf_documents');
400
401
            // Get UID of document with given record identifier.
402
            if ($pid) {
403
                $queryBuilder
404
                    ->where(
405
                        $queryBuilder->expr()->eq('tx_dlf_documents.uid', intval($uid)),
406
                        $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($pid)),
407
                        Helper::whereExpression('tx_dlf_documents')
408
                    );
409
            } else {
410
                $queryBuilder
411
                    ->where(
412
                        $queryBuilder->expr()->eq('tx_dlf_documents.uid', intval($uid)),
413
                        Helper::whereExpression('tx_dlf_documents')
414
                    );
415
            }
416
417
            $result = $queryBuilder
418
                ->setMaxResults(1)
419
                ->execute();
420
421
            if ($resArray = $result->fetch()) {
422
                $documentFormat = $resArray['document_format'];
423
            }
424
        } else {
425
            // Get document format from content of remote document
426
            // Cast to string for safety reasons.
427
            $location = (string) $uid;
428
            // Try to load a file from the url
429
            if (GeneralUtility::isValidUrl($location)) {
430
                // Load extension configuration
431
                $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
432
                // Set user-agent to identify self when fetching XML data.
433
                if (!empty($extConf['useragent'])) {
434
                    @ini_set('user_agent', $extConf['useragent']);
435
                }
436
                $content = GeneralUtility::getUrl($location);
437
                if ($content !== false) {
438
                    // TODO use single place to load xml
439
                    // Turn off libxml's error logging.
440
                    $libxmlErrors = libxml_use_internal_errors(true);
441
                    // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
442
                    $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
443
                    // Try to load XML from file.
444
                    $xml = simplexml_load_string($content);
445
                    // reset entity loader setting
446
                    libxml_disable_entity_loader($previousValueOfEntityLoader);
447
                    // Reset libxml's error logging.
448
                    libxml_use_internal_errors($libxmlErrors);
449
                    if ($xml !== false) {
450
                        /* @var $xml \SimpleXMLElement */
451
                        $xml->registerXPathNamespace('mets', 'http://www.loc.gov/METS/');
452
                        $xpathResult = $xml->xpath('//mets:mets');
453
                        $documentFormat = !empty($xpathResult) ? 'METS' : null;
454
                    } else {
455
                        // Try to load file as IIIF resource instead.
456
                        $contentAsJsonArray = json_decode($content, true);
457
                        if ($contentAsJsonArray !== null) {
458
                            // Load plugin configuration.
459
                            $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
460
                            IiifHelper::setUrlReader(IiifUrlReader::getInstance());
0 ignored issues
show
Bug introduced by
The type Kitodo\Dlf\Common\Document\IiifUrlReader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
461
                            IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']);
462
                            IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']);
463
                            $iiif = IiifHelper::loadIiifResource($contentAsJsonArray);
464
                            if ($iiif instanceof IiifResourceInterface) {
465
                                $documentFormat = 'IIIF';
466
                            }
467
                        }
468
                    }
469
                }
470
            }
471
        }
472
        // Sanitize input.
473
        $pid = max(intval($pid), 0);
474
        if ($documentFormat == 'METS') {
475
            $instance = new MetsDocument($uid, $pid, $xml);
476
        } elseif ($documentFormat == 'IIIF') {
477
            $instance = new IiifManifest($uid, $pid, $iiif);
478
        }
479
        // Save instance to registry.
480
        if (
481
            $instance instanceof self
482
            && $instance->ready) {
483
            self::$registry[Helper::digest($instance->uid)] = $instance;
484
            if ($instance->uid != $instance->location) {
485
                self::$registry[Helper::digest($instance->location)] = $instance;
486
            }
487
            // Load extension configuration
488
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
489
            // Save registry to session if caching is enabled.
490
            if (!empty($extConf['caching'])) {
491
                Helper::saveToSession(self::$registry, get_class($instance));
492
            }
493
            $instance->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(get_class($instance));
494
        }
495
        // Return new instance.
496
        return $instance;
497
    }
498
499
    /**
500
     * Source document PHP object which is represented by a Document instance
501
     *
502
     * @access protected
503
     *
504
     * @abstract
505
     *
506
     * @return \SimpleXMLElement|IiifResourceInterface An PHP object representation of
507
     * the current document. SimpleXMLElement for METS, IiifResourceInterface for IIIF
508
     */
509
    protected abstract function getDocument();
510
511
    /**
512
     * This extracts all the metadata for a logical structure node
513
     *
514
     * @access public
515
     *
516
     * @abstract
517
     *
518
     * @param string $id: The @ID attribute of the logical structure node (METS) or the @id property
519
     * of the Manifest / Range (IIIF)
520
     * @param int $cPid: The PID for the metadata definitions
521
     *                       (defaults to $this->cPid or $this->pid)
522
     *
523
     * @return array The logical structure node's / the IIIF resource's parsed metadata array
524
     */
525
    public abstract function getMetadata($id, $cPid = 0);
526
527
    /**
528
     * This gets the location of a downloadable file for a physical page or track
529
     *
530
     * @access public
531
     *
532
     * @abstract
533
     *
534
     * @param string $id: The @ID attribute of the file node (METS) or the @id property of the IIIF resource
535
     *
536
     * @return string    The file's location as URL
537
     */
538
    public abstract function getDownloadLocation($id);
539
540
    /**
541
     * This gets the location of a file representing a physical page or track
542
     *
543
     * @access public
544
     *
545
     * @abstract
546
     *
547
     * @param string $id: The @ID attribute of the file node (METS) or the @id property of the IIIF resource
548
     *
549
     * @return string The file's location as URL
550
     */
551
    public abstract function getFileLocation($id);
552
553
    /**
554
     * This gets the MIME type of a file representing a physical page or track
555
     *
556
     * @access public
557
     *
558
     * @abstract
559
     *
560
     * @param string $id: The @ID attribute of the file node
561
     *
562
     * @return string The file's MIME type
563
     */
564
    public abstract function getFileMimeType($id);
565
566
    /**
567
     * This gets details about a logical structure element
568
     *
569
     * @access public
570
     *
571
     * @abstract
572
     *
573
     * @param string $id: The @ID attribute of the logical structure node (METS) or
574
     * the @id property of the Manifest / Range (IIIF)
575
     * @param bool $recursive: Whether to include the child elements / resources
576
     *
577
     * @return array Array of the element's id, label, type and physical page indexes/mptr link
578
     */
579
    public abstract function getLogicalStructure($id, $recursive = false);
580
581
    /**
582
     * This returns the first corresponding physical page number of a given logical page label
583
     *
584
     * @access public
585
     *
586
     * @param string $logicalPage: The label (or a part of the label) of the logical page
587
     *
588
     * @return int The physical page number
589
     */
590
    public function getPhysicalPage($logicalPage)
591
    {
592
        if (
593
            !empty($this->lastSearchedPhysicalPage['logicalPage'])
594
            && $this->lastSearchedPhysicalPage['logicalPage'] == $logicalPage
595
        ) {
596
            return $this->lastSearchedPhysicalPage['physicalPage'];
597
        } else {
598
            $physicalPage = 0;
599
            foreach ($this->physicalStructureInfo as $page) {
600
                if (strpos($page['orderlabel'], $logicalPage) !== false) {
601
                    $this->lastSearchedPhysicalPage['logicalPage'] = $logicalPage;
602
                    $this->lastSearchedPhysicalPage['physicalPage'] = $physicalPage;
603
                    return $physicalPage;
604
                }
605
                $physicalPage++;
606
            }
607
        }
608
        return 1;
609
    }
610
611
    /**
612
     * This determines a title for the given document
613
     *
614
     * @access public
615
     *
616
     * @static
617
     *
618
     * @param int $uid: The UID of the document
619
     * @param bool $recursive: Search superior documents for a title, too?
620
     *
621
     * @return string The title of the document itself or a parent document
622
     */
623
    public static function getTitle($uid, $recursive = false)
624
    {
625
        $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
626
627
        $title = '';
628
        // Sanitize input.
629
        $uid = max(intval($uid), 0);
630
        if ($uid) {
631
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
632
                ->getQueryBuilderForTable('tx_dlf_documents');
633
634
            $result = $queryBuilder
635
                ->select(
636
                    'tx_dlf_documents.title',
637
                    'tx_dlf_documents.partof'
638
                )
639
                ->from('tx_dlf_documents')
640
                ->where(
641
                    $queryBuilder->expr()->eq('tx_dlf_documents.uid', $uid),
642
                    Helper::whereExpression('tx_dlf_documents')
643
                )
644
                ->setMaxResults(1)
645
                ->execute();
646
647
            if ($resArray = $result->fetch()) {
648
                // Get title information.
649
                $title = $resArray['title'];
650
                $partof = $resArray['partof'];
651
                // Search parent documents recursively for a title?
652
                if (
653
                    $recursive
654
                    && empty($title)
655
                    && intval($partof)
656
                    && $partof != $uid
657
                ) {
658
                    $title = self::getTitle($partof, true);
659
                }
660
            } else {
661
                $logger->warning('No document with UID ' . $uid . ' found or document not accessible');
662
            }
663
        } else {
664
            $logger->error('Invalid UID ' . $uid . ' for document');
665
        }
666
        return $title;
667
    }
668
669
    /**
670
     * This extracts all the metadata for the toplevel logical structure node / resource
671
     *
672
     * @access public
673
     *
674
     * @param int $cPid: The PID for the metadata definitions
675
     *
676
     * @return array The logical structure node's / resource's parsed metadata array
677
     */
678
    public function getTitleData($cPid = 0)
679
    {
680
        $titledata = $this->getMetadata($this->_getToplevelId(), $cPid);
681
        // Add information from METS structural map to titledata array.
682
        if ($this instanceof MetsDocument) {
683
            $this->addMetadataFromMets($titledata, $this->_getToplevelId());
684
        }
685
        // Set record identifier for METS file / IIIF manifest if not present.
686
        if (
687
            is_array($titledata)
688
            && array_key_exists('record_id', $titledata)
689
        ) {
690
            if (
691
                !empty($this->recordId)
692
                && !in_array($this->recordId, $titledata['record_id'])
693
            ) {
694
                array_unshift($titledata['record_id'], $this->recordId);
695
            }
696
        }
697
        return $titledata;
698
    }
699
700
    /**
701
     * Get the tree depth of a logical structure element within the table of content
702
     *
703
     * @access public
704
     *
705
     * @param string $logId: The id of the logical structure element whose depth is requested
706
     * @return int|bool tree depth as integer or false if no element with $logId exists within the TOC.
707
     */
708
    public function getStructureDepth($logId)
709
    {
710
        return $this->getTreeDepth($this->_getTableOfContents(), 1, $logId);
711
    }
712
713
    /**
714
     * Register all available namespaces for a \SimpleXMLElement object
715
     *
716
     * @access public
717
     *
718
     * @param \SimpleXMLElement|\DOMXPath &$obj: \SimpleXMLElement or \DOMXPath object
719
     *
720
     * @return void
721
     */
722
    public function registerNamespaces(&$obj)
723
    {
724
        // TODO Check usage. XML specific method does not seem to be used anywhere outside this class within the project, but it is public and may be used by extensions.
725
        $this->loadFormats();
726
        // Do we have a \SimpleXMLElement or \DOMXPath object?
727
        if ($obj instanceof \SimpleXMLElement) {
728
            $method = 'registerXPathNamespace';
729
        } elseif ($obj instanceof \DOMXPath) {
730
            $method = 'registerNamespace';
731
        } else {
732
            $this->logger->error('Given object is neither a SimpleXMLElement nor a DOMXPath instance');
1 ignored issue
show
Bug introduced by
The method error() does not exist on TYPO3\CMS\Core\Log\LogManager. ( Ignorable by Annotation )

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

732
            $this->logger->/** @scrutinizer ignore-call */ 
733
                           error('Given object is neither a SimpleXMLElement nor a DOMXPath instance');

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...
733
            return;
734
        }
735
        // Register metadata format's namespaces.
736
        foreach ($this->formats as $enc => $conf) {
737
            $obj->$method(strtolower($enc), $conf['namespaceURI']);
738
        }
739
    }
740
741
    /**
742
     * This saves the document to the database and index
743
     *
744
     * @access public
745
     *
746
     * @param int $pid: The PID of the saved record
747
     * @param int $core: The UID of the Solr core for indexing
748
     * @param int|string $owner: UID or index_name of owner to set while indexing
749
     *
750
     * @return bool true on success or false on failure
751
     */
752
    public function save($pid = 0, $core = 0, $owner = null)
753
    {
754
        if (\TYPO3_MODE !== 'BE') {
755
            $this->logger->error('Saving a document is only allowed in the backend');
756
            return false;
757
        }
758
        // Make sure $pid is a non-negative integer.
759
        $pid = max(intval($pid), 0);
760
        // Make sure $core is a non-negative integer.
761
        $core = max(intval($core), 0);
762
        // If $pid is not given, try to get it elsewhere.
763
        if (
764
            !$pid
765
            && $this->pid
766
        ) {
767
            // Retain current PID.
768
            $pid = $this->pid;
769
        } elseif (!$pid) {
770
            $this->logger->error('Invalid PID ' . $pid . ' for document saving');
771
            return false;
772
        }
773
        // Set PID for metadata definitions.
774
        $this->cPid = $pid;
775
        // Set UID placeholder if not updating existing record.
776
        if ($pid != $this->pid) {
777
            $this->uid = uniqid('NEW');
0 ignored issues
show
Bug introduced by
The property uid is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
778
        }
779
        // Get metadata array.
780
        $metadata = $this->getTitleData($pid);
781
        // Check for record identifier.
782
        if (empty($metadata['record_id'][0])) {
783
            $this->logger->error('No record identifier found to avoid duplication');
784
            return false;
785
        }
786
        // Load plugin configuration.
787
        $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
788
789
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
790
            ->getQueryBuilderForTable('tx_dlf_structures');
791
792
        // Get UID for structure type.
793
        $result = $queryBuilder
794
            ->select('tx_dlf_structures.uid AS uid')
795
            ->from('tx_dlf_structures')
796
            ->where(
797
                $queryBuilder->expr()->eq('tx_dlf_structures.pid', intval($pid)),
798
                $queryBuilder->expr()->eq('tx_dlf_structures.index_name', $queryBuilder->expr()->literal($metadata['type'][0])),
799
                Helper::whereExpression('tx_dlf_structures')
800
            )
801
            ->setMaxResults(1)
802
            ->execute();
803
804
        if ($resArray = $result->fetch()) {
805
            $structure = $resArray['uid'];
806
        } else {
807
            $this->logger->error('Could not identify document/structure type "' . $queryBuilder->expr()->literal($metadata['type'][0]) . '"');
808
            return false;
809
        }
810
        $metadata['type'][0] = $structure;
811
812
        // Remove appended "valueURI" from authors' names for storing in database.
813
        foreach ($metadata['author'] as $i => $author) {
814
            $splitName = explode(chr(31), $author);
815
            $metadata['author'][$i] = $splitName[0];
816
        }
817
818
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
819
            ->getQueryBuilderForTable('tx_dlf_collections');
820
        // Get hidden records, too.
821
        $queryBuilder
822
            ->getRestrictions()
823
            ->removeByType(HiddenRestriction::class);
824
825
        // Get UIDs for collections.
826
        $result = $queryBuilder
827
            ->select(
828
                'tx_dlf_collections.index_name AS index_name',
829
                'tx_dlf_collections.uid AS uid'
830
            )
831
            ->from('tx_dlf_collections')
832
            ->where(
833
                $queryBuilder->expr()->eq('tx_dlf_collections.pid', intval($pid)),
834
                $queryBuilder->expr()->in('tx_dlf_collections.sys_language_uid', [-1, 0])
835
            )
836
            ->execute();
837
838
        $collUid = [];
839
        while ($resArray = $result->fetch()) {
840
            $collUid[$resArray['index_name']] = $resArray['uid'];
841
        }
842
        $collections = [];
843
        foreach ($metadata['collection'] as $collection) {
844
            if (!empty($collUid[$collection])) {
845
                // Add existing collection's UID.
846
                $collections[] = $collUid[$collection];
847
            } else {
848
                // Insert new collection.
849
                $collNewUid = uniqid('NEW');
850
                $collData['tx_dlf_collections'][$collNewUid] = [
851
                    'pid' => $pid,
852
                    'label' => $collection,
853
                    'index_name' => $collection,
854
                    'oai_name' => (!empty($conf['publishNewCollections']) ? Helper::getCleanString($collection) : ''),
855
                    'description' => '',
856
                    'documents' => 0,
857
                    'owner' => 0,
858
                    'status' => 0,
859
                ];
860
                $substUid = Helper::processDBasAdmin($collData);
861
                // Prevent double insertion.
862
                unset($collData);
863
                // Add new collection's UID.
864
                $collections[] = $substUid[$collNewUid];
865
                if (!(\TYPO3_REQUESTTYPE & \TYPO3_REQUESTTYPE_CLI)) {
866
                    Helper::addMessage(
867
                        htmlspecialchars(sprintf(Helper::getMessage('flash.newCollection'), $collection, $substUid[$collNewUid])),
868
                        Helper::getMessage('flash.attention', true),
869
                        \TYPO3\CMS\Core\Messaging\FlashMessage::INFO,
870
                        true
871
                    );
872
                }
873
            }
874
        }
875
        $metadata['collection'] = $collections;
876
877
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
878
            ->getQueryBuilderForTable('tx_dlf_libraries');
879
880
        // Get UID for owner.
881
        if (empty($owner)) {
882
            $owner = empty($metadata['owner'][0]) ? $metadata['owner'][0] : 'default';
883
        }
884
        if (!MathUtility::canBeInterpretedAsInteger($owner)) {
885
            $result = $queryBuilder
886
                ->select('tx_dlf_libraries.uid AS uid')
887
                ->from('tx_dlf_libraries')
888
                ->where(
889
                    $queryBuilder->expr()->eq('tx_dlf_libraries.pid', intval($pid)),
890
                    $queryBuilder->expr()->eq('tx_dlf_libraries.index_name', $queryBuilder->expr()->literal($owner)),
891
                    Helper::whereExpression('tx_dlf_libraries')
892
                )
893
                ->setMaxResults(1)
894
                ->execute();
895
896
            if ($resArray = $result->fetch()) {
897
                $ownerUid = $resArray['uid'];
898
            } else {
899
                // Insert new library.
900
                $libNewUid = uniqid('NEW');
901
                $libData['tx_dlf_libraries'][$libNewUid] = [
902
                    'pid' => $pid,
903
                    'label' => $owner,
904
                    'index_name' => $owner,
905
                    'website' => '',
906
                    'contact' => '',
907
                    'image' => '',
908
                    'oai_label' => '',
909
                    'oai_base' => '',
910
                    'opac_label' => '',
911
                    'opac_base' => '',
912
                    'union_label' => '',
913
                    'union_base' => '',
914
                ];
915
                $substUid = Helper::processDBasAdmin($libData);
916
                // Add new library's UID.
917
                $ownerUid = $substUid[$libNewUid];
918
                if (!(\TYPO3_REQUESTTYPE & \TYPO3_REQUESTTYPE_CLI)) {
919
                    Helper::addMessage(
920
                        htmlspecialchars(sprintf(Helper::getMessage('flash.newLibrary'), $owner, $ownerUid)),
921
                        Helper::getMessage('flash.attention', true),
922
                        \TYPO3\CMS\Core\Messaging\FlashMessage::INFO,
923
                        true
924
                    );
925
                }
926
            }
927
            $owner = $ownerUid;
928
        }
929
        $metadata['owner'][0] = $owner;
930
        // Get UID of parent document.
931
        $partof = $this->getParentDocumentUidForSaving($pid, $core, $owner);
932
        // Use the date of publication or title as alternative sorting metric for parts of multi-part works.
933
        if (!empty($partof)) {
934
            if (
935
                empty($metadata['volume'][0])
936
                && !empty($metadata['year'][0])
937
            ) {
938
                $metadata['volume'] = $metadata['year'];
939
            }
940
            if (empty($metadata['volume_sorting'][0])) {
941
                // If METS @ORDER is given it is preferred over year_sorting and year.
942
                if (!empty($metadata['mets_order'][0])) {
943
                    $metadata['volume_sorting'][0] = $metadata['mets_order'][0];
944
                } elseif (!empty($metadata['year_sorting'][0])) {
945
                    $metadata['volume_sorting'][0] = $metadata['year_sorting'][0];
946
                } elseif (!empty($metadata['year'][0])) {
947
                    $metadata['volume_sorting'][0] = $metadata['year'][0];
948
                }
949
            }
950
            // If volume_sorting is still empty, try to use title_sorting or METS @ORDERLABEL finally (workaround for newspapers)
951
            if (empty($metadata['volume_sorting'][0])) {
952
                if (!empty($metadata['title_sorting'][0])) {
953
                    $metadata['volume_sorting'][0] = $metadata['title_sorting'][0];
954
                } elseif (!empty($metadata['mets_orderlabel'][0])) {
955
                    $metadata['volume_sorting'][0] = $metadata['mets_orderlabel'][0];
956
                }
957
            }
958
        }
959
960
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
961
            ->getQueryBuilderForTable('tx_dlf_metadata');
962
963
        // Get metadata for lists and sorting.
964
        $result = $queryBuilder
965
            ->select(
966
                'tx_dlf_metadata.index_name AS index_name',
967
                'tx_dlf_metadata.is_listed AS is_listed',
968
                'tx_dlf_metadata.is_sortable AS is_sortable'
969
            )
970
            ->from('tx_dlf_metadata')
971
            ->where(
972
                $queryBuilder->expr()->orX(
973
                    $queryBuilder->expr()->eq('tx_dlf_metadata.is_listed', 1),
974
                    $queryBuilder->expr()->eq('tx_dlf_metadata.is_sortable', 1)
975
                ),
976
                $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($pid)),
977
                Helper::whereExpression('tx_dlf_metadata')
978
            )
979
            ->execute();
980
981
        $listed = [];
982
        $sortable = [];
983
984
        while ($resArray = $result->fetch()) {
985
            if (!empty($metadata[$resArray['index_name']])) {
986
                if ($resArray['is_listed']) {
987
                    $listed[$resArray['index_name']] = $metadata[$resArray['index_name']];
988
                }
989
                if ($resArray['is_sortable']) {
990
                    $sortable[$resArray['index_name']] = $metadata[$resArray['index_name']][0];
991
                }
992
            }
993
        }
994
        // Fill data array.
995
        $data['tx_dlf_documents'][$this->uid] = [
996
            'pid' => $pid,
997
            $GLOBALS['TCA']['tx_dlf_documents']['ctrl']['enablecolumns']['starttime'] => 0,
998
            $GLOBALS['TCA']['tx_dlf_documents']['ctrl']['enablecolumns']['endtime'] => 0,
999
            'prod_id' => $metadata['prod_id'][0],
1000
            'location' => $this->location,
1001
            'record_id' => $metadata['record_id'][0],
1002
            'opac_id' => $metadata['opac_id'][0],
1003
            'union_id' => $metadata['union_id'][0],
1004
            'urn' => $metadata['urn'][0],
1005
            'purl' => $metadata['purl'][0],
1006
            'title' => $metadata['title'][0],
1007
            'title_sorting' => $metadata['title_sorting'][0],
1008
            'author' => implode('; ', $metadata['author']),
1009
            'year' => implode('; ', $metadata['year']),
1010
            'place' => implode('; ', $metadata['place']),
1011
            'thumbnail' => $this->_getThumbnail(true),
1012
            'metadata' => serialize($listed),
1013
            'metadata_sorting' => serialize($sortable),
1014
            'structure' => $metadata['type'][0],
1015
            'partof' => $partof,
1016
            'volume' => $metadata['volume'][0],
1017
            'volume_sorting' => $metadata['volume_sorting'][0],
1018
            'license' => $metadata['license'][0],
1019
            'terms' => $metadata['terms'][0],
1020
            'restrictions' => $metadata['restrictions'][0],
1021
            'out_of_print' => $metadata['out_of_print'][0],
1022
            'rights_info' => $metadata['rights_info'][0],
1023
            'collections' => $metadata['collection'],
1024
            'mets_label' => $metadata['mets_label'][0],
1025
            'mets_orderlabel' => $metadata['mets_orderlabel'][0],
1026
            'mets_order' => $metadata['mets_order'][0],
1027
            'owner' => $metadata['owner'][0],
1028
            'solrcore' => $core,
1029
            'status' => 0,
1030
            'document_format' => $metadata['document_format'][0],
1031
        ];
1032
        // Unhide hidden documents.
1033
        if (!empty($conf['unhideOnIndex'])) {
1034
            $data['tx_dlf_documents'][$this->uid][$GLOBALS['TCA']['tx_dlf_documents']['ctrl']['enablecolumns']['disabled']] = 0;
1035
        }
1036
        // Process data.
1037
        $newIds = Helper::processDBasAdmin($data);
1038
        // Replace placeholder with actual UID.
1039
        if (strpos($this->uid, 'NEW') === 0) {
1040
            $this->uid = $newIds[$this->uid];
1041
            $this->pid = $pid;
0 ignored issues
show
Bug introduced by
The property pid is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1042
            $this->parentId = $partof;
0 ignored issues
show
Bug introduced by
The property parentId is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1043
        }
1044
        if (!(\TYPO3_REQUESTTYPE & \TYPO3_REQUESTTYPE_CLI)) {
1045
            Helper::addMessage(
1046
                htmlspecialchars(sprintf(Helper::getMessage('flash.documentSaved'), $metadata['title'][0], $this->uid)),
1047
                Helper::getMessage('flash.done', true),
1048
                \TYPO3\CMS\Core\Messaging\FlashMessage::OK,
1049
                true
1050
            );
1051
        }
1052
        // Add document to index.
1053
        if ($core) {
1054
            return Indexer::add($this, $core);
1055
        } else {
1056
            $this->logger->notice('Invalid UID "' . $core . '" for Solr core');
1 ignored issue
show
Bug introduced by
The method notice() does not exist on TYPO3\CMS\Core\Log\LogManager. ( Ignorable by Annotation )

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

1056
            $this->logger->/** @scrutinizer ignore-call */ 
1057
                           notice('Invalid UID "' . $core . '" for Solr core');

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...
1057
            return false;
1058
        }
1059
    }
1060
1061
    /**
1062
     * This ensures that the recordId, if existent, is retrieved from the document
1063
     *
1064
     * @access protected
1065
     *
1066
     * @abstract
1067
     *
1068
     * @param int $pid: ID of the configuration page with the recordId config
1069
     *
1070
     */
1071
    protected abstract function establishRecordId($pid);
1072
1073
    /**
1074
     * Get the ID of the parent document if the current document has one. Also save a parent document
1075
     * to the database and the Solr index if their $pid and the current $pid differ.
1076
     * Currently only applies to METS documents.
1077
     *
1078
     * @access protected
1079
     *
1080
     * @abstract
1081
     *
1082
     * @return int The parent document's id.
1083
     */
1084
    protected abstract function getParentDocumentUidForSaving($pid, $core, $owner);
1085
1086
    /**
1087
     * This sets some basic class properties
1088
     *
1089
     * @access protected
1090
     *
1091
     * @abstract
1092
     *
1093
     * @return void
1094
     */
1095
    protected abstract function init();
1096
1097
    /**
1098
     * METS/IIIF specific part of loading a location
1099
     *
1100
     * @access protected
1101
     *
1102
     * @abstract
1103
     *
1104
     * @param string $location: The URL of the file to load
1105
     *
1106
     * @return bool true on success or false on failure
1107
     */
1108
    protected abstract function loadLocation($location);
1109
1110
    /**
1111
     * Reuse any document object that might have been already loaded to determine wether document is METS or IIIF
1112
     *
1113
     * @access protected
1114
     *
1115
     * @abstract
1116
     *
1117
     * @param \SimpleXMLElement|IiifResourceInterface $preloadedDocument: any instance that has already been loaded
1118
     *
1119
     * @return bool true if $preloadedDocument can actually be reused, false if it has to be loaded again
1120
     */
1121
    protected abstract function setPreloadedDocument($preloadedDocument);
1122
1123
    /**
1124
     * Load XML file / IIIF resource from URL
1125
     *
1126
     * @access protected
1127
     *
1128
     * @param string $location: The URL of the file to load
1129
     *
1130
     * @return bool true on success or false on failure
1131
     */
1132
    protected function load($location)
1133
    {
1134
        // Load XML / JSON-LD file.
1135
        if (GeneralUtility::isValidUrl($location)) {
1136
            // Load extension configuration
1137
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
1138
            // Set user-agent to identify self when fetching XML / JSON-LD data.
1139
            if (!empty($extConf['useragent'])) {
1140
                @ini_set('user_agent', $extConf['useragent']);
1141
            }
1142
            // the actual loading is format specific
1143
            return $this->loadLocation($location);
1144
        } else {
1145
            $this->logger->error('Invalid file location "' . $location . '" for document loading');
1146
        }
1147
        return false;
1148
    }
1149
1150
    /**
1151
     * Register all available data formats
1152
     *
1153
     * @access protected
1154
     *
1155
     * @return void
1156
     */
1157
    protected function loadFormats()
1158
    {
1159
        if (!$this->formatsLoaded) {
1160
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1161
                ->getQueryBuilderForTable('tx_dlf_formats');
1162
1163
            // Get available data formats from database.
1164
            $result = $queryBuilder
1165
                ->select(
1166
                    'tx_dlf_formats.type AS type',
1167
                    'tx_dlf_formats.root AS root',
1168
                    'tx_dlf_formats.namespace AS namespace',
1169
                    'tx_dlf_formats.class AS class'
1170
                )
1171
                ->from('tx_dlf_formats')
1172
                ->where(
1173
                    $queryBuilder->expr()->eq('tx_dlf_formats.pid', 0)
1174
                )
1175
                ->execute();
1176
1177
            while ($resArray = $result->fetch()) {
1178
                // Update format registry.
1179
                $this->formats[$resArray['type']] = [
1180
                    'rootElement' => $resArray['root'],
1181
                    'namespaceURI' => $resArray['namespace'],
1182
                    'class' => $resArray['class']
1183
                ];
1184
            }
1185
            $this->formatsLoaded = true;
1186
        }
1187
    }
1188
1189
    /**
1190
     * Traverse a logical (sub-) structure tree to find the structure with the requested logical id and return it's depth.
1191
     *
1192
     * @access protected
1193
     *
1194
     * @param array $structure: logical structure array
1195
     * @param int $depth: current tree depth
1196
     * @param string $logId: ID of the logical structure whose depth is requested
1197
     *
1198
     * @return int|bool: false if structure with $logId is not a child of this substructure,
1199
     * or the actual depth.
1200
     */
1201
    protected function getTreeDepth($structure, $depth, $logId)
1202
    {
1203
        foreach ($structure as $element) {
1204
            if ($element['id'] == $logId) {
1205
                return $depth;
1206
            } elseif (array_key_exists('children', $element)) {
1207
                $foundInChildren = $this->getTreeDepth($element['children'], $depth + 1, $logId);
1208
                if ($foundInChildren !== false) {
1209
                    return $foundInChildren;
1210
                }
1211
            }
1212
        }
1213
        return false;
1214
    }
1215
1216
    /**
1217
     * This returns $this->cPid via __get()
1218
     *
1219
     * @access protected
1220
     *
1221
     * @return int The PID of the metadata definitions
1222
     */
1223
    protected function _getCPid()
1224
    {
1225
        return $this->cPid;
1226
    }
1227
1228
    /**
1229
     * This returns $this->location via __get()
1230
     *
1231
     * @access protected
1232
     *
1233
     * @return string The location of the document
1234
     */
1235
    protected function _getLocation()
1236
    {
1237
        return $this->location;
1238
    }
1239
1240
    /**
1241
     * Format specific part of building the document's metadata array
1242
     *
1243
     * @access protected
1244
     *
1245
     * @abstract
1246
     *
1247
     * @param int $cPid
1248
     */
1249
    protected abstract function prepareMetadataArray($cPid);
1250
1251
    /**
1252
     * This builds an array of the document's metadata
1253
     *
1254
     * @access protected
1255
     *
1256
     * @return array Array of metadata with their corresponding logical structure node ID as key
1257
     */
1258
    protected function _getMetadataArray()
1259
    {
1260
        // Set metadata definitions' PID.
1261
        $cPid = ($this->cPid ? $this->cPid : $this->pid);
1262
        if (!$cPid) {
1263
            $this->logger->error('Invalid PID ' . $cPid . ' for metadata definitions');
1264
            return [];
1265
        }
1266
        if (
1267
            !$this->metadataArrayLoaded
1268
            || $this->metadataArray[0] != $cPid
1269
        ) {
1270
            $this->prepareMetadataArray($cPid);
1271
            $this->metadataArray[0] = $cPid;
0 ignored issues
show
Bug introduced by
The property metadataArray is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1272
            $this->metadataArrayLoaded = true;
1273
        }
1274
        return $this->metadataArray;
1275
    }
1276
1277
    /**
1278
     * This returns $this->numPages via __get()
1279
     *
1280
     * @access protected
1281
     *
1282
     * @return int The total number of pages and/or tracks
1283
     */
1284
    protected function _getNumPages()
1285
    {
1286
        $this->_getPhysicalStructure();
1287
        return $this->numPages;
1288
    }
1289
1290
    /**
1291
     * This returns $this->parentId via __get()
1292
     *
1293
     * @access protected
1294
     *
1295
     * @return int The UID of the parent document or zero if not applicable
1296
     */
1297
    protected function _getParentId()
1298
    {
1299
        return $this->parentId;
1300
    }
1301
1302
    /**
1303
     * This builds an array of the document's physical structure
1304
     *
1305
     * @access protected
1306
     *
1307
     * @abstract
1308
     *
1309
     * @return array Array of physical elements' id, type, label and file representations ordered
1310
     * by @ORDER attribute / IIIF Sequence's Canvases
1311
     */
1312
    protected abstract function _getPhysicalStructure();
1313
1314
    /**
1315
     * This gives an array of the document's physical structure metadata
1316
     *
1317
     * @access protected
1318
     *
1319
     * @return array Array of elements' type, label and file representations ordered by @ID attribute / Canvas order
1320
     */
1321
    protected function _getPhysicalStructureInfo()
1322
    {
1323
        // Is there no physical structure array yet?
1324
        if (!$this->physicalStructureLoaded) {
1325
            // Build physical structure array.
1326
            $this->_getPhysicalStructure();
1327
        }
1328
        return $this->physicalStructureInfo;
1329
    }
1330
1331
    /**
1332
     * This returns $this->pid via __get()
1333
     *
1334
     * @access protected
1335
     *
1336
     * @return int The PID of the document or zero if not in database
1337
     */
1338
    protected function _getPid()
1339
    {
1340
        return $this->pid;
1341
    }
1342
1343
    /**
1344
     * This returns $this->ready via __get()
1345
     *
1346
     * @access protected
1347
     *
1348
     * @return bool Is the document instantiated successfully?
1349
     */
1350
    protected function _getReady()
1351
    {
1352
        return $this->ready;
1353
    }
1354
1355
    /**
1356
     * This returns $this->recordId via __get()
1357
     *
1358
     * @access protected
1359
     *
1360
     * @return mixed The METS file's / IIIF manifest's record identifier
1361
     */
1362
    protected function _getRecordId()
1363
    {
1364
        return $this->recordId;
1365
    }
1366
1367
    /**
1368
     * This returns $this->rootId via __get()
1369
     *
1370
     * @access protected
1371
     *
1372
     * @return int The UID of the root document or zero if not applicable
1373
     */
1374
    protected function _getRootId()
1375
    {
1376
        if (!$this->rootIdLoaded) {
1377
            if ($this->parentId) {
1378
                $parent = self::getInstance($this->parentId, $this->pid);
1379
                $this->rootId = $parent->rootId;
0 ignored issues
show
Bug introduced by
The property rootId is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1380
            }
1381
            $this->rootIdLoaded = true;
1382
        }
1383
        return $this->rootId;
1384
    }
1385
1386
    /**
1387
     * This returns the smLinks between logical and physical structMap (METS) and models the
1388
     * relation between IIIF Canvases and Manifests / Ranges in the same way
1389
     *
1390
     * @access protected
1391
     *
1392
     * @abstract
1393
     *
1394
     * @return array The links between logical and physical nodes / Range, Manifest and Canvas
1395
     */
1396
    protected abstract function _getSmLinks();
1397
1398
    /**
1399
     * This builds an array of the document's logical structure
1400
     *
1401
     * @access protected
1402
     *
1403
     * @return array Array of structure nodes' id, label, type and physical page indexes/mptr / Canvas link with original hierarchy preserved
1404
     */
1405
    protected function _getTableOfContents()
1406
    {
1407
        // Is there no logical structure array yet?
1408
        if (!$this->tableOfContentsLoaded) {
1409
            // Get all logical structures.
1410
            $this->getLogicalStructure('', true);
1411
            $this->tableOfContentsLoaded = true;
1412
        }
1413
        return $this->tableOfContents;
1414
    }
1415
1416
    /**
1417
     * This returns the document's thumbnail location
1418
     *
1419
     * @access protected
1420
     *
1421
     * @abstract
1422
     *
1423
     * @param bool $forceReload: Force reloading the thumbnail instead of returning the cached value
1424
     *
1425
     * @return string The document's thumbnail location
1426
     */
1427
    protected abstract function _getThumbnail($forceReload = false);
1428
1429
    /**
1430
     * This returns the ID of the toplevel logical structure node
1431
     *
1432
     * @access protected
1433
     *
1434
     * @abstract
1435
     *
1436
     * @return string The logical structure node's ID
1437
     */
1438
    protected abstract function _getToplevelId();
1439
1440
    /**
1441
     * This returns $this->uid via __get()
1442
     *
1443
     * @access protected
1444
     *
1445
     * @return mixed The UID or the URL of the document
1446
     */
1447
    protected function _getUid()
1448
    {
1449
        return $this->uid;
1450
    }
1451
1452
    /**
1453
     * This sets $this->cPid via __set()
1454
     *
1455
     * @access protected
1456
     *
1457
     * @param int $value: The new PID for the metadata definitions
1458
     *
1459
     * @return void
1460
     */
1461
    protected function _setCPid($value)
1462
    {
1463
        $this->cPid = max(intval($value), 0);
1464
    }
1465
1466
    /**
1467
     * This magic method is invoked each time a clone is called on the object variable
1468
     *
1469
     * @access protected
1470
     *
1471
     * @return void
1472
     */
1473
    protected function __clone()
1474
    {
1475
        // This method is defined as protected because singleton objects should not be cloned.
1476
    }
1477
1478
    /**
1479
     * This is a singleton class, thus the constructor should be private/protected
1480
     * (Get an instance of this class by calling \Kitodo\Dlf\Common\Document\Document::getInstance())
1481
     *
1482
     * @access protected
1483
     *
1484
     * @param int $uid: The UID of the document to parse or URL to XML file
1485
     * @param int $pid: If > 0, then only document with this PID gets loaded
1486
     * @param \SimpleXMLElement|IiifResourceInterface $preloadedDocument: Either null or the \SimpleXMLElement
1487
     * or IiifResourceInterface that has been loaded to determine the basic document format.
1488
     *
1489
     * @return void
1490
     */
1491
    protected function __construct($uid, $pid, $preloadedDocument)
1492
    {
1493
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1494
            ->getQueryBuilderForTable('tx_dlf_documents');
1495
        $location = '';
1496
        // Prepare to check database for the requested document.
1497
        if (MathUtility::canBeInterpretedAsInteger($uid)) {
1498
            $whereClause = $queryBuilder->expr()->andX(
1499
                $queryBuilder->expr()->eq('tx_dlf_documents.uid', intval($uid)),
1500
                Helper::whereExpression('tx_dlf_documents')
1501
            );
1502
        } else {
1503
            // Try to load METS file / IIIF manifest.
1504
            if ($this->setPreloadedDocument($preloadedDocument) || (GeneralUtility::isValidUrl($uid)
1505
                && $this->load($uid))) {
1506
                // Initialize core METS object.
1507
                $this->init();
1508
                if ($this->getDocument() !== null) {
1509
                    // Cast to string for safety reasons.
1510
                    $location = (string) $uid;
1511
                    $this->establishRecordId($pid);
1512
                } else {
1513
                    // No METS / IIIF part found.
1514
                    return;
1515
                }
1516
            } else {
1517
                // Loading failed.
1518
                return;
1519
            }
1520
            if (
1521
                !empty($location)
1522
                && !empty($this->recordId)
1523
            ) {
1524
                // Try to match record identifier or location (both should be unique).
1525
                $whereClause = $queryBuilder->expr()->andX(
1526
                    $queryBuilder->expr()->orX(
1527
                        $queryBuilder->expr()->eq('tx_dlf_documents.location', $queryBuilder->expr()->literal($location)),
1528
                        $queryBuilder->expr()->eq('tx_dlf_documents.record_id', $queryBuilder->expr()->literal($this->recordId))
1529
                    ),
1530
                    Helper::whereExpression('tx_dlf_documents')
1531
                );
1532
            } else {
1533
                // Can't persistently identify document, don't try to match at all.
1534
                $whereClause = '1=-1';
1535
            }
1536
        }
1537
        // Check for PID if needed.
1538
        if ($pid) {
1539
            $whereClause = $queryBuilder->expr()->andX(
1540
                $whereClause,
1541
                $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($pid))
1542
            );
1543
        }
1544
        // Get document PID and location from database.
1545
        $result = $queryBuilder
1546
            ->select(
1547
                'tx_dlf_documents.uid AS uid',
1548
                'tx_dlf_documents.pid AS pid',
1549
                'tx_dlf_documents.record_id AS record_id',
1550
                'tx_dlf_documents.partof AS partof',
1551
                'tx_dlf_documents.thumbnail AS thumbnail',
1552
                'tx_dlf_documents.location AS location'
1553
            )
1554
            ->from('tx_dlf_documents')
1555
            ->where($whereClause)
1556
            ->setMaxResults(1)
1557
            ->execute();
1558
1559
        if ($resArray = $result->fetch()) {
1560
            $this->uid = $resArray['uid'];
0 ignored issues
show
Bug introduced by
The property uid is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1561
            $this->pid = $resArray['pid'];
0 ignored issues
show
Bug introduced by
The property pid is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1562
            $this->recordId = $resArray['record_id'];
0 ignored issues
show
Bug introduced by
The property recordId is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1563
            $this->parentId = $resArray['partof'];
0 ignored issues
show
Bug introduced by
The property parentId is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1564
            $this->thumbnail = $resArray['thumbnail'];
0 ignored issues
show
Bug introduced by
The property thumbnail is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1565
            $this->location = $resArray['location'];
0 ignored issues
show
Bug introduced by
The property location is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1566
            $this->thumbnailLoaded = true;
1567
            // Load XML file if necessary...
1568
            if (
1569
                $this->getDocument() === null
1570
                && $this->load($this->location)
1571
            ) {
1572
                // ...and set some basic properties.
1573
                $this->init();
1574
            }
1575
            // Do we have a METS / IIIF object now?
1576
            if ($this->getDocument() !== null) {
1577
                // Set new location if necessary.
1578
                if (!empty($location)) {
1579
                    $this->location = $location;
1580
                }
1581
                // Document ready!
1582
                $this->ready = true;
0 ignored issues
show
Bug introduced by
The property ready is declared read-only in Kitodo\Dlf\Common\Document\Document.
Loading history...
1583
            }
1584
        } elseif ($this->getDocument() !== null) {
1585
            // Set location as UID for documents not in database.
1586
            $this->uid = $location;
1587
            $this->location = $location;
1588
            // Document ready!
1589
            $this->ready = true;
1590
        } else {
1591
            $this->logger->error('No document with UID ' . $uid . ' found or document not accessible');
1592
        }
1593
    }
1594
1595
    /**
1596
     * This magic method is called each time an invisible property is referenced from the object
1597
     *
1598
     * @access public
1599
     *
1600
     * @param string $var: Name of variable to get
1601
     *
1602
     * @return mixed Value of $this->$var
1603
     */
1604
    public function __get($var)
1605
    {
1606
        $method = '_get' . ucfirst($var);
1607
        if (
1608
            !property_exists($this, $var)
1609
            || !method_exists($this, $method)
1610
        ) {
1611
            $this->logger->warning('There is no getter function for property "' . $var . '"');
1 ignored issue
show
Bug introduced by
The method warning() does not exist on TYPO3\CMS\Core\Log\LogManager. ( Ignorable by Annotation )

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

1611
            $this->logger->/** @scrutinizer ignore-call */ 
1612
                           warning('There is no getter function for property "' . $var . '"');

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...
1612
            return;
1613
        } else {
1614
            return $this->$method();
1615
        }
1616
    }
1617
1618
    /**
1619
     * This magic method is called each time an invisible property is checked for isset() or empty()
1620
     *
1621
     * @access public
1622
     *
1623
     * @param string $var: Name of variable to check
1624
     *
1625
     * @return bool true if variable is set and not empty, false otherwise
1626
     */
1627
    public function __isset($var)
1628
    {
1629
        return !empty($this->__get($var));
1630
    }
1631
1632
    /**
1633
     * This magic method is called each time an invisible property is referenced from the object
1634
     *
1635
     * @access public
1636
     *
1637
     * @param string $var: Name of variable to set
1638
     * @param mixed $value: New value of variable
1639
     *
1640
     * @return void
1641
     */
1642
    public function __set($var, $value)
1643
    {
1644
        $method = '_set' . ucfirst($var);
1645
        if (
1646
            !property_exists($this, $var)
1647
            || !method_exists($this, $method)
1648
        ) {
1649
            $this->logger->warning('There is no setter function for property "' . $var . '"');
1650
        } else {
1651
            $this->$method($value);
1652
        }
1653
    }
1654
}
1655