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.

Issues (188)

Classes/Common/IiifManifest.php (8 issues)

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 Flow\JSONPath\JSONPath;
16
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
17
use TYPO3\CMS\Core\Database\ConnectionPool;
18
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
19
use TYPO3\CMS\Core\Log\LogManager;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationContainerInterface;
22
use Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationInterface;
23
use Ubl\Iiif\Presentation\Common\Model\Resources\CanvasInterface;
24
use Ubl\Iiif\Presentation\Common\Model\Resources\ContentResourceInterface;
25
use Ubl\Iiif\Presentation\Common\Model\Resources\IiifResourceInterface;
26
use Ubl\Iiif\Presentation\Common\Model\Resources\ManifestInterface;
27
use Ubl\Iiif\Presentation\Common\Model\Resources\RangeInterface;
28
use Ubl\Iiif\Presentation\Common\Vocabulary\Motivation;
29
use Ubl\Iiif\Presentation\V1\Model\Resources\AbstractIiifResource1;
30
use Ubl\Iiif\Presentation\V2\Model\Resources\AbstractIiifResource2;
31
use Ubl\Iiif\Presentation\V3\Model\Resources\AbstractIiifResource3;
32
use Ubl\Iiif\Services\AbstractImageService;
33
use Ubl\Iiif\Services\Service;
34
use Ubl\Iiif\Tools\IiifHelper;
35
36
/**
37
 * IiifManifest class for the 'dlf' extension.
38
 *
39
 * @package TYPO3
40
 * @subpackage dlf
41
 *
42
 * @access public
43
 *
44
 * @property int $cPid this holds the PID for the configuration
45
 * @property-read array $formats this holds the configuration for all supported metadata encodings
46
 * @property bool $formatsLoaded flag with information if the available metadata formats are loaded
47
 * @property-read bool $hasFulltext flag with information if there are any fulltext files available
48
 * @property array $lastSearchedPhysicalPage the last searched logical and physical page
49
 * @property array $logicalUnits this holds the logical units
50
 * @property-read array $metadataArray this holds the documents' parsed metadata array
51
 * @property bool $metadataArrayLoaded flag with information if the metadata array is loaded
52
 * @property-read int $numPages the holds the total number of pages
53
 * @property-read int $parentId this holds the UID of the parent document or zero if not multi-volumed
54
 * @property-read array $physicalStructure this holds the physical structure
55
 * @property-read array $physicalStructureInfo this holds the physical structure metadata
56
 * @property bool $physicalStructureLoaded flag with information if the physical structure is loaded
57
 * @property-read int $pid this holds the PID of the document or zero if not in database
58
 * @property array $rawTextArray this holds the documents' raw text pages with their corresponding structMap//div's ID (METS) or Range / Manifest / Sequence ID (IIIF) as array key
59
 * @property-read bool $ready Is the document instantiated successfully?
60
 * @property-read string $recordId the METS file's / IIIF manifest's record identifier
61
 * @property array $registry this holds the singleton object of the document
62
 * @property-read int $rootId this holds the UID of the root document or zero if not multi-volumed
63
 * @property-read array $smLinks this holds the smLinks between logical and physical structMap
64
 * @property bool $smLinksLoaded flag with information if the smLinks are loaded
65
 * @property-read array $tableOfContents this holds the logical structure
66
 * @property bool $tableOfContentsLoaded flag with information if the table of contents is loaded
67
 * @property-read string $thumbnail this holds the document's thumbnail location
68
 * @property bool $thumbnailLoaded flag with information if the thumbnail is loaded
69
 * @property-read string $toplevelId this holds the toplevel structure's "@ID" (METS) or the manifest's "@id" (IIIF)
70
 * @property \SimpleXMLElement $xml this holds the whole XML file as \SimpleXMLElement object
71
 * @property string $asJson this holds the manifest file as string for serialization purposes
72
 * @property ManifestInterface $iiif a PHP object representation of a IIIF manifest
73
 * @property string $iiifVersion 'IIIF1', 'IIIF2' or 'IIIF3', depending on the API $this->iiif conforms to
74
 * @property bool $hasFulltextSet flag if document has already been analyzed for presence of the fulltext for the Solr index
75
 * @property array $originalMetadataArray this holds the original manifest's parsed metadata array with their corresponding resource (Manifest / Sequence / Range) ID as array key
76
 * @property array $mimeTypes this holds the mime types of linked resources in the manifest (extracted during parsing) for later us
77
 * 
78
 */
