Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
66.67% |
96 / 144 |
|
52.94% |
9 / 17 |
CRAP | |
0.00% |
0 / 1 |
ResourceFile | |
66.67% |
96 / 144 |
|
52.94% |
9 / 17 |
187.93 | |
0.00% |
0 / 1 |
validate | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
isPackage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
readVeronaMetaData | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
7 | |||
readVeronaMetadataV3 | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
3 | |||
getPlayerMetaElementV3 | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
getPlayerTitleV3 | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
readVeronaMetadataV35 | |
14.29% |
3 / 21 |
|
0.00% |
0 / 1 |
14.08 | |||
getPreferredTranslationV35 | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
readVeronaMetadataV4 | |
81.82% |
18 / 22 |
|
0.00% |
0 / 1 |
5.15 | |||
getPreferredTranslationV4 | |
71.43% |
5 / 7 |
|
0.00% |
0 / 1 |
8.14 | |||
getPlayerMetaElementV4 | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
applyMeta | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
4 | |||
analyzeMeta | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
5 | |||
validatePackage | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
2.26 | |||
getPackageContentPath | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
installPackage | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
uninstallPackage | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** @noinspection PhpUnhandledExceptionInspection */ |
3 | declare(strict_types=1); |
4 | |
5 | class ResourceFile extends File { |
6 | const type = 'Resource'; |
7 | const canBeRelationSubject = false; |
8 | const canBeRelationObject = true; |
9 | |
10 | protected function validate(): void { |
11 | parent::validate(); |
12 | |
13 | $this->id = strtoupper($this->name); |
14 | |
15 | if (FileExt::has($this->getPath(), 'HTML')) { |
16 | $this->readVeronaMetaData(); |
17 | $this->id = strtoupper($this->getVeronaModuleId() . '-' . $this->versionMayor . '.' . $this->versionMinor); |
18 | } |
19 | |
20 | if ($this->isPackage()) { |
21 | $this->validatePackage(); |
22 | } |
23 | } |
24 | |
25 | public function isPackage(): bool { |
26 | return FileExt::has($this->getPath(), 'ITCR.ZIP'); |
27 | } |
28 | |
29 | // player is not it's own class, because player and other resources are stores in the same dir |
30 | private function readVeronaMetaData() { |
31 | if ($this->isValid() and $this->getContent()) { |
32 | $document = new DOMDocument(); |
33 | $document->loadHTML($this->getContent(), LIBXML_NOERROR); |
34 | |
35 | if ($metaV4Problem = $this->readVeronaMetadataV4($document)) { |
36 | if (!$this->readVeronaMetadataV35($document)) { |
37 | if (!$this->readVeronaMetadataV3($document)) { |
38 | $this->report('warning', $metaV4Problem); |
39 | } |
40 | } |
41 | } |
42 | } |
43 | |
44 | if (!$this->getVersion()) { |
45 | list( |
46 | $this->veronaModuleId, |
47 | , |
48 | $this->versionMayor, |
49 | $this->versionMinor, |
50 | $this->versionPatch, |
51 | $this->versionLabel, |
52 | ) = array_values(Version::guessFromFileName(basename($this->getPath()))); |
53 | |
54 | $this->report('warning', 'Metadata missing. Version guessed from Filename.'); |
55 | } |
56 | |
57 | $this->applyMeta(); |
58 | $this->analyzeMeta(); |
59 | } |
60 | |
61 | /** |
62 | * This was a temporary way of defining meta-data of a player until in Verona4 a definitive way was defined. Since |
63 | * we produced a bunch of player-versions including this kind of metadata we should support it as long as we support |
64 | * Verona3. |
65 | * |
66 | * @deprecated |
67 | */ |
68 | private function readVeronaMetadataV3(DOMDocument $document): bool { |
69 | $this->label = $this->getPlayerTitleV3($document); |
70 | |
71 | $meta = $this->getPlayerMetaElementV3($document); |
72 | if (!$meta or !$meta->getAttribute('content')) { |
73 | return false; |
74 | } |
75 | |
76 | $this->veronaModuleId = $meta->getAttribute('content'); |
77 | |
78 | list( |
79 | $this->versionMayor, |
80 | $this->versionMinor, |
81 | $this->versionPatch, |
82 | $this->versionLabel |
83 | ) = array_values(Version::split($meta->getAttribute('data-version'))); |
84 | |
85 | $this->veronaVersion = $meta->getAttribute('data-api-version'); |
86 | $this->description = $meta->getAttribute('data-description'); |
87 | $this->veronaModuleType = 'player'; |
88 | |
89 | $this->report('warning', 'Metadata in meta-tag is deprecated!'); |
90 | return true; |
91 | } |
92 | |
93 | private function getPlayerMetaElementV3(DOMDocument $document): ?DOMElement { |
94 | $metaElements = $document->getElementsByTagName('meta'); |
95 | foreach ($metaElements as $metaElement) { |
96 | /* @var $metaElement DOMElement */ |
97 | if ($metaElement->getAttribute('name') == 'application-name') { |
98 | return $metaElement; |
99 | } |
100 | } |
101 | return null; |
102 | } |
103 | |
104 | private function getPlayerTitleV3(DOMDocument $document): string { |
105 | $titleElements = $document->getElementsByTagName('title'); |
106 | if (!count($titleElements)) { |
107 | return ''; |
108 | } |
109 | $titleElement = $titleElements[0]; |
110 | /* @var $titleElement DOMElement */ |
111 | return $titleElement->textContent; |
112 | } |
113 | |
114 | /** |
115 | * This was another temporary way of defining meta-data of a player until in Verona4 a definitive way was defined. |
116 | * Since we produced a bunch of player-versions including this kind of metadata we should support it as long as |
117 | * we support Verona3. |
118 | * |
119 | * @deprecated |
120 | */ |
121 | private function readVeronaMetadataV35(DOMDocument $document): bool { |
122 | $metaElem = $this->getPlayerMetaElementV4($document); |
123 | if (!$metaElem) { |
124 | return false; |
125 | } |
126 | try { |
127 | $meta = JSON::decode($metaElem->textContent, true); |
128 | } catch (Exception) { |
129 | return false; |
130 | } |
131 | if ($meta["@context"] !== "https://w3id.org/iqb/verona-modules") { |
132 | return false; |
133 | } |
134 | $this->label = $this->getPreferredTranslationV35($meta['name']); |
135 | $this->description = $this->getPreferredTranslationV35($meta['description']); |
136 | $this->veronaModuleId = $meta['@id']; |
137 | $this->veronaVersion = $meta['apiVersion']; |
138 | list( |
139 | $this->versionMayor, |
140 | $this->versionMinor, |
141 | $this->versionPatch, |
142 | $this->versionLabel |
143 | ) = array_values(Version::split($meta['version'])); |
144 | $this->veronaModuleType = $meta['@type']; |
145 | |
146 | $this->report('warning', 'Deprecated meta-data-format found!'); |
147 | return true; |
148 | } |
149 | |
150 | private function getPreferredTranslationV35(?array $multiLangItem): string { |
151 | if (!$multiLangItem or !count($multiLangItem)) { |
152 | return ''; |
153 | } |
154 | |
155 | $first = array_keys($multiLangItem)[0]; |
156 | return $multiLangItem['de'] |
157 | ?? $multiLangItem['en'] |
158 | ?? $multiLangItem[$first]; |
159 | } |
160 | |
161 | private function readVeronaMetadataV4(DOMDocument $document): ?string { |
162 | $metaElem = $this->getPlayerMetaElementV4($document); |
163 | if (!$metaElem) { |
164 | return "No Metadata Element"; |
165 | } |
166 | try { |
167 | $meta = JSON::decode($metaElem->textContent, true); |
168 | } catch (Exception $e) { |
169 | return "Could not read metadata: {$e->getMessage()}"; |
170 | } |
171 | if (!isset($meta['$schema'])) { |
172 | return "Could not read metadata: \$schema missing"; |
173 | } |
174 | if ($meta['$schema'] !== "https://raw.githubusercontent.com/verona-interfaces/metadata/master/verona-module-metadata.json") { |
175 | return "Wrong metadata-schema: {$meta['$schema']}"; |
176 | } |
177 | $this->label = $this->getPreferredTranslationV4($meta['name']); |
178 | $this->description = $this->getPreferredTranslationV4($meta['description']); |
179 | $this->veronaModuleId = $meta['id']; |
180 | $this->veronaVersion = $meta['specVersion']; |
181 | list( |
182 | $this->versionMayor, |
183 | $this->versionMinor, |
184 | $this->versionPatch, |
185 | $this->versionLabel |
186 | ) = array_values(Version::split($meta['version'])); |
187 | $this->veronaModuleType = $meta['type']; |
188 | return null; |
189 | } |
190 | |
191 | private function getPreferredTranslationV4(?array $multiLangItem): string { |
192 | if (!$multiLangItem or !count($multiLangItem)) { |
193 | return ''; |
194 | } |
195 | |
196 | foreach ($multiLangItem as $entry) { |
197 | if ($entry['lang'] == 'de') return $entry['value']; |
198 | } |
199 | foreach ($multiLangItem as $entry) { |
200 | if ($entry['lang'] == 'en') return $entry['value']; |
201 | } |
202 | |
203 | return $multiLangItem[0]['value']; |
204 | } |
205 | |
206 | private function getPlayerMetaElementV4(DOMDocument $document): ?DOMElement { |
207 | $metaElements = $document->getElementsByTagName('script'); |
208 | foreach ($metaElements as $metaElement) { |
209 | /* @var $metaElement DOMElement */ |
210 | if ($metaElement->getAttribute('type') == 'application/ld+json') { |
211 | return $metaElement; |
212 | } |
213 | } |
214 | return null; |
215 | } |
216 | |
217 | private function applyMeta(): void { |
218 | if (!$this->label and $this->veronaModuleId) { |
219 | $this->label = $this->veronaModuleId; |
220 | $this->label .= $this->getVersion() ? '-' . $this->getVersion() : ''; |
221 | } |
222 | } |
223 | |
224 | private function analyzeMeta(): void { |
225 | if ($this->veronaVersion) { |
226 | $this->report('info', "Verona-Version: $this->veronaVersion"); |
227 | } |
228 | |
229 | if ($this->veronaModuleId and $this->getVersion()) { |
230 | $recommendedFilename = "$this->veronaModuleId-{$this->getVersionMayorMinor()}.html"; |
231 | if ($recommendedFilename != $this->name) { |
232 | $this->report('warning', "Non-Standard-Filename: `$this->veronaModuleId-{$this->getVersionMayorMinor()}.html` expected."); |
233 | } |
234 | } |
235 | } |
236 | |
237 | public function validatePackage(): void { |
238 | try { |
239 | $meta = ZIP::readMeta($this->getPath()); |
240 | |
241 | $this->description = $meta['comment'] ?? ''; |
242 | |
243 | $this->report('info', "Contains {$meta['count']} files."); |
244 | |
245 | } catch (Exception $e) { |
246 | $this->report('error', "Could not read archive: {$e->getMessage()}"); |
247 | } |
248 | } |
249 | |
250 | private function getPackageContentPath(): string { |
251 | return dirname($this->getPath()) . '/' . basename($this->getName(), '.itcr.zip'); |
252 | } |
253 | |
254 | public function installPackage(): void { |
255 | $contentsDirName = $this->getPackageContentPath(); |
256 | |
257 | try { |
258 | $this->uninstallPackage(); |
259 | |
260 | } catch (Exception $e) { |
261 | $this->report('error', "Could not delete package files: {$e->getMessage()}"); |
262 | return; |
263 | } |
264 | |
265 | try { |
266 | ZIP::extract($this->getPath(), $contentsDirName); |
267 | |
268 | } catch (Exception $e) { |
269 | $this->report('error', "Could not extract package: {$e->getMessage()}"); |
270 | return; |
271 | } |
272 | } |
273 | |
274 | public function uninstallPackage(): void { |
275 | $contentsDirName = $this->getPackageContentPath(); |
276 | if (file_exists($contentsDirName)) { |
277 | if (is_dir($contentsDirName)) { |
278 | Folder::deleteContentsRecursive($contentsDirName); |
279 | rmdir($contentsDirName); |
280 | } else { |
281 | unlink($contentsDirName); |
282 | } |
283 | } |
284 | } |
285 | } |