ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilObjQuestionPool.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
23
34{
37
38 private array $mob_ids;
39 private array $file_ids;
42
43 public function __construct(int $a_id = 0, bool $a_call_by_reference = true)
44 {
45 global $DIC;
46 $this->component_repository = $DIC['component.repository'];
47 $this->benchmark = $DIC['ilBench'];
48
49 $local_dic = QuestionPoolDIC::dic();
50 $this->questionrepository = $local_dic['question.general_properties.repository'];
51
52 $this->type = 'qpl';
53
54 parent::__construct((int) $a_id, $a_call_by_reference);
55
56 $this->skill_service_enabled = false;
57 }
58
62 public function create($a_upload = false): int
63 {
64 $id = parent::create();
65
66 // meta data will be created by
67 // import parser
68 if (!$a_upload) {
69 $this->createMetaData();
70 }
71 return $id;
72 }
73
80 public function createReference(): int
81 {
82 $result = parent::createReference();
83 $this->saveToDb();
84 return $result;
85 }
86
93 public function update(): bool
94 {
95 $this->updateMetaData();
96 if (!parent::update()) {
97 return false;
98 }
99
100 // put here object specific stuff
101
102 return true;
103 }
104
110 public function read($a_force_db = false): void
111 {
112 parent::read($a_force_db);
113 $this->loadFromDb();
114 }
115
122 public function delete(): bool
123 {
124 // always call parent delete function first!!
125 if (!parent::delete()) {
126 return false;
127 }
128
129 // delete meta data
130 $this->deleteMetaData();
131
132 //put here your module specific stuff
133 $this->deleteQuestionpool();
134
135 $qsaImportFails = new ilAssQuestionSkillAssignmentImportFails($this->getId());
136 $qsaImportFails->deleteRegisteredImportFails();
137
138 return true;
139 }
140
141 public function deleteQuestionpool(): void
142 {
143 $questions = &$this->getAllQuestions();
144
145 if (count($questions)) {
146 foreach ($questions as $question_id) {
147 $this->deleteQuestion($question_id);
148 }
149 }
150
151 $qpl_data_dir = ilFileUtils::getDataDir() . '/qpl_data';
152 $directory = $qpl_data_dir . '/qpl_' . $this->getId();
153 if (is_dir($directory)) {
154 ilFileUtils::delDir($directory);
155 }
156 }
157
158 public function deleteQuestion(int $question_id): void
159 {
160 $question = assQuestion::instantiateQuestion($question_id);
161 $question->delete($question_id);
162 }
163
164 public function loadFromDb(): void
165 {
166 $result = $this->db->queryF(
167 'SELECT * FROM qpl_questionpool WHERE obj_fi = %s',
168 ['integer'],
169 [$this->getId()]
170 );
171 if ($result->numRows() == 1) {
172 $row = $this->db->fetchAssoc($result);
173 $this->setSkillServiceEnabled((bool) $row['skill_service']);
174 }
175 }
176
177 public function saveToDb(): void
178 {
179 $result = $this->db->queryF(
180 'SELECT id_questionpool FROM qpl_questionpool WHERE obj_fi = %s',
181 ['integer'],
182 [$this->getId()]
183 );
184
185 if ($result->numRows() == 1) {
186 $result = $this->db->update(
187 'qpl_questionpool',
188 [
189 'skill_service' => ['integer', (int) $this->isSkillServiceEnabled()],
190 'tstamp' => ['integer', time()]
191 ],
192 [
193 'obj_fi' => ['integer', $this->getId()]
194 ]
195 );
196 } else {
197 $next_id = $this->db->nextId('qpl_questionpool');
198
199 $result = $this->db->insert('qpl_questionpool', [
200 'id_questionpool' => ['integer', $next_id],
201 'skill_service' => ['integer', (int) $this->isSkillServiceEnabled()],
202 'tstamp' => ['integer', time()],
203 'obj_fi' => ['integer', $this->getId()]
204 ]);
205 }
206 }
207
208 public function getQuestiontype($question_id)
209 {
210 if ($question_id < 1) {
211 return null;
212 }
213
214 $result = $this->db->queryF(
215 'SELECT qpl_qst_type.type_tag FROM qpl_questions, qpl_qst_type WHERE qpl_questions.question_type_fi = qpl_qst_type.question_type_id AND qpl_questions.question_id = %s',
216 ['integer'],
217 [$question_id]
218 );
219
220 if ($result->numRows() == 1) {
221 $data = $this->db->fetchAssoc($result);
222 return $data['type_tag'];
223 }
224 return null;
225 }
226
227 public function isInUse(int $question_id): bool
228 {
229 $result = $this->db->queryF(
230 'SELECT COUNT(solution_id) solution_count FROM tst_solutions WHERE question_fi = %s',
231 ['integer'],
232 [$question_id]
233 );
234 $row = $this->db->fetchAssoc($result);
235 return $row['solution_count'];
236 }
237
238 public function createQuestion(string $question_type, int $question_id = -1)
239 {
240 if ($question_id > 0) {
241 return assQuestion::instantiateQuestionGUI($question_id);
242 }
243 $question_type_gui = $question_type . 'GUI';
244 $question_gui = new $question_type_gui();
245 return $question_gui;
246 }
247
248 public function duplicateQuestion(int $question_id): int
249 {
250 $question = $this->createQuestion('', $question_id);
252 $question->getObject()->getTitle()
253 );
254 $new_id = $question->getObject()->duplicate(false, $newtitle);
256 return $new_id;
257 }
258
259 public function copyQuestion(int $question_id, int $questionpool_to): int
260 {
261 $question_gui = $this->createQuestion('', $question_id);
262 if ($question_gui->getObject()->getObjId() == $questionpool_to) {
263 // the question is copied into the same question pool
264 return $this->duplicateQuestion($question_id);
265 } else {
266 // the question is copied into another question pool
268 $question_gui->getObject()->getTitle()
269 );
270
271 return $question_gui->getObject()->copyObject($this->getId(), $newtitle);
272 }
273 }
274
275 public function appendCounterToQuestionTitleIfNecessary(string $title): string
276 {
277 $result = $this->db->queryF(
278 "SELECT COUNT(question_id) AS cnt FROM qpl_questions WHERE obj_fi = %s AND title like %s",
279 ['integer','text'],
280 [$this->getId(), "{$title}%"]
281 );
282 $counter_object = $this->db->fetchObject($result);
283 if ($counter_object->cnt === 0) {
284 return $title;
285 }
286 return "{$title} ({$counter_object->cnt})";
287 }
288
289 public function getPrintviewQuestions(): array
290 {
291 $query_result = $this->db->queryF(
292 'SELECT qpl_questions.*, qpl_qst_type.type_tag, qpl_qst_type.plugin, qpl_questions.tstamp updated FROM qpl_questions, qpl_qst_type WHERE qpl_questions.original_id IS NULL AND qpl_questions.tstamp > 0 AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id AND qpl_questions.obj_fi = %s',
293 ['integer'],
294 [$this->getId()]
295 );
296 $rows = [];
297 $types = $this->getQuestionTypeTranslations();
298 if ($query_result->numRows()) {
299 while ($row = $this->db->fetchAssoc($query_result)) {
300 $row['ttype'] = $types[$row['type_tag']];
301 if ($row['plugin']) {
302 if ($this->isPluginActive($row['type_tag'])) {
303 array_push($rows, $row);
304 }
305 } else {
306 array_push($rows, $row);
307 }
308 }
309 }
310 return $rows;
311 }
312
316 private function exportXMLSettings($xmlWriter): void
317 {
318 $xmlWriter->xmlStartTag('Settings');
319 $xmlWriter->xmlElement('SkillService', null, (int) $this->isSkillServiceEnabled());
320 $xmlWriter->xmlEndTag('Settings');
321 }
322
329 public function objectToXmlWriter(ilXmlWriter &$a_xml_writer, $a_inst, $a_target_dir, &$expLog, $questions): void
330 {
331 $ilBench = $this->benchmark;
332
333 $this->mob_ids = [];
334 $this->file_ids = [];
335
336 $attrs = [];
337 $attrs['Type'] = 'Questionpool_Test';
338 $a_xml_writer->xmlStartTag('ContentObject', $attrs);
339
340 // MetaData
341 $this->exportTitleAndDescription($a_xml_writer);
342
343 // Settings
344 $this->exportXMLSettings($a_xml_writer);
345
346 // PageObjects
347 $expLog->write(date('[y-m-d H:i:s] ') . 'Start Export Page Objects');
348 $ilBench->start('ContentObjectExport', 'exportPageObjects');
349 $this->exportXMLPageObjects($a_xml_writer, $a_inst, $expLog, $questions);
350 $ilBench->stop('ContentObjectExport', 'exportPageObjects');
351 $expLog->write(date('[y-m-d H:i:s] ') . 'Finished Export Page Objects');
352
353 // MediaObjects
354 $expLog->write(date('[y-m-d H:i:s] ') . 'Start Export Media Objects');
355 $ilBench->start('ContentObjectExport', 'exportMediaObjects');
356 $this->exportXMLMediaObjects($a_xml_writer, $a_inst, $a_target_dir, $expLog);
357 $ilBench->stop('ContentObjectExport', 'exportMediaObjects');
358 $expLog->write(date('[y-m-d H:i:s] ') . 'Finished Export Media Objects');
359
360 // FileItems
361 $expLog->write(date('[y-m-d H:i:s] ') . 'Start Export File Items');
362 $ilBench->start('ContentObjectExport', 'exportFileItems');
363 $this->exportFileItems($a_target_dir, $expLog);
364 $ilBench->stop('ContentObjectExport', 'exportFileItems');
365 $expLog->write(date('[y-m-d H:i:s] ') . 'Finished Export File Items');
366
367 // skill assignments
368 $this->populateQuestionSkillAssignmentsXml($a_xml_writer, $questions);
369
370 $a_xml_writer->xmlEndTag('ContentObject');
371 }
372
377 protected function populateQuestionSkillAssignmentsXml(ilXmlWriter &$a_xml_writer, $questions): void
378 {
379 $assignmentList = new ilAssQuestionSkillAssignmentList($this->db);
380 $assignmentList->setParentObjId($this->getId());
381 $assignmentList->loadFromDb();
382 $assignmentList->loadAdditionalSkillData();
383
384 $skillQuestionAssignmentExporter = new ilAssQuestionSkillAssignmentExporter();
385 $skillQuestionAssignmentExporter->setXmlWriter($a_xml_writer);
386 $skillQuestionAssignmentExporter->setQuestionIds($questions);
387 $skillQuestionAssignmentExporter->setAssignmentList($assignmentList);
388 $skillQuestionAssignmentExporter->export();
389 }
390
391 public function exportTitleAndDescription(ilXmlWriter &$a_xml_writer): void
392 {
393 $a_xml_writer->xmlElement('Title', null, $this->getTitle());
394 $a_xml_writer->xmlElement('Description', null, $this->getDescription());
395 }
396
397 public function modifyExportIdentifier($a_tag, $a_param, $a_value)
398 {
399 if ($a_tag == 'Identifier' && $a_param == 'Entry') {
400 $a_value = ilUtil::insertInstIntoID($a_value);
401 }
402
403 return $a_value;
404 }
405
412 public function exportXMLPageObjects(&$a_xml_writer, $a_inst, &$expLog, $questions): void
413 {
414 $ilBench = $this->benchmark;
415
416 foreach ($questions as $question_id) {
417 $ilBench->start('ContentObjectExport', 'exportPageObject');
418 $expLog->write(date('[y-m-d H:i:s] ') . 'Page Object ' . $question_id);
419
420 $attrs = [];
421 $a_xml_writer->xmlStartTag('PageObject', $attrs);
422
423 // export xml to writer object
424 $ilBench->start('ContentObjectExport', 'exportPageObject_XML');
425 $page_object = new ilAssQuestionPage($question_id);
426 $page_object->buildDom();
427 $page_object->insertInstIntoIDs($a_inst);
428 $mob_ids = $page_object->collectMediaObjects(false);
429 $file_ids = ilPCFileList::collectFileItems($page_object, $page_object->getDomDoc());
430 $xml = $page_object->getXMLFromDom(false, false, false, '', true);
431 $xml = str_replace('&', '&amp;', $xml);
432 $a_xml_writer->appendXML($xml);
433 $page_object->freeDom();
434 unset($page_object);
435 $ilBench->stop('ContentObjectExport', 'exportPageObject_XML');
436
437 $ilBench->start("ContentObjectExport", "exportPageObject_CollectMedia");
438 foreach ($mob_ids as $mob_id) {
439 $this->mob_ids[$mob_id] = $mob_id;
440 }
441 $ilBench->stop('ContentObjectExport', 'exportPageObject_CollectMedia');
442
443 // collect all file items
444 $ilBench->start('ContentObjectExport', 'exportPageObject_CollectFileItems');
445 //$file_ids = $page_obj->getFileItemIds();
446 foreach ($file_ids as $file_id) {
447 $this->file_ids[$file_id] = $file_id;
448 }
449 $ilBench->stop('ContentObjectExport', 'exportPageObject_CollectFileItems');
450
451 $a_xml_writer->xmlEndTag("PageObject");
452
453 $ilBench->stop('ContentObjectExport', 'exportPageObject');
454 }
455 }
456
457 public function exportXMLMediaObjects(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog): void
458 {
459 foreach ($this->mob_ids as $mob_id) {
460 $expLog->write(date('[y-m-d H:i:s] ') . 'Media Object ' . $mob_id);
461 if (ilObjMediaObject::_exists((int) $mob_id)) {
462 $target_dir = $a_target_dir . DIRECTORY_SEPARATOR . 'objects'
463 . DIRECTORY_SEPARATOR . 'il_' . IL_INST_ID . '_mob_' . $mob_id;
464 ilFileUtils::createDirectory($target_dir);
465 $media_obj = new ilObjMediaObject((int) $mob_id);
466 $media_obj->exportXML($a_xml_writer, (int) $a_inst);
467 foreach ($media_obj->getMediaItems() as $item) {
468 $stream = $item->getLocationStream();
469 file_put_contents($target_dir . DIRECTORY_SEPARATOR . $item->getLocation(), $stream);
470 $stream->close();
471 }
472 unset($media_obj);
473 }
474 }
475 }
476
481 public function exportFileItems($target_dir, &$expLog): void
482 {
483 foreach ($this->file_ids as $file_id) {
484 $expLog->write(date("[y-m-d H:i:s] ") . "File Item " . $file_id);
485 $file_dir = $target_dir . '/objects/il_' . IL_INST_ID . '_file_' . $file_id;
486 ilFileUtils::makeDir($file_dir);
487 $file_obj = new ilObjFile((int) $file_id, false);
488 $source_file = $file_obj->getFile($file_obj->getVersion());
489 if (!is_file($source_file)) {
490 $source_file = $file_obj->getFile();
491 }
492 if (is_file($source_file)) {
493 copy($source_file, $file_dir . '/' . $file_obj->getFileName());
494 }
495 unset($file_obj);
496 }
497 }
498
504 public function createExportDirectory(): void
505 {
506 $qpl_data_dir = ilFileUtils::getDataDir() . '/qpl_data';
507 ilFileUtils::makeDir($qpl_data_dir);
508 if (!is_writable($qpl_data_dir)) {
509 $this->error->raiseError(
510 'Questionpool Data Directory (' . $qpl_data_dir
511 . ') not writeable.',
512 $this->error->FATAL
513 );
514 }
515
516 // create learning module directory (data_dir/lm_data/lm_<id>)
517 $qpl_dir = $qpl_data_dir . '/qpl_' . $this->getId();
518 ilFileUtils::makeDir($qpl_dir);
519 if (!@is_dir($qpl_dir)) {
520 $this->error->raiseError('Creation of Questionpool Directory failed.', $this->error->FATAL);
521 }
522 // create Export subdirectory (data_dir/lm_data/lm_<id>/Export)
524 if (!@is_dir($this->getExportDirectory('xlsx'))) {
525 $this->error->raiseError('Creation of Export Directory failed.', $this->error->FATAL);
526 }
528 if (!@is_dir($this->getExportDirectory('zip'))) {
529 $this->error->raiseError('Creation of Export Directory failed.', $this->error->FATAL);
530 }
531 }
532
536 public function getExportDirectory($type = ''): string
537 {
538 switch ($type) {
539 case 'xml':
540 $export_dir = ilExport::_getExportDirectory($this->getId(), $type, $this->getType());
541 break;
542 case 'xlsx':
543 case 'zip':
544 $export_dir = ilFileUtils::getDataDir() . "/qpl_data/qpl_{$this->getId()}/export_{$type}";
545 break;
546 default:
547 $export_dir = ilFileUtils::getDataDir() . '/qpl_data' . '/qpl_' . $this->getId() . '/export';
548 break;
549 }
550 return $export_dir;
551 }
552
558 public function &getAllQuestions(): array
559 {
560 $result = $this->db->queryF(
561 'SELECT question_id FROM qpl_questions WHERE obj_fi = %s AND qpl_questions.tstamp > 0 AND original_id IS NULL',
562 ['integer'],
563 [$this->getId()]
564 );
565 $questions = [];
566 while ($row = $this->db->fetchAssoc($result)) {
567 array_push($questions, $row['question_id']);
568 }
569 return $questions;
570 }
571
572 public function &getAllQuestionIds(): array
573 {
574 $query_result = $this->db->queryF(
575 'SELECT question_id, qpl_qst_type.type_tag, qpl_qst_type.plugin FROM qpl_questions, qpl_qst_type WHERE original_id IS NULL AND qpl_questions.tstamp > 0 AND obj_fi = %s AND complete = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id',
576 ['integer', 'text'],
577 [$this->getId(), 1]
578 );
579 $questions = [];
580 if ($query_result->numRows()) {
581 while ($row = $this->db->fetchAssoc($query_result)) {
582 if ($row['plugin']) {
583 if ($this->isPluginActive($row['type_tag'])) {
584 array_push($questions, $row['question_id']);
585 }
586 } else {
587 array_push($questions, $row['question_id']);
588 }
589 }
590 }
591 return $questions;
592 }
593
594 public function checkQuestionParent(int $question_id): bool
595 {
596 $row = $this->db->fetchAssoc(
597 $this->db->queryF(
598 'SELECT COUNT(question_id) cnt FROM qpl_questions WHERE question_id = %s AND obj_fi = %s',
599 ['integer', 'integer'],
600 [$question_id, $this->getId()]
601 )
602 );
603
604 return (bool) $row['cnt'];
605 }
606
611 public function getImportMapping(): array
612 {
613 return [];
614 }
615
623 public function questionsToXML($questions): string
624 {
625 $xml = '';
626 // export button was pressed
627 if (count($questions) > 0) {
628 foreach ($questions as $key => $value) {
629 $question = $this->createQuestion('', $value);
630 $xml .= $question->getObject()->toXML();
631 }
632 if (count($questions) > 1) {
633 $xml = preg_replace('/<\/questestinterop>\s*<.xml.*?>\s*<questestinterop>/', '', $xml);
634 }
635 }
636 $xml = preg_replace(
637 '/(<\?xml[^>]*?>)/',
638 '\\1' . '<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv1p2p1.dtd">',
639 $xml
640 );
641 return $xml;
642 }
643
644 protected static function _getQuestionCount(int $pool_id): int
645 {
646 global $DIC;
647 $ilDB = $DIC['ilDB'];
648 $result = $ilDB->queryF(
649 'SELECT COUNT(question_id) question_count FROM qpl_questions WHERE obj_fi = %s AND qpl_questions.tstamp > 0 AND original_id IS NULL AND complete = %s',
650 ['integer', 'text'],
651 [$pool_id, 1]
652 );
653 $row = $ilDB->fetchAssoc($result);
654 return $row['question_count'];
655 }
656
663 public static function _hasEqualPoints($a_obj_id, $is_reference = false): int
664 {
665 global $DIC;
666 $ilDB = $DIC['ilDB'];
667
668 if ($is_reference) {
669 $result = $ilDB->queryF(
670 'SELECT count(DISTINCT qpl_questions.points) equal_points FROM qpl_questions, object_reference WHERE object_reference.ref_id = %s AND qpl_questions.tstamp > 0 AND object_reference.obj_id = qpl_questions.obj_fi AND qpl_questions.original_id IS NULL',
671 ['integer'],
672 [$a_obj_id]
673 );
674 } else {
675 $result = $ilDB->queryF(
676 'SELECT count(DISTINCT points) equal_points FROM qpl_questions WHERE obj_fi = %s AND qpl_questions.tstamp > 0 AND qpl_questions.original_id IS NULL',
677 ['integer'],
678 [$a_obj_id]
679 );
680 }
681 if ($result->numRows() == 1) {
682 $row = $ilDB->fetchAssoc($result);
683 if ($row['equal_points'] == 1) {
684 return 1;
685 } else {
686 return 0;
687 }
688 }
689 return 0;
690 }
691
697 public function pasteFromClipboard(): bool
698 {
699 $success = false;
700 if (ilSession::get('qpl_clipboard') != null) {
701 $success = true;
702 foreach (ilSession::get('qpl_clipboard') as $question_object) {
703 if (strcmp($question_object['action'], 'move') == 0) {
704 $result = $this->db->queryF(
705 'SELECT obj_fi FROM qpl_questions WHERE question_id = %s',
706 ['integer'],
707 [$question_object['question_id']]
708 );
709 if ($result->numRows() == 1) {
710 $row = $this->db->fetchAssoc($result);
711 $source_questionpool = $row['obj_fi'];
712 $affectedRows = $this->db->manipulateF(
713 'UPDATE qpl_questions SET obj_fi = %s WHERE question_id = %s',
714 ['integer', 'integer'],
715 [$this->getId(), $question_object['question_id']]
716 );
717 if (!$affectedRows) {
718 $success = false;
719 }
720
721 $source_path = CLIENT_WEB_DIR . '/assessment/' . $source_questionpool . '/' . $question_object['question_id'] . '/';
722 if (@is_dir($source_path)) {
723 $target_path = CLIENT_WEB_DIR . '/assessment/' . $this->getId() . '/';
724 if (!@is_dir($target_path)) {
725 ilFileUtils::makeDirParents($target_path);
726 }
727 rename($source_path, $target_path . $question_object['question_id']);
728 }
729
730 ilObjQuestionPool::_updateQuestionCount($source_questionpool);
731 }
732 } else {
733 $new_question_id = $this->copyQuestion($question_object['question_id'], $this->getId());
734 if (!$new_question_id) {
735 $success = false;
736 }
737 }
738 }
739 }
740 // update question count of question pool
742 ilSession::clear('qpl_clipboard');
743
744 return $success;
745 }
746
753 public function copyToClipboard($question_id): void
754 {
755 if (ilSession::get('qpl_clipboard') == null) {
756 ilSession::set('qpl_clipboard', []);
757 }
758 $clip = ilSession::get('qpl_clipboard');
759 $clip[$question_id] = ['question_id' => $question_id, 'action' => 'copy'];
760 ilSession::set('qpl_clipboard', $clip);
761 }
762
769 public function moveToClipboard(int $question_id): void
770 {
771 if (ilSession::get('qpl_clipboard') == null) {
772 ilSession::set('qpl_clipboard', []);
773 }
774 $clip = ilSession::get('qpl_clipboard');
775 $clip[$question_id] = ['question_id' => $question_id, 'action' => 'move'];
776 ilSession::set('qpl_clipboard', $clip);
777 }
778
779 public function cleanupClipboard(int $deleted_question_id): void
780 {
781 if (ilSession::get('qpl_clipboard') == null) {
782 return;
783 }
784
785 $clip = ilSession::get('qpl_clipboard');
786 if (!isset($clip[$deleted_question_id])) {
787 return;
788 }
789
790 unset($clip[$deleted_question_id]);
791
792 if (!count($clip)) {
793 ilSession::clear('qpl_clipboard');
794 } else {
795 ilSession::set('qpl_clipboard', $clip);
796 }
797 }
798
806 public static function _isWriteable($object_id, $user_id): bool
807 {
808 global $DIC;
809 $rbacsystem = $DIC['rbacsystem'];
810
811 $refs = ilObject::_getAllReferences($object_id);
812 if (count($refs)) {
813 foreach ($refs as $ref_id) {
814 if ($rbacsystem->checkAccess('write', $ref_id) && (ilObject::_hasUntrashedReference($object_id))) {
815 return true;
816 }
817 }
818 }
819 return false;
820 }
821
829 public function getQuestionDetails($question_ids): array
830 {
831 $result = [];
832 $query_result = $this->db->query(
833 'SELECT qpl_questions.*, qpl_qst_type.type_tag '
834 . 'FROM qpl_questions, qpl_qst_type '
835 . 'WHERE qpl_questions.question_type_fi = qpl_qst_type.question_type_id '
836 . 'AND ' . $this->db->in(
837 'qpl_questions.question_id',
838 $question_ids,
839 false,
840 'integer'
841 ) . ' ORDER BY qpl_questions.title'
842 );
843 if ($query_result->numRows()) {
844 while ($row = $this->db->fetchAssoc($query_result)) {
845 array_push($result, $row);
846 }
847 }
848 return $result;
849 }
850
859 public function getDeleteableQuestionDetails($question_ids): array
860 {
861 $result = [];
862 $query_result = $this->db->query(
863 'SELECT qpl_questions.*, qpl_qst_type.type_tag FROM qpl_questions, qpl_qst_type WHERE qpl_questions.question_type_fi = qpl_qst_type.question_type_id AND '
864 . $this->db->in('qpl_questions.question_id', $question_ids, false, 'integer')
865 . ' ORDER BY qpl_questions.title'
866 );
867 if ($query_result->numRows()) {
868 while ($row = $this->db->fetchAssoc($query_result)) {
869 if (!$this->questionrepository->isUsedInRandomTest($row['question_id'])) {
870 array_push($result, $row);
871 } else {
872 // the question was used in a random test prior to ILIAS 3.7 so it was inserted
873 // as a reference to the original question pool object and not as a copy. To allow
874 // the deletion of the question pool object, a copy must be created and all database references
875 // of the original question must changed with the reference of the copy
876
877 // 1. Create a copy of the original question
878 $question = $this->createQuestion('', $row['question_id']);
879 $duplicate_id = $question->getObject()->duplicate(true);
880 if ($duplicate_id > 0) {
881 // 2. replace the question id in the solutions
882 $affectedRows = $this->db->manipulateF(
883 'UPDATE tst_solutions SET question_fi = %s WHERE question_fi = %s',
884 ['integer', 'integer'],
885 [$duplicate_id, $row['question_id']]
886 );
887
888 // 3. replace the question id in the question list of random tests
889 $affectedRows = $this->db->manipulateF(
890 'UPDATE tst_test_rnd_qst SET question_fi = %s WHERE question_fi = %s',
891 ['integer', 'integer'],
892 [$duplicate_id, $row['question_id']]
893 );
894
895 // 4. replace the question id in the test results
896 $affectedRows = $this->db->manipulateF(
897 'UPDATE tst_test_result SET question_fi = %s WHERE question_fi = %s',
898 ['integer', 'integer'],
899 [$duplicate_id, $row['question_id']]
900 );
901
902 // 5. replace the question id in the test&assessment log
903 $affectedRows = $this->db->manipulateF(
904 'UPDATE ass_log SET question_fi = %s WHERE question_fi = %s',
905 ['integer', 'integer'],
906 [$duplicate_id, $row['question_id']]
907 );
908
909 // 6. The original question can be deleted, so add it to the list of questions
910 array_push($result, $row);
911 }
912 }
913 }
914 }
915 return $result;
916 }
917
924 public static function _getAvailableQuestionpools(
925 bool $use_object_id = false,
926 bool $equal_points = false,
927 bool $could_be_offline = false,
928 bool $showPath = false,
929 bool $with_questioncount = false,
930 string $permission = 'read',
931 int $usr_id = 0
932 ): array {
933 global $DIC;
934 $ilUser = $DIC['ilUser'];
935 $ilDB = $DIC['ilDB'];
936 $lng = $DIC['lng'];
937
938 $result_array = [];
939 $qpls = ilUtil::_getObjectsByOperations('qpl', $permission, $usr_id > 0 ? $usr_id : $ilUser->getId(), -1);
940 $obj_ids = [];
941 foreach ($qpls as $ref_id) {
943 $obj_ids[$ref_id] = $obj_id;
944 }
945 $titles = ilObject::_prepareCloneSelection($qpls, 'qpl');
946 if (count($obj_ids)) {
947 $in = $ilDB->in('object_data.obj_id', $obj_ids, false, 'integer');
948 if ($could_be_offline) {
949 $result = $ilDB->query(
950 'SELECT qpl_questionpool.*, object_data.title FROM qpl_questionpool, object_data WHERE ' .
951 'qpl_questionpool.obj_fi = object_data.obj_id AND ' . $in . ' ORDER BY object_data.title'
952 );
953 } else {
954 $result = $ilDB->queryF(
955 'SELECT qpl_questionpool.*, object_data.title FROM qpl_questionpool, object_data WHERE ' .
956 'qpl_questionpool.obj_fi = object_data.obj_id AND ' . $in . ' AND object_data.offline = %s ' .
957 'ORDER BY object_data.title',
958 ['text'],
959 [0]
960 );
961 }
962 while ($row = $ilDB->fetchAssoc($result)) {
963 $add = true;
964 if ($equal_points !== false) {
965 if (!ilObjQuestionPool::_hasEqualPoints($row['obj_fi'])) {
966 $add = false;
967 }
968 }
969 if ($add) {
970 $ref_id = array_search($row['obj_fi'], $obj_ids);
971 $title = (($showPath) ? $titles[$ref_id] : $row['title']);
972 if ($with_questioncount) {
973 $title .= ' [' . $row['questioncount'] . ' ' . ($row['questioncount'] == 1 ? $lng->txt(
974 'ass_question'
975 ) : $lng->txt('assQuestions')) . ']';
976 }
977
978 if ($use_object_id) {
979 $result_array[$row['obj_fi']] = [
980 'qpl_id' => $row['obj_fi'],
981 'qpl_title' => $row['title'],
982 'title' => $title,
983 'count' => $row['questioncount']
984 ];
985 } else {
986 $result_array[$ref_id] = [
987 'qpl_id' => $row['obj_fi'],
988 'qpl_title' => $row['title'],
989 'title' => $title,
990 'count' => $row['questioncount']
991 ];
992 }
993 }
994 }
995 }
996 return $result_array;
997 }
998
999 public function getQplQuestions(): array
1000 {
1001 $questions = [];
1002 $result = $this->db->queryF(
1003 'SELECT qpl_questions.question_id FROM qpl_questions WHERE qpl_questions.original_id IS NULL AND qpl_questions.tstamp > 0 AND qpl_questions.obj_fi = %s',
1004 ['integer'],
1005 [$this->getId()]
1006 );
1007 while ($row = $this->db->fetchAssoc($result)) {
1008 array_push($questions, $row['question_id']);
1009 }
1010 return $questions;
1011 }
1012
1018 public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = false): ?ilObject
1019 {
1020 $new_obj = parent::cloneObject($target_id, $copy_id, $omit_tree);
1021
1022 $new_obj->update();
1023
1024 $new_obj->setSkillServiceEnabled($this->isSkillServiceEnabled());
1025 $new_obj->saveToDb();
1026
1027 // clone the questions in the question pool
1028 $questions = $this->getQplQuestions();
1029 $questionIdsMap = [];
1030 foreach ($questions as $question_id) {
1031 $newQuestionId = $new_obj->copyQuestion($question_id, $new_obj->getId());
1032 $questionIdsMap[$question_id] = $newQuestionId;
1033 }
1034
1035 $this->cloneMetaData($new_obj);
1036 $new_obj->updateMetaData();
1037
1038 $duplicator = new ilQuestionPoolTaxonomiesDuplicator();
1039 $duplicator->setSourceObjId($this->getId());
1040 $duplicator->setSourceObjType($this->getType());
1041 $duplicator->setTargetObjId($new_obj->getId());
1042 $duplicator->setTargetObjType($new_obj->getType());
1043 $duplicator->setQuestionIdMapping($questionIdsMap);
1044 $duplicator->duplicate($duplicator->getAllTaxonomiesForSourceObject());
1045
1046 $new_obj->saveToDb();
1047
1048 return $new_obj;
1049 }
1050
1051 public function getQuestionTypes($all_tags = false, $fixOrder = false, $withDeprecatedTypes = true): array
1052 {
1053 return self::_getQuestionTypes($all_tags, $fixOrder, $withDeprecatedTypes);
1054 }
1055
1056 public static function _getQuestionTypes($all_tags = false, $fixOrder = false, $withDeprecatedTypes = true): array
1057 {
1058 global $DIC;
1059 $ilDB = $DIC['ilDB'];
1060 $lng = $DIC['lng'];
1061 $component_factory = $DIC['component.factory'];
1062 $disabled_question_types = QuestionPoolDIC::dic()['global_test_settings']->getDisabledQuestionTypes();
1063
1064 $lng->loadLanguageModule('assessment');
1065 $result = $ilDB->query('SELECT * FROM qpl_qst_type');
1066 $types = [];
1067 while ($row = $ilDB->fetchAssoc($result)) {
1068 if ($all_tags || (!in_array($row['question_type_id'], $disabled_question_types))) {
1069 if ($row['plugin'] == 0) {
1070 $types[$lng->txt($row['type_tag'])] = $row;
1071 } else {
1072 foreach ($component_factory->getActivePluginsInSlot('qst') as $pl) {
1073 if (strcmp($pl->getQuestionType(), $row['type_tag']) == 0) {
1074 $types[$pl->getQuestionTypeTranslation()] = $row;
1075 }
1076 }
1077 }
1078 }
1079 }
1080
1082 $orderer = new ilAssQuestionTypeOrderer($types, $orderMode);
1083 $types = $orderer->getOrderedTypes($withDeprecatedTypes);
1084
1085 return $types;
1086 }
1087
1088 public static function getQuestionTypeByTypeId($type_id)
1089 {
1090 global $DIC;
1091 $ilDB = $DIC['ilDB'];
1092
1093 $query = 'SELECT type_tag FROM qpl_qst_type WHERE question_type_id = %s';
1094 $types = ['integer'];
1095 $values = [$type_id];
1096 $result = $ilDB->queryF($query, $types, $values);
1097
1098 if ($row = $ilDB->fetchAssoc($result)) {
1099 return $row['type_tag'];
1100 }
1101 return null;
1102 }
1103
1104 public static function getQuestionTypeTranslations(): array
1105 {
1106 global $DIC;
1107 $ilDB = $DIC['ilDB'];
1108 $lng = $DIC['lng'];
1109 $component_factory = $DIC['component.factory'];
1110
1111 $lng->loadLanguageModule('assessment');
1112 $result = $ilDB->query('SELECT * FROM qpl_qst_type');
1113 $types = [];
1114 while ($row = $ilDB->fetchAssoc($result)) {
1115 if ($row['plugin'] == 0) {
1116 $types[$row['type_tag']] = $lng->txt($row['type_tag']);
1117 } else {
1118 foreach ($component_factory->getActivePluginsInSlot('qst') as $pl) {
1119 if (strcmp($pl->getQuestionType(), $row['type_tag']) == 0) {
1120 $types[$row['type_tag']] = $pl->getQuestionTypeTranslation();
1121 }
1122 }
1123 }
1124 }
1125 ksort($types);
1126 return $types;
1127 }
1128
1132 public static function &_getSelfAssessmentQuestionTypes($all_tags = false): array
1133 {
1134 $allowed_types = [
1135 'assSingleChoice' => 1,
1136 'assMultipleChoice' => 2,
1137 'assKprimChoice' => 3,
1138 'assClozeTest' => 4,
1139 'assMatchingQuestion' => 5,
1140 'assOrderingQuestion' => 6,
1141 'assOrderingHorizontal' => 7,
1142 'assImagemapQuestion' => 8,
1143 'assTextSubset' => 9,
1144 'assErrorText' => 10,
1145 'assLongMenu' => 11
1146 ];
1147 $satypes = [];
1148 $qtypes = ilObjQuestionPool::_getQuestionTypes($all_tags);
1149 foreach ($qtypes as $k => $t) {
1150 if (isset($allowed_types[$t['type_tag']])) {
1151 $t['order'] = $allowed_types[$t['type_tag']];
1152 $satypes[$k] = $t;
1153 }
1154 }
1155 return $satypes;
1156 }
1157
1158 public function getQuestionList(): array
1159 {
1160 $questions = [];
1161 $result = $this->db->queryF(
1162 'SELECT qpl_questions.*, qpl_qst_type.* FROM qpl_questions, qpl_qst_type WHERE qpl_questions.original_id IS NULL AND qpl_questions.obj_fi = %s AND qpl_questions.tstamp > 0 AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id',
1163 ['integer'],
1164 [$this->getId()]
1165 );
1166 while ($row = $this->db->fetchAssoc($result)) {
1167 array_push($questions, $row);
1168 }
1169 return $questions;
1170 }
1171
1172 public static function _updateQuestionCount(int $object_id): void
1173 {
1174 global $DIC;
1175 $ilDB = $DIC['ilDB'];
1176 $ilDB->manipulateF(
1177 'UPDATE qpl_questionpool SET questioncount = %s, tstamp = %s WHERE obj_fi = %s',
1178 ['integer', 'integer', 'integer'],
1179 [ilObjQuestionPool::_getQuestionCount($object_id), time(), $object_id]
1180 );
1181 }
1182
1189 public function isPluginActive($questionType): bool
1190 {
1191 if (!$this->component_repository->getComponentByTypeAndName(
1193 'TestQuestionPool'
1194 )->getPluginSlotById('qst')->hasPluginName($questionType)) {
1195 return false;
1196 }
1197
1198 return $this->component_repository
1199 ->getComponentByTypeAndName(
1201 'TestQuestionPool'
1202 )
1203 ->getPluginSlotById(
1204 'qst'
1205 )
1206 ->getPluginByName(
1207 $questionType
1208 )->isActive();
1209 }
1210
1211 public function purgeQuestions(): void
1212 {
1213 $incompleteQuestionPurger = new ilAssIncompleteQuestionPurger($this->db);
1214 $incompleteQuestionPurger->setOwnerId($this->user->getId());
1215 $incompleteQuestionPurger->purge();
1216 }
1217
1223 public function getTaxonomyIds(): array
1224 {
1225 return ilObjTaxonomy::getUsageOfObject($this->getId());
1226 }
1227
1228 public function isSkillServiceEnabled(): bool
1229 {
1230 return $this->skill_service_enabled;
1231 }
1232
1233 public function setSkillServiceEnabled(bool $skill_service_enabled): void
1234 {
1235 $this->skill_service_enabled = $skill_service_enabled;
1236 }
1237
1238 private static $isSkillManagementGloballyActivated = null;
1239
1240 public static function isSkillManagementGloballyActivated(): ?bool
1241 {
1242 if (self::$isSkillManagementGloballyActivated === null) {
1243 $skmgSet = new ilSkillManagementSettings();
1244
1245 self::$isSkillManagementGloballyActivated = $skmgSet->isActivated();
1246 }
1247
1248 return self::$isSkillManagementGloballyActivated;
1249 }
1250
1251 public function fromXML(?string $xml_file): void
1252 {
1253 $parser = new ilObjQuestionPoolXMLParser($this, $xml_file);
1254 $parser->startParsing();
1255 }
1256}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
error(string $a_errmsg)
static instantiateQuestion(int $question_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const ORDER_MODE_FIX
order mode with fixed priority for ordering
const ORDER_MODE_ALPHA
order mode that orders by alphanumerical priority
Class ilBenchmark.
start(string $a_module, string $a_bench)
start measurement
static _getExportDirectory(int $a_obj_id, string $a_type="xml", string $a_obj_type="", string $a_entity="")
@depricated Get export directory for an repository object
static makeDirParents(string $a_dir)
Create a new directory and all parent directories.
static makeDir(string $a_dir)
creates a new directory and inherits all filesystem permissions of the parent directory You may pass ...
static delDir(string $a_dir, bool $a_clean_only=false)
removes a dir and all its content (subdirs and files) recursively
static getDataDir()
get data directory (outside webspace)
static createDirectory(string $a_dir, int $a_mod=0755)
create directory
txt(string $a_topic, string $a_default_lang_fallback_mod="")
gets the text for a given topic if the topic is not in the list, the topic itself with "-" will be re...
Class ilObjFile.
static _exists(int $id, bool $reference=false, ?string $type=null)
checks if an object exists in object_data
static _getQuestionTypes($all_tags=false, $fixOrder=false, $withDeprecatedTypes=true)
getImportMapping()
get array of (two) new created questions for import id
cleanupClipboard(int $deleted_question_id)
__construct(int $a_id=0, bool $a_call_by_reference=true)
static _isWriteable($object_id, $user_id)
Returns true, if the question pool is writeable by a given user.
static _getAvailableQuestionpools(bool $use_object_id=false, bool $equal_points=false, bool $could_be_offline=false, bool $showPath=false, bool $with_questioncount=false, string $permission='read', int $usr_id=0)
Returns the available question pools for the active user.
ilComponentRepository $component_repository
checkQuestionParent(int $question_id)
getExportDirectory($type='')
get export directory of questionpool
getTaxonomyIds()
get ids of all taxonomies corresponding to current pool
exportFileItems($target_dir, &$expLog)
export files of file itmes
GeneralQuestionPropertiesRepository $questionrepository
copyQuestion(int $question_id, int $questionpool_to)
setSkillServiceEnabled(bool $skill_service_enabled)
modifyExportIdentifier($a_tag, $a_param, $a_value)
duplicateQuestion(int $question_id)
static _getQuestionCount(int $pool_id)
read($a_force_db=false)
read object data from db into object
createReference()
Creates a database reference id for the object (saves the object to the database and creates a refere...
questionsToXML($questions)
Returns a QTI xml representation of a list of questions.
fromXML(?string $xml_file)
static & _getSelfAssessmentQuestionTypes($all_tags=false)
create($a_upload=false)
create questionpool object
objectToXmlWriter(ilXmlWriter &$a_xml_writer, $a_inst, $a_target_dir, &$expLog, $questions)
export pages of test to xml (see ilias_co.dtd)
exportXMLMediaObjects(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog)
createExportDirectory()
creates data directory for export files (data_dir/qpl_data/qpl_<id>/export, depending on data directo...
isPluginActive($questionType)
Checks wheather or not a question plugin with a given name is active.
exportXMLPageObjects(&$a_xml_writer, $a_inst, &$expLog, $questions)
export page objects to xml (see ilias_co.dtd)
static _hasEqualPoints($a_obj_id, $is_reference=false)
Checks a question pool for questions with the same maximum points.
getQuestionDetails($question_ids)
Returns an array containing the qpl_question and qpl_qst_type fields for an array of question ids.
static getQuestionTypeByTypeId($type_id)
cloneObject(int $target_id, int $copy_id=0, bool $omit_tree=false)
Creates a 1:1 copy of the object and places the copy in a given repository.
update()
update object data
createQuestion(string $question_type, int $question_id=-1)
copyToClipboard($question_id)
Copies a question to the clipboard.
deleteQuestion(int $question_id)
getDeleteableQuestionDetails($question_ids)
Returns an array containing the qpl_question and qpl_qst_type fields of deleteable questions for an a...
moveToClipboard(int $question_id)
Moves a question to the clipboard.
static _updateQuestionCount(int $object_id)
pasteFromClipboard()
Copies/Moves a question from the clipboard.
& getAllQuestions()
Retrieve an array containing all question ids of the questionpool.
appendCounterToQuestionTitleIfNecessary(string $title)
populateQuestionSkillAssignmentsXml(ilXmlWriter &$a_xml_writer, $questions)
exportTitleAndDescription(ilXmlWriter &$a_xml_writer)
getQuestionTypes($all_tags=false, $fixOrder=false, $withDeprecatedTypes=true)
static getUsageOfObject(int $a_obj_id, bool $a_include_titles=false)
Class ilObject Basic functions for all objects.
string $title
static _hasUntrashedReference(int $obj_id)
checks whether an object has at least one reference that is not in trash
static _getAllReferences(int $id)
get all reference ids for object ID
ilLanguage $lng
static _prepareCloneSelection(array $ref_ids, string $new_type, bool $show_path=true)
Prepare copy wizard object selection.
string $type
static _lookupObjId(int $ref_id)
static collectFileItems(ilPageObject $a_page, DOMDocument $a_domdoc)
Get all file items that are used within the page.
static get(string $a_var)
static clear(string $a_var)
static set(string $a_var, $a_val)
Set a value.
static insertInstIntoID(string $a_value)
inserts installation id into ILIAS id
static _getObjectsByOperations( $a_obj_type, string $a_operation, int $a_usr_id=0, int $limit=0)
Get all objects of a specific type and check access This function is not recursive,...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
xmlElement(string $tag, $attrs=null, $data=null, $encode=true, $escape=true)
Writes a basic element (no children, just textual content)
xmlEndTag(string $tag)
Writes an endtag.
xmlStartTag(string $tag, ?array $attrs=null, bool $empty=false, bool $encode=true, bool $escape=true)
Writes a starttag.
const CLIENT_WEB_DIR
Definition: constants.php:47
const IL_INST_ID
Definition: constants.php:40
Readable part of repository interface to ilComponentDataDB.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
global $lng
Definition: privfeed.php:31
global $DIC
Definition: shib_login.php:26