ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilCmiXapiContentUploadImporter.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21use ILIAS\FileUpload\DTO\UploadResult as FileUploadResult;
22use ILIAS\FileUpload\DTO\ProcessingStatus as FileUploadProcessingStatus;
23use ILIAS\FileUpload\Location as FileUploadResultLocation;
25
36{
37 public const RELATIVE_CONTENT_DIRECTORY_NAMEBASE = 'lm_data/lm_';
38
39 public const RELATIVE_XSD_DIRECTORY = 'components/ILIAS/CmiXapi/xml/contentschema';
40
41 public const IMP_FILE_EXTENSION_XML = 'xml';
42 public const IMP_FILE_EXTENSION_ZIP = 'zip';
43
44 public const CMI5_XML = 'cmi5.xml';
45 public const CMI5_XSD = 'cmi5_v1_CourseStructure.xsd';
46
47 public const TINCAN_XML = 'tincan.xml';
48 public const TINCAN_XSD = 'tincan.xsd';
49
53 protected static array $CONTENT_XML_FILENAMES = [
55 ];
56
60 protected static array $CONTENT_XSD_FILENAMES = [
61 self::CMI5_XML => self::CMI5_XSD,
62 self::TINCAN_XML => self::TINCAN_XSD
63 ];
64
66
67 private \ILIAS\DI\Container $dic;
68
73 {
74 global $DIC;
75 $this->dic = $DIC;
76 $this->object = $object;
77 }
78
82 public function ensureCreatedObjectDirectory(): void
83 {
84 global $DIC; /* @var \ILIAS\DI\Container $DIC */
85
86 if (!$DIC->filesystem()->web()->has($this->getWebDataDirRelativeObjectDirectory())) {
87 $DIC->filesystem()->web()->createDir($this->getWebDataDirRelativeObjectDirectory());
88 }
89 }
90
91 protected function sanitizeObjectDirectory(): void
92 {
94 implode(DIRECTORY_SEPARATOR, [
97 ])
98 );
99 }
100
105 public function importServerFile(string $serverFile): void
106 {
108
109 $this->handleFile($serverFile);
110
112 }
113
117 protected function handleFile(string $serverFile): void
118 {
119 $fileInfo = pathinfo($serverFile);
120
121 switch ($fileInfo['extension']) {
123
124 $this->handleXmlFile($serverFile);
125 break;
126
128
129 $this->handleZipContentUpload($serverFile);
130
131 if ($this->hasStoredContentXml()) {
132 $this->handleXmlFile($this->getStoredContentXml());
133 }
134
135 break;
136 }
137 }
138
144 public function importFormUpload(array $fileData): void
145 {
146 global $DIC;
148
149 $uploadResult = $this->getUpload(
150 $fileData['tmp_name']
151 );
152
153 $this->handleUpload($uploadResult);
154
156 }
157
162 protected function getUpload(?string $uploadFilePath): FileUploadResult
163 {
164 global $DIC; /* @var \ILIAS\DI\Container $DIC */
165
166 if ($DIC->upload()->hasUploads()) {
167 if (!$DIC->upload()->hasBeenProcessed()) {
168 $DIC->upload()->process();
169 }
170
171 /* @var FileUploadResult $result */
172
173 $results = $DIC->upload()->getResults();
174
175 if (isset($results[$uploadFilePath])) {
176 $result = $results[$uploadFilePath];
177
178 if ($result->isOK()) {
179 return $result;
180 }
181
183 'upload processing failed with message ' .
184 '"' . $result->getStatus()->getMessage() . '"'
185 );
186 }
187
188 throw new ilCmiXapiInvalidUploadContentException('upload lost during processing!');
189 }
190
191 throw new ilCmiXapiInvalidUploadContentException('no upload provided!');
192 }
193
197 protected function handleUpload(FileUploadResult $uploadResult): void
198 {
199 switch ($this->fetchFileExtension($uploadResult)) {
201
202 $this->handleXmlFileFromUpload($uploadResult->getName(), $uploadResult->getPath());
203 break;
204
206
207 $this->handleZipContentUpload($uploadResult->getPath());
208
209 if ($this->hasStoredContentXml()) {
210 $this->handleXmlFile($this->getStoredContentXml());
211 }
212
213 break;
214 }
215 }
216
220 protected function handleXmlFile(string $xmlFilePath): void
221 {
222 $dom = new DOMDocument();
223 $dom->load($xmlFilePath);
224
225 switch (basename($xmlFilePath)) {
226 case self::CMI5_XML:
227
228 $xsdFilePath = $this->getXsdFilePath(self::CMI5_XSD);
229 $this->validateXmlFile($dom, $xsdFilePath);
230
231 $this->initObjectFromCmi5Xml($dom);
232
233 break;
234
235 case self::TINCAN_XML:
236
237 $xsdFilePath = $this->getXsdFilePath(self::TINCAN_XSD);
238 $this->validateXmlFile($dom, $xsdFilePath);
239
240 $this->initObjectFromTincanXml($dom);
241
242 break;
243 }
244 }
245
249 protected function handleXmlFileFromUpload(string $xmlFileName, string $xmlFilePath): void
250 {
251 $dom = new DOMDocument();
252 $dom->load($xmlFilePath);
253 switch (basename($xmlFileName)) {
254 case self::CMI5_XML:
255
256 $xsdFilePath = $this->getXsdFilePath(self::CMI5_XSD);
257 $this->validateXmlFile($dom, $xsdFilePath);
258
259 $this->initObjectFromCmi5Xml($dom);
260
261 break;
262
263 case self::TINCAN_XML:
264
265 $xsdFilePath = $this->getXsdFilePath(self::TINCAN_XSD);
266 $this->validateXmlFile($dom, $xsdFilePath);
267
268 $this->initObjectFromTincanXml($dom);
269
270 break;
271 }
272 }
273
277 protected function validateXmlFile(DOMDocument $dom, $xsdFilePath): void
278 {
279 if (!$dom->schemaValidate($xsdFilePath)) {
280 throw new ilCmiXapiInvalidUploadContentException('invalid content xml given!');
281 }
282 }
283
284 protected function handleZipContentUpload(string $uploadFilePath): void
285 {
286 $targetPath = $this->getAbsoluteObjectDirectory();
287 $zar = new \ZipArchive();
288 $zar->open($uploadFilePath);
289 $zar->extractTo($targetPath);
290 $zar->close();
291 }
292
293 protected function getAbsoluteObjectDirectory(): string
294 {
295 $dirs = [
296 ILIAS_ABSOLUTE_PATH,
299 ];
300
301 return implode(DIRECTORY_SEPARATOR, $dirs);
302 }
303
304 public function getWebDataDirRelativeObjectDirectory(): string
305 {
306 return self::RELATIVE_CONTENT_DIRECTORY_NAMEBASE . $this->object->getId();
307 }
308
309 protected function fetchFileExtension(FileUploadResult $uploadResult): string
310 {
311 return pathinfo($uploadResult->getName(), PATHINFO_EXTENSION);
312 }
313
314 protected function hasStoredContentXml(): bool
315 {
316 return $this->getStoredContentXml() !== '';
317 }
318
319 protected function getStoredContentXml(): string
320 {
321 foreach (self::$CONTENT_XML_FILENAMES as $xmlFileName) {
322 $xmlFilePath = $this->getWebDataDirRelativeObjectDirectory() . DIRECTORY_SEPARATOR . $xmlFileName;
323
324 if ($this->dic->filesystem()->web()->has($xmlFilePath)) {
325 return $this->getAbsoluteObjectDirectory() . DIRECTORY_SEPARATOR . $xmlFileName;
326 }
327 }
328
329 return '';
330 }
331
332 protected function getXsdFilePath(string $xsdFileName): string
333 {
334 return ILIAS_ABSOLUTE_PATH . DIRECTORY_SEPARATOR . self::RELATIVE_XSD_DIRECTORY . DIRECTORY_SEPARATOR . $xsdFileName;
335 }
336
337 protected function initObjectFromCmi5Xml(DOMDocument $dom): void
338 {
339 $xPath = new DOMXPath($dom);
340
341 $courseNode = $xPath->query("//*[local-name()='course']")->item(0);
342 // TODO: multilanguage support
343 $title = $xPath->query("//*[local-name()='title']/*[local-name()='langstring']", $courseNode)->item(0)->nodeValue;
344 $this->object->setTitle(trim($title));
345
346 $description = $xPath->query("//*[local-name()='description']/*[local-name()='langstring']", $courseNode)->item(0)->nodeValue;
347 $this->object->setDescription(trim($description));
348
349 $publisherId = trim($courseNode->getAttribute('id'));
350 $this->object->setPublisherId($publisherId);
351
352 $activityId = $this->generateActivityId($publisherId);
353 $this->object->setActivityId($activityId);
354
355 $moveOn = '';
356
357 foreach ($xPath->query("//*[local-name()='au']") as $assignedUnitNode) {
358 $relativeLaunchUrl = $xPath->query("//*[local-name()='url']", $assignedUnitNode)->item(0)->nodeValue;
359 if (!empty($xPath->query("//*[local-name()='launchParameters']", $assignedUnitNode)->item(0)->nodeValue)) {
360 $launchParameters = $xPath->query(
361 "//*[local-name()='launchParameters']",
362 $assignedUnitNode
363 )->item(0)->nodeValue;
364 }
365 if (!empty($assignedUnitNode->getAttribute('moveOn'))) {
366 $moveOn = trim($assignedUnitNode->getAttribute('moveOn'));
367 }
368 if (!empty($xPath->query("//*[local-name()='entitlementKey']", $assignedUnitNode)->item(0)->nodeValue)) {
369 $entitlementKey = $xPath->query(
370 "//*[local-name()='entitlementKey']",
371 $assignedUnitNode
372 )->item(0)->nodeValue;
373 }
374 if (!empty($assignedUnitNode->getAttribute('masteryScore'))) {
375 $masteryScore = trim($assignedUnitNode->getAttribute('masteryScore'));
376 }
377
378 if (!empty($relativeLaunchUrl)) {
379 $this->object->setLaunchUrl(trim($relativeLaunchUrl));
380 }
381 if (!empty($launchParameters)) {
382 $this->object->setLaunchParameters(trim($launchParameters));
383 }
384 if (!empty($moveOn)) {
387 }
388 $this->object->setMoveOn($moveOn);
389 }
390 if (!empty($entitlementKey)) {
391 $this->object->setEntitlementKey($entitlementKey);
392 }
393 if (!empty($masteryScore)) {
394 $this->object->setMasteryScore((float) $masteryScore);
395 } else {
396 $this->object->setMasteryScore(ilObjCmiXapi::LMS_MASTERY_SCORE);
397 }
398
399 break; // TODO: manage multi au imports
400 }
401 $xml_str = $dom->saveXML();
402 $this->object->setXmlManifest($xml_str);
403 $this->object->update();
404 $this->object->save();
405
406 $lpSettings = new ilLPObjSettings($this->object->getId());
408
409 switch ($moveOn) {
412 break;
415 break;
418 break;
419 case ilCmiXapiLP::MOVEON_COMPLETED_AND_PASSED: // ich würde es noch implementieren
421 break;
422 }
423 $lpSettings->setMode($mode);
424 $lpSettings->update();
425 }
426
427 protected function initObjectFromTincanXml(DOMDocument $dom): void
428 {
429 $xPath = new DOMXPath($dom);
430
431 foreach ($xPath->query("//*[local-name()='activity']") as $activityNode) {
432 $title = $xPath->query("//*[local-name()='name']", $activityNode)->item(0)->nodeValue;
433 $this->object->setTitle(trim($title));
434
435 $description = $xPath->query("//*[local-name()='description']", $activityNode)->item(0)->nodeValue;
436 $this->object->setDescription(trim($description));
437
438 $activityId = $activityNode->getAttribute('id');
439 $this->object->setActivityId(trim($activityId));
440
441 $relativeLaunchUrl = $xPath->query("//*[local-name()='launch']", $activityNode)->item(0)->nodeValue;
442 $this->object->setLaunchUrl(trim($relativeLaunchUrl));
443
444 break; // TODO: manage multi activities imports
445 }
446
447 $xml_str = $dom->saveXML();
448 $this->object->setXmlManifest($xml_str);
449 $this->object->update();
450 $this->object->save();
451 }
452
453 private function generateActivityId(string $publisherId): string
454 {
455 global $DIC;
456 $objId = $this->object->getId();
457 return "https://ilias.de/cmi5/activityid/" . (new \Ramsey\Uuid\UuidFactory())->uuid3(ilCmiXapiUser::getIliasUuid(), $objId . '-' . $publisherId);
458 }
459}
Stream factory which enables the user to create streams without the knowledge of the concrete class.
Definition: Streams.php:32
__construct(ilObjCmiXapi $object)
ilCmiXapiContentUploadImporter constructor.
fetchFileExtension(FileUploadResult $uploadResult)
handleXmlFileFromUpload(string $xmlFileName, string $xmlFilePath)
const MOVEON_COMPLETED_OR_PASSED
const MOVEON_COMPLETED_AND_PASSED
static getWebspaceDir(string $mode="filesystem")
get webspace directory
static renameExecutables(string $a_dir)
Interface Location.
Definition: Location.php:33
$results
global $DIC
Definition: shib_login.php:26
$objId
Definition: xapitoken.php:57