1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4include_once("./Services/Export/classes/class.ilXmlImporter.php");
5
6/**
7 * Importer class for files
8 *
9 * @author Stefan Meyer <meyer@leifos.com>
10 * @version $Id$
11 * @ingroup ModulesLearningModule
12 */
13class ilTestImporter extends ilXmlImporter
14{
15    /**
16     * @var array
17     */
18    public static $finallyProcessedTestsRegistry = array();
19
20    /**
21     * Import XML
22     *
23     * @param
24     * @return
25     */
26    public function importXmlRepresentation($a_entity, $a_id, $a_xml, $a_mapping)
27    {
28        // Container import => test object already created
29        include_once "./Modules/Test/classes/class.ilObjTest.php";
30        ilObjTest::_setImportDirectory($this->getImportDirectoryContainer());
31
32        if ($new_id = $a_mapping->getMapping('Services/Container', 'objs', $a_id)) {
33            // container content
34            $newObj = ilObjectFactory::getInstanceByObjId($new_id, false);
35            $_SESSION['tst_import_subdir'] = $this->getImportPackageName();
36            $newObj->saveToDb(); // this generates test id first time
37            $questionParentObjId = $newObj->getId();
38            $newObj->setOfflineStatus(false);
39            $questionParentObjId = $newObj->getId();
40        } else {
41            // single object
42            $new_id = $a_mapping->getMapping('Modules/Test', 'tst', 'new_id');
43            $newObj = ilObjectFactory::getInstanceByObjId($new_id, false);
44
45            if (isset($_SESSION['tst_import_qst_parent'])) {
46                $questionParentObjId = $_SESSION['tst_import_qst_parent'];
47            } else {
48                $questionParentObjId = $newObj->getId();
49            }
50        }
51
52        $newObj->loadFromDb();
53
54        list($xml_file, $qti_file) = $this->parseXmlFileNames();
55
56        global $DIC; /* @var ILIAS\DI\Container $DIC */
57        if (!@file_exists($xml_file)) {
58            $DIC['ilLog']->write(__METHOD__ . ': Cannot find xml definition: ' . $xml_file);
59            return false;
60        }
61        if (!@file_exists($qti_file)) {
62            $DIC['ilLog']->write(__METHOD__ . ': Cannot find xml definition: ' . $qti_file);
63            return false;
64        }
65
66        /* @var ilObjTest $newObj */
67
68        // FIXME: Copied from ilObjTestGUI::importVerifiedFileObject
69        // TODO: move all logic to ilObjTest::importVerifiedFile and call
70        // this method from ilObjTestGUI and ilTestImporter
71        $newObj->mark_schema->flush();
72
73
74        if (isset($_SESSION['tst_import_idents'])) {
75            $idents = $_SESSION['tst_import_idents'];
76        } else {
77            $idents = null;
78        }
79
80        // start parsing of QTI files
81        include_once "./Services/QTI/classes/class.ilQTIParser.php";
82        $qtiParser = new ilQTIParser($qti_file, IL_MO_PARSE_QTI, $questionParentObjId, $idents);
83        $qtiParser->setTestObject($newObj);
84        $result = $qtiParser->startParsing();
85
86        // import page data
87        include_once("./Modules/LearningModule/classes/class.ilContObjParser.php");
88        $contParser = new ilContObjParser($newObj, $xml_file, basename($this->getImportDirectory()));
89        $contParser->setQuestionMapping($qtiParser->getImportMapping());
90        $contParser->startParsing();
91
92        foreach ($qtiParser->getQuestionIdMapping() as $oldQuestionId => $newQuestionId) {
93            $a_mapping->addMapping(
94                "Services/Taxonomy",
95                "tax_item",
96                "tst:quest:$oldQuestionId",
97                $newQuestionId
98            );
99
100            $a_mapping->addMapping(
101                "Services/Taxonomy",
102                "tax_item_obj_id",
103                "tst:quest:$oldQuestionId",
104                $newObj->getId()
105            );
106
107            $a_mapping->addMapping(
108                "Modules/Test",
109                "quest",
110                $oldQuestionId,
111                $newQuestionId
112            );
113        }
114
115        if ($newObj->isRandomTest()) {
116            $newObj->questions = array();
117            $this->importRandomQuestionSetConfig($newObj, $xml_file, $a_mapping);
118        }
119
120        // import test results
121        if (@file_exists($_SESSION["tst_import_results_file"])) {
122            include_once("./Modules/Test/classes/class.ilTestResultsImportParser.php");
123            $results = new ilTestResultsImportParser($_SESSION["tst_import_results_file"], $newObj);
124            $results->setQuestionIdMapping($a_mapping->getMappingsOfEntity('Modules/Test', 'quest'));
125            $results->setSrcPoolDefIdMapping($a_mapping->getMappingsOfEntity('Modules/Test', 'rnd_src_pool_def'));
126            $results->startParsing();
127        }
128
129        $newObj->saveToDb(); // this creates test_fi
130        $newObj->update(); // this saves ilObject data
131
132        // import skill assignments
133        $importedAssignmentList = $this->importQuestionSkillAssignments($a_mapping, $newObj, $xml_file);
134        $this->importSkillLevelThresholds($a_mapping, $importedAssignmentList, $newObj, $xml_file);
135
136        $a_mapping->addMapping("Modules/Test", "tst", $a_id, $newObj->getId());
137
138        ilObjTest::_setImportDirectory();
139    }
140
141    /**
142     * Final processing
143     *
144     * @param ilImportMapping $a_mapping
145     * @return
146     */
147    public function finalProcessing($a_mapping)
148    {
149        $maps = $a_mapping->getMappingsOfEntity("Modules/Test", "tst");
150
151        foreach ($maps as $old => $new) {
152            if ($old == "new_id" || (int) $old <= 0) {
153                continue;
154            }
155
156            if (isset(self::$finallyProcessedTestsRegistry[$new])) {
157                continue;
158            }
159
160            /* @var ilObjTest $testOBJ */
161            $testOBJ = ilObjectFactory::getInstanceByObjId($new, false);
162            if ($testOBJ->isRandomTest()) {
163                $this->finalRandomTestTaxonomyProcessing($a_mapping, $old, $new, $testOBJ);
164            }
165
166            self::$finallyProcessedTestsRegistry[$new] = true;
167        }
168    }
169
170    protected function finalRandomTestTaxonomyProcessing(ilImportMapping $mapping, $oldTstObjId, $newTstObjId, ilObjTest $testOBJ)
171    {
172        require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php';
173
174        // get all new taxonomies of this object and store usage for test object
175
176        $new_tax_ids = $mapping->getMapping(
177            'Services/Taxonomy',
178            'tax_usage_of_obj',
179            $oldTstObjId
180        );
181
182        if ($new_tax_ids !== false) {
183            $tax_ids = explode(":", $new_tax_ids);
184
185            foreach ($tax_ids as $tid) {
186                ilObjTaxonomy::saveUsage($tid, $newTstObjId);
187            }
188        }
189
190        // update all source pool definition's tax/taxNode ids with new mapped id
191        global $DIC; /* @var ILIAS\DI\Container $DIC */
192        $ilDB = $DIC['ilDB'];
193
194        require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionFactory.php';
195        $srcPoolDefFactory = new ilTestRandomQuestionSetSourcePoolDefinitionFactory(
196            $ilDB,
197            $testOBJ
198        );
199
200        require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionList.php';
201        $srcPoolDefList = new ilTestRandomQuestionSetSourcePoolDefinitionList(
202            $ilDB,
203            $testOBJ,
204            $srcPoolDefFactory
205        );
206
207        $srcPoolDefList->loadDefinitions();
208
209        foreach ($srcPoolDefList as $definition) {
210            // #21330
211            if (!is_array($definition->getMappedTaxonomyFilter()) || 0 === count($definition->getMappedTaxonomyFilter())) {
212                continue;
213            }
214
215            $definition->setMappedTaxonomyFilter(
216                $this->getNewMappedTaxonomyFilter(
217                    $mapping,
218                    $definition->getMappedTaxonomyFilter()
219                )
220            );
221            $definition->saveToDb();
222        }
223    }
224
225    /**
226     * @param ilImportMapping $mapping
227     * @param  array $mappedFilter
228     * @return array
229     */
230    protected function getNewMappedTaxonomyFilter(ilImportMapping $mapping, array $mappedFilter)
231    {
232        $newMappedFilter = array();
233
234        foreach ($mappedFilter as $taxId => $taxNodes) {
235            $newTaxId = $mapping->getMapping(
236                'Services/Taxonomy',
237                'tax',
238                $taxId
239            );
240
241            if (!$newTaxId) {
242                continue;
243            }
244
245            $newMappedFilter[$newTaxId] = array();
246
247            foreach ($taxNodes as $taxNodeId) {
248                $newTaxNodeId = $mapping->getMapping(
249                    'Services/Taxonomy',
250                    'tax_tree',
251                    $taxNodeId
252                );
253
254                if (!$newTaxNodeId) {
255                    continue;
256                }
257
258                $newMappedFilter[$newTaxId][] = $newTaxNodeId;
259            }
260        }
261
262        return $newMappedFilter;
263    }
264
265    /**
266     * Create qti and xml file name
267     * @return array
268     */
269    protected function parseXmlFileNames()
270    {
271        global $DIC; /* @var ILIAS\DI\Container $DIC */
272        $DIC['ilLog']->write(__METHOD__ . ': ' . $this->getImportDirectory());
273
274        $basename = basename($this->getImportDirectory());
275
276        $xml = $this->getImportDirectory() . '/' . $basename . '.xml';
277        $qti = $this->getImportDirectory() . '/' . preg_replace('/test|tst/', 'qti', $basename) . '.xml';
278
279        return array($xml,$qti);
280    }
281
282    private function getImportDirectoryContainer()
283    {
284        $dir = $this->getImportDirectory();
285        $dir = dirname($dir);
286        return $dir;
287    }
288
289    private function getImportPackageName()
290    {
291        $dir = $this->getImportDirectory();
292        $name = basename($dir);
293        return $name;
294    }
295
296    protected function importRandomQuestionSetConfig(ilObjTest $testOBJ, $xmlFile, $a_mapping)
297    {
298        require_once 'Modules/Test/classes/class.ilObjTestXMLParser.php';
299        $parser = new ilObjTestXMLParser($xmlFile);
300        $parser->setTestOBJ($testOBJ);
301        $parser->setImportMapping($a_mapping);
302        $parser->startParsing();
303    }
304
305    /**
306     * @param ilImportMapping $mappingRegistry
307     * @param ilObjTest $testOBJ
308     * @param string $xmlfile
309     * @return ilAssQuestionSkillAssignmentList
310     */
311    protected function importQuestionSkillAssignments(ilImportMapping $mapping, ilObjTest $testOBJ, $xmlFile)
312    {
313        require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssQuestionSkillAssignmentXmlParser.php';
314        $parser = new ilAssQuestionSkillAssignmentXmlParser($xmlFile);
315        $parser->startParsing();
316
317        require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssQuestionSkillAssignmentImporter.php';
318        $importer = new ilAssQuestionSkillAssignmentImporter();
319        $importer->setTargetParentObjId($testOBJ->getId());
320        $importer->setImportInstallationId($this->getInstallId());
321        $importer->setImportMappingRegistry($mapping);
322        $importer->setImportMappingComponent('Modules/Test');
323        $importer->setImportAssignmentList($parser->getAssignmentList());
324
325        $importer->import();
326
327        if ($importer->getFailedImportAssignmentList()->assignmentsExist()) {
328            require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssQuestionSkillAssignmentImportFails.php';
329            $qsaImportFails = new ilAssQuestionSkillAssignmentImportFails($testOBJ->getId());
330            $qsaImportFails->registerFailedImports($importer->getFailedImportAssignmentList());
331
332            $testOBJ->setOnline(false);
333        }
334
335        return $importer->getSuccessImportAssignmentList();
336    }
337
338    /**
339     * @param ilImportMapping $mapping
340     * @param ilAssQuestionSkillAssignmentList $assignmentList
341     * @param ilObjTest $testOBJ
342     * @param $xmlFile
343     */
344    protected function importSkillLevelThresholds(ilImportMapping $mapping, ilAssQuestionSkillAssignmentList $assignmentList, ilObjTest $testOBJ, $xmlFile)
345    {
346        require_once 'Modules/Test/classes/class.ilTestSkillLevelThresholdXmlParser.php';
347        $parser = new ilTestSkillLevelThresholdXmlParser($xmlFile);
348        $parser->startParsing();
349
350        require_once 'Modules/Test/classes/class.ilTestSkillLevelThresholdImporter.php';
351        $importer = new ilTestSkillLevelThresholdImporter();
352        $importer->setTargetTestId($testOBJ->getTestId());
353        $importer->setImportInstallationId($this->getInstallId());
354        $importer->setImportMappingRegistry($mapping);
355        $importer->setImportedQuestionSkillAssignmentList($assignmentList);
356        $importer->setImportThresholdList($parser->getSkillLevelThresholdImportList());
357        $importer->import();
358
359        if ($importer->getFailedThresholdImportSkillList()->skillsExist()) {
360            require_once 'Modules/Test/classes/class.ilTestSkillLevelThresholdImportFails.php';
361            $sltImportFails = new ilTestSkillLevelThresholdImportFails($testOBJ->getId());
362            $sltImportFails->registerFailedImports($importer->getFailedThresholdImportSkillList());
363
364            $testOBJ->setOfflineStatus(true);
365        }
366    }
367}
368