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.

DocumentAnnotation::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 5
rs 10
1
<?php
2
3
namespace Kitodo\Dlf\Common;
4
5
/**
6
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
7
 *
8
 * This file is part of the Kitodo and TYPO3 projects.
9
 *
10
 * @license GNU General Public License version 3 or later.
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 */
14
15
use DateTime;
16
use Kitodo\Dlf\Domain\Model\Annotation;
17
use Kitodo\Dlf\Domain\Model\Document;
18
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
19
use TYPO3\CMS\Core\Log\LogManager;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Core\Log\Logger;
22
23
/**
24
 * Implementation for displaying annotations from an annotation server to a document
25
 *
26
 * @package TYPO3
27
 * @subpackage dlf
28
 *
29
 * @access public
30
 */
31
class DocumentAnnotation
32
{
33
    /**
34
     * @var null|DocumentAnnotation
35
     */
36
    private static $instance;
37
38
    /**
39
     * @var array
40
     */
41
    protected $annotationData;
42
43
    /**
44
     * @var Document
45
     */
46
    protected $document;
47
48
    /**
49
     * @access protected
50
     * @var Logger This holds the logger
51
     */
52
    protected Logger $logger;
53
54
    /**
55
     * @param array $annotationData
56
     * @param Document $document
57
     */
58
    private function __construct($annotationData, $document)
59
    {
60
        $this->annotationData = $annotationData;
61
        $this->document = $document;
62
        $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
63
    }
64
65
    /**
66
     * Returns all annotations with valid targets.
67
     *
68
     * @return Annotation[]|array
69
     */
70
    public function getAnnotations()
71
    {
72
        if (empty($this->annotationData)) {
73
            return [];
74
        }
75
        $annotations = [];
76
        foreach ($this->annotationData as $item) {
77
            $annotation = new Annotation($item);
78
            $annotationTargets = $annotation->getTargets();
79
            $targetPages = [];
80
            foreach ($annotationTargets as $annotationTarget) {
81
                if ($annotationTarget->isValid()) {
82
                    if ($annotationTarget->getId()) {
83
                        if ($this->document->getCurrentDocument()->getFileLocation($annotationTarget->getId())) {
84
                            if ($this->document->getCurrentDocument() instanceof MetsDocument) {
85
                                if (
86
                                    $meiTargetPages = $this->getMeasurePagesByFileId(
87
                                        $annotationTarget->getId(), $annotationTarget->getRangeValue()
88
                                    )
89
                                ) {
90
                                    $targetPages[] = [
91
                                        'target' => $annotationTarget,
92
                                        'pages' => $meiTargetPages,
93
                                        'verovioRelevant' => true
94
                                    ];
95
                                } elseif (
96
                                    $audioTargetPages = $this->getAudioPagesByFileId(
97
                                        $annotationTarget->getId(), $annotationTarget->getRangeValue()
98
                                    )
99
                                ) {
100
                                    $targetPages[] = [
101
                                        'target' => $annotationTarget,
102
                                        'pages' => $audioTargetPages
103
                                    ];
104
                                } elseif ($fileIdTargetPages = $this->getPagesByFileId($annotationTarget->getId())) {
105
                                    $targetPages[] = [
106
                                        'target' => $annotationTarget,
107
                                        'pages' => $fileIdTargetPages
108
                                    ];
109
                                } else {
110
                                    $this->logger->warning(
111
                                        ' No target pages found! Annotation: "' . $annotation->getId() . '", '
112
                                        . 'Target: "' . $annotationTarget->getUrl() . '"'
113
                                    );
114
                                }
115
                            }
116
                        } elseif ($logicalTargetPages = $this->getPagesByLogicalId($annotationTarget->getId())) {
117
                            $targetPages[] = [
118
                                'target' => $annotationTarget,
119
                                'pages' => $logicalTargetPages
120
                            ];
121
                        } elseif ($physicalTargetPages = $this->getPagesByPhysicalId($annotationTarget->getId())) {
122
                            $targetPages[] = [
123
                                'target' => $annotationTarget,
124
                                'pages' => $physicalTargetPages
125
                            ];
126
                        } else {
127
                            $this->logger->warning(
128
                                ' No target pages found! Annotation: "' . $annotation->getId() . '", '
129
                                . 'Target: "' . $annotationTarget->getUrl() . '"'
130
                            );
131
                        }
132
                    } elseif ($annotationTarget->getObjectId()) {
133
                         $objectTargetPages = [];
134
                        foreach ($this->document->getCurrentDocument()->physicalStructureInfo as $physInfo) {
135
                             $order = $physInfo['order'];
136
                            if ($order) {
137
                                 $objectTargetPages[] = $order;
138
                            }
139
                        }
140
                        if ($objectTargetPages) {
141
                            $targetPages[] = [
142
                                'target' => $annotationTarget,
143
                                'pages' => $objectTargetPages
144
                            ];
145
                        }
146
                    } else {
147
                        $this->logger->warning(
148
                            ' No target pages found! Annotation: "' . $annotation->getId() . '", '
149
                            . 'Target: "' . $annotationTarget->getUrl() . '"'
150
                        );
151
                    }
152
                } else {
153
                    $this->logger->warning(
154
                        'Invalid target! Annotation: "' . $annotation->getId() . '", '
155
                        . 'Target: "' . $annotationTarget->getUrl() . '"'
156
                    );
157
                }
158
            }
159
            $annotation->setTargetPages($targetPages);
160
            $annotations[] = $annotation;
161
        }
162
        return $annotations;
163
    }
164
165
    /**
166
     * Gets the logicalId related page numbers
167
     *
168
     * @param string $logicalId
169
     * @return array
170
     */
171
    protected function getPagesByLogicalId($logicalId)
172
    {
173
        $pages = [];
174
        if (
175
            array_key_exists('l2p', $this->document->getCurrentDocument()->smLinks) &&
176
            array_key_exists($logicalId, $this->document->getCurrentDocument()->smLinks['l2p'])
177
        ) {
178
            $physicalIdentifiers = $this->document->getCurrentDocument()->smLinks['l2p'][$logicalId];
179
            foreach ($physicalIdentifiers as $physicalIdentifier) {
180
                if (array_key_exists($physicalIdentifier, $this->document->getCurrentDocument()->physicalStructureInfo)) {
181
                    $order = $this->document->getCurrentDocument()->physicalStructureInfo[$physicalIdentifier]['order'];
182
                    if (is_numeric($order)) {
183
                        $pages[] = $order;
184
                    }
185
                }
186
            }
187
        }
188
        return $pages;
189
    }
190
191
    /**
192
     * Gets the physicalId related page numbers
193
     * @param string $physicalId
194
     * @return array
195
     */
196
    protected function getPagesByPhysicalId($physicalId)
197
    {
198
        $pages = [];
199
        foreach ($this->document->getCurrentDocument()->physicalStructureInfo as $physicalInfo) {
200
            $order = $physicalInfo['order'];
201
            if (is_numeric($order)) {
202
                $pages[] = $order;
203
            }
204
        }
205
        if (array_key_exists($physicalId, $this->document->getCurrentDocument()->physicalStructureInfo)) {
206
            if ($this->document->getCurrentDocument()->physicalStructureInfo[$physicalId]['type'] === 'physSequence') {
207
                return $pages;
208
            }
209
            return [$this->document->getCurrentDocument()->physicalStructureInfo[$physicalId]['order']];
210
        }
211
        return [];
212
    }
213
214
    /**
215
     * Gets the fileId related page numbers
216
     *
217
     * @param string $fileId
218
     * @return array
219
     */
220
    protected function getPagesByFileId($fileId)
221
    {
222
        $pages = [];
223
        foreach ($this->document->getCurrentDocument()->physicalStructureInfo as $physicalInfo) {
224
            if (
225
                array_key_exists('files', $physicalInfo) &&
226
                is_array($physicalInfo['files']) &&
227
                $physicalInfo['type'] !== 'physSequence'
228
            ) {
229
                foreach ($physicalInfo['files'] as $file) {
230
                    if ($file === $fileId) {
231
                        $pages[] = $physicalInfo['order'];
232
                    }
233
                }
234
            }
235
        }
236
        return $pages;
237
    }
238
239
    /**
240
     * Gets the fileId and audio related page numbers
241
     *
242
     * @param string $fileId
243
     * @param string $range
244
     * @return array
245
     */
246
    protected function getAudioPagesByFileId($fileId, $range = null)
247
    {
248
        $tracks = [];
249
        foreach ($this->document->getCurrentDocument()->physicalStructureInfo as $physicalInfo) {
250
            if (array_key_exists('tracks', $physicalInfo) && is_array($physicalInfo['tracks'])) {
251
                foreach ($physicalInfo['tracks'] as $track) {
252
                    if ($track['fileid'] === $fileId && $track['betype'] === 'TIME') {
253
                        $track['order'] = $physicalInfo['order'];
254
                        $tracks[] = $track;
255
                    }
256
                }
257
            }
258
        }
259
        if ($tracks && $range) {
260
            list($from, $to) = array_map('trim', explode(',', $range));
261
            $from = sprintf('%02.6f', (empty($from) ? "0" : $from));
262
            $intervalFrom = \DateTime::createFromFormat('U.u', $from);
263
            if (empty($to)) {
264
                $intervalTo = null;
265
            } else {
266
                $to = sprintf('%02.6f', $to);
267
                $intervalTo = \DateTime::createFromFormat('U.u', $to);
268
            }
269
            foreach ($tracks as $index => $track) {
270
                $begin = new DateTime("1970-01-01 " . $track['begin']);
271
                $extent = new DateTime("1970-01-01 " . $track['extent']);
272
                $diff = (new DateTime("1970-01-01 00:00:00"))->diff($extent);
273
                $end = (new DateTime("1970-01-01 " . $track['begin']))->add($diff);
274
                if (
275
                    !(
276
                        $intervalFrom < $end && (
277
                            $intervalTo === null || $intervalTo > $begin
278
                        )
279
                    )
280
                ) {
281
                    unset($tracks[$index]);
282
                }
283
            }
284
        }
285
        // Get the related page numbers
286
        $trackPages = [];
287
        foreach ($tracks as $track) {
288
            if ($track['order'] !== null) {
289
                $trackPages[] = $track['order'];
290
            }
291
        }
292
        return $trackPages;
293
    }
294
295
    /**
296
     * Gets the fileId and measure range related page numbers from the musical structMap
297
     *
298
     * @param string $fileId
299
     * @param string $range
300
     * @return array
301
     */
302
    protected function getMeasurePagesByFileId($fileId, $range = null)
303
    {
304
        // Get all measures referencing the fileid
305
        $measures = [];
306
        // Get the related page numbers
307
        $measurePages = [];
308
        $measureIndex = 1;
309
        $startOrder = 0;
310
        $endOrder = 0;
311
        if ($this->document->getCurrentDocument() instanceof MetsDocument) {
312
            foreach ($this->document->getCurrentDocument()->musicalStructureInfo as $key => $musicalInfo) {
313
                if ($musicalInfo['type'] === 'measure' && is_array($musicalInfo['files'])) {
314
                    foreach ($musicalInfo['files'] as $file) {
315
                        if ($file['fileid'] === $fileId && $file['type'] === 'IDREF') {
316
                            $measures[] = $musicalInfo;
317
                        }
318
                    }
319
                    if ($measureIndex === 1) {
320
                        $startOrder = $musicalInfo['order'];
321
                    }
322
                    $endOrder = $musicalInfo['order'];
323
                    $measureIndex += 1;
324
                }
325
            }
326
            // Filter measures by the given range of measure numbers
327
            if ($measures && $range && !preg_match("/\ball\b/", $range)) {
328
                $measureNumbers = [];
329
                $range = preg_replace("/\bend\b/", $endOrder, $range);
330
                $range = preg_replace("/\bstart\b/", $startOrder, $range);
331
                $ranges = array_map('trim', explode(',', $range));
332
                foreach ($ranges as $measureNumber) {
333
                    if (preg_match('/\d+-\d+/', $measureNumber)) {
334
                        list($from, $to) = array_map('trim', explode('-', $measureNumber));
335
                        $measureNumbers = array_merge($measureNumbers, range($from, $to));
336
                    } else {
337
                        $measureNumbers[] = (int) $measureNumber;
338
                    }
339
                }
340
                foreach ($measures as $key => $measure) {
341
                    if (!in_array($measure['order'], $measureNumbers)) {
342
                        unset($measures[$key]);
343
                    }
344
                }
345
            }
346
            foreach ($measures as $measure) {
347
                $measurePages[$measure['order']] = $this->document->getCurrentDocument()->musicalStructure[$measure['order']]['page'];
348
            }
349
        }
350
        return $measurePages;
351
    }
352
353
    /**
354
     * Returns the raw data of all annotations with a valid verovio target
355
     *
356
     * @return array
357
     */
358
    public function getVerovioRelevantAnnotations()
359
    {
360
        $annotations = [];
361
        /** @var Annotation $annotation */
362
        foreach ($this->getAnnotations() as $annotation) {
363
            if ($annotation->isVerovioRelevant()) {
364
                $annotations[] = $annotation->getRawData();
365
            }
366
        }
367
        return $annotations;
368
    }
369
370
    /**
371
     * Loads all annotation data from the annotation server
372
     *
373
     * @param Document $document
374
     * @return array
375
     */
376
    protected static function loadData($document)
377
    {
378
        $annotationData = [];
379
        $conf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('dlf');
380
        $apiBaseUrl = $conf['annotationServerUrl'];
381
        if ($apiBaseUrl && $document->getCurrentDocument() instanceof MetsDocument) {
382
            $purl = $document->getCurrentDocument()->mets->xpath('//mods:mods/mods:identifier[@type="purl"]');
383
            if (count($purl) > 0) {
384
                $annotationRequest = new AnnotationRequest($apiBaseUrl);
385
                $annotationData = $annotationRequest->getAll((string) $purl[0]);
386
            }
387
        }
388
        return $annotationData;
389
    }
390
391
    /**
392
     * @param $document
393
     * @return DocumentAnnotation|null
394
     *
395
     */
396
    public static function getInstance($document)
397
    {
398
        if (self::$instance == null) {
399
            $annotationData = self::loadData($document);
400
            self::$instance = new DocumentAnnotation($annotationData, $document);
401
        }
402
        return self::$instance;
403
    }
404
}
405