Passed
Pull Request — master (#123)
by
unknown
11:14
created

DocumentAnnotation   F

Complexity

Total Complexity 79

Size/Duplication

Total Lines 372
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 185
c 1
b 0
f 0
dl 0
loc 372
rs 2.08
wmc 79

10 Methods

Rating   Name   Duplication   Size   Complexity  
C getAnnotations() 0 93 17
C getAudioPagesByFileId() 0 47 17
A __construct() 0 5 1
A getInstance() 0 7 2
B getPagesByFileId() 0 17 7
C getMeasurePagesByFileId() 0 49 17
A getVerovioRelevantAnnotations() 0 10 3
A getPagesByLogicalId() 0 18 6
A loadData() 0 13 4
A getPagesByPhysicalId() 0 16 5

How to fix   Complexity   

Complex Class

Complex classes like DocumentAnnotation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocumentAnnotation, and based on these observations, apply Extract Interface, too.

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