79
final class IiifManifest extends AbstractDocument
80
{
81
    /**
82
     * @access protected
83
     * @var string This holds the manifest file as string for serialization purposes
84
     *
85
     * @see __sleep() / __wakeup()
86
     */
87
    protected string $asJson = '';
88
89
    /**
90
     * @access protected
91
     * @var ManifestInterface|null A PHP object representation of a IIIF manifest
92
     */
93
    protected ?ManifestInterface $iiif;
94
95
    /**
96
     * @access protected
97
     * @var string 'IIIF1', 'IIIF2' or 'IIIF3', depending on the API $this->iiif conforms to: IIIF Metadata API 1, IIIF Presentation API 2 or 3
98
     */
99
    protected string $iiifVersion;
100
101
    /**
102
     * @access protected
103
     * @var bool Document has already been analyzed if it contains fulltext for the Solr index
104
     */
105
    protected bool $hasFulltextSet = false;
106
107
    /**
108
     * @access protected
109
     * @var array This holds the original manifest's parsed metadata array with their corresponding resource (Manifest / Sequence / Range) ID as array key
110
     */
111
    protected array $originalMetadataArray = [];
112
113
    /**
114
     * @access protected
115
     * @var array Holds the mime types of linked resources in the manifest (extracted during parsing) for later use
116
     */
117
    protected array $mimeTypes = [];
118
119
    /**
120
     * @see AbstractDocument::establishRecordId()
121
     */
122
    protected function establishRecordId(int $pid): void
123
    {
124
        if ($this->iiif !== null) {
125
            /*
126
             *  FIXME This will not consistently work because we can not be sure to have the pid at hand. It may miss
127
             *  if the plugin that actually loads the manifest allows content from other pages.
128
             *  Up until now the cPid is only set after the document has been initialized. We need it before to
129
             *  check the configuration.
130
             *  TODO Saving / indexing should still work - check!
131
             */
132
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
133
                ->getQueryBuilderForTable('tx_dlf_metadata');
134
            // Get hidden records, too.
135
            $queryBuilder
136
                ->getRestrictions()
137
                ->removeByType(HiddenRestriction::class);
138
            $result = $queryBuilder
139
                ->select('tx_dlf_metadataformat.xpath AS querypath')
140
                ->from('tx_dlf_metadata')
141
                ->from('tx_dlf_metadataformat')
142
                ->from('tx_dlf_formats')
143
                ->where(
144
                    $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $pid),
145
                    $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', (int) $pid),
146
                    $queryBuilder->expr()->orX(
147
                        $queryBuilder->expr()->andX(
148
                            $queryBuilder->expr()->eq('tx_dlf_metadata.uid', 'tx_dlf_metadataformat.parent_id'),
149
                            $queryBuilder->expr()->eq('tx_dlf_metadataformat.encoded', 'tx_dlf_formats.uid'),
150
                            $queryBuilder->expr()->eq('tx_dlf_metadata.index_name', $queryBuilder->createNamedParameter('record_id')),
151
                            $queryBuilder->expr()->eq('tx_dlf_formats.type', $queryBuilder->createNamedParameter($this->getIiifVersion()))
152
                        ),
153
                        $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0)
154
                    )
155
                )
156
                ->execute();
157
            while ($resArray = $result->fetchAssociative()) {
158
                $recordIdPath = $resArray['querypath'];
159
                if (!empty($recordIdPath)) {
160
                    try {
161
                        $this->recordId = $this->iiif->jsonPath($recordIdPath);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->iiif->jsonPath($recordIdPath) can also be of type Ubl\Iiif\Presentation\Co...urces\ManifestInterface or Ubl\Iiif\Presentation\Co...s\AbstractIiifResource1 or Ubl\Iiif\Presentation\Co...s\AbstractIiifResource2 or Ubl\Iiif\Presentation\Co...s\AbstractIiifResource3. However, the property $recordId 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...
162
                    } catch (\Exception $e) {
163
                        $this->logger->warning('Could not evaluate JSONPath to get IIIF record ID');
164
                    }
165
                }
166
            }
167
            // For now, it's a hardcoded ID, not only as a fallback
168
            if (!isset($this->recordId)) {
169
                $this->recordId = $this->iiif->getId();
170
            }
171
        }
172
    }
173
174
    /**
175
     * @see AbstractDocument::getDocument()
176
     */
177
    protected function getDocument(): IiifResourceInterface
178
    {
179
        return $this->iiif;
180
    }
181
182
    /**
183
     * Returns a string representing the Metadata / Presentation API version which the IIIF resource
184
     * conforms to. This is used for example to extract metadata according to configured patterns.
185
     *
186
     * @access public
187
     *
188
     * @return string 'IIIF1' if the resource is a Metadata API 1 resource, 'IIIF2' / 'IIIF3' if
189
     * the resource is a Presentation API 2 / 3 resource
190
     */
191
    public function getIiifVersion(): string
192
    {
193
        if (!isset($this->iiifVersion)) {
194
            if ($this->iiif instanceof AbstractIiifResource1) {
195
                $this->iiifVersion = 'IIIF1';
196
            } elseif ($this->iiif instanceof AbstractIiifResource2) {
197
                $this->iiifVersion = 'IIIF2';
198
            } elseif ($this->iiif instanceof AbstractIiifResource3) {
199
                $this->iiifVersion = 'IIIF3';
200
            }
201
        }
202
        return $this->iiifVersion;
203
    }
204
205
    /**
206
     * True if getUseGroups() has been called and $this->useGrps is loaded
207
     *
208
     * @var bool
209
     * @access protected
210
     */
211
    protected bool $useGrpsLoaded = false;
212
213
    /**
214
     * Holds the configured useGrps as array.
215
     *
216
     * @var array
217
     * @access protected
218
     */
219
    protected array $useGrps = [];
220
221
    /**
222
     * IiifManifest also populates the physical structure array entries for matching
223
     * 'fileGrp's. To do that, the configuration has to be loaded; afterwards configured
224
     * 'fileGrp's for thumbnails, downloads, audio, fulltext and the 'fileGrp's for images
225
     * can be requested with this method.
226
     *
227
     * @access protected
228
     *
229
     * @param string $use
230
     *
231
     * @return array|string
232
     */
233
    protected function getUseGroups(string $use)
234
    {
235
        if (!$this->useGrpsLoaded) {
236
            // Get configured USE attributes.
237
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
238
            if (!empty($extConf['fileGrpImages'])) {
239
                $this->useGrps['fileGrpImages'] = GeneralUtility::trimExplode(',', $extConf['fileGrpImages']);
240
            }
241
            if (!empty($extConf['fileGrpThumbs'])) {
242
                $this->useGrps['fileGrpThumbs'] = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
243
            }
244
            if (!empty($extConf['fileGrpDownload'])) {
245
                $this->useGrps['fileGrpDownload'] = GeneralUtility::trimExplode(',', $extConf['fileGrpDownload']);
246
            }
247
            if (!empty($extConf['fileGrpFulltext'])) {
248
                $this->useGrps['fileGrpFulltext'] = GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']);
249
            }
250
            if (!empty($extConf['fileGrpAudio'])) {
251
                $this->useGrps['fileGrpAudio'] = GeneralUtility::trimExplode(',', $extConf['fileGrpAudio']);
252
            }
253
            $this->useGrpsLoaded = true;
254
        }
255
        return array_key_exists($use, $this->useGrps) ? $this->useGrps[$use] : [];
256
    }
