ILIAS  trunk Revision v12.0_alpha-377-g3641b37b9db
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 foreach ($this->getQplQuestions() as $question_id) {
144 $this->deleteQuestion($question_id);
145 }
146
147 $directory = ilFileUtils::getDataDir() . "/qpl_data/qpl_{$this->getId()}";
148 if (is_dir($directory)) {
149 ilFileUtils::delDir($directory);
150 }
151 }
152
153 public function deleteQuestion(int $question_id): void
154 {
155 $question = assQuestion::instantiateQuestion($question_id);
156 $question->delete($question_id);
157 }
158
159 public function loadFromDb(): void
160 {
161 $result = $this->db->queryF(
162 'SELECT * FROM qpl_questionpool WHERE obj_fi = %s',
163 ['integer'],
164 [$this->getId()]
165 );
166 if ($result->numRows() == 1) {
167 $row = $this->db->fetchAssoc($result);
168 $this->setSkillServiceEnabled((bool) $row['skill_service']);
169 }
170 }
171
172 public function saveToDb(): void
173 {
174 $result = $this->db->queryF(
175 'SELECT id_questionpool FROM qpl_questionpool WHERE obj_fi = %s',
176 ['integer'],
177 [$this->getId()]
178 );
179
180 if ($result->numRows() == 1) {
181 $result = $this->db->update(
182 'qpl_questionpool',
183 [
184 'skill_service' => ['integer', (int) $this->isSkillServiceEnabled()],
185 'tstamp' => ['integer', time()]
186 ],
187 [
188 'obj_fi' => ['integer', $this->getId()]
189 ]
190 );
191 } else {
192 $next_id = $this->db->nextId('qpl_questionpool');
193
194 $result = $this->db->insert('qpl_questionpool', [
195 'id_questionpool' => ['integer', $next_id],
196 'skill_service' => ['integer', (int) $this->isSkillServiceEnabled()],
197 'tstamp' => ['integer', time()],
198 'obj_fi' => ['integer', $this->getId()]
199 ]);
200 }
201 }
202
203 public function getQuestiontype($question_id)
204 {
205 if ($question_id < 1) {
206 return null;
207 }
208
209 $result = $this->db->queryF(
210 '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',
211 ['integer'],
212 [$question_id]
213 );
214
215 if ($result->numRows() == 1) {
216 $data = $this->db->fetchAssoc($result);
217 return $data['type_tag'];
218 }
219 return null;
220 }
221
222 public function isInUse(int $question_id): bool
223 {
224 $result = $this->db->queryF(
225 'SELECT COUNT(solution_id) solution_count FROM tst_solutions WHERE question_fi = %s',
226 ['integer'],
227 [$question_id]
228 );
229 $row = $this->db->fetchAssoc($result);
230 return $row['solution_count'];
231 }
232
233 public function createQuestion(string $question_type, int $question_id = -1)
234 {
235 if ($question_id > 0) {
236 return assQuestion::instantiateQuestionGUI($question_id);
237 }
238 $question_type_gui = $question_type . 'GUI';
239 $question_gui = new $question_type_gui();
240 return $question_gui;
241 }
242
243 public function duplicateQuestion(int $question_id): int
244 {
245 $question = $this->createQuestion('', $question_id);
247 $question->getObject()->getTitle()
248 );
249 $new_id = $question->getObject()->duplicate(false, $newtitle);
251 return $new_id;
252 }
253
254 public function copyQuestion(int $question_id, int $questionpool_to): int
255 {
256 $question_gui = $this->createQuestion('', $question_id);
257 if ($question_gui->getObject()->getObjId() == $questionpool_to) {
258 // the question is copied into the same question pool
259 return $this->duplicateQuestion($question_id);
260 } else {
261 // the question is copied into another question pool
263 $question_gui->getObject()->getTitle()
264 );
265
266 return $question_gui->getObject()->copyObject($this->getId(), $newtitle);
267 }
268 }
269
270 public function appendCounterToQuestionTitleIfNecessary(string $title): string
271 {
272 $result = $this->db->queryF(
273 "SELECT COUNT(question_id) AS cnt FROM qpl_questions WHERE obj_fi = %s AND title like %s",
274 ['integer','text'],
275 [$this->getId(), "{$title}%"]
276 );
277 $counter_object = $this->db->fetchObject($result);
278 if ($counter_object->cnt === 0) {
279 return $title;
280 }
281 return "{$title} ({$counter_object->cnt})";
282 }
283
284 public function getPrintviewQuestions(): array
285 {
286 $query_result = $this->db->queryF(
287 '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',
288 ['integer'],
289 [$this->getId()]
290 );
291 $rows = [];
292 $types = $this->getQuestionTypeTranslations();
293 if ($query_result->numRows()) {
294 while ($row = $this->db->fetchAssoc($query_result)) {
295 $row['ttype'] = $types[$row['type_tag']];
296 if ($row['plugin']) {
297 if ($this->isPluginActive($row['type_tag'])) {
298 array_push($rows, $row);
299 }
300 } else {
301 array_push($rows, $row);
302 }
303 }
304 }
305 return $rows;
306 }
307
311 private function exportXMLSettings($xmlWriter): void
312 {
313 $xmlWriter->xmlStartTag('Settings');
314 $xmlWriter->xmlElement('SkillService', null, (int) $this->isSkillServiceEnabled());
315 $xmlWriter->xmlEndTag('Settings');
316 }
317
324 public function objectToXmlWriter(ilXmlWriter &$a_xml_writer, $a_inst, $a_target_dir, &$expLog, $questions): void
325 {
326 $ilBench = $this->benchmark;
327
328 $this->mob_ids = [];
329 $this->file_ids = [];
330
331 $attrs = [];
332 $attrs['Type'] = 'Questionpool_Test';
333 $a_xml_writer->xmlStartTag('ContentObject', $attrs);
334
335 // MetaData
336 $this->exportTitleAndDescription($a_xml_writer);
337
338 // Settings
339 $this->exportXMLSettings($a_xml_writer);
340
341 // PageObjects
342 $expLog->write(date('[y-m-d H:i:s] ') . 'Start Export Page Objects');
343 $ilBench->start('ContentObjectExport', 'exportPageObjects');
344 $this->exportXMLPageObjects($a_xml_writer, $a_inst, $expLog, $questions);
345 $ilBench->stop('ContentObjectExport', 'exportPageObjects');
346 $expLog->write(date('[y-m-d H:i:s] ') . 'Finished Export Page Objects');
347
348 // MediaObjects
349 $expLog->write(date('[y-m-d H:i:s] ') . 'Start Export Media Objects');
350 $ilBench->start('ContentObjectExport', 'exportMediaObjects');
351 $this->exportXMLMediaObjects($a_xml_writer, $a_inst, $a_target_dir, $expLog);
352 $ilBench->stop('ContentObjectExport', 'exportMediaObjects');
353 $expLog->write(date('[y-m-d H:i:s] ') . 'Finished Export Media Objects');
354
355 // FileItems
356 $expLog->write(date('[y-m-d H:i:s] ') . 'Start Export File Items');
357 $ilBench->start('ContentObjectExport', 'exportFileItems');
358 $this->exportFileItems($a_target_dir, $expLog);
359 $ilBench->stop('ContentObjectExport', 'exportFileItems');
360 $expLog->write(date('[y-m-d H:i:s] ') . 'Finished Export File Items');
361
362 // skill assignments
363 $this->populateQuestionSkillAssignmentsXml($a_xml_writer, $questions);
364
365 $a_xml_writer->xmlEndTag('ContentObject');
366 }
367
372 protected function populateQuestionSkillAssignmentsXml(ilXmlWriter &$a_xml_writer, $questions): void
373 {
374 $assignmentList = new ilAssQuestionSkillAssignmentList($this->db);
375 $assignmentList->setParentObjId($this->getId());
376 $assignmentList->loadFromDb();
377 $assignmentList->loadAdditionalSkillData();
378
379 $skillQuestionAssignmentExporter = new ilAssQuestionSkillAssignmentExporter();
380 $skillQuestionAssignmentExporter->setXmlWriter($a_xml_writer);
381 $skillQuestionAssignmentExporter->setQuestionIds($questions);
382 $skillQuestionAssignmentExporter->setAssignmentList($assignmentList);
383 $skillQuestionAssignmentExporter->export();
384 }
385
386 public function exportTitleAndDescription(ilXmlWriter &$a_xml_writer): void
387 {
388 $a_xml_writer->xmlElement('Title', null, $this->getTitle());
389 $a_xml_writer->xmlElement('Description', null, $this->getDescription());
390 }
391
392 public function modifyExportIdentifier($a_tag, $a_param, $a_value)
393 {
394 if ($a_tag == 'Identifier' && $a_param == 'Entry') {
395 $a_value = ilUtil::insertInstIntoID($a_value);
396 }
397
398 return $a_value;
399 }
400
407 public function exportXMLPageObjects(&$a_xml_writer, $a_inst, &$expLog, $questions): void
408 {
409 $ilBench = $this->benchmark;
410
411 foreach ($questions as $question_id) {
412 $ilBench->start('ContentObjectExport', 'exportPageObject');
413 $expLog->write(date('[y-m-d H:i:s] ') . 'Page Object ' . $question_id);
414
415 $attrs = [];
416 $a_xml_writer->xmlStartTag('PageObject', $attrs);
417
418 // export xml to writer object
419 $ilBench->start('ContentObjectExport', 'exportPageObject_XML');
420 $page_object = new ilAssQuestionPage($question_id);
421 $page_object->buildDom();
422 $page_object->insertInstIntoIDs($a_inst);
423 $mob_ids = $page_object->collectMediaObjects(false);
424 $file_ids = ilPCFileList::collectFileItems($page_object, $page_object->getDomDoc());
425 $xml = $page_object->getXMLFromDom(false, false, false, '', true);
426 $xml = str_replace('&', '&amp;', $xml);
427 $a_xml_writer->appendXML($xml);
428 $page_object->freeDom();
429 unset($page_object);
430 $ilBench->stop('ContentObjectExport', 'exportPageObject_XML');
431
432 $ilBench->start("ContentObjectExport", "exportPageObject_CollectMedia");
433 foreach ($mob_ids as $mob_id) {
434 $this->mob_ids[$mob_id] = $mob_id;
435 }
436 $ilBench->stop('ContentObjectExport', 'exportPageObject_CollectMedia');
437
438 // collect all file items
439 $ilBench->start('ContentObjectExport', 'exportPageObject_CollectFileItems');
440 //$file_ids = $page_obj->getFileItemIds();
441 foreach ($file_ids as $file_id) {
442 $this->file_ids[$file_id] = $file_id;
443 }
444 $ilBench->stop('ContentObjectExport', 'exportPageObject_CollectFileItems');
445
446 $a_xml_writer->xmlEndTag("PageObject");
447
448 $ilBench->stop('ContentObjectExport', 'exportPageObject');
449 }
450 }
451
452 public function exportXMLMediaObjects(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog): void
453 {
454 foreach ($this->mob_ids as $mob_id) {
455 $expLog->write(date('[y-m-d H:i:s] ') . 'Media Object ' . $mob_id);
456 if (ilObjMediaObject::_exists((int) $mob_id)) {
457 $target_dir = $a_target_dir . DIRECTORY_SEPARATOR . 'objects'
458 . DIRECTORY_SEPARATOR . 'il_' . IL_INST_ID . '_mob_' . $mob_id;
459 ilFileUtils::createDirectory($target_dir);
460 $media_obj = new ilObjMediaObject((int) $mob_id);
461 $media_obj->exportXML($a_xml_writer, (int) $a_inst);
462 foreach ($media_obj->getMediaItems() as $item) {
463 $stream = $item->getLocationStream();
464 file_put_contents($target_dir . DIRECTORY_SEPARATOR . $item->getLocation(), $stream);
465 $stream->close();
466 }
467 unset($media_obj);
468 }
469 }
470 }
471
476 public function exportFileItems($target_dir, &$expLog): void
477 {
478 foreach ($this->file_ids as $file_id) {
479 $expLog->write(date("[y-m-d H:i:s] ") . "File Item " . $file_id);
480 $file_dir = $target_dir . '/objects/il_' . IL_INST_ID . '_file_' . $file_id;
481 ilFileUtils::makeDir($file_dir);
482 $file_obj = new ilObjFile((int) $file_id, false);
483 $source_file = $file_obj->getFile($file_obj->getVersion());
484 if (!is_file($source_file)) {
485 $source_file = $file_obj->getFile();
486 }
487 if (is_file($source_file)) {
488 copy($source_file, $file_dir . '/' . $file_obj->getFileName());
489 }
490 unset($file_obj);
491 }
492 }
493
499 public function createExportDirectory(): void
500 {
501 $qpl_data_dir = ilFileUtils::getDataDir() . '/qpl_data';
502 ilFileUtils::makeDir($qpl_data_dir);
503 if (!is_writable($qpl_data_dir)) {
504 $this->error->raiseError(
505 'Questionpool Data Directory (' . $qpl_data_dir
506 . ') not writeable.',
507 $this->error->FATAL
508 );
509 }
510
511 // create learning module directory (data_dir/lm_data/lm_<id>)
512 $qpl_dir = $qpl_data_dir . '/qpl_' . $this->getId();
513 ilFileUtils::makeDir($qpl_dir);
514 if (!@is_dir($qpl_dir)) {
515 $this->error->raiseError('Creation of Questionpool Directory failed.', $this->error->FATAL);
516 }
517 // create Export subdirectory (data_dir/lm_data/lm_<id>/Export)
519 if (!@is_dir($this->getExportDirectory('xlsx'))) {
520 $this->error->raiseError('Creation of Export Directory failed.', $this->error->FATAL);
521 }
523 if (!@is_dir($this->getExportDirectory('zip'))) {
524 $this->error->raiseError('Creation of Export Directory failed.', $this->error->FATAL);
525 }
526 }
527
531 public function getExportDirectory($type = ''): string
532 {
533 switch ($type) {
534 case 'xml':
535 $export_dir = ilExport::_getExportDirectory($this->getId(), $type, $this->getType());
536 break;
537 case 'xlsx':
538 case 'zip':
539 $export_dir = ilFileUtils::getDataDir() . "/qpl_data/qpl_{$this->getId()}/export_{$type}";
540 break;
541 default:
542 $export_dir = ilFileUtils::getDataDir() . '/qpl_data' . '/qpl_' . $this->getId() . '/export';
543 break;
544 }
545 return $export_dir;
546 }
547
553 public function &getAllQuestions(): array
554 {
555 $result = $this->db->queryF(
556 'SELECT question_id FROM qpl_questions WHERE obj_fi = %s AND qpl_questions.tstamp > 0 AND original_id IS NULL',
557 ['integer'],
558 [$this->getId()]
559 );
560 $questions = [];
561 while ($row = $this->db->fetchAssoc($result)) {
562 array_push($questions, $row['question_id']);
563 }
564 return $questions;
565 }
566
567 public function &getAllQuestionIds(): array
568 {
569 $query_result = $this->db->queryF(
570 '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',
571 ['integer', 'text'],
572 [$this->getId(), 1]
573 );
574 $questions = [];
575 if ($query_result->numRows()) {
576 while ($row = $this->db->fetchAssoc($query_result)) {
577 if ($row['plugin']) {
578 if ($this->isPluginActive($row['type_tag'])) {
579 array_push($questions, $row['question_id']);
580 }
581 } else {
582 array_push($questions, $row['question_id']);
583 }
584 }
585 }
586 return $questions;
587 }
588
589 public function checkQuestionParent(int $question_id): bool
590 {
591 $row = $this->db->fetchAssoc(
592 $this->db->queryF(
593 'SELECT COUNT(question_id) cnt FROM qpl_questions WHERE question_id = %s AND obj_fi = %s',
594 ['integer', 'integer'],
595 [$question_id, $this->getId()]
596 )
597 );
598
599 return (bool) $row['cnt'];
600 }
601
606 public function getImportMapping(): array
607 {
608 return [];
609 }
610
618 public function questionsToXML($questions): string
619 {
620 $xml = '';
621 // export button was pressed
622 if (count($questions) > 0) {
623 foreach ($questions as $key => $value) {
624 $question = $this->createQuestion('', $value);
625 $xml .= $question->getObject()->toXML();
626 }
627 if (count($questions) > 1) {
628 $xml = preg_replace('/<\/questestinterop>\s*<.xml.*?>\s*<questestinterop>/', '', $xml);
629 }
630 }
631 $xml = preg_replace(
632 '/(<\?xml[^>]*?>)/',
633 '\\1' . '<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv1p2p1.dtd">',
634 $xml
635 );
636 return $xml;
637 }
638
639 protected static function _getQuestionCount(int $pool_id): int
640 {
641 global $DIC;
642 $ilDB = $DIC['ilDB'];
643 $result = $ilDB->queryF(
644 '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',
645 ['integer', 'text'],
646 [$pool_id, 1]
647 );
648 $row = $ilDB->fetchAssoc($result);
649 return $row['question_count'];
650 }
651
658 public static function _hasEqualPoints($a_obj_id, $is_reference = false): int
659 {
660 global $DIC;
661 $ilDB = $DIC['ilDB'];
662
663 if ($is_reference) {
664 $result = $ilDB->queryF(
665 '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',
666 ['integer'],
667 [$a_obj_id]
668 );
669 } else {
670 $result = $ilDB->queryF(
671 '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',
672 ['integer'],
673 [$a_obj_id]
674 );
675 }
676 if ($result->numRows() == 1) {
677 $row = $ilDB->fetchAssoc($result);
678 if ($row['equal_points'] == 1) {
679 return 1;
680 } else {
681 return 0;
682 }
683 }
684 return 0;
685 }
686
692 public function pasteFromClipboard(): bool
693 {
694 $success = false;
695 if (ilSession::get('qpl_clipboard') != null) {
696 $success = true;
697 foreach (ilSession::get('qpl_clipboard') as $question_object) {
698 if (strcmp($question_object['action'], 'move') == 0) {
699 $result = $this->db->queryF(
700 'SELECT obj_fi FROM qpl_questions WHERE question_id = %s',
701 ['integer'],
702 [$question_object['question_id']]
703 );
704 if ($result->numRows() == 1) {
705 $row = $this->db->fetchAssoc($result);
706 $source_questionpool = $row['obj_fi'];
707 $affectedRows = $this->db->manipulateF(
708 'UPDATE qpl_questions SET obj_fi = %s WHERE question_id = %s',
709 ['integer', 'integer'],
710 [$this->getId(), $question_object['question_id']]
711 );
712 if (!$affectedRows) {
713 $success = false;
714 }
715
716 $source_path = CLIENT_WEB_DIR . '/assessment/' . $source_questionpool . '/' . $question_object['question_id'] . '/';
717 if (@is_dir($source_path)) {
718 $target_path = CLIENT_WEB_DIR . '/assessment/' . $this->getId() . '/';
719 if (!@is_dir($target_path)) {
720 ilFileUtils::makeDirParents($target_path);
721 }
722 rename($source_path, $target_path . $question_object['question_id']);
723 }
724
725 ilObjQuestionPool::_updateQuestionCount($source_questionpool);
726 }
727 } else {
728 $new_question_id = $this->copyQuestion($question_object['question_id'], $this->getId());
729 if (!$new_question_id) {
730 $success = false;
731 }
732 }
733 }
734 }
735 // update question count of question pool
737 ilSession::clear('qpl_clipboard');
738
739 return $success;
740 }
741
748 public function copyToClipboard($question_id): void
749 {
750 if (ilSession::get('qpl_clipboard') == null) {
751 ilSession::set('qpl_clipboard', []);
752 }
753 $clip = ilSession::get('qpl_clipboard');
754 $clip[$question_id] = ['question_id' => $question_id, 'action' => 'copy'];
755 ilSession::set('qpl_clipboard', $clip);
756 }
757
764 public function moveToClipboard(int $question_id): void
765 {
766 if (ilSession::get('qpl_clipboard') == null) {
767 ilSession::set('qpl_clipboard', []);
768 }
769 $clip = ilSession::get('qpl_clipboard');
770 $clip[$question_id] = ['question_id' => $question_id, 'action' => 'move'];
771 ilSession::set('qpl_clipboard', $clip);
772 }
773
774 public function cleanupClipboard(int $deleted_question_id): void
775 {
776 if (ilSession::get('qpl_clipboard') == null) {
777 return;
778 }
779
780 $clip = ilSession::get('qpl_clipboard');
781 if (!isset($clip[$deleted_question_id])) {
782 return;
783 }
784
785 unset($clip[$deleted_question_id]);
786
787 if (!count($clip)) {
788 ilSession::clear('qpl_clipboard');
789 } else {
790 ilSession::set('qpl_clipboard', $clip);
791 }
792 }
793
801 public static function _isWriteable($object_id, $user_id): bool
802 {
803 global $DIC;
804 $rbacsystem = $DIC['rbacsystem'];
805
806 $refs = ilObject::_getAllReferences($object_id);
807 if (count($refs)) {
808 foreach ($refs as $ref_id) {
809 if ($rbacsystem->checkAccess('write', $ref_id) && (ilObject::_hasUntrashedReference($object_id))) {
810 return true;
811 }
812 }
813 }
814 return false;
815 }
816
824 public function getQuestionDetails($question_ids): array
825 {
826 $result = [];
827 $query_result = $this->db->query(
828 'SELECT qpl_questions.*, qpl_qst_type.type_tag '
829 . 'FROM qpl_questions, qpl_qst_type '
830 . 'WHERE qpl_questions.question_type_fi = qpl_qst_type.question_type_id '
831 . 'AND ' . $this->db->in(
832 'qpl_questions.question_id',
833 $question_ids,
834 false,
835 'integer'
836 ) . ' ORDER BY qpl_questions.title'
837 );
838 if ($query_result->numRows()) {
839 while ($row = $this->db->fetchAssoc($query_result)) {
840 array_push($result, $row);
841 }
842 }
843 return $result;
844 }
845
854 public function getDeleteableQuestionDetails($question_ids): array
855 {
856 $result = [];
857 $query_result = $this->db->query(
858 '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 '
859 . $this->db->in('qpl_questions.question_id', $question_ids, false, 'integer')
860 . ' ORDER BY qpl_questions.title'
861 );
862 if ($query_result->numRows()) {
863 while ($row = $this->db->fetchAssoc($query_result)) {
864 if (!$this->questionrepository->isUsedInRandomTest($row['question_id'])) {
865 array_push($result, $row);
866 } else {
867 // the question was used in a random test prior to ILIAS 3.7 so it was inserted
868 // as a reference to the original question pool object and not as a copy. To allow
869 // the deletion of the question pool object, a copy must be created and all database references
870 // of the original question must changed with the reference of the copy
871
872 // 1. Create a copy of the original question
873 $question = $this->createQuestion('', $row['question_id']);
874 $duplicate_id = $question->getObject()->duplicate(true);
875 if ($duplicate_id > 0) {
876 // 2. replace the question id in the solutions
877 $affectedRows = $this->db->manipulateF(
878 'UPDATE tst_solutions SET question_fi = %s WHERE question_fi = %s',
879 ['integer', 'integer'],
880 [$duplicate_id, $row['question_id']]
881 );
882
883 // 3. replace the question id in the question list of random tests
884 $affectedRows = $this->db->manipulateF(
885 'UPDATE tst_test_rnd_qst SET question_fi = %s WHERE question_fi = %s',
886 ['integer', 'integer'],
887 [$duplicate_id, $row['question_id']]
888 );
889
890 // 4. replace the question id in the test results
891 $affectedRows = $this->db->manipulateF(
892 'UPDATE tst_test_result SET question_fi = %s WHERE question_fi = %s',
893 ['integer', 'integer'],
894 [$duplicate_id, $row['question_id']]
895 );
896
897 // 5. replace the question id in the test&assessment log
898 $affectedRows = $this->db->manipulateF(
899 'UPDATE ass_log SET question_fi = %s WHERE question_fi = %s',
900 ['integer', 'integer'],
901 [$duplicate_id, $row['question_id']]
902 );
903
904 // 6. The original question can be deleted, so add it to the list of questions
905 array_push($result, $row);
906 }
907 }
908 }
909 }
910 return $result;
911 }
912
919 public static function _getAvailableQuestionpools(
920 bool $use_object_id = false,
921 bool $equal_points = false,
922 bool $could_be_offline = false,
923 bool $showPath = false,
924 bool $with_questioncount = false,
925 string $permission = 'read',
926 int $usr_id = 0
927 ): array {
928 global $DIC;
929 $ilUser = $DIC['ilUser'];
930 $ilDB = $DIC['ilDB'];
931 $lng = $DIC['lng'];
932
933 $result_array = [];
934 $qpls = ilUtil::_getObjectsByOperations('qpl', $permission, $usr_id > 0 ? $usr_id : $ilUser->getId(), -1);
935 $obj_ids = [];
936 foreach ($qpls as $ref_id) {
938 $obj_ids[$ref_id] = $obj_id;
939 }
940 $titles = ilObject::_prepareCloneSelection($qpls, 'qpl');
941 if (count($obj_ids)) {
942 $in = $ilDB->in('object_data.obj_id', $obj_ids, false, 'integer');
943 if ($could_be_offline) {
944 $result = $ilDB->query(
945 'SELECT qpl_questionpool.*, object_data.title FROM qpl_questionpool, object_data WHERE ' .
946 'qpl_questionpool.obj_fi = object_data.obj_id AND ' . $in . ' ORDER BY object_data.title'
947 );
948 } else {
949 $result = $ilDB->queryF(
950 'SELECT qpl_questionpool.*, object_data.title FROM qpl_questionpool, object_data WHERE ' .
951 'qpl_questionpool.obj_fi = object_data.obj_id AND ' . $in . ' AND object_data.offline = %s ' .
952 'ORDER BY object_data.title',
953 ['text'],
954 [0]
955 );
956 }
957 while ($row = $ilDB->fetchAssoc($result)) {
958 $add = true;
959 if ($equal_points !== false) {
960 if (!ilObjQuestionPool::_hasEqualPoints($row['obj_fi'])) {
961 $add = false;
962 }
963 }
964 if ($add) {
965 $ref_id = array_search($row['obj_fi'], $obj_ids);
966 $title = (($showPath) ? $titles[$ref_id] : $row['title']);
967 if ($with_questioncount) {
968 $title .= ' [' . $row['questioncount'] . ' ' . ($row['questioncount'] == 1 ? $lng->txt(
969 'ass_question'
970 ) : $lng->txt('assQuestions')) . ']';
971 }
972
973 if ($use_object_id) {
974 $result_array[$row['obj_fi']] = [
975 'qpl_id' => $row['obj_fi'],
976 'qpl_title' => $row['title'],
977 'title' => $title,
978 'count' => $row['questioncount']
979 ];
980 } else {
981 $result_array[$ref_id] = [
982 'qpl_id' => $row['obj_fi'],
983 'qpl_title' => $row['title'],
984 'title' => $title,
985 'count' => $row['questioncount']
986 ];
987 }
988 }
989 }
990 }
991 return $result_array;
992 }
993
994 public function getQplQuestions(): array
995 {
996 $questions = [];
997 $result = $this->db->queryF(
998 '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',
1000 [$this->getId()]
1001 );
1002 while ($row = $this->db->fetchAssoc($result)) {
1003 $questions[] = $row['question_id'];
1004 }
1005 return $questions;
1006 }
1007
1013 public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = false): ?ilObject
1014 {
1015 $new_obj = parent::cloneObject($target_id, $copy_id, $omit_tree);
1016
1017 $new_obj->update();
1018
1019 $new_obj->setSkillServiceEnabled($this->isSkillServiceEnabled());
1020 $new_obj->saveToDb();
1021
1022 // clone the questions in the question pool
1023 $questions = $this->getQplQuestions();
1024 $questionIdsMap = [];
1025 foreach ($questions as $question_id) {
1026 $newQuestionId = $new_obj->copyQuestion($question_id, $new_obj->getId());
1027 $questionIdsMap[$question_id] = $newQuestionId;
1028 }
1029
1030 $this->cloneMetaData($new_obj);
1031 $new_obj->updateMetaData();
1032
1033 $duplicator = new ilQuestionPoolTaxonomiesDuplicator();
1034 $duplicator->setSourceObjId($this->getId());
1035 $duplicator->setSourceObjType($this->getType());
1036 $duplicator->setTargetObjId($new_obj->getId());
1037 $duplicator->setTargetObjType($new_obj->getType());
1038 $duplicator->setQuestionIdMapping($questionIdsMap);
1039 $duplicator->duplicate($duplicator->getAllTaxonomiesForSourceObject());
1040
1041 $new_obj->saveToDb();
1042
1043 return $new_obj;
1044 }
1045
1046 public function getQuestionTypes($all_tags = false, $fixOrder = false, $withDeprecatedTypes = true): array
1047 {
1048 return self::_getQuestionTypes($all_tags, $fixOrder, $withDeprecatedTypes);
1049 }
1050
1051 public static function _getQuestionTypes($all_tags = false, $fixOrder = false, $withDeprecatedTypes = true): array
1052 {
1053 global $DIC;
1054 $ilDB = $DIC['ilDB'];
1055 $lng = $DIC['lng'];
1056 $component_factory = $DIC['component.factory'];
1057 $disabled_question_types = QuestionPoolDIC::dic()['global_test_settings']->getDisabledQuestionTypes();
1058
1059 $lng->loadLanguageModule('assessment');
1060 $result = $ilDB->query('SELECT * FROM qpl_qst_type');
1061 $types = [];
1062 while ($row = $ilDB->fetchAssoc($result)) {
1063 if ($all_tags || (!in_array($row['question_type_id'], $disabled_question_types))) {
1064 if ($row['plugin'] == 0) {
1065 $types[$lng->txt($row['type_tag'])] = $row;
1066 } else {
1067 foreach ($component_factory->getActivePluginsInSlot('qst') as $pl) {
1068 if (strcmp($pl->getQuestionType(), $row['type_tag']) == 0) {
1069 $types[$pl->getQuestionTypeTranslation()] = $row;
1070 }
1071 }
1072 }
1073 }
1074 }
1075
1077 $orderer = new ilAssQuestionTypeOrderer($types, $orderMode);
1078 $types = $orderer->getOrderedTypes($withDeprecatedTypes);
1079
1080 return $types;
1081 }
1082
1083 public static function getQuestionTypeByTypeId($type_id)
1084 {
1085 global $DIC;
1086 $ilDB = $DIC['ilDB'];
1087
1088 $query = 'SELECT type_tag FROM qpl_qst_type WHERE question_type_id = %s';
1089 $types = ['integer'];
1090 $values = [$type_id];
1091 $result = $ilDB->queryF($query, $types, $values);
1092
1093 if ($row = $ilDB->fetchAssoc($result)) {
1094 return $row['type_tag'];
1095 }
1096 return null;
1097 }
1098
1099 public static function getQuestionTypeTranslations(): array
1100 {
1101 global $DIC;
1102 $ilDB = $DIC['ilDB'];
1103 $lng = $DIC['lng'];
1104 $component_factory = $DIC['component.factory'];
1105
1106 $lng->loadLanguageModule('assessment');
1107 $result = $ilDB->query('SELECT * FROM qpl_qst_type');
1108 $types = [];
1109 while ($row = $ilDB->fetchAssoc($result)) {
1110 if ($row['plugin'] == 0) {
1111 $types[$row['type_tag']] = $lng->txt($row['type_tag']);
1112 } else {
1113 foreach ($component_factory->getActivePluginsInSlot('qst') as $pl) {
1114 if (strcmp($pl->getQuestionType(), $row['type_tag']) == 0) {
1115 $types[$row['type_tag']] = $pl->getQuestionTypeTranslation();
1116 }
1117 }
1118 }
1119 }
1120 ksort($types);
1121 return $types;
1122 }
1123
1127 public static function &_getSelfAssessmentQuestionTypes($all_tags = false): array
1128 {
1129 $allowed_types = [
1130 'assSingleChoice' => 1,
1131 'assMultipleChoice' => 2,
1132 'assKprimChoice' => 3,
1133 'assClozeTest' => 4,
1134 'assMatchingQuestion' => 5,
1135 'assOrderingQuestion' => 6,
1136 'assOrderingHorizontal' => 7,
1137 'assImagemapQuestion' => 8,
1138 'assTextSubset' => 9,
1139 'assErrorText' => 10,
1140 'assLongMenu' => 11
1141 ];
1142 $satypes = [];
1143 $qtypes = ilObjQuestionPool::_getQuestionTypes($all_tags);
1144 foreach ($qtypes as $k => $t) {
1145 if (isset($allowed_types[$t['type_tag']])) {
1146 $t['order'] = $allowed_types[$t['type_tag']];
1147 $satypes[$k] = $t;
1148 }
1149 }
1150 return $satypes;
1151 }
1152
1153 public function getQuestionList(): array
1154 {
1155 $questions = [];
1156 $result = $this->db->queryF(
1157 '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',
1158 ['integer'],
1159 [$this->getId()]
1160 );
1161 while ($row = $this->db->fetchAssoc($result)) {
1162 array_push($questions, $row);
1163 }
1164 return $questions;
1165 }
1166
1167 public static function _updateQuestionCount(int $object_id): void
1168 {
1169 global $DIC;
1170 $ilDB = $DIC['ilDB'];
1171 $ilDB->manipulateF(
1172 'UPDATE qpl_questionpool SET questioncount = %s, tstamp = %s WHERE obj_fi = %s',
1173 ['integer', 'integer', 'integer'],
1174 [ilObjQuestionPool::_getQuestionCount($object_id), time(), $object_id]
1175 );
1176 }
1177
1184 public function isPluginActive($questionType): bool
1185 {
1186 if (!$this->component_repository->getComponentByTypeAndName(
1188 'TestQuestionPool'
1189 )->getPluginSlotById('qst')->hasPluginName($questionType)) {
1190 return false;
1191 }
1192
1193 return $this->component_repository
1194 ->getComponentByTypeAndName(
1196 'TestQuestionPool'
1197 )
1198 ->getPluginSlotById(
1199 'qst'
1200 )
1201 ->getPluginByName(
1202 $questionType
1203 )->isActive();
1204 }
1205
1206 public function purgeQuestions(): void
1207 {
1208 $incompleteQuestionPurger = new ilAssIncompleteQuestionPurger($this->db);
1209 $incompleteQuestionPurger->setOwnerId($this->user->getId());
1210 $incompleteQuestionPurger->purge();
1211 }
1212
1218 public function getTaxonomyIds(): array
1219 {
1220 return ilObjTaxonomy::getUsageOfObject($this->getId());
1221 }
1222
1223 public function isSkillServiceEnabled(): bool
1224 {
1225 return $this->skill_service_enabled;
1226 }
1227
1228 public function setSkillServiceEnabled(bool $skill_service_enabled): void
1229 {
1230 $this->skill_service_enabled = $skill_service_enabled;
1231 }
1232
1233 private static $isSkillManagementGloballyActivated = null;
1234
1235 public static function isSkillManagementGloballyActivated(): ?bool
1236 {
1237 if (self::$isSkillManagementGloballyActivated === null) {
1238 $skmgSet = new ilSkillManagementSettings();
1239
1240 self::$isSkillManagementGloballyActivated = $skmgSet->isActivated();
1241 }
1242
1243 return self::$isSkillManagementGloballyActivated;
1244 }
1245
1246 public function fromXML(?string $xml_file): void
1247 {
1248 $parser = new ilObjQuestionPoolXMLParser($this, $xml_file);
1249 $parser->startParsing();
1250 }
1251}
$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