1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4require_once 'Modules/TestQuestionPool/classes/class.ilQuestionPoolTaxonomiesDuplicator.php'; 5require_once 'Modules/TestQuestionPool/classes/class.assQuestion.php'; 6require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php'; 7 8/** 9 * @author Björn Heyser <bheyser@databay.de> 10 * @version $Id$ 11 * 12 * @package Modules/Test 13 */ 14class ilTestRandomQuestionSetStagingPoolBuilder 15{ 16 /** 17 * @var ilDBInterface 18 */ 19 public $db = null; 20 21 /** 22 * @var ilObjTest 23 */ 24 public $testOBJ = null; 25 26 public function __construct(ilDBInterface $db, ilObjTest $testOBJ) 27 { 28 $this->db = $db; 29 $this->testOBJ = $testOBJ; 30 } 31 32 // ================================================================================================================= 33 34 public function rebuild(ilTestRandomQuestionSetSourcePoolDefinitionList $sourcePoolDefinitionList) 35 { 36 $this->reset(); 37 38 // fau: taxFilter/typeFilter - copy only the needed questions, and copy every question only once 39 // TODO-RND2017: remove non cheap methods and rename cheap ones 40 #$this->build($sourcePoolDefinitionList); 41 $this->buildCheap($sourcePoolDefinitionList); 42 // fau. 43 } 44 45 public function reset() 46 { 47 $this->removeMirroredTaxonomies(); 48 49 $this->removeStagedQuestions(); 50 51 $this->cleanupTestSettings(); 52 } 53 54 private function removeMirroredTaxonomies() 55 { 56 $taxonomyIds = ilObjTaxonomy::getUsageOfObject($this->testOBJ->getId()); 57 58 foreach ($taxonomyIds as $taxId) { 59 $taxonomy = new ilObjTaxonomy($taxId); 60 $taxonomy->delete(); 61 } 62 } 63 64 private function removeStagedQuestions() 65 { 66 $query = 'SELECT * FROM tst_rnd_cpy WHERE tst_fi = %s'; 67 $res = $this->db->queryF( 68 $query, 69 array('integer'), 70 array($this->testOBJ->getTestId()) 71 ); 72 73 while ($row = $this->db->fetchAssoc($res)) { 74 $question = assQuestion::_instanciateQuestion($row['qst_fi']); 75 76 if ($question instanceof assQuestion) { 77 $question->delete($row['qst_fi']); 78 } else { 79 global $DIC; /* @var ILIAS\DI\Container $DIC */ 80 $DIC['ilLog']->warning( 81 "could not delete staged random question (ref={$this->testOBJ->getRefId()} / qst={$row['qst_fi']})" 82 ); 83 } 84 } 85 86 $query = "DELETE FROM tst_rnd_cpy WHERE tst_fi = %s"; 87 $this->db->manipulateF($query, array('integer'), array($this->testOBJ->getTestId())); 88 } 89 90 private function build(ilTestRandomQuestionSetSourcePoolDefinitionList $sourcePoolDefinitionList) 91 { 92 $involvedSourcePoolIds = $sourcePoolDefinitionList->getInvolvedSourcePoolIds(); 93 94 foreach ($involvedSourcePoolIds as $sourcePoolId) { 95 $questionIdMapping = $this->stageQuestionsFromSourcePool($sourcePoolId); 96 97 $taxonomiesKeysMap = $this->mirrorSourcePoolTaxonomies($sourcePoolId, $questionIdMapping); 98 99 $this->applyMappedTaxonomiesKeys($sourcePoolDefinitionList, $taxonomiesKeysMap, $sourcePoolId); 100 } 101 } 102 103 private function stageQuestionsFromSourcePool($sourcePoolId) 104 { 105 $questionIdMapping = array(); 106 107 $query = 'SELECT question_id FROM qpl_questions WHERE obj_fi = %s AND complete = %s AND original_id IS NULL'; 108 $res = $this->db->queryF($query, array('integer', 'text'), array($sourcePoolId, 1)); 109 110 while ($row = $this->db->fetchAssoc($res)) { 111 $question = assQuestion::_instanciateQuestion($row['question_id']); 112 $duplicateId = $question->duplicate(true, null, null, null, $this->testOBJ->getId()); 113 114 $nextId = $this->db->nextId('tst_rnd_cpy'); 115 $this->db->insert('tst_rnd_cpy', array( 116 'copy_id' => array('integer', $nextId), 117 'tst_fi' => array('integer', $this->testOBJ->getTestId()), 118 'qst_fi' => array('integer', $duplicateId), 119 'qpl_fi' => array('integer', $sourcePoolId) 120 )); 121 122 $questionIdMapping[ $row['question_id'] ] = $duplicateId; 123 } 124 125 return $questionIdMapping; 126 } 127 128 // fau: taxFilter/typeFilter - select only the needed questions, and copy every question only once 129 private function buildCheap(ilTestRandomQuestionSetSourcePoolDefinitionList $sourcePoolDefinitionList) 130 { 131 // TODO-RND2017: refactor using assQuestionList and wrap with assQuestionListCollection for unioning 132 133 $questionIdMappingPerPool = array(); 134 135 // select questions to be copied by the definitions 136 // note: a question pool may appear many times in this list 137 138 /* @var ilTestRandomQuestionSetSourcePoolDefinition $definition */ 139 foreach ($sourcePoolDefinitionList as $definition) { 140 $taxFilter = $definition->getOriginalTaxonomyFilter(); 141 $typeFilter = $definition->getTypeFilter(); 142 $lifecycleFilter = $definition->getLifecycleFilter(); 143 144 if (!empty($taxFilter)) { 145 require_once 'Services/Taxonomy/classes/class.ilObjTaxonomy.php'; 146 147 $filterItems = null; 148 foreach ($taxFilter as $taxId => $nodeIds) { 149 $taxItems = array(); 150 foreach ($nodeIds as $nodeId) { 151 $nodeItems = ilObjTaxonomy::getSubTreeItems( 152 'qpl', 153 $definition->getPoolId(), 154 'quest', 155 $taxId, 156 $nodeId 157 ); 158 159 foreach ($nodeItems as $nodeItem) { 160 $taxItems[] = $nodeItem['item_id']; 161 } 162 } 163 164 $filterItems = isset($filterItems) ? array_intersect($filterItems, array_unique($taxItems)) : array_unique($taxItems); 165 } 166 167 // stage only the questions applying to the tax/type filter 168 // and save the duplication map for later use 169 170 $questionIdMappingPerPool = $this->stageQuestionsFromSourcePoolCheap( 171 $definition->getPoolId(), 172 $questionIdMappingPerPool, 173 array_values($filterItems), 174 $typeFilter, 175 $lifecycleFilter 176 ); 177 } else { 178 // stage only the questions applying to the tax/type filter 179 // and save the duplication map for later use 180 181 $questionIdMappingPerPool = $this->stageQuestionsFromSourcePoolCheap( 182 $definition->getPoolId(), 183 $questionIdMappingPerPool, 184 null, 185 $typeFilter, 186 $lifecycleFilter 187 ); 188 } 189 } 190 191 // copy the taxonomies to the test and map them 192 foreach ($questionIdMappingPerPool as $sourcePoolId => $questionIdMapping) { 193 $taxonomiesKeysMap = $this->mirrorSourcePoolTaxonomies($sourcePoolId, $questionIdMapping); 194 $this->applyMappedTaxonomiesKeys($sourcePoolDefinitionList, $taxonomiesKeysMap, $sourcePoolId); 195 } 196 } 197 198 private function stageQuestionsFromSourcePoolCheap($sourcePoolId, $questionIdMappingPerPool, $filterIds = null, $typeFilter = null, $lifecycleFilter = null) 199 { 200 $query = 'SELECT question_id FROM qpl_questions WHERE obj_fi = %s AND complete = %s AND original_id IS NULL'; 201 if (!empty($filterIds)) { 202 $query .= ' AND ' . $this->db->in('question_id', $filterIds, false, 'integer'); 203 } 204 if (!empty($typeFilter)) { 205 $query .= ' AND ' . $this->db->in('question_type_fi', $typeFilter, false, 'integer'); 206 } 207 if (!empty($lifecycleFilter)) { 208 $query .= ' AND ' . $this->db->in('lifecycle', $lifecycleFilter, false, 'text'); 209 } 210 $res = $this->db->queryF($query, array('integer', 'text'), array($sourcePoolId, 1)); 211 212 while ($row = $this->db->fetchAssoc($res)) { 213 if (!isset($questionIdMappingPerPool[$sourcePoolId])) { 214 $questionIdMappingPerPool[$sourcePoolId] = array(); 215 } 216 if (!isset($questionIdMappingPerPool[$sourcePoolId][ $row['question_id'] ])) { 217 $question = assQuestion::_instantiateQuestion($row['question_id']); 218 $duplicateId = $question->duplicate(true, null, null, null, $this->testOBJ->getId()); 219 220 $nextId = $this->db->nextId('tst_rnd_cpy'); 221 $this->db->insert('tst_rnd_cpy', array( 222 'copy_id' => array('integer', $nextId), 223 'tst_fi' => array('integer', $this->testOBJ->getTestId()), 224 'qst_fi' => array('integer', $duplicateId), 225 'qpl_fi' => array('integer', $sourcePoolId) 226 )); 227 228 $questionIdMappingPerPool[$sourcePoolId][ $row['question_id'] ] = $duplicateId; 229 } 230 } 231 232 return $questionIdMappingPerPool; 233 } 234 // fau. 235 236 private function mirrorSourcePoolTaxonomies($sourcePoolId, $questionIdMapping) 237 { 238 $duplicator = new ilQuestionPoolTaxonomiesDuplicator(); 239 240 $duplicator->setSourceObjId($sourcePoolId); 241 $duplicator->setSourceObjType('qpl'); 242 $duplicator->setTargetObjId($this->testOBJ->getId()); 243 $duplicator->setTargetObjType($this->testOBJ->getType()); 244 $duplicator->setQuestionIdMapping($questionIdMapping); 245 246 $duplicator->duplicate($duplicator->getAllTaxonomiesForSourceObject()); 247 248 return $duplicator->getDuplicatedTaxonomiesKeysMap(); 249 } 250 251 /** 252 * @param ilTestRandomQuestionSetSourcePoolDefinitionList $sourcePoolDefinitionList 253 * @param ilQuestionPoolDuplicatedTaxonomiesKeysMap $taxonomiesKeysMap 254 * @param integer $sourcePoolId 255 */ 256 private function applyMappedTaxonomiesKeys(ilTestRandomQuestionSetSourcePoolDefinitionList $sourcePoolDefinitionList, ilQuestionPoolDuplicatedTaxonomiesKeysMap $taxonomiesKeysMap, $sourcePoolId) 257 { 258 foreach ($sourcePoolDefinitionList as $definition) { 259 /** @var ilTestRandomQuestionSetSourcePoolDefinition $definition */ 260 261 if ($definition->getPoolId() == $sourcePoolId) { 262 // fau: taxFilter/typeFilter - map the enhanced taxonomy filter 263 #$definition->setMappedFilterTaxId( 264 # $taxonomiesKeysMap->getMappedTaxonomyId($definition->getOriginalFilterTaxId()) 265 #); 266 267 #$definition->setMappedFilterTaxNodeId( 268 # $taxonomiesKeysMap->getMappedTaxNodeId($definition->getOriginalFilterTaxNodeId()) 269 #); 270 271 $definition->mapTaxonomyFilter($taxonomiesKeysMap); 272 // fau. 273 } 274 } 275 } 276 277 private function cleanupTestSettings() 278 { 279 $this->testOBJ->setResultFilterTaxIds(array()); 280 $this->testOBJ->saveToDb(true); 281 } 282 283 // ================================================================================================================= 284} 285