257
258
    /**
259
     * @see AbstractDocument::magicGetPhysicalStructure()
260
     */
261
    protected function magicGetPhysicalStructure(): array
262
    {
263
        // Is there no physical structure array yet?
264
        if (!$this->physicalStructureLoaded) {
265
            if ($this->iiif == null || !($this->iiif instanceof ManifestInterface)) {
266
                return [];
267
            }
268
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
269
            $iiifId = $this->iiif->getId();
270
            $this->physicalStructureInfo[$iiifId]['id'] = $iiifId;
271
            $this->physicalStructureInfo[$iiifId]['dmdId'] = $iiifId;
272
            $this->physicalStructureInfo[$iiifId]['label'] = $this->iiif->getLabelForDisplay();
273
            $this->physicalStructureInfo[$iiifId]['orderlabel'] = $this->iiif->getLabelForDisplay();
274
            $this->physicalStructureInfo[$iiifId]['type'] = 'physSequence';
275
            $this->physicalStructureInfo[$iiifId]['contentIds'] = null;
276
277
            $this->setFileUseDownload($iiifId, $this->iiif);
278
            $this->setFileUseFulltext($iiifId, $this->iiif);
279
280
            $fileUseThumbs = $this->getUseGroups('fileGrpThumbs');
281
            $fileUses = $this->getUseGroups('fileGrpImages');
282
283
            if (!empty($this->iiif->getDefaultCanvases())) {
284
                // canvases have not order property, but the context defines canveses as @list with a specific order, so we can provide an alternative
285
                $elements = [];
286
                $canvasOrder = 0;
287
                foreach ($this->iiif->getDefaultCanvases() as $canvas) {
288
                    $canvasOrder++;
289
                    $thumbnailUrl = $canvas->getThumbnailUrl();
290
                    // put thumbnails in thumbnail filegroup
291
                    if (
292
                        !empty($thumbnailUrl)
293
                        && empty($this->physicalStructureInfo[$iiifId]['files'][$fileUseThumbs[0]])
294
                    ) {
295
                        $this->physicalStructureInfo[$iiifId]['files'][$fileUseThumbs[0]] = $thumbnailUrl;
296
                    }
297
                    // populate structural metadata info
298
                    $elements[$canvasOrder] = $canvas->getId();
299
                    $this->physicalStructureInfo[$elements[$canvasOrder]]['id'] = $canvas->getId();
300
                    $this->physicalStructureInfo[$elements[$canvasOrder]]['dmdId'] = null;
301
                    $this->physicalStructureInfo[$elements[$canvasOrder]]['label'] = $canvas->getLabelForDisplay();
302
                    $this->physicalStructureInfo[$elements[$canvasOrder]]['orderlabel'] = $canvas->getLabelForDisplay();
303
                    // assume that a canvas always represents a page
304
                    $this->physicalStructureInfo[$elements[$canvasOrder]]['type'] = 'page';
305
                    $this->physicalStructureInfo[$elements[$canvasOrder]]['contentIds'] = null;
306
                    $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'] = null;
307
                    if (!empty($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING))) {
308
                        $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'] = [];
309
                        foreach ($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING) as $annotationContainer) {
310
                            $this->physicalStructureInfo[$elements[$canvasOrder]]['annotationContainers'][] = $annotationContainer->getId();
311
                            if ($extConf['indexAnnotations']) {
312
                                $this->hasFulltext = true;
313
                                $this->hasFulltextSet = true;
314
                            }
315
                        }
316
                    }
317
318
                    $this->setFileUseFulltext($elements[$canvasOrder], $canvas);
319
320
                    if (!empty($fileUses)) {
321
                        $image = $canvas->getImageAnnotations()[0];
322
                        foreach ($fileUses as $fileUse) {
323
                            if ($image->getBody() !== null && $image->getBody() instanceof ContentResourceInterface) {
324
                                $this->physicalStructureInfo[$elements[$canvasOrder]]['files'][$fileUse] = $image->getBody()->getId();
325
                            }
326
                        }
327
                    }
328
                    if (!empty($thumbnailUrl)) {
329
                        $this->physicalStructureInfo[$elements[$canvasOrder]]['files'][$fileUseThumbs] = $thumbnailUrl;
330
                    }
331
332
                    $this->setFileUseDownload($elements[$canvasOrder], $canvas);
333
                }
334
                $this->numPages = $canvasOrder;
335
                // Merge and re-index the array to get nice numeric indexes.
336
                array_unshift($elements, $iiifId);
337
                $this->physicalStructure = $elements;
338
            }
339
            $this->physicalStructureLoaded = true;
340
        }
341
        return $this->physicalStructure;
342
    }
343
344
    /**
345
     * @see AbstractDocument::getDownloadLocation()
346
     */
347
    public function getDownloadLocation(string $id): string
348
    {
349
        $fileLocation = $this->getFileLocation($id);
350
        $resource = $this->iiif->getContainedResourceById($fileLocation);
351
        if ($resource instanceof AbstractImageService) {
352
            return $resource->getImageUrl();
353
        }
354
        return $fileLocation;
355
    }
356
357
    /**
358
     * @see AbstractDocument::getFileInfo()
359
     */
360
    public function getFileInfo($id): ?array
361
    {
362
        if (empty($this->fileInfos[$id]['location'])) {
363
            $this->fileInfos[$id]['location'] = $this->getFileLocation($id);
364
        }
365
366
        if (empty($this->fileInfos[$id]['mimeType'])) {
367
            $this->fileInfos[$id]['mimeType'] = $this->getFileMimeType($id);
368
        }
369
370
        return $this->fileInfos[$id];
371
    }
372
373
    /**
374
     * @see AbstractDocument::getFileLocation()
375
     */
376
    public function getFileLocation(string $id): string
377
    {
378
        if ($id == null) {
379
            return '';
380
        }
381
        $resource = $this->iiif->getContainedResourceById($id);
382
        if (isset($resource)) {
383
            if ($resource instanceof CanvasInterface) {
384
                // TODO: Cannot call method getSingleService() on array<Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationInterface>.
385
                // @phpstan-ignore-next-line
386
                return (!empty($resource->getImageAnnotations()) && $resource->getImageAnnotations()->getSingleService() != null) ? $resource->getImageAnnotations()[0]->getSingleService()->getId() : $id;
387
            } elseif ($resource instanceof ContentResourceInterface) {
388
                return $resource->getSingleService() instanceof Service ? $resource->getSingleService()->getId() : $id;
0 ignored issues
show
$resource->getSingleService() is always a sub-type of Ubl\Iiif\Services\Service.
Loading history...
389
            } elseif ($resource instanceof AbstractImageService) {
390
                return $resource->getId();
391
            } elseif ($resource instanceof AnnotationContainerInterface) {
392
                return $id;
393
            }
394
        }
395
        return $id;
396
    }
397
398
    /**
399
     * @see AbstractDocument::getFileMimeType()
400
     */
401
    public function getFileMimeType(string $id): string
402
    {
403
        $fileResource = $this->iiif->getContainedResourceById($id);
404
        if ($fileResource instanceof CanvasInterface) {
405
            $format = "application/vnd.kitodo.iiif";
406
        } elseif ($fileResource instanceof AnnotationInterface) {
407
            $format = "application/vnd.kitodo.iiif";
408
        } elseif ($fileResource instanceof ContentResourceInterface) {
409
            if ($fileResource->isText() || $fileResource->isImage() && ($fileResource->getSingleService() == null || !($fileResource->getSingleService() instanceof AbstractImageService))) {
410
                // Support static images without an image service
411
                return $fileResource->getFormat();
412
            }
413
            $format = "application/vnd.kitodo.iiif";
414
        } elseif ($fileResource instanceof AbstractImageService) {
415
            $format = "application/vnd.kitodo.iiif";
416
        } else {
417
            // Assumptions: this can only be the thumbnail and the thumbnail is a jpeg - TODO determine mimetype
418
            $format = "image/jpeg";
419
        }
420
        return $format;
421
    }
422
423
    /**
424
     * @see AbstractDocument::getLogicalStructure()
425
     */
426
    public function getLogicalStructure(string $id, bool $recursive = false): array
427
    {
428
        $details = [];
429
        if (!$recursive && !empty($this->logicalUnits[$id])) {
430
            return $this->logicalUnits[$id];
431
        } elseif (!empty($id)) {
432
            $logUnits[] = $this->iiif->getContainedResourceById($id);
433
        } else {
434
            $logUnits[] = $this->iiif;
435
        }
436
        // TODO: Variable $logUnits in empty() always exists and is not falsy.
437
        // @phpstan-ignore-next-line
438
        if (!empty($logUnits)) {
439
            if (!$recursive) {
440
                $details = $this->getLogicalStructureInfo($logUnits[0]);
441
            } else {
442
                // cache the ranges - they might occur multiple times in the structures "tree" - with full data as well as referenced as id
443
                $processedStructures = [];
444
                foreach ($logUnits as $logUnit) {
445
                    if (array_search($logUnit->getId(), $processedStructures) == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing array_search($logUnit->g..., $processedStructures) of type integer|string against false; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
446
                        $this->tableOfContents[] = $this->getLogicalStructureInfo($logUnit, true, $processedStructures);
447
                    }
448
                }
449
            }
450
        }
451
        return $details;
452
    }
453
454
    /**
455
     * Get the details about a IIIF resource (manifest or range) in the logical structure
456
     *
457
     * @access protected
458
     *
459
     * @param IiifResourceInterface $resource IIIF resource, either a manifest or range.
460
     * @param bool $recursive Whether to include the child elements
461
     * @param array $processedStructures IIIF resources that already have been processed
462
     *
463
     * @return array Logical structure array
464
     */
465
    protected function getLogicalStructureInfo(IiifResourceInterface $resource, bool $recursive = false, array &$processedStructures = []): array
466
    {
467
        $details = [];
468
        $details['id'] = $resource->getId();
469
        $details['dmdId'] = '';
470
        $details['label'] = $resource->getLabelForDisplay() ?? '';
471
        $details['orderlabel'] = $resource->getLabelForDisplay() ?? '';
472
        $details['contentIds'] = '';
473
        $details['volume'] = '';
474
        $details['pagination'] = '';
475
        $cPid = ($this->cPid ? $this->cPid : $this->pid);
476
        if ($details['id'] == $this->magicGetToplevelId()) {
477
            $metadata = $this->getMetadata($details['id'], $cPid);
478
            if (!empty($metadata['type'][0])) {
479
                $details['type'] = $metadata['type'][0];
480
            }
481
        }
482
        $details['thumbnailId'] = $resource->getThumbnailUrl();
483
        $details['points'] = '';
484
        // Load structural mapping
485
        $this->magicGetSmLinks();
486
        // Load physical structure.
487
        $this->magicGetPhysicalStructure();
488
489
        if ($resource instanceof ManifestInterface || $resource instanceof RangeInterface) {
490
            $startCanvas = $resource->getStartCanvasOrFirstCanvas();
491
        }
492
        if (isset($startCanvas)) {
493
            $details['pagination'] = $startCanvas->getLabel();
494
            $startCanvasIndex = array_search($startCanvas, $this->iiif->getDefaultCanvases());
495
            if ($startCanvasIndex !== false) {
496
                $details['points'] = $startCanvasIndex + 1;
497
            }
498
        }
499
        $useGroups = $this->getUseGroups('fileGrpImages');
500
        if (is_string($useGroups)) {
501
            $useGroups = [$useGroups];
0 ignored issues
show
The assignment to $useGroups is dead and can be removed.
Loading history...
502
        }
503
        // Keep for later usage.
504
        $this->logicalUnits[$details['id']] = $details;
505
        // Walk the structure recursively? And are there any children of the current element?
506
        if ($recursive) {
507
            $processedStructures[] = $resource->getId();
508
            $details['children'] = [];
509
            if ($resource instanceof ManifestInterface && $resource->getRootRanges() != null) {
510
                $rangesToAdd = [];
511
                $rootRanges = [];
512
                if (count($this->iiif->getRootRanges()) == 1 && $this->iiif->getRootRanges()[0]->isTopRange()) {
513
                    $rangesToAdd = $this->iiif->getRootRanges()[0]->getMemberRangesAndRanges();
0 ignored issues
show
The method getMemberRangesAndRanges() does not exist on Ubl\Iiif\Presentation\Co...esources\RangeInterface. It seems like you code against a sub-type of Ubl\Iiif\Presentation\Co...esources\RangeInterface such as Ubl\Iiif\Presentation\V2\Model\Resources\Range2. ( Ignorable by Annotation )

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

513
                    /** @scrutinizer ignore-call */ 
514
                    $rangesToAdd = $this->iiif->getRootRanges()[0]->getMemberRangesAndRanges();
Loading history...
514
                } else {
515
                    $rangesToAdd = $this->iiif->getRootRanges();
516
                }
517
                foreach ($rangesToAdd as $range) {
518
                    $rootRanges[] = $range;
519
                }
520
                foreach ($rootRanges as $range) {
521
                    if ((array_search($range->getId(), $processedStructures) == false)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing array_search($range->get..., $processedStructures) of type integer|string against false; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
522
                        $details['children'][] = $this->getLogicalStructureInfo($range, true, $processedStructures);
523
                    }
524
                }
525
            } elseif ($resource instanceof RangeInterface) {
526
                if (!empty($resource->getAllRanges())) {
527
                    foreach ($resource->getAllRanges() as $range) {
528
                        if ((array_search($range->getId(), $processedStructures) == false)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing array_search($range->get..., $processedStructures) of type integer|string against false; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
529
                            $details['children'][] = $this->getLogicalStructureInfo($range, true, $processedStructures);
530
                        }
531
                    }
532
                }
533
            }
534
        }
535
        return $details;
536
    }
537
538
    /**
539
     * Returns metadata for IIIF resources with the ID $id in there original form in
540
     * the manifest, but prepared for display to the user.
541
     *
542
     * @access public
543
     *
544
     * @param string $id the ID of the IIIF resource
545
     * @param bool $withDescription add description / summary to the return value
546
     * @param bool $withRights add attribution and license / rights and requiredStatement to the return value
547
     * @param bool $withRelated add related links / homepage to the return value
548
     *
549
     * @return array
550
     *
551
     * @todo This method is still in experimental; the method signature may change.
552
     */
553
    public function getManifestMetadata(string $id, bool $withDescription = true, bool $withRights = true, bool $withRelated = true): array
554
    {
555
        if (!empty($this->originalMetadataArray[$id])) {
556
            return $this->originalMetadataArray[$id];
557
        }
558
        $iiifResource = $this->iiif->getContainedResourceById($id);
559
        $result = [];
560
        if ($iiifResource != null) {
561
            if (!empty($iiifResource->getLabel())) {
562
                $result['label'] = $iiifResource->getLabel();
563
            }
564
            if (!empty($iiifResource->getMetadata())) {
565
                $result['metadata'] = [];
566
                foreach ($iiifResource->getMetadataForDisplay() as $metadata) {
567
                    $result['metadata'][$metadata['label']] = $metadata['value'];
568
                }
569
            }
570
            if ($withDescription && !empty($iiifResource->getSummary())) {
571
                $result["description"] = $iiifResource->getSummaryForDisplay();
572
            }
573
            if ($withRights) {
574
                if (!empty($iiifResource->getRights())) {
575
                    $result["rights"] = $iiifResource->getRights();
576
                }
577
                if (!empty($iiifResource->getRequiredStatement())) {
578
                    $result["requiredStatement"] = $iiifResource->getRequiredStatementForDisplay();
579
                }
580
            }
581
            if ($withRelated && !empty($iiifResource->getWeblinksForDisplay())) {
582
                $result["weblinks"] = [];
583
                foreach ($iiifResource->getWeblinksForDisplay() as $link) {
584
                    $key = array_key_exists("label", $link) ? $link["label"] : $link["@id"];
585
                    $result["weblinks"][$key] = $link["@id"];
586
                }
587
            }
588
        }
589
        return $result;
590
    }
591
592
    /**
593
     * @see AbstractDocument::getMetadata()
594
     */
595
    public function getMetadata(string $id, int $cPid = 0): array
596
    {
597
        if (!empty($this->metadataArray[$id]) && $this->metadataArray[0] == $cPid) {
598
            return $this->metadataArray[$id];
599
        }
600
601
        $metadata = $this->initializeMetadata('IIIF');
602
603
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
604
            ->getQueryBuilderForTable('tx_dlf_metadata');
605
        // Get hidden records, too.
606
        $queryBuilder
607
            ->getRestrictions()
608
            ->removeByType(HiddenRestriction::class);
609
        $result = $queryBuilder
610
            ->select(
611
                'tx_dlf_metadata.index_name AS index_name',
612
                'tx_dlf_metadataformat.xpath AS xpath',
613
                'tx_dlf_metadataformat.xpath_sorting AS xpath_sorting',
614
                'tx_dlf_metadata.is_sortable AS is_sortable',
615
                'tx_dlf_metadata.default_value AS default_value',
616
                'tx_dlf_metadata.format AS format'
617
            )
618
            ->from('tx_dlf_metadata')
619
            ->from('tx_dlf_metadataformat')
620
            ->from('tx_dlf_formats')
621
            ->where(
622
                $queryBuilder->expr()->eq('tx_dlf_metadata.pid', (int) $cPid),
623
                $queryBuilder->expr()->eq('tx_dlf_metadataformat.pid', (int) $cPid),
624
                $queryBuilder->expr()->orX(
625
                    $queryBuilder->expr()->andX(
626
                        $queryBuilder->expr()->eq('tx_dlf_metadata.uid', 'tx_dlf_metadataformat.parent_id'),
627
                        $queryBuilder->expr()->eq('tx_dlf_metadataformat.encoded', 'tx_dlf_formats.uid'),
628
                        $queryBuilder->expr()->eq('tx_dlf_formats.type', $queryBuilder->createNamedParameter($this->getIiifVersion()))
629
                    ),
630
                    $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0)
631
                )
632
            )
633
            ->execute();
634
        $iiifResource = $this->iiif->getContainedResourceById($id);
635
        while ($resArray = $result->fetchAssociative()) {
636
            // Set metadata field's value(s).
637
            if ($resArray['format'] > 0 && !empty($resArray['xpath'])) {
638
                $values = $iiifResource->jsonPath($resArray['xpath']);
639
                if (is_string($values)) {
640
                    $metadata[$resArray['index_name']] = [trim((string) $values)];
641
                } elseif ($values instanceof JSONPath && is_array($values->data()) && count($values->data()) > 1) {
642
                    $metadata[$resArray['index_name']] = [];
643
                    foreach ($values->data() as $value) {
644
                        $metadata[$resArray['index_name']][] = trim((string) $value);
645
                    }
646
                }
647
            }
648
            // Set default value if applicable.
649
            if (empty($metadata[$resArray['index_name']][0]) && strlen($resArray['default_value']) > 0) {
650
                $metadata[$resArray['index_name']] = [$resArray['default_value']];
651
            }
652
            // Set sorting value if applicable.
653
            if (!empty($metadata[$resArray['index_name']]) && $resArray['is_sortable']) {
654
                if ($resArray['format'] > 0 && !empty($resArray['xpath_sorting'])) {
655
                    $values = $iiifResource->jsonPath($resArray['xpath_sorting']);
656
                    if (is_string($values)) {
657
                        $metadata[$resArray['index_name'] . '_sorting'][0] = [trim((string) $values)];
658
                    } elseif ($values instanceof JSONPath && is_array($values->data()) && count($values->data()) > 1) {
659
                        $metadata[$resArray['index_name']] = [];
660
                        foreach ($values->data() as $value) {
661
                            $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $value);
662
                        }
663
                    }
664
                }
665
                if (empty($metadata[$resArray['index_name'] . '_sorting'][0])) {
666
                    $metadata[$resArray['index_name'] . '_sorting'][0] = $metadata[$resArray['index_name']][0];
667
                }
668
            }
669
        }
670
        // Set date to empty string if not present.
671
        if (empty($metadata['date'][0])) {
672
            $metadata['date'][0] = '';
673
        }
674
        return $metadata;
675
    }
676
677
    /**
678
     * @see AbstractDocument::magicGetSmLinks()
679
     */
680
    protected function magicGetSmLinks(): array
681
    {
682
        if (!$this->smLinksLoaded && isset($this->iiif) && $this->iiif instanceof ManifestInterface) {
683
            if (!empty($this->iiif->getDefaultCanvases())) {
684
                foreach ($this->iiif->getDefaultCanvases() as $canvas) {
685
                    $this->smLinkCanvasToResource($canvas, $this->iiif);
686
                }
687
            }
688
            if (!empty($this->iiif->getStructures())) {
689
                foreach ($this->iiif->getStructures() as $range) {
690
                    $this->smLinkRangeCanvasesRecursively($range);
691
                }
692
            }
693
            $this->smLinksLoaded = true;
694
        }
695
        return $this->smLinks;
696
    }
697
698
    /**
699
     * Construct a link between a range and it's sub ranges and all contained canvases.
700
     *
701
     * @access private
702
     *
703
     * @param RangeInterface $range Current range whose canvases shall be linked
704
     * 
705
     * @return void
706
     */
707
    private function smLinkRangeCanvasesRecursively(RangeInterface $range): void
708
    {
709
        // map range's canvases including all child ranges' canvases
710
        if (!$range->isTopRange()) {
711
            foreach ($range->getAllCanvasesRecursively() as $canvas) {
712
                $this->smLinkCanvasToResource($canvas, $range);
713
            }
714
        }
715
        // recursive call for all ranges
716
        if (!empty($range->getAllRanges())) {
717
            foreach ($range->getAllRanges() as $childRange) {
718
                $this->smLinkRangeCanvasesRecursively($childRange);
719
            }
720
        }
721
    }
722
723
    /**
724
     * Link a single canvas to a containing range
725
     *
726
     * @access private
727
     *
728
     * @param CanvasInterface $canvas
729
     * @param IiifResourceInterface $resource
730
     * 
731
     * @return void
732
     */
733
    private function smLinkCanvasToResource(CanvasInterface $canvas, IiifResourceInterface $resource): void
734
    {
735
        $this->smLinks['l2p'][$resource->getId()][] = $canvas->getId();
736
        if (!is_array($this->smLinks['p2l'][$canvas->getId()]) || !in_array($resource->getId(), $this->smLinks['p2l'][$canvas->getId()])) {
737
            $this->smLinks['p2l'][$canvas->getId()][] = $resource->getId();
738
        }
739
    }
740
741
    /**
742
     * @see AbstractDocument::getFullText()
743
     */
744
    //TODO: rewrite it to get full OCR
745
    public function getFullText(string $id): string
746
    {
747
        $rawText = '';
748
        // Get text from raw text array if available.
749
        if (!empty($this->rawTextArray[$id])) {
750
            return $this->rawTextArray[$id];
751
        }
752
        $this->ensureHasFulltextIsSet();
753
        if ($this->hasFulltext) {
754
            // Load physical structure ...
755
            $this->magicGetPhysicalStructure();
756
            // ... and extension configuration.
757
            $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
758
            $fileGrpsFulltext = GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']);
759
            if (!empty($this->physicalStructureInfo[$id])) {
760
                while ($fileGrpFulltext = array_shift($fileGrpsFulltext)) {
761
                    if (!empty($this->physicalStructureInfo[$id]['files'][$fileGrpFulltext])) {
762
                        $rawText = parent::getFullTextFromXml($id);
763
                        break;
764
                    }
765
                }
766
                if ($extConf['indexAnnotations'] == 1) {
767
                    $iiifResource = $this->iiif->getContainedResourceById($id);
768
                    // Get annotation containers
769
                    $annotationContainerIds = $this->physicalStructureInfo[$id]['annotationContainers'];
770
                    if (!empty($annotationContainerIds)) {
771
                        $annotationTexts = $this->getAnnotationTexts($annotationContainerIds, $iiifResource->getId());
772
                        $rawText .= implode(' ', $annotationTexts);
773
                    }
774
                }
775
            } else {
776
                $this->logger->warning('Invalid structure resource @id "' . $id . '"');
777
                return $rawText;
778
            }
779
            $this->rawTextArray[$id] = $rawText;
780
        }
781
        return $rawText;
782
    }
783
784
    /**
785
     * Returns the underlying IiifResourceInterface.
786
     *
787
     * @access public
788
     *
789
     * @return IiifResourceInterface
790
     */
791
    public function getIiif(): IiifResourceInterface
792
    {
793
        return $this->iiif;
794
    }
795
796
    /**
797
     * @see AbstractDocument::init()
798
     */
799
    protected function init(string $location, array $settings = []): void
800
    {
801
        $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
802
    }
803
804
    /**
805
     * @see AbstractDocument::loadLocation()
806
     */
807
    protected function loadLocation(string $location): bool
808
    {
809
        $fileResource = GeneralUtility::getUrl($location);
810
        if ($fileResource !== false) {
811
            $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
812
            IiifHelper::setUrlReader(IiifUrlReader::getInstance());
813
            IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']);
814
            IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']);
815
            $resource = IiifHelper::loadIiifResource($fileResource);
816
            if ($resource instanceof ManifestInterface) {
817
                $this->iiif = $resource;
818
                return true;
819
            }
820
        }
821
        $this->logger->error('Could not load IIIF manifest from "' . $location . '"');
822
        return false;
823
    }
824
825
    /**
826
     * @see AbstractDocument::prepareMetadataArray()
827
     */
828
    protected function prepareMetadataArray(int $cPid): void
829
    {
830
        $id = $this->iiif->getId();
831
        $this->metadataArray[(string) $id] = $this->getMetadata((string) $id, $cPid);
832
    }
833
834
    /**
835
     * @see AbstractDocument::setPreloadedDocument()
836
     */
837
    protected function setPreloadedDocument($preloadedDocument): bool
838
    {
839
        if ($preloadedDocument instanceof ManifestInterface) {
840
            $this->iiif = $preloadedDocument;
841
            return true;
842
        }
843
        return false;
844
    }
845
846
    /**
847
     * @see AbstractDocument::ensureHasFulltextIsSet()
848
     */
849
    protected function ensureHasFulltextIsSet(): void
850
    {
851
        /*
852
         *  TODO Check annotations and annotation lists of canvas for ALTO documents.
853
         *  Example:
854
         *  https://digi.ub.uni-heidelberg.de/diglit/iiif/hirsch_hamburg1933_04_25/manifest.json links
855
         *  https://digi.ub.uni-heidelberg.de/diglit/iiif/hirsch_hamburg1933_04_25/list/0001.json
856
         */
857
        if (!$this->hasFulltextSet && $this->iiif instanceof ManifestInterface) {
858
            $manifest = $this->iiif;
859
            $canvases = $manifest->getDefaultCanvases();
860
            foreach ($canvases as $canvas) {
861
                if (
862
                    !empty($canvas->getSeeAlsoUrlsForFormat("application/alto+xml")) ||
863
                    !empty($canvas->getSeeAlsoUrlsForProfile("http://www.loc.gov/standards/alto/"))
864
                ) {
865
                    $this->hasFulltextSet = true;
866
                    $this->hasFulltext = true;
867
                    return;
868
                }
869
                $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
870
                if ($extConf['indexAnnotations'] == 1 && !empty($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING))) {
871
                    foreach ($canvas->getPossibleTextAnnotationContainers(Motivation::PAINTING) as $annotationContainer) {
872
                        $textAnnotations = $annotationContainer->getTextAnnotations(Motivation::PAINTING);
873
                        if ($textAnnotations != null) {
874
                            foreach ($textAnnotations as $annotation) {
875
                                if (
876
                                    $annotation->getBody() != null &&
877
                                    $annotation->getBody()->getFormat() == "text/plain" &&
878
                                    $annotation->getBody()->getChars() != null
879
                                ) {
880
                                    $this->hasFulltextSet = true;
881
                                    $this->hasFulltext = true;
882
                                    return;
883
                                }
884
                            }
885
                        }
886
                    }
887
                }
888
            }
889
            $this->hasFulltextSet = true;
890
        }
891
    }
892
893
    /**
894
     * @see AbstractDocument::magicGetThumbnail()
895
     */
896
    protected function magicGetThumbnail(bool $forceReload = false): string
897
    {
898
        return $this->iiif->getThumbnailUrl();
899
    }
900
901
    /**
902
     * @see AbstractDocument::magicGetToplevelId()
903
     */
904
    protected function magicGetToplevelId(): string
905
    {
906
        if (empty($this->toplevelId)) {
907
            if (isset($this->iiif)) {
908
                $this->toplevelId = $this->iiif->getId();
909
            }
910
        }
911
        return $this->toplevelId;
912
    }
913
914
    /**
915
     * Get annotation texts.
916
     *
917
     * @access private
918
     *
919
     * @param array $annotationContainerIds
920
     * @param string $iiifId
921
     *
922
     * @return array
923
     */
924
    private function getAnnotationTexts($annotationContainerIds, $iiifId): array
925
    {
926
        $annotationTexts = [];
927
        foreach ($annotationContainerIds as $annotationListId) {
928
            $annotationContainer = $this->iiif->getContainedResourceById($annotationListId);
929
            /* @var $annotationContainer \Ubl\Iiif\Presentation\Common\Model\Resources\AnnotationContainerInterface */
930
            foreach ($annotationContainer->getTextAnnotations(Motivation::PAINTING) as $annotation) {
0 ignored issues
show
The method getTextAnnotations() does not exist on Ubl\Iiif\Presentation\Co...s\IiifResourceInterface. It seems like you code against a sub-type of Ubl\Iiif\Presentation\Co...s\IiifResourceInterface such as Ubl\Iiif\Presentation\Co...ationContainerInterface or Ubl\Iiif\Presentation\V2...sources\AnnotationList2 or Ubl\Iiif\Presentation\V3...sources\AnnotationPage3 or Ubl\Iiif\Presentation\V1...sources\AnnotationList1 or Ubl\Iiif\Presentation\V2...sources\AnnotationList2 or Ubl\Iiif\Presentation\V3...sources\AnnotationPage3 or Ubl\Iiif\Presentation\V1...sources\AnnotationList1. ( Ignorable by Annotation )

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

930
            foreach ($annotationContainer->/** @scrutinizer ignore-call */ getTextAnnotations(Motivation::PAINTING) as $annotation) {
Loading history...
931
                if (
932
                    $annotation->getTargetResourceId() == $iiifId &&
933
                    $annotation->getBody() != null && $annotation->getBody()->getChars() != null
934
                ) {
935
                    $annotationTexts[] = $annotation->getBody()->getChars();
936
                }
937
            }
938
        }
939
        return $annotationTexts;
940
    }
941
942
    /**
943
     * Set files used for download (PDF).
944
     *
945
     * @access private
946
     *
947
     * @param string $iiifId
948
     * @param IiifResourceInterface $iiif
949
     *
950
     * @return void
951
     */
952
    private function setFileUseDownload(string $iiifId, $iiif): void
953
    {
954
        $fileUseDownload = $this->getUseGroups('fileGrpDownload');
955
956
        if (!empty($fileUseDownload)) {
957
            $docPdfRendering = $iiif->getRenderingUrlsForFormat('application/pdf');
958
            if (!empty($docPdfRendering)) {
959
                $this->physicalStructureInfo[$iiifId]['files'][$fileUseDownload[0]] = $docPdfRendering[0];
960
            }
961
        }
962
    }
963
964
    /**
965
     * Set files used for full text (ALTO).
966
     *
967
     * @access private
968
     *
969
     * @param string $iiifId
970
     * @param IiifResourceInterface $iiif
971
     *
972
     * @return void
973
     */
974
    private function setFileUseFulltext(string $iiifId, $iiif): void
975
    {
976
        $fileUseFulltext = $this->getUseGroups('fileGrpFulltext');
977
978
        if (!empty($fileUseFulltext)) {
979
            $alto = $iiif->getSeeAlsoUrlsForFormat('application/alto+xml');
980
            if (empty($alto)) {
981
                $alto = $iiif->getSeeAlsoUrlsForProfile('http://www.loc.gov/standards/alto/', true);
982
            }
983
            if (!empty($alto)) {
984
                $this->mimeTypes[$alto[0]] = 'application/alto+xml';
985
                $this->physicalStructureInfo[$iiifId]['files'][$fileUseFulltext[0]] = $alto[0];
986
                $this->hasFulltext = true;
987
                $this->hasFulltextSet = true;
988
            }
989
        }
990
    }
991
992
    /**
993
     * This magic method is executed after the object is deserialized
994
     * @see __sleep()
995
     *
996
     * @access public
997
     *
998
     * @return void
999
     */
1000
    public function __wakeup(): void
1001
    {
1002
        $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey);
1003
        IiifHelper::setUrlReader(IiifUrlReader::getInstance());
1004
        IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']);
1005
        IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']);
1006
        $resource = IiifHelper::loadIiifResource($this->asJson);
1007
        if ($resource instanceof ManifestInterface) {
1008
            $this->asJson = '';
1009
            $this->iiif = $resource;
1010
            $this->init('');
1011
        } else {
1012
            $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
1013
            $this->logger->error('Could not load IIIF after deserialization');
1014
        }
1015
    }
1016
1017
    /**
1018
     * @access public
1019
     *
1020
     * @return string[]
1021
     */
1022
    public function __sleep(): array
1023
    {
1024
        // TODO implement serialization in IIIF library
1025
        $jsonArray = $this->iiif->getOriginalJsonArray();
1026
        $this->asJson = json_encode($jsonArray);
1027
        return ['uid', 'pid', 'recordId', 'parentId', 'asJson'];
1028
    }
1029
}
1030