ILIAS  release_8 Revision v8.24
class.ilObjTest.php
Go to the documentation of this file.
1<?php
2
19require_once 'Modules/Test/classes/inc.AssessmentConstants.php';
20
21use ILIAS\Refinery\Factory as Refinery;
22
34{
36
37 #region Properties
38
39 public const QUESTION_SET_TYPE_FIXED = 'FIXED_QUEST_SET';
40 public const QUESTION_SET_TYPE_RANDOM = 'RANDOM_QUEST_SET';
41 private const QUESTION_SET_TYPE_DYNAMIC = 'DYNAMIC_QUEST_SET';
42
44 private bool $skillServiceEnabled = false;
45 private array $resultFilterTaxIds = array();
46 private ?int $specific_answer_feedback = null;
47 private ?bool $activation_limited = null;
48 private array $mob_ids;
49 private array $file_ids;
50 private bool $online;
51 private Refinery $refinery;
52 private \ILIAS\Test\InternalRequestService $testrequest;
53 protected int $_kiosk;
54 public int $test_id;
56 public string $author;
57
61 public $metadata;
62 public array $questions;
63 protected bool $introductionEnabled;
64 protected string $introduction;
65
75
80 public int $nr_of_tries;
81 protected bool $blockPassesAfterPassedEnabled = false;
83 public int $title_output;
84 public $processing_time; // Initialized as string, but cannot be declared
85 public $enable_processing_time; // Initialized as int, but cannot be declared
87
92
93 protected ?string $starting_time;
94
99
100 protected ?string $ending_time;
101
106 protected $ects_output = false;
107
111 protected ?float $ects_fx = null;
112 protected array $ects_grades = array();
113
116
123
128 protected ?string $password;
129
134
140 protected $allowedUsers;
141
148
149 public int $anonymity;
150
151 public int $show_cancel;
152
153 public int $show_marker;
154
156
158
163
168
175 private string $_finalstatement;
176
177 private bool $_showinfo;
178
179 private bool $_forcejs = true;
180
186
188
189 protected int $mailnottype;
190
191 protected int $exportsettings;
192
194
195 protected $oldOnlineStatus = null;
196
197 protected bool $print_best_solution_with_result = true;
198
199 private ?bool $offeringQuestionHintsEnabled = null;
200
201 private ?bool $obligationsEnabled = null;
202
204
206
208
209 protected bool $autosave;
210 protected int $autosave_ival;
211
218 private $passDeletionAllowed = null;
219
225 private $participantDataExist = null;
226
227 protected bool $enable_examview;
228 protected bool $show_examview_html;
229 protected bool $show_examview_pdf;
230 protected bool $enable_archiving;
231
232 private int $redirection_mode = 0;
233 private ?string $redirection_url = null;
234
237
238 protected bool $sign_submission;
239
242 protected ?string $char_selector_definition;
243
246
249
254
255 protected bool $testFinalBroken;
256
258
262 protected $pass_waiting = "00:000:00:00:00";
263 #endregion
264
268
275 public function __construct($a_id = 0, bool $a_call_by_reference = true)
276 {
277 global $DIC;
278 $this->db = $DIC['ilDB'];
279 $ilUser = $DIC['ilUser'];
280 $lng = $DIC['lng'];
281 $this->refinery = $DIC['refinery'];
282 $this->type = "tst";
283 $this->testrequest = $DIC->test()->internal()->request();
284
285 $lng->loadLanguageModule("assessment");
286 $this->mark_schema = new ASS_MarkSchema();
287 $this->mark_schema->createSimpleSchema(
288 $lng->txt("failed_short"),
289 $lng->txt("failed_official"),
290 0,
291 0,
292 $lng->txt("passed_short"),
293 $lng->txt("passed_official"),
294 50,
295 1
296 );
297
298 $this->score_settings = null;
299 $this->test_id = -1;
300 $this->author = $ilUser->fullname;
301 $this->introductionEnabled = false;
302 $this->introduction = "";
303 $this->questions = array();
304 $this->sequence_settings = TEST_FIXED_SEQUENCE;
305 $this->instant_verification = 0;
306 $this->answer_feedback_points = 0;
307 $this->reporting_date = "";
308 $this->nr_of_tries = 0;
309 $this->_kiosk = 0;
310 $this->use_previous_answers = 1;
311 $this->title_output = 0;
312 $this->starting_time = "";
313 $this->ending_time = "";
314 $this->processing_time = "";
315 $this->enable_processing_time = "0";
316 $this->reset_processing_time = 0;
317 $this->ects_output = false;
318 $this->ects_fx = null;
319 $this->shuffle_questions = false;
320 $this->mailnottype = 0;
321 $this->show_summary = 8;
322 $this->answer_feedback = 0;
323 $this->password = "";
324 $this->allowedUsers = "";
325 $this->_showfinalstatement = false;
326 $this->_finalstatement = "";
327 $this->_showinfo = true;
328 $this->_forcejs = true;
329 $this->_customStyle = "";
330 $this->allowedUsersTimeGap = "";
331 $this->anonymity = 0;
332 $this->show_cancel = 0;
333 $this->show_marker = 0;
334 $this->fixed_participants = 0;
335 $this->testSession = false;
336 $this->testSequence = false;
337 $this->mailnotification = 0;
338
339 $this->ects_grades = array(
340 'A' => 90,
341 'B' => 65,
342 'C' => 35,
343 'D' => 10,
344 'E' => 0
345 );
346
347 $this->autosave = false;
348 $this->autosave_ival = 30000;
349
350 $this->enable_examview = false;
351 $this->show_examview_html = false;
352 $this->show_examview_pdf = false;
353 $this->enable_archiving = false;
354
355 $this->template_id = '';
356 $this->redirection_mode = 0;
357 $this->redirection_url = null;
358 $this->show_exam_id_in_test_pass_enabled = false;
359 $this->sign_submission = false;
360 $this->char_selector_availability = 0;
361 $this->char_selector_definition = null;
362
363 $this->showGradingStatusEnabled = true;
364 $this->showGradingMarkEnabled = true;
365
366 $this->followupQuestionAnswerFixationEnabled = false;
367 $this->instantFeedbackAnswerFixationEnabled = false;
368
369 $this->testFinalBroken = false;
370
371 $this->tmpCopyWizardCopyId = null;
372
373 parent::__construct($a_id, $a_call_by_reference);
374 }
375
379 public function getTitleFilenameCompliant(): string
380 {
382 }
383
384 public function getTmpCopyWizardCopyId(): ?int
385 {
387 }
388
389 public function setTmpCopyWizardCopyId(int $tmpCopyWizardCopyId): void
390 {
391 $this->tmpCopyWizardCopyId = $tmpCopyWizardCopyId;
392 }
393
394 public function create(): int
395 {
396 $this->setOfflineStatus(true);
397 $id = parent::create();
398 $this->createMetaData();
399 return $id;
400 }
401
402 public function update(): bool
403 {
404 if (!parent::update()) {
405 return false;
406 }
407
408 // put here object specific stuff
409 $this->updateMetaData();
410 return true;
411 }
412
413 public function read(): void
414 {
415 parent::read();
416 $this->loadFromDb();
417 }
418
419 public function delete(): bool
420 {
421 // always call parent delete function first!!
422 if (!parent::delete()) {
423 return false;
424 }
425
426 // delet meta data
427 $this->deleteMetaData();
428
429 //put here your module specific stuff
430 $this->deleteTest();
431
432 $qsaImportFails = new ilAssQuestionSkillAssignmentImportFails($this->getId());
433 $qsaImportFails->deleteRegisteredImportFails();
434 $sltImportFails = new ilTestSkillLevelThresholdImportFails($this->getId());
435 $sltImportFails->deleteRegisteredImportFails();
436
437 return true;
438 }
439
440 public function deleteTest(): void
441 {
442 global $DIC;
443 $tree = $DIC['tree'];
444 $ilDB = $DIC['ilDB'];
445 $component_repository = $DIC['component.repository'];
446 $lng = $DIC['lng'];
447
448 $participantData = new ilTestParticipantData($ilDB, $lng);
449 $participantData->load($this->getTestId());
450 $this->removeTestResults($participantData);
451
452 $ilDB->manipulateF(
453 "DELETE FROM tst_mark WHERE test_fi = %s",
454 array('integer'),
455 array($this->getTestId())
456 );
457
458 $ilDB->manipulateF(
459 "DELETE FROM tst_tests WHERE test_id = %s",
460 array('integer'),
461 array($this->getTestId())
462 );
463
464 $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $component_repository, $this);
465 $testQuestionSetConfigFactory->getQuestionSetConfig()->removeQuestionSetRelatedData();
466
467 $tst_data_dir = ilFileUtils::getDataDir() . "/tst_data";
468 $directory = $tst_data_dir . "/tst_" . $this->getId();
469 if (is_dir($directory)) {
470 ilFileUtils::delDir($directory);
471 }
472 $mobs = ilObjMediaObject::_getMobsOfObject("tst:html", $this->getId());
473 // remaining usages are not in text anymore -> delete them
474 // and media objects (note: delete method of ilObjMediaObject
475 // checks whether object is used in another context; if yes,
476 // the object is not deleted!)
477 foreach ($mobs as $mob) {
478 ilObjMediaObject::_removeUsage($mob, "tst:html", $this->getId());
479 if (ilObjMediaObject::_exists($mob)) {
480 $mob_obj = new ilObjMediaObject($mob);
481 $mob_obj->delete();
482 }
483 }
484 }
485
491 public function createExportDirectory(): void
492 {
493 $tst_data_dir = ilFileUtils::getDataDir() . "/tst_data";
494 ilFileUtils::makeDir($tst_data_dir);
495 if (!is_writable($tst_data_dir)) {
496 $this->ilias->raiseError("Test Data Directory (" . $tst_data_dir
497 . ") not writeable.", $this->ilias->error_obj->MESSAGE);
498 }
499
500 // create learning module directory (data_dir/lm_data/lm_<id>)
501 $tst_dir = $tst_data_dir . "/tst_" . $this->getId();
502 ilFileUtils::makeDir($tst_dir);
503 if (!@is_dir($tst_dir)) {
504 $this->ilias->raiseError("Creation of Test Directory failed.", $this->ilias->error_obj->MESSAGE);
505 }
506 // create Export subdirectory (data_dir/lm_data/lm_<id>/Export)
507 $export_dir = $tst_dir . "/export";
508 ilFileUtils::makeDir($export_dir);
509 if (!@is_dir($export_dir)) {
510 $this->ilias->raiseError("Creation of Export Directory failed.", $this->ilias->error_obj->MESSAGE);
511 }
512 }
513
514 public function getExportDirectory(): string
515 {
516 $export_dir = ilFileUtils::getDataDir() . "/tst_data" . "/tst_" . $this->getId() . "/export";
517 return $export_dir;
518 }
519
525 public function getExportFiles(string $dir = ''): array
526 {
527 // quit if import dir not available
528 if (!@is_dir($dir) || !is_writable($dir)) {
529 return array();
530 }
531
532 $files = array();
533 foreach (new DirectoryIterator($dir) as $file) {
537 if ($file->isDir()) {
538 continue;
539 }
540
541 $files[] = $file->getBasename();
542 }
543
544 sort($files);
545
546 return $files;
547 }
548
549 public static function _setImportDirectory($a_import_dir = null): void
550 {
551 if (strlen($a_import_dir)) {
552 ilSession::set('tst_import_dir', $a_import_dir);
553 } else {
554 ilSession::clear('tst_import_dir');
555 }
556 }
557
563 public static function _getImportDirectory()
564 {
565 if (strlen(ilSession::get('tst_import_dir'))) {
566 return ilSession::get('tst_import_dir');
567 }
568 return null;
569 }
570
572 public function getImportDirectory()
573 {
575 }
576
582 public static function _createImportDirectory(): string
583 {
584 global $DIC;
585 $ilias = $DIC['ilias'];
586 $tst_data_dir = ilFileUtils::getDataDir() . "/tst_data";
587 ilFileUtils::makeDir($tst_data_dir);
588
589 if (!is_writable($tst_data_dir)) {
590 $ilias->raiseError("Test Data Directory (" . $tst_data_dir
591 . ") not writeable.", $ilias->error_obj->FATAL);
592 }
593
594 // create test directory (data_dir/tst_data/tst_import)
595 $tst_dir = $tst_data_dir . "/tst_import";
596 ilFileUtils::makeDir($tst_dir);
597 if (!@is_dir($tst_dir)) {
598 $ilias->raiseError("Creation of test import directory failed.", $ilias->error_obj->FATAL);
599 }
600
601 // assert that this is empty and does not contain old data
602 ilFileUtils::delDir($tst_dir, true);
603
604 return $tst_dir;
605 }
606
607 public function hasSingleChoiceQuestions(): bool
608 {
609 global $DIC;
610 $ilDB = $DIC['ilDB'];
611
612 $result = $ilDB->queryF(
613 "SELECT DISTINCT(qpl_qst_type.type_tag) foundtypes FROM qpl_questions, tst_test_result, qpl_qst_type, tst_active WHERE tst_test_result.question_fi = qpl_questions.question_id AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id AND tst_test_result.active_fi = tst_active.active_id AND tst_active.test_fi = %s",
614 array('integer'),
615 array($this->getTestId())
616 );
617 $hasSC = false;
618 while ($row = $ilDB->fetchAssoc($result)) {
619 if (strcmp($row['foundtypes'], 'assSingleChoice') == 0) {
620 $hasSC = true;
621 }
622 }
623 return $hasSC;
624 }
625
626 public function isSingleChoiceTest(): bool
627 {
628 global $DIC;
629 $ilDB = $DIC['ilDB'];
630
631 $result = $ilDB->queryF(
632 "SELECT DISTINCT(qpl_qst_type.type_tag) foundtypes FROM qpl_questions, tst_test_result, qpl_qst_type, tst_active WHERE tst_test_result.question_fi = qpl_questions.question_id AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id AND tst_test_result.active_fi = tst_active.active_id AND tst_active.test_fi = %s",
633 array('integer'),
634 array($this->getTestId())
635 );
636 if ($result->numRows() == 1) {
637 $row = $ilDB->fetchAssoc($result);
638 if (strcmp($row['foundtypes'], 'assSingleChoice') == 0) {
639 return true;
640 } else {
641 return false;
642 }
643 }
644 return false;
645 }
646
647 public function isSingleChoiceTestWithoutShuffle(): bool
648 {
649 global $DIC;
650 $ilDB = $DIC['ilDB'];
651
652 if (!$this->hasSingleChoiceQuestions()) {
653 return false;
654 }
655
656 $result = $ilDB->queryF(
657 "
658 SELECT DISTINCT(qpl_qst_sc.shuffle) foundshuffles
659 FROM qpl_questions,
660 qpl_qst_sc,
661 tst_test_result,
662 qpl_qst_type,
663 tst_active
664 WHERE tst_test_result.question_fi = qpl_questions.question_id
665 AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id
666 AND tst_test_result.active_fi = tst_active.active_id
667 AND qpl_questions.question_id = qpl_qst_sc.question_fi
668 AND tst_active.test_fi = %s
669 AND qpl_qst_type.type_tag = %s
670 ",
671 array('integer', 'text'),
672 array($this->getTestId(), 'assSingleChoice')
673 );
674 if ($result->numRows() == 1) {
675 $row = $ilDB->fetchAssoc($result);
676 return ($row['foundshuffles'] == 0);
677 }
678 return false;
679 }
680
681 final public function isComplete(ilTestQuestionSetConfig $testQuestionSetConfig): bool
682 {
683 if (!count($this->mark_schema->mark_steps)) {
684 return false;
685 }
686
687 if (!$testQuestionSetConfig->isQuestionSetConfigured()) {
688 return false;
689 }
690
691 return true;
692 }
693
694 public function _isComplete($obj_id): bool
695 {
696 global $DIC;
697 $tree = $DIC['tree'];
698 $ilDB = $DIC['ilDB'];
699 $component_repository = $DIC['component.repository'];
700
701 $test = new ilObjTest($obj_id, false);
702 $test->loadFromDb();
703
704 $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $component_repository, $test);
705
706 return $test->isComplete($testQuestionSetConfigFactory->getQuestionSetConfig());
707 }
708
709 public function saveECTSStatus(): void
710 {
714 global $DIC;
715 $ilDB = $DIC['ilDB'];
716
717 if ($this->getTestId() > 0) {
718 $grades = $this->getECTSGrades();
719 $ilDB->manipulateF(
720 "UPDATE tst_tests
721 SET ects_output = %s, ects_a = %s, ects_b = %s, ects_c = %s, ects_d = %s, ects_e = %s, ects_fx = %s
722 WHERE test_id = %s",
723 array('text', 'float', 'float', 'float', 'float', 'float', 'float', 'integer'),
724 array(
725 (int) $this->getECTSOutput(),
726 $grades['A'], $grades['B'], $grades['C'], $grades['D'], $grades['E'],
727 $this->getECTSFX(),
728 $this->getTestId()
729 )
730 );
731 }
732 }
733
734 public function saveCompleteStatus(ilTestQuestionSetConfig $testQuestionSetConfig): void
735 {
736 global $DIC;
737 $ilDB = $DIC['ilDB'];
738
739 $complete = 0;
740 if ($this->isComplete($testQuestionSetConfig)) {
741 $complete = 1;
742 }
743 if ($this->getTestId() > 0) {
744 $ilDB->manipulateF(
745 "UPDATE tst_tests SET complete = %s WHERE test_id = %s",
746 array('text', 'integer'),
747 array($complete, $this->test_id)
748 );
749 }
750 }
751
756 public function getAllRTEContent(): array
757 {
758 $result = array();
759 array_push($result, $this->getIntroduction());
760 array_push($result, $this->getFinalStatement());
761 return $result;
762 }
763
767 public function cleanupMediaobjectUsage(): void
768 {
769 $completecontent = "";
770 foreach ($this->getAllRTEContent() as $content) {
771 $completecontent .= $content;
772 }
774 $completecontent,
775 $this->getType() . ":html",
776 $this->getId()
777 );
778 }
779
780 public function saveToDb(bool $properties_only = false): void
781 {
782 global $DIC;
783 $tree = $DIC['tree'];
784 $ilDB = $DIC['ilDB'];
785 $component_repository = $DIC['component.repository'];
786
787 // moved online_status to ilObjectActivation (see below)
788
789 // cleanup RTE images
791
792 $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $component_repository, $this);
793 $testQuestionSetConfig = $testQuestionSetConfigFactory->getQuestionSetConfig();
794
795 if ($this->test_id == -1) {
796 // Create new dataset
797 $next_id = $ilDB->nextId('tst_tests');
798
799 $ilDB->insert('tst_tests', array(
800 'test_id' => array('integer', $next_id),
801 'obj_fi' => array('integer', $this->getId()),
802 'author' => array('text', $this->getAuthor()),
803 'intro_enabled' => array('integer', (int) $this->isIntroductionEnabled()),
804 'introduction' => array('text', ilRTE::_replaceMediaObjectImageSrc((string) $this->getIntroduction(), 0)),
805 'finalstatement' => array('text', ilRTE::_replaceMediaObjectImageSrc((string) $this->getFinalStatement(), 0)),
806 'showinfo' => array('integer', $this->getShowInfo()),
807 'forcejs' => array('integer', $this->getForceJS()),
808 'customstyle' => array('text', $this->getCustomStyle()),
809 'showfinalstatement' => array('integer', $this->getShowFinalStatement()),
810 'sequence_settings' => array('integer', $this->getSequenceSettings()),
811 'score_reporting' => array('integer', $this->getScoreReporting()),
812 'instant_verification' => array('text', $this->getInstantFeedbackSolution()),
813 'answer_feedback_points' => array('text', $this->getAnswerFeedbackPoints()),
814 'answer_feedback' => array('text', $this->getAnswerFeedback()),
815 'anonymity' => array('text', $this->getAnonymity()),
816 'show_cancel' => array('text', $this->getShowCancel()),
817 'show_marker' => array('integer', $this->getShowMarker()),
818 'fixed_participants' => array('text', $this->getFixedParticipants()),
819 'nr_of_tries' => array('integer', $this->getNrOfTries()),
820 'block_after_passed' => array('integer', (int) $this->isBlockPassesAfterPassedEnabled()),
821 'kiosk' => array('integer', $this->getKiosk()),
822 'use_previous_answers' => array('text', $this->getUsePreviousAnswers()),
823 'title_output' => array('text', $this->getTitleOutput()),
824 'processing_time' => array('text', $this->getProcessingTime()),
825 'enable_processing_time' => array('text', $this->getEnableProcessingTime()),
826 'reset_processing_time' => array('integer', $this->getResetProcessingTime()),
827 'starting_time_enabled' => array('integer', $this->isStartingTimeEnabled()),
828 'starting_time' => array('integer', $this->getStartingTime()),
829 'ending_time_enabled' => array('integer', $this->isEndingTimeEnabled()),
830 'ending_time' => array('integer', $this->getEndingTime()),
831 'complete' => array('text', $this->isComplete($testQuestionSetConfig)),
832 'ects_output' => array('text', $this->getECTSOutput()),
833 'ects_a' => array('float', strlen($this->ects_grades["A"]) ? $this->ects_grades["A"] : 90),// defaults as per db definition
834 'ects_b' => array('float', strlen($this->ects_grades["B"]) ? $this->ects_grades["B"] : 65),
835 'ects_c' => array('float', strlen($this->ects_grades["C"]) ? $this->ects_grades["C"] : 35),
836 'ects_d' => array('float', strlen($this->ects_grades["D"]) ? $this->ects_grades["D"] : 10),
837 'ects_e' => array('float', strlen($this->ects_grades["E"]) ? $this->ects_grades["E"] : 0),
838 'ects_fx' => array('float', $this->getECTSFX()),
839 'shuffle_questions' => array('text', $this->getShuffleQuestions()),
840 'show_summary' => array('integer', $this->getListOfQuestionsSettings()),
841 'password_enabled' => array('integer', (int) $this->isPasswordEnabled()),
842 'password' => array('text', $this->getPassword()),
843 'limit_users_enabled' => array('integer', (int) $this->isLimitUsersEnabled()),
844 'allowedusers' => array('integer', $this->getAllowedUsers()),
845 'alloweduserstimegap' => array('integer', $this->getAllowedUsersTimeGap()),
846 'mailnottype' => array('integer', $this->getMailNotificationType()),
847 'mailnotification' => array('integer', $this->getMailNotification()),
848 'created' => array('integer', time()),
849 'tstamp' => array('integer', time()),
850 'enabled_view_mode' => array('text', $this->getEnabledViewMode()),
851 'template_id' => array('integer', $this->getTemplate()),
852 'obligations_enabled' => array('integer', (int) $this->areObligationsEnabled()),
853 'offer_question_hints' => array('integer', (int) $this->isOfferingQuestionHintsEnabled()),
854 'specific_feedback' => array('integer', $this->getSpecificAnswerFeedback()),
855 'autosave' => array('integer', (int) $this->getAutosave()),
856 'autosave_ival' => array('integer', $this->getAutosaveIval()),
857 'enable_examview' => array('integer', (int) $this->getEnableExamview()),
858 'show_examview_html' => array('integer', (int) $this->getShowExamviewHtml()),
859 'show_examview_pdf' => array('integer', (int) $this->getShowExamviewPdf()),
860 'redirection_mode' => array('integer', $this->getRedirectionMode()),
861 'redirection_url' => array('text', (string) $this->getRedirectionUrl()),
862 'enable_archiving' => array('integer', (int) $this->getEnableArchiving()),
863 'examid_in_test_pass' => array('integer', (int) $this->isShowExamIdInTestPassEnabled()),
864 'sign_submission' => array('integer', (int) $this->getSignSubmission()),
865 'question_set_type' => array('text', $this->getQuestionSetType()),
866 'char_selector_availability' => array('integer', $this->getCharSelectorAvailability()),
867 'char_selector_definition' => array('text', (string) $this->getCharSelectorDefinition()),
868 'skill_service' => array('integer', (int) $this->isSkillServiceEnabled()),
869 'result_tax_filters' => array('text', serialize($this->getResultFilterTaxIds())),
870 'follow_qst_answer_fixation' => array('integer', (int) $this->isFollowupQuestionAnswerFixationEnabled()),
871 'inst_fb_answer_fixation' => array('integer', (int) $this->isInstantFeedbackAnswerFixationEnabled()),
872 'force_inst_fb' => array('integer', (int) $this->isForceInstantFeedbackEnabled()),
873 'broken' => array('integer', (int) $this->isTestFinalBroken()),
874 'pass_waiting' => array('text', $this->getPassWaiting())
875 ));
876
877 $this->test_id = $next_id;
878
880 $this->logAction($this->lng->txtlng("assessment", "log_create_new_test", ilObjAssessmentFolder::_getLogLanguage()));
881 }
882 } else {
883 // Modify existing dataset
884 $oldrow = array();
886 $result = $ilDB->queryF(
887 "SELECT * FROM tst_tests WHERE test_id = %s",
888 array('integer'),
889 array($this->test_id)
890 );
891 if ($result->numRows() == 1) {
892 $oldrow = $ilDB->fetchAssoc($result);
893 }
894 }
895
896 $ilDB->update(
897 'tst_tests',
898 array(
899 'author' => array('text', $this->getAuthor()),
900 'intro_enabled' => array('integer', (int) $this->isIntroductionEnabled()),
901 'introduction' => array('text', ilRTE::_replaceMediaObjectImageSrc((string) $this->getIntroduction(), 0)),
902 'finalstatement' => array('text', ilRTE::_replaceMediaObjectImageSrc((string) $this->getFinalStatement(), 0)),
903 'showinfo' => array('integer', $this->getShowInfo()),
904 'forcejs' => array('integer', $this->getForceJS()),
905 'customstyle' => array('text', $this->getCustomStyle()),
906 'showfinalstatement' => array('integer', $this->getShowFinalStatement()),
907 'sequence_settings' => array('integer', $this->getSequenceSettings()),
908 'score_reporting' => array('integer', $this->getScoreReporting()),
909 'instant_verification' => array('text', $this->getInstantFeedbackSolution()),
910 'answer_feedback_points' => array('text', $this->getAnswerFeedbackPoints()),
911 'answer_feedback' => array('text', $this->getGenericAnswerFeedback()),
912 'anonymity' => array('text', $this->getAnonymity()),
913 'show_cancel' => array('text', $this->getShowCancel()),
914 'show_marker' => array('integer', $this->getShowMarker()),
915 'fixed_participants' => array('text', $this->getFixedParticipants()),
916 'nr_of_tries' => array('integer', $this->getNrOfTries()),
917 'block_after_passed' => array('integer', (int) $this->isBlockPassesAfterPassedEnabled()),
918 'kiosk' => array('integer', $this->getKiosk()),
919 'use_previous_answers' => array('text', $this->getUsePreviousAnswers()),
920 'title_output' => array('text', $this->getTitleOutput()),
921 'processing_time' => array('text', $this->getProcessingTime()),
922 'enable_processing_time' => array('text', $this->getEnableProcessingTime()),
923 'reset_processing_time' => array('integer', $this->getResetProcessingTime()),
924 'starting_time_enabled' => array('integer', $this->isStartingTimeEnabled()),
925 'starting_time' => array('integer', $this->getStartingTime()),
926 'ending_time_enabled' => array('integer', $this->isEndingTimeEnabled()),
927 'ending_time' => array('integer', $this->getEndingTime()),
928 'complete' => array('text', $this->isComplete($testQuestionSetConfig)),
929 'ects_output' => array('text', $this->getECTSOutput()),
930 'ects_a' => array('float', strlen($this->ects_grades["A"]) ? $this->ects_grades["A"] : null),
931 'ects_b' => array('float', strlen($this->ects_grades["B"]) ? $this->ects_grades["B"] : null),
932 'ects_c' => array('float', strlen($this->ects_grades["C"]) ? $this->ects_grades["C"] : null),
933 'ects_d' => array('float', strlen($this->ects_grades["D"]) ? $this->ects_grades["D"] : null),
934 'ects_e' => array('float', strlen($this->ects_grades["E"]) ? $this->ects_grades["E"] : null),
935 'ects_fx' => array('float', $this->getECTSFX()),
936 'shuffle_questions' => array('text', $this->getShuffleQuestions()),
937 'show_summary' => array('integer', $this->getListOfQuestionsSettings()),
938 'password_enabled' => array('integer', (int) $this->isPasswordEnabled()),
939 'password' => array('text', $this->getPassword()),
940 'limit_users_enabled' => array('integer', (int) $this->isLimitUsersEnabled()),
941 'allowedusers' => array('integer', $this->getAllowedUsers()),
942 'alloweduserstimegap' => array('integer', $this->getAllowedUsersTimeGap()),
943 'mailnottype' => array('integer', $this->getMailNotificationType()),
944 'exportsettings' => array('integer', $this->getExportSettings()),
945 'mailnotification' => array('integer', $this->getMailNotification()),
946 'tstamp' => array('integer', time()),
947 'enabled_view_mode' => array('text', $this->getEnabledViewMode()),
948 'template_id' => array('integer', $this->getTemplate()),
949 'obligations_enabled' => array('integer', (int) $this->areObligationsEnabled()),
950 'offer_question_hints' => array('integer', (int) $this->isOfferingQuestionHintsEnabled()),
951 'specific_feedback' => array('integer', $this->getSpecificAnswerFeedback()),
952 'autosave' => array('integer', (int) $this->getAutosave()),
953 'autosave_ival' => array('integer', $this->getAutosaveIval()),
954 'enable_examview' => array('integer', (int) $this->getEnableExamview()),
955 'show_examview_html' => array('integer', (int) $this->getShowExamviewHtml()),
956 'show_examview_pdf' => array('integer', (int) $this->getShowExamviewPdf()),
957 'redirection_mode' => array('integer', $this->getRedirectionMode()),
958 'redirection_url' => array('text', (string) $this->getRedirectionUrl()),
959 'enable_archiving' => array('integer', (int) $this->getEnableArchiving()),
960 'examid_in_test_pass' => array('integer', (int) $this->isShowExamIdInTestPassEnabled()),
961 'sign_submission' => array('integer', (int) $this->getSignSubmission()),
962 'question_set_type' => array('text', $this->getQuestionSetType()),
963 'char_selector_availability' => array('integer', $this->getCharSelectorAvailability()),
964 'char_selector_definition' => array('text', (string) $this->getCharSelectorDefinition()),
965 'skill_service' => array('integer', (int) $this->isSkillServiceEnabled()),
966 'result_tax_filters' => array('text', serialize($this->getResultFilterTaxIds())),
967 'show_grading_status' => array('integer', (int) $this->isShowGradingStatusEnabled()),
968 'show_grading_mark' => array('integer', (int) $this->isShowGradingMarkEnabled()),
969 'follow_qst_answer_fixation' => array('integer', (int) $this->isFollowupQuestionAnswerFixationEnabled()),
970 'inst_fb_answer_fixation' => array('integer', (int) $this->isInstantFeedbackAnswerFixationEnabled()),
971 'force_inst_fb' => array('integer', (int) $this->isForceInstantFeedbackEnabled()),
972 'broken' => array('integer', (int) $this->isTestFinalBroken()),
973 'pass_waiting' => array('text', $this->getPassWaiting())
974 ),
975 array(
976 'test_id' => array('integer', $this->getTestId())
977 )
978 );
979
981 $logresult = $ilDB->queryF(
982 "SELECT * FROM tst_tests WHERE test_id = %s",
983 array('integer'),
984 array($this->getTestId())
985 );
986 $newrow = array();
987 if ($logresult->numRows() == 1) {
988 $newrow = $ilDB->fetchAssoc($logresult);
989 }
990 $changed_fields = array();
991 foreach ($oldrow as $key => $value) {
992 if (strcmp($oldrow[$key], $newrow[$key]) != 0) {
993 array_push($changed_fields, "$key: " . $oldrow[$key] . " => " . $newrow[$key]);
994 }
995 }
996 $changes = join(", ", $changed_fields);
997 if (count($changed_fields) > 0) {
998 $this->logAction($this->lng->txtlng("assessment", "log_modified_test", ilObjAssessmentFolder::_getLogLanguage()) . " [" . $changes . "]");
999 }
1000 }
1001 if ($this->evalTotalPersons() > 0) {
1002 // reset the finished status of participants if the nr of test passes did change
1003 if ($this->getNrOfTries() > 0) {
1004 // set all unfinished tests with nr of passes >= allowed passes finished
1005 $aresult = $ilDB->queryF(
1006 "SELECT active_id FROM tst_active WHERE test_fi = %s AND tries >= %s AND submitted = %s",
1007 array('integer', 'integer', 'integer'),
1008 array($this->getTestId(), $this->getNrOfTries(), 0)
1009 );
1010 while ($row = $ilDB->fetchAssoc($aresult)) {
1011 $ilDB->manipulateF(
1012 "UPDATE tst_active SET submitted = %s, submittimestamp = %s WHERE active_id = %s",
1013 array('integer', 'timestamp', 'integer'),
1014 array(1, date('Y-m-d H:i:s'), $row["active_id"])
1015 );
1016 }
1017
1018 // set all finished tests with nr of passes < allowed passes not finished
1019 $aresult = $ilDB->queryF(
1020 "SELECT active_id FROM tst_active WHERE test_fi = %s AND tries < %s AND submitted = %s",
1021 array('integer', 'integer', 'integer'),
1022 array($this->getTestId(), $this->getNrOfTries() - 1, 1)
1023 );
1024 while ($row = $ilDB->fetchAssoc($aresult)) {
1025 $ilDB->manipulateF(
1026 "UPDATE tst_active SET submitted = %s, submittimestamp = %s WHERE active_id = %s",
1027 array('integer', 'timestamp', 'integer'),
1028 array(0, null, $row["active_id"])
1029 );
1030 }
1031 } else {
1032 // set all finished tests with nr of passes >= allowed passes not finished
1033 $aresult = $ilDB->queryF(
1034 "SELECT active_id FROM tst_active WHERE test_fi = %s AND submitted = %s",
1035 array('integer', 'integer'),
1036 array($this->getTestId(), 1)
1037 );
1038 while ($row = $ilDB->fetchAssoc($aresult)) {
1039 $ilDB->manipulateF(
1040 "UPDATE tst_active SET submitted = %s, submittimestamp = %s WHERE active_id = %s",
1041 array('integer', 'timestamp', 'integer'),
1042 array(0, null, $row["active_id"])
1043 );
1044 }
1045 }
1046 }
1047 }
1048
1049 if (!$this->getOldOnlineStatus() && !$this->getOfflineStatus()) {
1050 global $DIC;
1051 $ilUser = $DIC['ilUser'];
1052 $newsItem = new ilNewsItem();
1053 $newsItem->setContext($this->getId(), 'tst');
1054 $newsItem->setPriority(NEWS_NOTICE);
1055 $newsItem->setTitle('new_test_online');
1056 $newsItem->setContentIsLangVar(true);
1057 $newsItem->setContent('');
1058 $newsItem->setUserId($ilUser->getId());
1059 $newsItem->setVisibility(NEWS_USERS);
1060 $newsItem->create();
1061 } elseif ($this->getOldOnlineStatus() && !$this->getOfflineStatus()) {
1062 ilNewsItem::deleteNewsOfContext($this->getId(), 'tst');
1063 } elseif (!$this->getOfflineStatus()) {
1064 $newsId = ilNewsItem::getFirstNewsIdForContext($this->getId(), 'tst');
1065 if ($newsId > 0) {
1066 $newsItem = new ilNewsItem($newsId);
1067 $newsItem->setTitle('new_test_online');
1068 $newsItem->setContentIsLangVar(true);
1069 $newsItem->setContent('');
1070 $newsItem->update();
1071 }
1072 }
1073
1074 // moved activation to ilObjectActivation
1075 if ($this->ref_id) {
1076 ilObjectActivation::getItem($this->ref_id);
1077
1078 $item = new ilObjectActivation();
1079 if (!$this->isActivationLimited()) {
1080 $item->setTimingType(ilObjectActivation::TIMINGS_DEACTIVATED);
1081 } else {
1082 $item->setTimingType(ilObjectActivation::TIMINGS_ACTIVATION);
1083 $item->setTimingStart($this->getActivationStartingTime());
1084 $item->setTimingEnd($this->getActivationEndingTime());
1085 $item->toggleVisible($this->getActivationVisibility());
1086 }
1087
1088 $item->update($this->ref_id);
1089 }
1090
1091 if (!$properties_only) {
1092 if ($this->getQuestionSetType() == self::QUESTION_SET_TYPE_FIXED) {
1093 $this->saveQuestionsToDb();
1094 }
1095
1096 $this->mark_schema->saveToDb($this->test_id);
1097 }
1098 }
1099
1100 public function saveQuestionsToDb(): void
1101 {
1102 global $DIC;
1103 $ilDB = $DIC['ilDB'];
1104
1105 $oldquestions = array();
1107 $result = $ilDB->queryF(
1108 "SELECT question_fi FROM tst_test_question WHERE test_fi = %s ORDER BY sequence",
1109 array('integer'),
1110 array($this->getTestId())
1111 );
1112 if ($result->numRows() > 0) {
1113 while ($row = $ilDB->fetchAssoc($result)) {
1114 array_push($oldquestions, $row["question_fi"]);
1115 }
1116 }
1117 }
1118 // workaround for lost obligations
1119 // this method is called if a question is removed
1120 $currentQuestionsObligationsQuery = 'SELECT question_fi, obligatory FROM tst_test_question WHERE test_fi = %s';
1121 $rset = $ilDB->queryF($currentQuestionsObligationsQuery, array('integer'), array($this->getTestId()));
1122 while ($row = $ilDB->fetchAssoc($rset)) {
1123 $obligatoryQuestionState[$row['question_fi']] = $row['obligatory'];
1124 }
1125 // delete existing category relations
1126 $affectedRows = $ilDB->manipulateF(
1127 "DELETE FROM tst_test_question WHERE test_fi = %s",
1128 array('integer'),
1129 array($this->getTestId())
1130 );
1131 // create new category relations
1132 foreach ($this->questions as $key => $value) {
1133 // workaround for import witout obligations information
1134 if (!isset($obligatoryQuestionState[$value]) || is_null($obligatoryQuestionState[$value])) {
1135 $obligatoryQuestionState[$value] = 0;
1136 }
1137
1138 // insert question
1139 $next_id = $ilDB->nextId('tst_test_question');
1140 $ilDB->insert('tst_test_question', array(
1141 'test_question_id' => array('integer', $next_id),
1142 'test_fi' => array('integer', $this->getTestId()),
1143 'question_fi' => array('integer', $value),
1144 'sequence' => array('integer', $key),
1145 'obligatory' => array('integer', $obligatoryQuestionState[$value]),
1146 'tstamp' => array('integer', time())
1147 ));
1148 }
1150 $result = $ilDB->queryF(
1151 "SELECT question_fi FROM tst_test_question WHERE test_fi = %s ORDER BY sequence",
1152 array('integer'),
1153 array($this->getTestId())
1154 );
1155 $newquestions = array();
1156 if ($result->numRows() > 0) {
1157 while ($row = $ilDB->fetchAssoc($result)) {
1158 array_push($newquestions, $row["question_fi"]);
1159 }
1160 }
1161 foreach ($oldquestions as $index => $question_id) {
1162 if (strcmp($newquestions[$index], $question_id) != 0) {
1163 $pos = array_search($question_id, $newquestions);
1164 if ($pos === false) {
1165 $this->logAction($this->lng->txtlng("assessment", "log_question_removed", ilObjAssessmentFolder::_getLogLanguage()), $question_id);
1166 } else {
1167 $this->logAction($this->lng->txtlng("assessment", "log_question_position_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . ($index + 1) . " => " . ($pos + 1), $question_id);
1168 }
1169 }
1170 }
1171 foreach ($newquestions as $index => $question_id) {
1172 if (array_search($question_id, $oldquestions) === false) {
1173 $this->logAction($this->lng->txtlng("assessment", "log_question_added", ilObjAssessmentFolder::_getLogLanguage()) . ": " . ($index + 1), $question_id);
1174 }
1175 }
1176 }
1177 }
1178
1184 protected function isNewRandomTest(): bool
1185 {
1186 global $DIC;
1187 $ilDB = $DIC['ilDB'];
1188 $result = $ilDB->queryF(
1189 'SELECT copy_id FROM tst_rnd_cpy WHERE tst_fi = %s',
1190 array('integer'),
1191 array($this->getTestId())
1192 );
1193 return $result->numRows() > 0;
1194 }
1195
1203 public function randomSelectQuestions(
1204 int $nr_of_questions,
1205 int $questionpool,
1206 $use_obj_id = 0,
1207 $qpls = "",
1208 $pass = null
1209 ): array {
1210 global $DIC;
1211 $rbacsystem = $DIC['rbacsystem'];
1212 $ilDB = $DIC['ilDB'];
1213
1214 // retrieve object id instead of ref id if necessary
1215 if (($questionpool != 0) && (!$use_obj_id)) {
1216 $questionpool = ilObject::_lookupObjId($questionpool);
1217 }
1218
1219 // get original ids of all existing questions in the test
1220 $result = $ilDB->queryF(
1221 "SELECT qpl_questions.original_id FROM qpl_questions, tst_test_question WHERE qpl_questions.question_id = tst_test_question.question_fi AND qpl_questions.tstamp > 0 AND tst_test_question.test_fi = %s",
1222 array("integer"),
1223 array($this->getTestId())
1224 );
1225 $original_ids = array();
1226 $paramtypes = array();
1227 $paramvalues = array();
1228 while ($row = $ilDB->fetchAssoc($result)) {
1229 array_push($original_ids, $row['original_id']);
1230 }
1231
1232 $available = "";
1233 // get a list of all available questionpools
1234 if (($questionpool == 0) && (!is_array($qpls))) {
1235 $available_pools = array_keys(ilObjQuestionPool::_getAvailableQuestionpools($use_object_id = true, $equal_points = false, $could_be_offline = false, $showPath = false, $with_questioncount = false, "read", ilObject::_lookupOwner($this->getId())));
1236 if (count($available_pools)) {
1237 $available = " AND " . $ilDB->in('obj_fi', $available_pools, false, 'integer');
1238 } else {
1239 return array();
1240 }
1241 }
1242
1243 $constraint_qpls = "";
1244 $result_array = array();
1245 if ($questionpool == 0) {
1246 if (is_array($qpls)) {
1247 if (count($qpls) > 0) {
1248 $constraint_qpls = " AND " . $ilDB->in('obj_fi', $qpls, false, 'integer');
1249 }
1250 }
1251 }
1252
1253 $original_clause = "";
1254 if (count($original_ids)) {
1255 $original_clause = " AND " . $ilDB->in('question_id', $original_ids, true, 'integer');
1256 }
1257
1258 if ($questionpool == 0) {
1259 $result = $ilDB->queryF(
1260 "SELECT question_id FROM qpl_questions WHERE original_id IS NULL $available $constraint_qpls AND owner > %s AND complete = %s $original_clause",
1261 array('integer', 'text'),
1262 array(0, "1")
1263 );
1264 } else {
1265 $result = $ilDB->queryF(
1266 "SELECT question_id FROM qpl_questions WHERE original_id IS NULL AND obj_fi = %s AND owner > %s AND complete = %s $original_clause",
1267 array('integer','integer', 'text'),
1268 array($questionpool, 0, "1")
1269 );
1270 }
1271 $found_ids = array();
1272 while ($row = $ilDB->fetchAssoc($result)) {
1273 array_push($found_ids, $row['question_id']);
1274 }
1275 $nr_of_questions = ($nr_of_questions > count($found_ids)) ? count($found_ids) : $nr_of_questions;
1276 if ($nr_of_questions == 0) {
1277 return array();
1278 }
1279 $rand_keys = array_rand($found_ids, $nr_of_questions);
1280 $result = array();
1281 if (is_array($rand_keys)) {
1282 foreach ($rand_keys as $key) {
1283 $result[$found_ids[$key]] = $found_ids[$key];
1284 }
1285 } else {
1286 $result[$found_ids[$rand_keys]] = $found_ids[$rand_keys];
1287 }
1288 return $result;
1289 }
1290
1294 public function getNrOfResultsForPass($active_id, $pass): int
1295 {
1296 global $DIC;
1297 $ilDB = $DIC['ilDB'];
1298
1299 $result = $ilDB->queryF(
1300 "SELECT test_result_id FROM tst_test_result WHERE active_fi = %s AND pass = %s",
1301 array('integer','integer'),
1302 array($active_id, $pass)
1303 );
1304 return $result->numRows();
1305 }
1306
1311 public function hasRandomQuestionsForPass(int $active_id, int $pass): bool
1312 {
1313 global $DIC;
1314 $ilDB = $DIC['ilDB'];
1315 $result = $ilDB->queryF(
1316 "SELECT test_random_question_id FROM tst_test_rnd_qst WHERE active_fi = %s AND pass = %s",
1317 array('integer','integer'),
1318 array($active_id, $pass)
1319 );
1320 return ($result->numRows() > 0) ? true : false;
1321 }
1322
1323 public function loadFromDb(): void
1324 {
1325 global $DIC;
1326 $ilDB = $DIC['ilDB'];
1327
1328 $result = $ilDB->queryF(
1329 "SELECT * FROM tst_tests WHERE obj_fi = %s",
1330 array('integer'),
1331 array($this->getId())
1332 );
1333 if ($result->numRows() == 1) {
1334 $data = $ilDB->fetchObject($result);
1335 $this->setTestId($data->test_id);
1336
1337 if ($data->author) {
1338 if (strlen($this->getAuthor()) == 0) {
1339 $this->saveAuthorToMetadata($data->author);
1340 }
1341 $this->setAuthor($data->author);
1342 }
1343
1344 $this->setIntroductionEnabled($data->intro_enabled);
1345 $this->setIntroduction(ilRTE::_replaceMediaObjectImageSrc((string) $data->introduction, 1));
1346 $this->setShowInfo($data->showinfo);
1347 $this->setFinalStatement(ilRTE::_replaceMediaObjectImageSrc((string) $data->finalstatement, 1));
1348 $this->setForceJS($data->forcejs);
1349 $this->setCustomStyle($data->customstyle);
1350 $this->setShowFinalStatement($data->showfinalstatement);
1351 $this->setSequenceSettings($data->sequence_settings);
1352 $this->setInstantFeedbackSolution($data->instant_verification);
1353 $this->setAnswerFeedbackPoints($data->answer_feedback_points);
1354 $this->setAnswerFeedback($data->answer_feedback);
1355 $this->setAnonymity($data->anonymity);
1356 $this->setShowCancel($data->show_cancel);
1357 $this->setShowMarker($data->show_marker);
1358 $this->setFixedParticipants($data->fixed_participants);
1359 $this->setNrOfTries($data->nr_of_tries);
1360 $this->setBlockPassesAfterPassedEnabled((bool) $data->block_after_passed);
1361 $this->setKiosk($data->kiosk);
1362 $this->setUsePreviousAnswers($data->use_previous_answers);
1363 $this->setRedirectionMode($data->redirection_mode);
1364 $this->setRedirectionUrl($data->redirection_url);
1365 $this->setTitleOutput($data->title_output);
1366 $this->setProcessingTime($data->processing_time);
1367 $this->setEnableProcessingTime($data->enable_processing_time);
1368 $this->setResetProcessingTime($data->reset_processing_time);
1369 $this->setReportingDate($data->reporting_date);
1370 $this->setShuffleQuestions($data->shuffle_questions);
1371 $this->setStartingTimeEnabled($data->starting_time_enabled);
1372 $this->setStartingTime($data->starting_time);
1373 $this->setEndingTimeEnabled($data->ending_time_enabled);
1374 $this->setEndingTime($data->ending_time);
1375 $this->setListOfQuestionsSettings($data->show_summary);
1376 $this->setECTSOutput($data->ects_output);
1377 $this->setECTSGrades(
1378 array(
1379 "A" => $data->ects_a,
1380 "B" => $data->ects_b,
1381 "C" => $data->ects_c,
1382 "D" => $data->ects_d,
1383 "E" => $data->ects_e
1384 )
1385 );
1386 $this->setECTSFX($data->ects_fx);
1387 $this->mark_schema->flush();
1388 $this->mark_schema->loadFromDb($this->getTestId());
1389 $this->setMailNotification($data->mailnotification);
1390 $this->setMailNotificationType($data->mailnottype);
1391 $this->setPasswordEnabled($data->password_enabled);
1392 $this->setPassword($data->password);
1393 $this->setLimitUsersEnabled($data->limit_users_enabled);
1394 $this->setAllowedUsers($data->allowedusers);
1395 $this->setAllowedUsersTimeGap($data->alloweduserstimegap);
1396 $this->setObligationsEnabled($data->obligations_enabled);
1397 $this->setOfferingQuestionHintsEnabled($data->offer_question_hints);
1398 $this->setEnabledViewMode($data->enabled_view_mode);
1399 $this->setTemplate($data->template_id);
1400 $this->setOldOnlineStatus(!$this->getOfflineStatus());
1401 $this->setSpecificAnswerFeedback((int) $data->specific_feedback);
1402 $this->setAutosave((bool) $data->autosave);
1403 $this->setAutosaveIval((int) $data->autosave_ival);
1404 $this->setEnableExamview((bool) $data->enable_examview);
1405 $this->setShowExamviewHtml((bool) $data->show_examview_html);
1406 $this->setShowExamviewPdf((bool) $data->show_examview_pdf);
1407 $this->setEnableArchiving((bool) $data->enable_archiving);
1408 $this->setShowExamIdInTestPassEnabled((bool) $data->examid_in_test_pass);
1409 $this->setSignSubmission((bool) $data->sign_submission);
1410 $this->setQuestionSetType($data->question_set_type);
1411 $this->setCharSelectorAvailability((int) $data->char_selector_availability);
1412 $this->setCharSelectorDefinition($data->char_selector_definition);
1413 $this->setSkillServiceEnabled((bool) $data->skill_service);
1414 $this->setShowGradingStatusEnabled((bool) $data->show_grading_status);
1415 $this->setShowGradingMarkEnabled((bool) $data->show_grading_mark);
1416 $this->setFollowupQuestionAnswerFixationEnabled((bool) $data->follow_qst_answer_fixation);
1417 $this->setInstantFeedbackAnswerFixationEnabled((bool) $data->inst_fb_answer_fixation);
1418 $this->setForceInstantFeedbackEnabled((bool) $data->force_inst_fb);
1419 $this->setTestFinalBroken((bool) $data->broken);
1420 $this->setPassWaiting($data->pass_waiting);
1421 $this->loadQuestions();
1422 }
1423
1424 // moved activation to ilObjectActivation
1425 if (isset($this->ref_id)) {
1426 $activation = ilObjectActivation::getItem($this->ref_id);
1427 switch ($activation["timing_type"]) {
1429 $this->setActivationLimited(true);
1430 $this->setActivationStartingTime($activation["timing_start"]);
1431 $this->setActivationEndingTime($activation["timing_end"]);
1432 $this->setActivationVisibility($activation["visible"]);
1433 break;
1434
1435 default:
1436 $this->setActivationLimited(false);
1437 break;
1438 }
1439 }
1440 }
1441
1448 public function loadQuestions($active_id = "", $pass = null): void
1449 {
1450 global $DIC;
1451 $ilUser = $DIC['ilUser'];
1452 $ilDB = $DIC['ilDB'];
1453
1454 $tags_trafo = $this->refinery->string()->stripTags();
1455
1456 $this->questions = array();
1457 if ($this->isRandomTest()) {
1458 if (strcmp($active_id, "") == 0) {
1459 $active_id = $this->getActiveIdOfUser($ilUser->getId());
1460 }
1461 if (is_null($pass)) {
1462 $pass = self::_getPass($active_id);
1463 }
1464 $result = $ilDB->queryF(
1465 "SELECT tst_test_rnd_qst.* FROM tst_test_rnd_qst, qpl_questions WHERE tst_test_rnd_qst.active_fi = %s AND qpl_questions.question_id = tst_test_rnd_qst.question_fi AND tst_test_rnd_qst.pass = %s ORDER BY sequence",
1466 array('integer', 'integer'),
1467 array($active_id, $pass)
1468 );
1469 // The following is a fix for random tests prior to ILIAS 3.8. If someone started a random test in ILIAS < 3.8, there
1470 // is only one test pass (pass = 0) in tst_test_rnd_qst while with ILIAS 3.8 there are questions for every test pass.
1471 // To prevent problems with tests started in an older version and continued in ILIAS 3.8, the first pass should be taken if
1472 // no questions are present for a newer pass.
1473 if ($result->numRows() == 0) {
1474 $result = $ilDB->queryF(
1475 "SELECT tst_test_rnd_qst.* FROM tst_test_rnd_qst, qpl_questions WHERE tst_test_rnd_qst.active_fi = %s AND qpl_questions.question_id = tst_test_rnd_qst.question_fi AND tst_test_rnd_qst.pass = 0 ORDER BY sequence",
1476 array('integer'),
1477 array($active_id)
1478 );
1479 }
1480 } else {
1481 $result = $ilDB->queryF(
1482 "SELECT tst_test_question.* FROM tst_test_question, qpl_questions WHERE tst_test_question.test_fi = %s AND qpl_questions.question_id = tst_test_question.question_fi ORDER BY sequence",
1483 array('integer'),
1484 array($this->test_id)
1485 );
1486 }
1487 $index = 1;
1488 if ($this->test_id !== -1) {
1489 //Omit loading of questions for non-id'ed test
1490 while ($data = $ilDB->fetchAssoc($result)) {
1491 $this->questions[$index++] = $data["question_fi"];
1492 }
1493 }
1494 }
1495
1496 public function isIntroductionEnabled(): bool
1497 {
1498 return $this->introductionEnabled;
1499 }
1500
1504 public function setIntroductionEnabled($introductionEnabled): void
1505 {
1506 $this->introductionEnabled = $introductionEnabled;
1507 }
1508
1509 public function getIntroduction(): string
1510 {
1511 return $this->introduction;
1512 }
1513
1514 public function setIntroduction(string $introduction): void
1515 {
1516 $this->introduction = $this->getHtmlQuestionContentPurifier()->purify($introduction);
1517 }
1518
1519 public function setFinalStatement(string $a_statement): void
1520 {
1521 $this->_finalstatement = $a_statement;
1522 }
1523
1531 public function setShowInfo($a_info = 1)
1532 {
1533 $this->_showinfo = ($a_info) ? 1 : 0;
1534 }
1535
1543 public function setForceJS($a_js = 1)
1544 {
1545 $this->_forcejs = ($a_js) ? 1 : 0;
1546 }
1547
1555 public function setCustomStyle($a_customStyle = null)
1556 {
1557 $this->_customStyle = $a_customStyle;
1558 }
1559
1564 public function getCustomStyle(): ?string
1565 {
1566 return (strlen($this->_customStyle)) ? $this->_customStyle : null;
1567 }
1568
1576 public function setShowFinalStatement($show = 0)
1577 {
1578 $this->_showfinalstatement = ($show) ? 1 : 0;
1579 }
1580
1581 public function getFinalStatement(): string
1582 {
1583 return $this->_finalstatement;
1584 }
1585
1593 public function getShowInfo(): int
1594 {
1595 return ($this->_showinfo) ? 1 : 0;
1596 }
1597
1605 public function getForceJS(): int
1606 {
1607 return ($this->_forcejs) ? 1 : 0;
1608 }
1609
1617 public function getShowFinalStatement(): int
1618 {
1619 return ($this->_showfinalstatement) ? 1 : 0;
1620 }
1621
1629 public function getTestId(): int
1630 {
1631 return $this->test_id;
1632 }
1633
1634 public function getECTSOutput(): int
1635 {
1636 return ($this->ects_output) ? 1 : 0;
1637 }
1638
1639 public function setECTSOutput($a_ects_output): void
1640 {
1641 $this->ects_output = $a_ects_output ? 1 : 0;
1642 }
1643
1644 public function getECTSFX(): ?float
1645 {
1646 return $this->ects_fx;
1647 }
1648
1649 public function setECTSFX($a_ects_fx): void
1650 {
1651 $this->ects_fx = (float) str_replace(",", ".", $a_ects_fx);
1652 }
1653
1654 public function getECTSGrades(): array
1655 {
1656 return $this->ects_grades;
1657 }
1658
1659 public function setECTSGrades(array $a_ects_grades): void
1660 {
1661 $this->ects_grades = $a_ects_grades;
1662 }
1663
1669 public function getSequenceSettings(): int
1670 {
1671 return ($this->sequence_settings) ? $this->sequence_settings : 0;
1672 }
1673
1679 public function setSequenceSettings($sequence_settings = 0): void
1680 {
1681 $this->sequence_settings = $sequence_settings;
1682 }
1683
1684 public function isPostponingEnabled(): bool
1685 {
1686 return (bool) $this->getSequenceSettings();
1687 }
1688
1689 public function setPostponingEnabled($postponingEnabled): void
1690 {
1691 $this->setSequenceSettings((int) $postponingEnabled);
1692 }
1693
1700 public function setInstantFeedbackSolution($instant_feedback = 0): void
1701 {
1702 switch ($instant_feedback) {
1703 case 1:
1704 $this->instant_verification = 1;
1705 break;
1706 default:
1707 $this->instant_verification = 0;
1708 break;
1709 }
1710 }
1711
1718 public function setAnswerFeedback($answer_feedback = 0): void
1719 {
1720 switch ($answer_feedback) {
1721 case 1:
1722 $this->answer_feedback = 1;
1723 break;
1724 default:
1725 $this->answer_feedback = 0;
1726 break;
1727 }
1728 }
1729
1730 public function setGenericAnswerFeedback(int $generic_answer_feedback = 0): void
1731 {
1732 switch ($generic_answer_feedback) {
1733 case 1:
1734 $this->answer_feedback = 1;
1735 break;
1736 default:
1737 $this->answer_feedback = 0;
1738 break;
1739 }
1740 }
1741
1748 public function setAnswerFeedbackPoints($answer_feedback_points = 0): void
1749 {
1750 switch ($answer_feedback_points) {
1751 case 1:
1752 $this->answer_feedback_points = 1;
1753 break;
1754 default:
1755 $this->answer_feedback_points = 0;
1756 break;
1757 }
1758 }
1759
1764 public function setReportingDate($reporting_date): void
1765 {
1766 if (!$reporting_date) {
1767 $this->reporting_date = '';
1768 $this->setECTSOutput(false);
1769 } else {
1770 $this->reporting_date = $reporting_date;
1771 }
1772 }
1773
1774 public const SCORE_REPORTING_DISABLED = 0;
1775 public const SCORE_REPORTING_FINISHED = 1;
1776 public const SCORE_REPORTING_IMMIDIATLY = 2;
1777 public const SCORE_REPORTING_DATE = 3;
1778 public const SCORE_REPORTING_AFTER_PASSED = 4;
1779
1780 public function getScoreReporting(): int
1781 {
1782 if ($this->getTestId() !== -1) {
1783 return $this->getScoreSettings()->getResultSummarySettings()->getScoreReporting();
1784 }
1785 return 0;
1786 }
1787
1788 public function isScoreReportingEnabled(): bool
1789 {
1790 switch ($this->getScoreReporting()) {
1791 case self::SCORE_REPORTING_FINISHED:
1792 case self::SCORE_REPORTING_IMMIDIATLY:
1793 case self::SCORE_REPORTING_DATE:
1794 case self::SCORE_REPORTING_AFTER_PASSED:
1795
1796 return true;
1797
1798 case self::SCORE_REPORTING_DISABLED:
1799 default:
1800
1801 return false;
1802 }
1803 }
1804
1813 {
1814 return ($this->instant_verification) ? $this->instant_verification : 0;
1815 }
1816
1825 public function getAnswerFeedback(): int
1826 {
1827 return ($this->answer_feedback) ? $this->answer_feedback : 0;
1828 }
1829
1837 {
1838 return ($this->answer_feedback) ? $this->answer_feedback : 0;
1839 }
1840
1848 public function getAnswerFeedbackPoints(): int
1849 {
1850 return ($this->answer_feedback_points) ? $this->answer_feedback_points : 0;
1851 }
1852
1860 public function getCountSystem(): int
1861 {
1862 return $this->getScoreSettings()->getScoringSettings()->getCountSystem();
1863 }
1864
1872 public static function _getCountSystem($active_id)
1873 {
1874 global $DIC;
1875 $ilDB = $DIC['ilDB'];
1876 $result = $ilDB->queryF(
1877 "SELECT tst_tests.count_system FROM tst_tests, tst_active WHERE tst_active.active_id = %s AND tst_active.test_fi = tst_tests.test_id",
1878 array('integer'),
1879 array($active_id)
1880 );
1881 if ($result->numRows()) {
1882 $row = $ilDB->fetchAssoc($result);
1883 return $row["count_system"];
1884 }
1885 return false;
1886 }
1887
1895 public function getScoreCutting(): int
1896 {
1897 return $this->getScoreSettings()->getScoringSettings()->getScoreCutting();
1898 }
1899
1907 public function getPassScoring(): int
1908 {
1909 return $this->getScoreSettings()->getScoringSettings()->getPassScoring();
1910 }
1911
1919 public static function _getPassScoring($active_id): int
1920 {
1921 global $DIC;
1922 $ilDB = $DIC['ilDB'];
1923 $result = $ilDB->queryF(
1924 "SELECT tst_tests.pass_scoring FROM tst_tests, tst_active WHERE tst_tests.test_id = tst_active.test_fi AND tst_active.active_id = %s",
1925 array('integer'),
1926 array($active_id)
1927 );
1928 if ($result->numRows()) {
1929 $row = $ilDB->fetchAssoc($result);
1930 return $row["pass_scoring"];
1931 }
1932 return 0;
1933 }
1934
1942 public static function _getScoreCutting($active_id): bool
1943 {
1944 global $DIC;
1945 $ilDB = $DIC['ilDB'];
1946 $result = $ilDB->queryF(
1947 "SELECT tst_tests.score_cutting FROM tst_tests, tst_active WHERE tst_active.active_id = %s AND tst_tests.test_id = tst_active.test_fi",
1948 array('integer'),
1949 array($active_id)
1950 );
1951 if ($result->numRows()) {
1952 $row = $ilDB->fetchAssoc($result);
1953 return $row["score_cutting"];
1954 }
1955 return false;
1956 }
1957
1966 public function getReportingDate(): ?DateTimeImmutable
1967 {
1968 return $this->getScoreSettings()->getResultSummarySettings()->getReportingDate();
1969 }
1970
1978 public function getNrOfTries(): int
1979 {
1980 return ($this->nr_of_tries) ? $this->nr_of_tries : 0;
1981 }
1982
1986 public function isBlockPassesAfterPassedEnabled(): bool
1987 {
1988 return $this->blockPassesAfterPassedEnabled;
1989 }
1990
1994 public function setBlockPassesAfterPassedEnabled($blockPassesAfterPassedEnabled)
1995 {
1996 $this->blockPassesAfterPassedEnabled = $blockPassesAfterPassedEnabled;
1997 }
1998
2006 public function getKiosk(): int
2007 {
2008 return ($this->_kiosk) ? $this->_kiosk : 0;
2009 }
2010
2011
2019 public function setKiosk($kiosk = 0)
2020 {
2021 $this->_kiosk = $kiosk;
2022 }
2023
2031 public function getKioskMode(): bool
2032 {
2033 if (($this->_kiosk & 1) > 0) {
2034 return true;
2035 } else {
2036 return false;
2037 }
2038 }
2039
2047 public function setKioskMode($a_kiosk = false)
2048 {
2049 if ($a_kiosk) {
2050 $this->_kiosk = $this->_kiosk | 1;
2051 } else {
2052 if ($this->getKioskMode()) {
2053 $this->_kiosk = $this->_kiosk ^ 1;
2054 }
2055 }
2056 }
2057
2065 public function getShowKioskModeTitle(): bool
2066 {
2067 if (($this->_kiosk & 2) > 0) {
2068 return true;
2069 } else {
2070 return false;
2071 }
2072 }
2073
2080 public function setShowKioskModeTitle($a_title = false)
2081 {
2082 if ($a_title) {
2083 $this->_kiosk = $this->_kiosk | 2;
2084 } else {
2085 if ($this->getShowKioskModeTitle()) {
2086 $this->_kiosk = $this->_kiosk ^ 2;
2087 }
2088 }
2089 }
2090
2098 public function getShowKioskModeParticipant(): bool
2099 {
2100 if (($this->_kiosk & 4) > 0) {
2101 return true;
2102 } else {
2103 return false;
2104 }
2105 }
2106
2113 public function setShowKioskModeParticipant($a_participant = false)
2114 {
2115 if ($a_participant) {
2116 $this->_kiosk = $this->_kiosk | 4;
2117 } else {
2118 if ($this->getShowKioskModeParticipant()) {
2119 $this->_kiosk = $this->_kiosk ^ 4;
2120 }
2121 }
2122 }
2123
2131 public function getUsePreviousAnswers(): int
2132 {
2133 return ($this->use_previous_answers) ? $this->use_previous_answers : 0;
2134 }
2135
2143 public function getTitleOutput(): int
2144 {
2145 return ($this->title_output) ? $this->title_output : 0;
2146 }
2147
2156 public function _getTitleOutput($active_id): int
2157 {
2158 global $DIC;
2159 $ilDB = $DIC['ilDB'];
2160
2161 $result = $ilDB->queryF(
2162 "SELECT tst_tests.title_output FROM tst_tests, tst_active WHERE tst_tests.test_id = tst_active.test_fi AND tst_active.active_id = %s",
2163 array('integer'),
2164 array($active_id)
2165 );
2166 if ($result->numRows()) {
2167 $row = $ilDB->fetchAssoc($result);
2168 return $row["title_output"];
2169 }
2170 return 0;
2171 }
2172
2173 public function isPreviousSolutionReuseEnabled($active_id): bool
2174 {
2175 $result = $this->db->queryF(
2176 "SELECT tst_tests.use_previous_answers FROM tst_tests, tst_active WHERE tst_tests.test_id = tst_active.test_fi AND tst_active.active_id = %s",
2177 array("integer"),
2178 array($active_id)
2179 );
2180 if ($result->numRows()) {
2181 $row = $this->db->fetchAssoc($result);
2182 $test_allows_reuse = $row["use_previous_answers"];
2183 }
2184
2185 if ($test_allows_reuse === '1') {
2186 $res = $this->user->getPref("tst_use_previous_answers");
2187 if ($res === '1') {
2188 return true;
2189 }
2190 }
2191 return false;
2192 }
2193
2201 public function getProcessingTime()
2202 {
2203 return (strlen($this->processing_time)) ? $this->processing_time : null;
2204 }
2205
2209 public function getProcessingTimeAsArray(): array
2210 {
2211 if (strlen($this->processing_time)) {
2212 if (preg_match("/(\d{2}):(\d{2}):(\d{2})/is", $this->processing_time, $matches)) {
2213 if ((int) $matches[1] + (int) $matches[2] + (int) $matches[3] == 0) {
2214 return $this->getEstimatedWorkingTime();
2215 } else {
2216 return array(
2217 'hh' => $matches[1],
2218 'mm' => $matches[2],
2219 'ss' => $matches[3],
2220 );
2221 }
2222 }
2223 }
2224 return $this->getEstimatedWorkingTime();
2225 }
2226
2228 {
2229 if (strlen($this->processing_time)) {
2230 if (preg_match("/(\d{2}):(\d{2}):(\d{2})/is", $this->processing_time, $matches)) {
2231 return ($matches[1] * 60) + $matches[2];
2232 }
2233 }
2234
2235 return self::DEFAULT_PROCESSING_TIME_MINUTES;
2236 }
2237
2245 public function getProcessingTimeInSeconds($active_id = ""): int
2246 {
2247 if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $this->getProcessingTime(), $matches)) {
2248 $extratime = $this->getExtraTime($active_id) * 60;
2249 return ($matches[1] * 3600) + ($matches[2] * 60) + $matches[3] + $extratime;
2250 } else {
2251 return 0;
2252 }
2253 }
2254
2263 {
2264 if ($this->getEndingTime() != 0) {
2265 $ending = $this->getEndingTime();
2266 $now = time();
2267 return $ending - $now;
2268 } else {
2269 return 0;
2270 }
2271 }
2272
2280 public function getEnableProcessingTime()
2281 {
2282 return ($this->enable_processing_time) ? $this->enable_processing_time : 0;
2283 }
2284
2292 public function getResetProcessingTime(): int
2293 {
2294 return ($this->reset_processing_time) ? $this->reset_processing_time : 0;
2295 }
2296
2297 public function isStartingTimeEnabled(): ?bool
2298 {
2299 return $this->starting_time_enabled;
2300 }
2301
2305 public function setStartingTimeEnabled($starting_time_enabled)
2306 {
2307 $this->starting_time_enabled = $starting_time_enabled;
2308 }
2309
2317 public function getStartingTime()
2318 {
2319 return ($this->starting_time != 0) ? $this->starting_time : 0;
2320 }
2321
2329 public function setStartingTime($starting_time = null)
2330 {
2331 $this->starting_time = $starting_time;
2332 }
2333
2334 public function isEndingTimeEnabled(): ?bool
2335 {
2336 return $this->ending_time_enabled;
2337 }
2338
2342 public function setEndingTimeEnabled($ending_time_enabled)
2343 {
2344 $this->ending_time_enabled = $ending_time_enabled;
2345 }
2346
2354 public function getEndingTime()
2355 {
2356 return ($this->ending_time != 0) ? $this->ending_time : 0;
2357 }
2358
2366 public function setEndingTime($ending_time = null)
2367 {
2368 $this->ending_time = $ending_time;
2369 }
2370
2378 public function setNrOfTries($nr_of_tries = 0)
2379 {
2380 $this->nr_of_tries = $nr_of_tries;
2381 }
2382
2390 public function setUsePreviousAnswers($use_previous_answers = 1)
2391 {
2392 if ($use_previous_answers) {
2393 $this->use_previous_answers = 1;
2394 } else {
2395 $this->use_previous_answers = 0;
2396 }
2397 }
2398
2399 public function setRedirectionMode($redirection_mode = 0)
2400 {
2401 $this->redirection_mode = $redirection_mode;
2402 }
2403 public function getRedirectionMode(): int
2404 {
2405 return $this->redirection_mode;
2406 }
2407 public function setRedirectionUrl($redirection_url = null)
2408 {
2409 $this->redirection_url = $redirection_url;
2410 }
2411 public function getRedirectionUrl(): ?string
2412 {
2413 return $this->redirection_url;
2414 }
2415
2423 public function setTitleOutput($title_output = 0)
2424 {
2425 switch ($title_output) {
2426 case 1:
2427 $this->title_output = 1;
2428 break;
2429 case 2:
2430 $this->title_output = 2;
2431 break;
2432 default:
2433 $this->title_output = 0;
2434 break;
2435 }
2436 }
2437
2445 public function setProcessingTime($processing_time = "00:00:00")
2446 {
2447 $this->processing_time = $processing_time;
2448 }
2449
2450 public function setProcessingTimeByMinutes($minutes)
2451 {
2452 $this->processing_time = sprintf("%02d:%02d:00", floor($minutes / 60), $minutes % 60);
2453 }
2454
2462 public function setEnableProcessingTime($enable = 0)
2463 {
2464 if ($enable) {
2465 $this->enable_processing_time = "1";
2466 } else {
2467 $this->enable_processing_time = "0";
2468 }
2469 }
2470
2478 public function setResetProcessingTime($reset = 0)
2479 {
2480 if ($reset) {
2481 $this->reset_processing_time = 1;
2482 } else {
2483 $this->reset_processing_time = 0;
2484 }
2485 }
2486
2490 public function isPasswordEnabled(): ?bool
2491 {
2492 return $this->passwordEnabled;
2493 }
2494
2498 public function setPasswordEnabled($passwordEnabled)
2499 {
2500 $this->passwordEnabled = $passwordEnabled;
2501 }
2502
2503 public function getPassword(): ?string
2504 {
2505 return (strlen($this->password)) ? $this->password : null;
2506 }
2507
2515 public function setPassword($a_password = null): void
2516 {
2517 $this->password = $a_password;
2518 }
2519
2523 public function getPassWaiting(): string
2524 {
2525 return $this->pass_waiting ?? '';
2526 }
2527
2531 public function setPassWaiting($pass_waiting)
2532 {
2533 $this->pass_waiting = $pass_waiting;
2534 }
2538 public function isPassWaitingEnabled(): bool
2539 {
2540 if (array_sum(explode(':', $this->getPassWaiting())) > 0) {
2541 return true;
2542 }
2543 return false;
2544 }
2545
2551 public function removeQuestionFromSequences($questionId, $activeIds, ilTestReindexedSequencePositionMap $reindexedSequencePositionMap): void
2552 {
2553 global $DIC; /* @var ILIAS\DI\Container $DIC */
2554
2555 $testSequenceFactory = new ilTestSequenceFactory(
2556 $DIC->database(),
2557 $DIC->language(),
2558 $DIC['refinery'],
2559 $DIC['component.repository'],
2560 $this
2561 );
2562
2563 foreach ($activeIds as $activeId) {
2564 $passSelector = new ilTestPassesSelector($DIC->database(), $this);
2565 $passSelector->setActiveId($activeId);
2566
2567 foreach ($passSelector->getExistingPasses() as $pass) {
2568 $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($activeId, $pass);
2569 $testSequence->loadFromDb();
2570
2571 $testSequence->removeQuestion($questionId, $reindexedSequencePositionMap);
2572 $testSequence->saveToDb();
2573 }
2574 }
2575 }
2576
2580 public function removeQuestions(array $removeQuestionIds): void
2581 {
2582 foreach ($removeQuestionIds as $value) {
2583 $this->removeQuestion((int) $value);
2584 }
2585
2586 $this->reindexFixedQuestionOrdering();
2587 }
2588
2589 public function removeQuestion(int $question_id): void
2590 {
2591 try {
2592 $question = self::_instanciateQuestion($question_id);
2594 $this->logAction(
2595 $this->lng->txtlng("assessment", "log_question_removed", ilObjAssessmentFolder::_getLogLanguage()),
2596 $question_id
2597 );
2598 }
2599 $question->delete($question_id);
2600 } catch (InvalidArgumentException $e) {
2601 $this->log->error($e->getMessage());
2602 $this->log->error($e->getTraceAsString());
2603 }
2604 }
2605
2615 {
2616 $this->removeTestResultsByUserIds($userIds);
2617
2618 global $DIC;
2619 $ilDB = $DIC['ilDB'];
2620 $lng = $DIC['lng'];
2621
2622 $participantData = new ilTestParticipantData($ilDB, $lng);
2623 $participantData->setUserIdsFilter($userIds);
2624 $participantData->load($this->getTestId());
2625
2626 $this->removeTestActives($participantData->getActiveIds());
2627 }
2628
2629 public function removeTestResults(ilTestParticipantData $participantData)
2630 {
2631 if (count($participantData->getAnonymousActiveIds())) {
2632 $this->removeTestResultsByActiveIds($participantData->getAnonymousActiveIds());
2633 }
2634
2635 if (count($participantData->getUserIds())) {
2636 /* @var ilTestLP $testLP */
2637 $testLP = ilObjectLP::getInstance($this->getId());
2638 $testLP->setTestObject($this);
2639 $testLP->resetLPDataForUserIds($participantData->getUserIds(), false);
2640 }
2641
2642 if (count($participantData->getActiveIds())) {
2643 $this->removeTestActives($participantData->getActiveIds());
2644 }
2645 }
2646
2647 public function removeTestResultsByUserIds($userIds)
2648 {
2649 $ilDB = $this->db;
2650 $lng = $this->lng;
2651
2652 $participantData = new ilTestParticipantData($ilDB, $lng);
2653 $participantData->setUserIdsFilter($userIds);
2654 $participantData->load($this->getTestId());
2655
2656 $IN_userIds = $ilDB->in('usr_id', $participantData->getUserIds(), false, 'integer');
2657 $ilDB->manipulateF(
2658 "DELETE FROM usr_pref WHERE $IN_userIds AND keyword = %s",
2659 array('text'),
2660 array("tst_password_" . $this->getTestId())
2661 );
2662
2663 if (count($participantData->getActiveIds())) {
2664 $this->removeTestResultsByActiveIds($participantData->getActiveIds());
2665 }
2666 }
2667
2668 public function removeTestResultsByActiveIds($activeIds)
2669 {
2670 $ilDB = $this->db;
2671
2672 $IN_activeIds = $ilDB->in('active_fi', $activeIds, false, 'integer');
2673
2674 $ilDB->manipulate("DELETE FROM tst_solutions WHERE $IN_activeIds");
2675 $ilDB->manipulate("DELETE FROM tst_qst_solved WHERE $IN_activeIds");
2676 $ilDB->manipulate("DELETE FROM tst_test_result WHERE $IN_activeIds");
2677 $ilDB->manipulate("DELETE FROM tst_pass_result WHERE $IN_activeIds");
2678 $ilDB->manipulate("DELETE FROM tst_result_cache WHERE $IN_activeIds");
2679 $ilDB->manipulate("DELETE FROM tst_sequence WHERE $IN_activeIds");
2680 $ilDB->manipulate("DELETE FROM tst_times WHERE $IN_activeIds");
2681
2682 if ($this->isRandomTest()) {
2683 $ilDB->manipulate("DELETE FROM tst_test_rnd_qst WHERE $IN_activeIds");
2684 } elseif ($this->isDynamicTest()) {
2685 $ilDB->manipulate("DELETE FROM tst_seq_qst_tracking WHERE $IN_activeIds");
2686 $ilDB->manipulate("DELETE FROM tst_seq_qst_answstatus WHERE $IN_activeIds");
2687 $ilDB->manipulate("DELETE FROM tst_seq_qst_postponed WHERE $IN_activeIds");
2688 $ilDB->manipulate("DELETE FROM tst_seq_qst_checked WHERE $IN_activeIds");
2689 }
2690
2691 foreach ($activeIds as $active_id) {
2692 // remove file uploads
2693 if (@is_dir(CLIENT_WEB_DIR . "/assessment/tst_" . $this->getTestId() . "/$active_id")) {
2694 ilFileUtils::delDir(CLIENT_WEB_DIR . "/assessment/tst_" . $this->getTestId() . "/$active_id");
2695 }
2696
2698 $this->logAction(sprintf($this->lng->txtlng("assessment", "log_selected_user_data_removed", ilObjAssessmentFolder::_getLogLanguage()), $this->userLookupFullName($this->_getUserIdFromActiveId($active_id))));
2699 }
2700 }
2701
2703 }
2704
2705 public function removeTestActives($activeIds)
2706 {
2707 global $DIC;
2708 $ilDB = $DIC['ilDB'];
2709
2710 $IN_activeIds = $ilDB->in('active_id', $activeIds, false, 'integer');
2711 $ilDB->manipulate("DELETE FROM tst_active WHERE $IN_activeIds");
2712 }
2713
2721 public function questionMoveUp($question_id)
2722 {
2723 global $DIC;
2724 $ilDB = $DIC['ilDB'];
2725
2726 // Move a question up in sequence
2727 $result = $ilDB->queryF(
2728 "SELECT * FROM tst_test_question WHERE test_fi=%s AND question_fi=%s",
2729 array('integer', 'integer'),
2730 array($this->getTestId(), $question_id)
2731 );
2732 $data = $ilDB->fetchObject($result);
2733 if ($data->sequence > 1) {
2734 // OK, it's not the top question, so move it up
2735 $result = $ilDB->queryF(
2736 "SELECT * FROM tst_test_question WHERE test_fi=%s AND sequence=%s",
2737 array('integer','integer'),
2738 array($this->getTestId(), $data->sequence - 1)
2739 );
2740 $data_previous = $ilDB->fetchObject($result);
2741 // change previous dataset
2742 $ilDB->manipulateF(
2743 "UPDATE tst_test_question SET sequence=%s WHERE test_question_id=%s",
2744 array('integer','integer'),
2745 array($data->sequence, $data_previous->test_question_id)
2746 );
2747 // move actual dataset up
2748 $ilDB->manipulateF(
2749 "UPDATE tst_test_question SET sequence=%s WHERE test_question_id=%s",
2750 array('integer','integer'),
2751 array($data->sequence - 1, $data->test_question_id)
2752 );
2754 $this->logAction($this->lng->txtlng("assessment", "log_question_position_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . ($data->sequence) . " => " . ($data->sequence - 1), $question_id);
2755 }
2756 }
2757 $this->loadQuestions();
2758 }
2759
2767 public function questionMoveDown($question_id)
2768 {
2769 global $DIC;
2770 $ilDB = $DIC['ilDB'];
2771
2772 // Move a question down in sequence
2773 $result = $ilDB->queryF(
2774 "SELECT * FROM tst_test_question WHERE test_fi=%s AND question_fi=%s",
2775 array('integer','integer'),
2776 array($this->getTestId(), $question_id)
2777 );
2778 $data = $ilDB->fetchObject($result);
2779 $result = $ilDB->queryF(
2780 "SELECT * FROM tst_test_question WHERE test_fi=%s AND sequence=%s",
2781 array('integer','integer'),
2782 array($this->getTestId(), $data->sequence + 1)
2783 );
2784 if ($result->numRows() == 1) {
2785 // OK, it's not the last question, so move it down
2786 $data_next = $ilDB->fetchObject($result);
2787 // change next dataset
2788 $ilDB->manipulateF(
2789 "UPDATE tst_test_question SET sequence=%s WHERE test_question_id=%s",
2790 array('integer','integer'),
2791 array($data->sequence, $data_next->test_question_id)
2792 );
2793 // move actual dataset down
2794 $ilDB->manipulateF(
2795 "UPDATE tst_test_question SET sequence=%s WHERE test_question_id=%s",
2796 array('integer','integer'),
2797 array($data->sequence + 1, $data->test_question_id)
2798 );
2800 $this->logAction($this->lng->txtlng("assessment", "log_question_position_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . ($data->sequence) . " => " . ($data->sequence + 1), $question_id);
2801 }
2802 }
2803 $this->loadQuestions();
2804 }
2805
2812 public function duplicateQuestionForTest($question_id): int
2813 {
2814 $question = ilObjTest::_instanciateQuestion($question_id);
2815 $duplicate_id = $question->duplicate(true, '', '', '', $this->getId());
2816 return $duplicate_id;
2817 }
2818
2827 public function insertQuestion(ilTestQuestionSetConfig $testQuestionSetConfig, $question_id, $linkOnly = false): int
2828 {
2829 global $DIC;
2830 $ilDB = $DIC['ilDB'];
2831 if ($linkOnly) {
2832 $duplicate_id = $question_id;
2833 } else {
2834 $duplicate_id = $this->duplicateQuestionForTest($question_id);
2835 }
2836
2837 // get maximum sequence index in test
2838 $result = $ilDB->queryF(
2839 "SELECT MAX(sequence) seq FROM tst_test_question WHERE test_fi=%s",
2840 array('integer'),
2841 array($this->getTestId())
2842 );
2843 $sequence = 1;
2844
2845 if ($result->numRows() == 1) {
2846 $data = $ilDB->fetchObject($result);
2847 $sequence = $data->seq + 1;
2848 }
2849
2850 $next_id = $ilDB->nextId('tst_test_question');
2851 $affectedRows = $ilDB->manipulateF(
2852 "INSERT INTO tst_test_question (test_question_id, test_fi, question_fi, sequence, tstamp) VALUES (%s, %s, %s, %s, %s)",
2853 array('integer', 'integer','integer','integer','integer'),
2854 array($next_id, $this->getTestId(), $duplicate_id, $sequence, time())
2855 );
2856 if ($affectedRows == 1) {
2858 $this->logAction($this->lng->txtlng("assessment", "log_question_added", ilObjAssessmentFolder::_getLogLanguage()) . ": " . $sequence, $duplicate_id);
2859 }
2860 }
2861 // remove test_active entries, because test has changed
2862 $affectedRows = $ilDB->manipulateF(
2863 "DELETE FROM tst_active WHERE test_fi = %s",
2864 array('integer'),
2865 array($this->getTestId())
2866 );
2867 $this->loadQuestions();
2868 $this->saveCompleteStatus($testQuestionSetConfig);
2869 return $duplicate_id;
2870 }
2871
2879 public function &getQuestionTitles(): array
2880 {
2881 $titles = array();
2882 if ($this->getQuestionSetType() == self::QUESTION_SET_TYPE_FIXED) {
2883 global $DIC;
2884 $ilDB = $DIC['ilDB'];
2885 $result = $ilDB->queryF(
2886 "SELECT qpl_questions.title FROM tst_test_question, qpl_questions WHERE tst_test_question.test_fi = %s AND tst_test_question.question_fi = qpl_questions.question_id ORDER BY tst_test_question.sequence",
2887 array('integer'),
2888 array($this->getTestId())
2889 );
2890 while ($row = $ilDB->fetchAssoc($result)) {
2891 array_push($titles, $row["title"]);
2892 }
2893 }
2894 return $titles;
2895 }
2896
2904 public function &getQuestionTitlesAndIndexes(): array
2905 {
2906 $titles = array();
2907 if ($this->getQuestionSetType() == self::QUESTION_SET_TYPE_FIXED) {
2908 global $DIC;
2909 $ilDB = $DIC['ilDB'];
2910 $result = $ilDB->queryF(
2911 "SELECT qpl_questions.title, qpl_questions.question_id FROM tst_test_question, qpl_questions WHERE tst_test_question.test_fi = %s AND tst_test_question.question_fi = qpl_questions.question_id ORDER BY tst_test_question.sequence",
2912 array('integer'),
2913 array($this->getTestId())
2914 );
2915 while ($row = $ilDB->fetchAssoc($result)) {
2916 $titles[$row['question_id']] = $row["title"];
2917 }
2918 }
2919 return $titles;
2920 }
2921
2922 // fau: testNav - add number parameter (to show if title should not be shown)
2932 public function getQuestionTitle($title, $nr = null): string
2933 {
2934 if ($this->getTitleOutput() !== 2) {
2935 return $title;
2936 }
2937
2938 if ($this->getTitleOutput() === 2 && isset($nr)) {
2939 return $this->lng->txt("ass_question") . ' ' . $nr;
2940 }
2941
2942 return $this->lng->txt("ass_question");
2943 }
2944 // fau.
2945
2954 public function getQuestionDataset($question_id): object
2955 {
2956 global $DIC;
2957 $ilDB = $DIC['ilDB'];
2958
2959 $result = $ilDB->queryF(
2960 "SELECT qpl_questions.*, qpl_qst_type.type_tag FROM qpl_questions, qpl_qst_type WHERE qpl_questions.question_id = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id",
2961 array('integer'),
2962 array($question_id)
2963 );
2964 $row = $ilDB->fetchObject($result);
2965 return $row;
2966 }
2967
2974 public function &getExistingQuestions($pass = null): array
2975 {
2976 global $DIC;
2977 $ilUser = $DIC['ilUser'];
2978 $ilDB = $DIC['ilDB'];
2979
2980 $existing_questions = array();
2981 $active_id = $this->getActiveIdOfUser($ilUser->getId());
2982 if ($this->isRandomTest()) {
2983 if (is_null($pass)) {
2984 $pass = 0;
2985 }
2986 $result = $ilDB->queryF(
2987 "SELECT qpl_questions.original_id FROM qpl_questions, tst_test_rnd_qst WHERE tst_test_rnd_qst.active_fi = %s AND tst_test_rnd_qst.question_fi = qpl_questions.question_id AND tst_test_rnd_qst.pass = %s",
2988 array('integer','integer'),
2989 array($active_id, $pass)
2990 );
2991 } else {
2992 $result = $ilDB->queryF(
2993 "SELECT qpl_questions.original_id FROM qpl_questions, tst_test_question WHERE tst_test_question.test_fi = %s AND tst_test_question.question_fi = qpl_questions.question_id",
2994 array('integer'),
2995 array($this->getTestId())
2996 );
2997 }
2998 while ($data = $ilDB->fetchObject($result)) {
2999 if ($data->original_id === null) {
3000 continue;
3001 }
3002
3003 array_push($existing_questions, $data->original_id);
3004 }
3005 return $existing_questions;
3006 }
3007
3015 public function getQuestionType($question_id)
3016 {
3017 global $DIC;
3018 $ilDB = $DIC['ilDB'];
3019
3020 if ($question_id < 1) {
3021 return -1;
3022 }
3023 $result = $ilDB->queryF(
3024 "SELECT type_tag FROM qpl_questions, qpl_qst_type WHERE qpl_questions.question_id = %s AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id",
3025 array('integer'),
3026 array($question_id)
3027 );
3028 if ($result->numRows() == 1) {
3029 $data = $ilDB->fetchObject($result);
3030 return $data->type_tag;
3031 } else {
3032 return "";
3033 }
3034 }
3035
3042 public function startWorkingTime($active_id, $pass)
3043 {
3044 global $DIC;
3045 $ilDB = $DIC['ilDB'];
3046
3047 $next_id = $ilDB->nextId('tst_times');
3048 $affectedRows = $ilDB->manipulateF(
3049 "INSERT INTO tst_times (times_id, active_fi, started, finished, pass, tstamp) VALUES (%s, %s, %s, %s, %s, %s)",
3050 array('integer', 'integer', 'timestamp', 'timestamp', 'integer', 'integer'),
3051 array($next_id, $active_id, date("Y-m-d H:i:s"), date("Y-m-d H:i:s"), $pass, time())
3052 );
3053 return $next_id;
3054 }
3055
3062 public function updateWorkingTime($times_id)
3063 {
3064 global $DIC;
3065 $ilDB = $DIC['ilDB'];
3066
3067 $affectedRows = $ilDB->manipulateF(
3068 "UPDATE tst_times SET finished = %s, tstamp = %s WHERE times_id = %s",
3069 array('timestamp', 'integer', 'integer'),
3070 array(date('Y-m-d H:i:s'), time(), $times_id)
3071 );
3072 }
3073
3080 public function &getWorkedQuestions($active_id, $pass = null): array
3081 {
3082 global $DIC;
3083 $ilUser = $DIC['ilUser'];
3084 $ilDB = $DIC['ilDB'];
3085
3086 if (is_null($pass)) {
3087 $result = $ilDB->queryF(
3088 "SELECT question_fi FROM tst_solutions WHERE active_fi = %s AND pass = %s GROUP BY question_fi",
3089 array('integer','integer'),
3090 array($active_id, 0)
3091 );
3092 } else {
3093 $result = $ilDB->queryF(
3094 "SELECT question_fi FROM tst_solutions WHERE active_fi = %s AND pass = %s GROUP BY question_fi",
3095 array('integer','integer'),
3096 array($active_id, $pass)
3097 );
3098 }
3099 $result_array = array();
3100 while ($row = $ilDB->fetchAssoc($result)) {
3101 array_push($result_array, $row["question_fi"]);
3102 }
3103 return $result_array;
3104 }
3105
3114 public function isTestFinishedToViewResults($active_id, $currentpass): bool
3115 {
3116 $num = ilObjTest::lookupPassResultsUpdateTimestamp($active_id, $currentpass);
3117 return ((($currentpass > 0) && ($num == 0)) || $this->isTestFinished($active_id)) ? true : false;
3118 }
3119
3126 public function &getAllQuestions($pass = null): array
3127 {
3128 global $DIC;
3129 $ilUser = $DIC['ilUser'];
3130 $ilDB = $DIC['ilDB'];
3131
3132 $result_array = array();
3133 if ($this->isRandomTest()) {
3134 $active_id = $this->getActiveIdOfUser($ilUser->getId());
3135 $this->loadQuestions($active_id, $pass);
3136 if (count($this->questions) == 0) {
3137 return $result_array;
3138 }
3139 if (is_null($pass)) {
3140 $pass = self::_getPass($active_id);
3141 }
3142 $result = $ilDB->queryF(
3143 "SELECT qpl_questions.* FROM qpl_questions, tst_test_rnd_qst WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id AND tst_test_rnd_qst.active_fi = %s AND tst_test_rnd_qst.pass = %s AND " . $ilDB->in('qpl_questions.question_id', $this->questions, false, 'integer'),
3144 array('integer','integer'),
3145 array($active_id, $pass)
3146 );
3147 } else {
3148 if (count($this->questions) == 0) {
3149 return $result_array;
3150 }
3151 $result = $ilDB->query("SELECT qpl_questions.* FROM qpl_questions, tst_test_question WHERE tst_test_question.question_fi = qpl_questions.question_id AND " . $ilDB->in('qpl_questions.question_id', $this->questions, false, 'integer'));
3152 }
3153 while ($row = $ilDB->fetchAssoc($result)) {
3154 $result_array[$row["question_id"]] = $row;
3155 }
3156 return $result_array;
3157 }
3158
3167 public function getActiveIdOfUser($user_id = "", $anonymous_id = ""): ?int
3168 {
3169 global $DIC;
3170 $ilDB = $DIC['ilDB'];
3171 $ilUser = $DIC['ilUser'];
3172
3173 if (!$user_id) {
3174 $user_id = $ilUser->getId();
3175 }
3176
3177 $tst_access_code = ilSession::get('tst_access_code');
3178 if (is_array($tst_access_code) &&
3179 $ilUser->getId() === ANONYMOUS_USER_ID &&
3180 isset($tst_access_code[$this->getTestId()]) &&
3181 $tst_access_code[$this->getTestId()] !== '') {
3182 $result = $ilDB->queryF(
3183 'SELECT active_id FROM tst_active WHERE user_fi = %s AND test_fi = %s AND anonymous_id = %s',
3184 ['integer', 'integer', 'text'],
3185 [$user_id, $this->test_id, $tst_access_code[$this->getTestId()]]
3186 );
3187 } elseif ((string) $anonymous_id !== '') {
3188 $result = $ilDB->queryF(
3189 'SELECT active_id FROM tst_active WHERE user_fi = %s AND test_fi = %s AND anonymous_id = %s',
3190 ['integer', 'integer', 'text'],
3191 [$user_id, $this->test_id, $anonymous_id]
3192 );
3193 } else {
3194 if ($ilUser->getId() === ANONYMOUS_USER_ID) {
3195 return null;
3196 }
3197 $result = $ilDB->queryF(
3198 'SELECT active_id FROM tst_active WHERE user_fi = %s AND test_fi = %s',
3199 ['integer', 'integer'],
3200 [$user_id, $this->test_id]
3201 );
3202 }
3203
3204 if ($result->numRows()) {
3205 $row = $ilDB->fetchAssoc($result);
3206 return (int) $row['active_id'];
3207 }
3208
3209 return 0;
3210 }
3211
3212 public static function _getActiveIdOfUser($user_id = "", $test_id = "")
3213 {
3214 global $DIC;
3215 $ilDB = $DIC['ilDB'];
3216 $ilUser = $DIC['ilUser'];
3217
3218 if (!$user_id) {
3219 $user_id = $ilUser->id;
3220 }
3221 if (!$test_id) {
3222 return "";
3223 }
3224 $result = $ilDB->queryF(
3225 "SELECT tst_active.active_id FROM tst_active WHERE user_fi = %s AND test_fi = %s",
3226 array('integer', 'integer'),
3227 array($user_id, $test_id)
3228 );
3229 if ($result->numRows()) {
3230 $row = $ilDB->fetchAssoc($result);
3231 return $row["active_id"];
3232 } else {
3233 return "";
3234 }
3235 }
3236
3243 public function pcArrayShuffle($array): array
3244 {
3245 $keys = array_keys($array);
3246 shuffle($keys);
3247 $result = array();
3248 foreach ($keys as $key) {
3249 $result[$key] = $array[$key];
3250 }
3251 return $result;
3252 }
3253
3260 public function &getTestResult(
3261 $active_id,
3262 $pass = null,
3263 bool $ordered_sequence = false,
3264 bool $considerHiddenQuestions = true,
3265 bool $considerOptionalQuestions = true
3266 ): array {
3267 global $DIC;
3268 $tree = $DIC['tree'];
3269 $ilDB = $DIC['ilDB'];
3270 $lng = $DIC['lng'];
3271 $refinery = $DIC['refinery'];
3272 $component_repository = $DIC['component.repository'];
3273
3274 $results = $this->getResultsForActiveId($active_id);
3275
3276 if ($pass === null) {
3277 $pass = $results['pass'];
3278 }
3279
3280 $testSessionFactory = new ilTestSessionFactory($this);
3281 $testSession = $testSessionFactory->getSession($active_id);
3282
3283 $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $refinery, $component_repository, $this);
3284 $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($active_id, $pass);
3285
3286 if ($this->isDynamicTest()) {
3287 $dynamicQuestionSetConfig = new ilObjTestDynamicQuestionSetConfig($tree, $ilDB, $component_repository, $this);
3288 $dynamicQuestionSetConfig->loadFromDb();
3289
3290 $testSequence->loadFromDb($dynamicQuestionSetConfig);
3291 $testSequence->loadQuestions($dynamicQuestionSetConfig, new ilTestDynamicQuestionSetFilterSelection());
3292
3293 $sequence = $testSequence->getUserSequenceQuestions();
3294 } else {
3295 $testSequence->setConsiderHiddenQuestionsEnabled($considerHiddenQuestions);
3296 $testSequence->setConsiderOptionalQuestionsEnabled($considerOptionalQuestions);
3297
3298 $testSequence->loadFromDb();
3299 $testSequence->loadQuestions();
3300
3301 if ($ordered_sequence) {
3302 $sequence = $testSequence->getOrderedSequenceQuestions();
3303 } else {
3304 $sequence = $testSequence->getUserSequenceQuestions();
3305 }
3306 }
3307
3308 $arrResults = [];
3309
3310 $query = "
3311 SELECT tst_test_result.question_fi,
3312 tst_test_result.points reached,
3313 tst_test_result.hint_count requested_hints,
3314 tst_test_result.hint_points hint_points,
3315 tst_test_result.answered answered
3316
3317 FROM tst_test_result
3318
3319 LEFT JOIN tst_solutions
3320 ON tst_solutions.active_fi = tst_test_result.active_fi
3321 AND tst_solutions.question_fi = tst_test_result.question_fi
3322
3323 WHERE tst_test_result.active_fi = %s
3324 AND tst_test_result.pass = %s
3325 ";
3326
3327 $solutionresult = $ilDB->queryF(
3328 $query,
3329 array('integer', 'integer'),
3330 array($active_id, $pass)
3331 );
3332
3333 while ($row = $ilDB->fetchAssoc($solutionresult)) {
3334 $arrResults[ $row['question_fi'] ] = $row;
3335 }
3336
3337 $numWorkedThrough = count($arrResults);
3338
3339 $IN_question_ids = $ilDB->in('qpl_questions.question_id', $sequence, false, 'integer');
3340
3341 $query = "
3342 SELECT qpl_questions.*,
3343 qpl_qst_type.type_tag,
3344 qpl_sol_sug.question_fi has_sug_sol
3345
3346 FROM qpl_qst_type,
3347 qpl_questions
3348
3349 LEFT JOIN qpl_sol_sug
3350 ON qpl_sol_sug.question_fi = qpl_questions.question_id
3351
3352 WHERE qpl_qst_type.question_type_id = qpl_questions.question_type_fi
3353 AND $IN_question_ids
3354 ";
3355
3356 $result = $ilDB->query($query);
3357
3358 $unordered = [];
3359
3360 $key = 1;
3361
3362 $obligationsAnswered = true;
3363
3364 while ($row = $ilDB->fetchAssoc($result)) {
3365 if (!isset($arrResults[ $row['question_id'] ])) {
3366 $percentvalue = 0.0;
3367 } else {
3368 $percentvalue = (
3369 $row['points'] ? $arrResults[$row['question_id']]['reached'] / $row['points'] : 0
3370 );
3371 }
3372 if ($percentvalue < 0) {
3373 $percentvalue = 0.0;
3374 }
3375
3376 $data = [
3377 "nr" => "$key",
3378 "title" => ilLegacyFormElementsUtil::prepareFormOutput($row['title']),
3379 "max" => round($row['points'], 2),
3380 "reached" => round($arrResults[$row['question_id']]['reached'] ?? 0, 2),
3381 'requested_hints' => $arrResults[$row['question_id']]['requested_hints'] ?? 0,
3382 'hint_points' => $arrResults[$row['question_id']]['hint_points'] ?? 0,
3383 "percent" => sprintf("%2.2f ", ($percentvalue) * 100) . "%",
3384 "solution" => ($row['has_sug_sol']) ? assQuestion::_getSuggestedSolutionOutput($row['question_id']) : '',
3385 "type" => $row["type_tag"],
3386 "qid" => $row['question_id'],
3387 "original_id" => $row["original_id"],
3388 "workedthrough" => isset($arrResults[$row['question_id']]) ? 1 : 0,
3389 'answered' => $arrResults[$row['question_id']]['answered'] ?? 0
3390 ];
3391
3392 if (!isset($arrResults[ $row['question_id'] ]['answered']) || !$arrResults[ $row['question_id'] ]['answered']) {
3393 $obligationsAnswered = false;
3394 }
3395
3396 $unordered[ $row['question_id'] ] = $data;
3397
3398 $key++;
3399 }
3400
3401 $numQuestionsTotal = count($unordered);
3402
3403 $pass_max = 0;
3404 $pass_reached = 0;
3405 $pass_requested_hints = 0;
3406 $pass_hint_points = 0;
3407 $key = 1;
3408
3409 $found = [];
3410
3411 foreach ($sequence as $qid) {
3412 // building pass point sums based on prepared data
3413 // for question that exists in users qst sequence
3414 $pass_max += round($unordered[$qid]['max'], 2);
3415 $pass_reached += round($unordered[$qid]['reached'], 2);
3416 $pass_requested_hints += $unordered[$qid]['requested_hints'];
3417 $pass_hint_points += $unordered[$qid]['hint_points'];
3418
3419 // pickup prepared data for question
3420 // that exists in users qst sequence
3421 $unordered[$qid]['nr'] = $key;
3422 array_push($found, $unordered[$qid]);
3423
3424 // increment key counter
3425 $key++;
3426 }
3427
3428 $unordered = null;
3429
3430 if ($this->getScoreCutting() == 1) {
3431 if ($results['reached_points'] < 0) {
3432 $results['reached_points'] = 0;
3433 }
3434
3435 if ($pass_reached < 0) {
3436 $pass_reached = 0;
3437 }
3438 }
3439
3440 $found['pass']['total_max_points'] = $pass_max;
3441 $found['pass']['total_reached_points'] = $pass_reached;
3442 $found['pass']['total_requested_hints'] = $pass_requested_hints;
3443 $found['pass']['total_hint_points'] = $pass_hint_points;
3444 $found['pass']['percent'] = ($pass_max > 0) ? $pass_reached / $pass_max : 0;
3445 $found['pass']['obligationsAnswered'] = $obligationsAnswered;
3446 $found['pass']['num_workedthrough'] = $numWorkedThrough;
3447 $found['pass']['num_questions_total'] = $numQuestionsTotal;
3448
3449 $found["test"]["total_max_points"] = $results['max_points'];
3450 $found["test"]["total_reached_points"] = $results['reached_points'];
3451 $found["test"]["total_requested_hints"] = $results['hint_count'];
3452 $found["test"]["total_hint_points"] = $results['hint_points'];
3453 $found["test"]["result_pass"] = $results['pass'];
3454 $found['test']['result_tstamp'] = $results['tstamp'];
3455 $found['test']['obligations_answered'] = $results['obligations_answered'];
3456
3457 if ((!$found['pass']['total_reached_points']) or (!$found['pass']['total_max_points'])) {
3458 $percentage = 0.0;
3459 } else {
3460 $percentage = ($found['pass']['total_reached_points'] / $found['pass']['total_max_points']) * 100.0;
3461
3462 if ($percentage < 0) {
3463 $percentage = 0.0;
3464 }
3465 }
3466
3467 $found["test"]["passed"] = $results['passed'];
3468
3469 return $found;
3470 }
3471
3478 public function evalTotalPersons(): int
3479 {
3480 global $DIC;
3481 $ilDB = $DIC['ilDB'];
3482
3483 $result = $ilDB->queryF(
3484 "SELECT COUNT(active_id) total FROM tst_active WHERE test_fi = %s",
3485 array('integer'),
3486 array($this->getTestId())
3487 );
3488 $row = $ilDB->fetchAssoc($result);
3489 return $row["total"];
3490 }
3491
3498 public function getCompleteWorkingTime($user_id): int
3499 {
3500 global $DIC;
3501 $ilDB = $DIC['ilDB'];
3502
3503 $result = $ilDB->queryF(
3504 "SELECT tst_times.* FROM tst_active, tst_times WHERE tst_active.test_fi = %s AND tst_active.active_id = tst_times.active_fi AND tst_active.user_fi = %s",
3505 array('integer','integer'),
3506 array($this->getTestId(), $user_id)
3507 );
3508 $time = 0;
3509 while ($row = $ilDB->fetchAssoc($result)) {
3510 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
3511 $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3512 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
3513 $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3514 $time += ($epoch_2 - $epoch_1);
3515 }
3516 return $time;
3517 }
3518
3526 {
3527 return $this->_getCompleteWorkingTimeOfParticipants($this->getTestId());
3528 }
3529
3537 public function &_getCompleteWorkingTimeOfParticipants($test_id): array
3538 {
3539 global $DIC;
3540 $ilDB = $DIC['ilDB'];
3541
3542 $result = $ilDB->queryF(
3543 "SELECT tst_times.* FROM tst_active, tst_times WHERE tst_active.test_fi = %s AND tst_active.active_id = tst_times.active_fi ORDER BY tst_times.active_fi, tst_times.started",
3544 array('integer'),
3545 array($test_id)
3546 );
3547 $time = 0;
3548 $times = array();
3549 while ($row = $ilDB->fetchAssoc($result)) {
3550 if (!array_key_exists($row["active_fi"], $times)) {
3551 $times[$row["active_fi"]] = 0;
3552 }
3553 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
3554 $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3555 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
3556 $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3557 $times[$row["active_fi"]] += ($epoch_2 - $epoch_1);
3558 }
3559 return $times;
3560 }
3561
3568 public function getCompleteWorkingTimeOfParticipant($active_id): int
3569 {
3570 global $DIC;
3571 $ilDB = $DIC['ilDB'];
3572
3573 $result = $ilDB->queryF(
3574 "SELECT tst_times.* FROM tst_active, tst_times WHERE tst_active.test_fi = %s AND tst_active.active_id = tst_times.active_fi AND tst_active.active_id = %s ORDER BY tst_times.active_fi, tst_times.started",
3575 array('integer','integer'),
3576 array($this->getTestId(), $active_id)
3577 );
3578 $time = 0;
3579 while ($row = $ilDB->fetchAssoc($result)) {
3580 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
3581 $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3582 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
3583 $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3584 $time += ($epoch_2 - $epoch_1);
3585 }
3586 return $time;
3587 }
3588
3595 public static function _getWorkingTimeOfParticipantForPass($active_id, $pass): int
3596 {
3597 global $DIC;
3598 $ilDB = $DIC['ilDB'];
3599
3600 $result = $ilDB->queryF(
3601 "SELECT * FROM tst_times WHERE active_fi = %s AND pass = %s ORDER BY started",
3602 array('integer','integer'),
3603 array($active_id, $pass)
3604 );
3605 $time = 0;
3606 while ($row = $ilDB->fetchAssoc($result)) {
3607 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
3608 $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3609 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
3610 $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3611 $time += ($epoch_2 - $epoch_1);
3612 }
3613 return $time;
3614 }
3615
3623 public function getVisitTimeOfParticipant($active_id): array
3624 {
3625 return ilObjTest::_getVisitTimeOfParticipant($this->getTestId(), $active_id);
3626 }
3627
3636 public function _getVisitTimeOfParticipant($test_id, $active_id): array
3637 {
3638 global $DIC;
3639 $ilDB = $DIC['ilDB'];
3640
3641 $result = $ilDB->queryF(
3642 "SELECT tst_times.* FROM tst_active, tst_times WHERE tst_active.test_fi = %s AND tst_active.active_id = tst_times.active_fi AND tst_active.active_id = %s ORDER BY tst_times.started",
3643 array('integer','integer'),
3644 array($test_id, $active_id)
3645 );
3646 $firstvisit = 0;
3647 $lastvisit = 0;
3648 while ($row = $ilDB->fetchAssoc($result)) {
3649 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
3650 $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3651 if ($firstvisit == 0 || $epoch_1 < $firstvisit) {
3652 $firstvisit = $epoch_1;
3653 }
3654 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
3655 $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3656 if ($epoch_2 > $lastvisit) {
3657 $lastvisit = $epoch_2;
3658 }
3659 }
3660 return array("firstvisit" => $firstvisit, "lastvisit" => $lastvisit);
3661 }
3662
3666 public function evalStatistical($active_id): array
3667 {
3668 global $DIC;
3669 $ilDB = $DIC['ilDB'];
3670 // $ilBench = $DIC['ilBench'];
3671 $pass = ilObjTest::_getResultPass($active_id);
3672 $test_result = &$this->getTestResult($active_id, $pass);
3673 $result = $ilDB->queryF(
3674 "SELECT tst_times.* FROM tst_active, tst_times WHERE tst_active.active_id = %s AND tst_active.active_id = tst_times.active_fi",
3675 array('integer'),
3676 array($active_id)
3677 );
3678 $times = array();
3679 $first_visit = 0;
3680 $last_visit = 0;
3681 while ($row = $ilDB->fetchObject($result)) {
3682 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row->started, $matches);
3683 $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3684 if (!$first_visit) {
3685 $first_visit = $epoch_1;
3686 }
3687 if ($epoch_1 < $first_visit) {
3688 $first_visit = $epoch_1;
3689 }
3690 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row->finished, $matches);
3691 $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
3692 if (!$last_visit) {
3693 $last_visit = $epoch_2;
3694 }
3695 if ($epoch_2 > $last_visit) {
3696 $last_visit = $epoch_2;
3697 }
3698 $times[$row->active_fi] += ($epoch_2 - $epoch_1);
3699 }
3700 $max_time = 0;
3701 foreach ($times as $key => $value) {
3702 $max_time += $value;
3703 }
3704 if ((!$test_result["test"]["total_reached_points"]) or (!$test_result["test"]["total_max_points"])) {
3705 $percentage = 0.0;
3706 } else {
3707 $percentage = ($test_result["test"]["total_reached_points"] / $test_result["test"]["total_max_points"]) * 100.0;
3708 if ($percentage < 0) {
3709 $percentage = 0.0;
3710 }
3711 }
3712 $mark_obj = $this->mark_schema->getMatchingMark($percentage);
3713 $first_date = getdate($first_visit);
3714 $last_date = getdate($last_visit);
3715 $qworkedthrough = 0;
3716 foreach ($test_result as $key => $value) {
3717 if (preg_match("/\d+/", $key)) {
3718 $qworkedthrough += $value["workedthrough"];
3719 }
3720 }
3721 if (!$qworkedthrough) {
3722 $atimeofwork = 0;
3723 } else {
3724 $atimeofwork = $max_time / $qworkedthrough;
3725 }
3726
3727 $obligationsAnswered = $test_result["test"]["obligations_answered"];
3728
3729 $result_mark = "";
3730 $passed = "";
3731
3732 if ($mark_obj) {
3733 $result_mark = $mark_obj->getShortName();
3734
3735 if ($mark_obj->getPassed() && $obligationsAnswered) {
3736 $passed = 1;
3737 } else {
3738 $passed = 0;
3739 }
3740 }
3741 $percent_worked_through = 0;
3742 if (count($this->questions)) {
3743 $percent_worked_through = $qworkedthrough / count($this->questions);
3744 }
3745 $result_array = array(
3746 "qworkedthrough" => $qworkedthrough,
3747 "qmax" => count($this->questions),
3748 "pworkedthrough" => $percent_worked_through,
3749 "timeofwork" => $max_time,
3750 "atimeofwork" => $atimeofwork,
3751 "firstvisit" => $first_date,
3752 "lastvisit" => $last_date,
3753 "resultspoints" => $test_result["test"]["total_reached_points"],
3754 "maxpoints" => $test_result["test"]["total_max_points"],
3755 "resultsmarks" => $result_mark,
3756 "passed" => $passed,
3757 "distancemedian" => "0"
3758 );
3759 foreach ($test_result as $key => $value) {
3760 if (preg_match("/\d+/", $key)) {
3761 $result_array[$key] = $value;
3762 }
3763 }
3764 return $result_array;
3765 }
3766
3774 public function &getTotalPointsPassedArray(): array
3775 {
3776 $totalpoints_array = array();
3777 $all_users = $this->evalTotalParticipantsArray();
3778 foreach ($all_users as $active_id => $user_name) {
3779 $test_result = &$this->getTestResult($active_id);
3780 $reached = $test_result["test"]["total_reached_points"];
3781 $total = $test_result["test"]["total_max_points"];
3782 $percentage = $total != 0 ? $reached / $total : 0;
3783 $mark = $this->mark_schema->getMatchingMark($percentage * 100.0);
3784
3785 $obligationsAnswered = $test_result["test"]["obligations_answered"];
3786
3787 if ($mark) {
3788 if ($mark->getPassed() && $obligationsAnswered) {
3789 array_push($totalpoints_array, $test_result["test"]["total_reached_points"]);
3790 }
3791 }
3792 }
3793 return $totalpoints_array;
3794 }
3795
3801 public function &getParticipants(): array
3802 {
3803 global $DIC;
3804 $ilDB = $DIC['ilDB'];
3805 $result = $ilDB->queryF(
3806 "SELECT tst_active.active_id, usr_data.usr_id, usr_data.firstname, usr_data.lastname, usr_data.title, usr_data.login FROM tst_active LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id WHERE tst_active.test_fi = %s ORDER BY usr_data.lastname ASC",
3807 array('integer'),
3808 array($this->getTestId())
3809 );
3810 $persons_array = array();
3811 while ($row = $ilDB->fetchAssoc($result)) {
3812 $name = $this->lng->txt("anonymous");
3813 $fullname = $this->lng->txt("anonymous");
3814 $login = "";
3815 if (!$this->getAnonymity()) {
3816 if (strlen($row["firstname"] . $row["lastname"] . $row["title"]) == 0) {
3817 $name = $this->lng->txt("deleted_user");
3818 $fullname = $this->lng->txt("deleted_user");
3819 $login = $this->lng->txt("unknown");
3820 } else {
3821 $login = $row["login"];
3822 if ($row["usr_id"] == ANONYMOUS_USER_ID) {
3823 $name = $this->lng->txt("anonymous");
3824 $fullname = $this->lng->txt("anonymous");
3825 } else {
3826 $name = trim($row["lastname"] . ", " . $row["firstname"] . " " . $row["title"]);
3827 $fullname = trim($row["title"] . " " . $row["firstname"] . " " . $row["lastname"]);
3828 }
3829 }
3830 }
3831 $persons_array[$row["active_id"]] = array(
3832 "name" => $name,
3833 "fullname" => $fullname,
3834 "login" => $login
3835 );
3836 }
3837 return $persons_array;
3838 }
3839
3846 public function evalTotalPersonsArray($name_sort_order = "asc"): array
3847 {
3848 global $DIC;
3849 $ilDB = $DIC['ilDB'];
3850 $result = $ilDB->queryF(
3851 "SELECT tst_active.user_fi, tst_active.active_id, usr_data.firstname, usr_data.lastname, usr_data.title FROM tst_active LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id WHERE tst_active.test_fi = %s ORDER BY usr_data.lastname " . strtoupper($name_sort_order),
3852 array('integer'),
3853 array($this->getTestId())
3854 );
3855 $persons_array = array();
3856 while ($row = $ilDB->fetchAssoc($result)) {
3857 if ($this->getAccessFilteredParticipantList() && !$this->getAccessFilteredParticipantList()->isActiveIdInList($row["active_id"])) {
3858 continue;
3859 }
3860
3861 if ($this->getAnonymity()) {
3862 $persons_array[$row["active_id"]] = $this->lng->txt("anonymous");
3863 } else {
3864 if (strlen($row["firstname"] . $row["lastname"] . $row["title"]) == 0) {
3865 $persons_array[$row["active_id"]] = $this->lng->txt("deleted_user");
3866 } else {
3867 if ($row["user_fi"] == ANONYMOUS_USER_ID) {
3868 $persons_array[$row["active_id"]] = $row["lastname"];
3869 } else {
3870 $persons_array[$row["active_id"]] = trim($row["lastname"] . ", " . $row["firstname"] . " " . $row["title"]);
3871 }
3872 }
3873 }
3874 }
3875 return $persons_array;
3876 }
3877
3883 public function evalTotalParticipantsArray($name_sort_order = "asc"): array
3884 {
3885 global $DIC;
3886 $ilDB = $DIC['ilDB'];
3887 $result = $ilDB->queryF(
3888 "SELECT tst_active.user_fi, tst_active.active_id, usr_data.login, usr_data.firstname, usr_data.lastname, usr_data.title FROM tst_active LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id WHERE tst_active.test_fi = %s ORDER BY usr_data.lastname " . strtoupper($name_sort_order),
3889 array('integer'),
3890 array($this->getTestId())
3891 );
3892 $persons_array = array();
3893 while ($row = $ilDB->fetchAssoc($result)) {
3894 if ($this->getAnonymity()) {
3895 $persons_array[$row["active_id"]] = array("name" => $this->lng->txt("anonymous"));
3896 } else {
3897 if (strlen($row["firstname"] . $row["lastname"] . $row["title"]) == 0) {
3898 $persons_array[$row["active_id"]] = array("name" => $this->lng->txt("deleted_user"));
3899 } else {
3900 if ($row["user_fi"] == ANONYMOUS_USER_ID) {
3901 $persons_array[$row["active_id"]] = array("name" => $row["lastname"]);
3902 } else {
3903 $persons_array[$row["active_id"]] = array("name" => trim($row["lastname"] . ", " . $row["firstname"] . " " . $row["title"]), "login" => $row["login"]);
3904 }
3905 }
3906 }
3907 }
3908 return $persons_array;
3909 }
3910
3917 public function &getQuestionsOfTest($active_id): array
3918 {
3919 global $DIC;
3920 $ilDB = $DIC['ilDB'];
3921 if ($this->isRandomTest()) {
3922 $ilDB->setLimit($this->getQuestionCount(), 0);
3923 $result = $ilDB->queryF(
3924 "SELECT tst_test_rnd_qst.sequence, tst_test_rnd_qst.question_fi, " .
3925 "tst_test_rnd_qst.pass, qpl_questions.points " .
3926 "FROM tst_test_rnd_qst, qpl_questions " .
3927 "WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id " .
3928 "AND tst_test_rnd_qst.active_fi = %s ORDER BY tst_test_rnd_qst.sequence",
3929 array('integer'),
3930 array($active_id)
3931 );
3932 } else {
3933 $result = $ilDB->queryF(
3934 "SELECT tst_test_question.sequence, tst_test_question.question_fi, " .
3935 "qpl_questions.points " .
3936 "FROM tst_test_question, tst_active, qpl_questions " .
3937 "WHERE tst_test_question.question_fi = qpl_questions.question_id " .
3938 "AND tst_active.active_id = %s AND tst_active.test_fi = tst_test_question.test_fi",
3939 array('integer'),
3940 array($active_id)
3941 );
3942 }
3943 $qtest = array();
3944 if ($result->numRows()) {
3945 while ($row = $ilDB->fetchAssoc($result)) {
3946 array_push($qtest, $row);
3947 }
3948 }
3949 return $qtest;
3950 }
3951
3958 public function &getQuestionsOfPass($active_id, $pass): array
3959 {
3960 global $DIC;
3961 $ilDB = $DIC['ilDB'];
3962 if ($this->isRandomTest()) {
3963 $ilDB->setLimit($this->getQuestionCount(), 0);
3964 $result = $ilDB->queryF(
3965 "SELECT tst_test_rnd_qst.sequence, tst_test_rnd_qst.question_fi, " .
3966 "qpl_questions.points " .
3967 "FROM tst_test_rnd_qst, qpl_questions " .
3968 "WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id " .
3969 "AND tst_test_rnd_qst.active_fi = %s AND tst_test_rnd_qst.pass = %s " .
3970 "ORDER BY tst_test_rnd_qst.sequence",
3971 array('integer', 'integer'),
3972 array($active_id, $pass)
3973 );
3974 } else {
3975 $result = $ilDB->queryF(
3976 "SELECT tst_test_question.sequence, tst_test_question.question_fi, " .
3977 "qpl_questions.points " .
3978 "FROM tst_test_question, tst_active, qpl_questions " .
3979 "WHERE tst_test_question.question_fi = qpl_questions.question_id " .
3980 "AND tst_active.active_id = %s AND tst_active.test_fi = tst_test_question.test_fi",
3981 array('integer'),
3982 array($active_id)
3983 );
3984 }
3985 $qpass = array();
3986 if ($result->numRows()) {
3987 while ($row = $ilDB->fetchAssoc($result)) {
3988 array_push($qpass, $row);
3989 }
3990 }
3991 return $qpass;
3992 }
3993
3998
3999
4001 {
4002 return $this->accessFilteredParticipantList;
4003 }
4004
4008 public function setAccessFilteredParticipantList($accessFilteredParticipantList): void
4009 {
4010 $this->accessFilteredParticipantList = $accessFilteredParticipantList;
4011 }
4012
4017 {
4018 $list = new ilTestParticipantList($this);
4019 $list->initializeFromDbRows($this->getTestParticipants());
4020
4021 $list = $list->getAccessFilteredList(
4023 );
4024
4025 return $list;
4026 }
4027
4028 public function getUnfilteredEvaluationData(): ilTestEvaluationData
4029 {
4031 global $DIC;
4032
4033 $ilDB = $DIC->database();
4034
4035 $data = new ilTestEvaluationData($this);
4036
4037 $query = "
4038 SELECT tst_test_result.*,
4039 qpl_questions.original_id,
4040 qpl_questions.title questiontitle,
4041 qpl_questions.points maxpoints
4042
4043 FROM tst_test_result, qpl_questions, tst_active
4044
4045 WHERE tst_active.active_id = tst_test_result.active_fi
4046 AND qpl_questions.question_id = tst_test_result.question_fi
4047 AND tst_active.test_fi = %s
4048
4049 ORDER BY tst_active.active_id ASC, tst_test_result.pass ASC, tst_test_result.tstamp DESC
4050 ";
4051
4052 $result = $ilDB->queryF(
4053 $query,
4054 array('integer'),
4055 array($this->getTestId())
4056 );
4057
4058 $pass = null;
4059 $checked = array();
4060 $datasets = 0;
4061 $questionData = [];
4062
4063 while ($row = $ilDB->fetchAssoc($result)) {
4064 if (!$data->participantExists($row["active_fi"])) {
4065 continue;
4066 }
4067
4068 $participantObject = $data->getParticipant($row["active_fi"]);
4069 $passObject = $participantObject->getPass($row["pass"]);
4070
4071 if (!($passObject instanceof ilTestEvaluationPassData)) {
4072 continue;
4073 }
4074
4075 $passObject->addAnsweredQuestion(
4076 $row["question_fi"],
4077 $row["maxpoints"],
4078 $row["points"],
4079 $row['answered'],
4080 null,
4081 $row['manual']
4082 );
4083 }
4084
4085 foreach (array_keys($data->getParticipants()) as $active_id) {
4086 if ($this->isRandomTest()) {
4087 for ($testpass = 0; $testpass <= $data->getParticipant($active_id)->getLastPass(); $testpass++) {
4088 $ilDB->setLimit($this->getQuestionCount(), 0);
4089
4090 $query = "
4091 SELECT tst_test_rnd_qst.sequence, tst_test_rnd_qst.question_fi, qpl_questions.original_id,
4092 tst_test_rnd_qst.pass, qpl_questions.points, qpl_questions.title
4093 FROM tst_test_rnd_qst, qpl_questions
4094 WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id
4095 AND tst_test_rnd_qst.pass = %s
4096 AND tst_test_rnd_qst.active_fi = %s ORDER BY tst_test_rnd_qst.sequence
4097 ";
4098
4099 $result = $ilDB->queryF(
4100 $query,
4101 array('integer','integer'),
4102 array($testpass, $active_id)
4103 );
4104
4105 if ($result->numRows()) {
4106 while ($row = $ilDB->fetchAssoc($result)) {
4107 $tpass = array_key_exists("pass", $row) ? $row["pass"] : 0;
4108
4109 $data->getParticipant($active_id)->addQuestion(
4110 $row["original_id"],
4111 $row["question_fi"],
4112 $row["points"],
4113 $row["sequence"],
4114 $tpass
4115 );
4116
4117 $data->addQuestionTitle($row["question_fi"], $row["title"]);
4118 }
4119 }
4120 }
4121 } elseif ($this->isDynamicTest()) {
4122 $lastPass = $data->getParticipant($active_id)->getLastPass();
4123 for ($testpass = 0; $testpass <= $lastPass; $testpass++) {
4124 $dynamicQuestionSetConfig = new ilObjTestDynamicQuestionSetConfig(
4125 $DIC->repositoryTree(),
4126 $DIC->database(),
4127 $DIC['component.repository'],
4128 $this
4129 );
4130 $dynamicQuestionSetConfig->loadFromDb();
4131
4132 $testSequenceFactory = new ilTestSequenceFactory($DIC->database(), $DIC->language(), $DIC['refinery'], $DIC['component.repository'], $this);
4133 $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($active_id, $testpass);
4134
4135 $testSequence->loadFromDb($dynamicQuestionSetConfig);
4136 $testSequence->loadQuestions($dynamicQuestionSetConfig, new ilTestDynamicQuestionSetFilterSelection());
4137
4138 $sequence = $testSequence->getUserSequenceQuestions();
4139
4140 $questionsIdsToRequest = array_diff(array_values($sequence), array_values($questionData));
4141 if (count($questionsIdsToRequest) > 0) {
4142 $questionIdsCondition = ' ' . $DIC->database()->in('question_id', array_values($questionsIdsToRequest), false, 'integer') . ' ';
4143
4144 $res = $DIC->database()->queryF(
4145 "
4146 SELECT *
4147 FROM qpl_questions
4148 WHERE {$questionIdsCondition}",
4149 array('integer'),
4150 array($active_id)
4151 );
4152 while ($row = $DIC->database()->fetchAssoc($res)) {
4153 $questionData[$row['question_id']] = $row;
4154 $data->addQuestionTitle($row['question_id'], $row['title']);
4155 }
4156 }
4157
4158 foreach ($sequence as $questionId) {
4159 if (!isset($questionData[$questionId])) {
4160 continue;
4161 }
4162
4163 $row = $questionData[$questionId];
4164
4165 $data->getParticipant(
4166 $active_id
4167 )->addQuestion(
4168 $row['original_id'],
4169 $row['question_id'],
4170 $row['points'],
4171 null,
4172 $testpass
4173 );
4174 }
4175 }
4176 } else {
4177 $query = "
4178 SELECT tst_test_question.sequence, tst_test_question.question_fi,
4179 qpl_questions.points, qpl_questions.title, qpl_questions.original_id
4180 FROM tst_test_question, tst_active, qpl_questions
4181 WHERE tst_test_question.question_fi = qpl_questions.question_id
4182 AND tst_active.active_id = %s
4183 AND tst_active.test_fi = tst_test_question.test_fi
4184 ORDER BY tst_test_question.sequence
4185 ";
4186
4187 $result = $ilDB->queryF(
4188 $query,
4189 array('integer'),
4190 array($active_id)
4191 );
4192
4193 if ($result->numRows()) {
4194 $questionsbysequence = array();
4195
4196 while ($row = $ilDB->fetchAssoc($result)) {
4197 $questionsbysequence[$row["sequence"]] = $row;
4198 }
4199
4200 $seqresult = $ilDB->queryF(
4201 "SELECT * FROM tst_sequence WHERE active_fi = %s",
4202 array('integer'),
4203 array($active_id)
4204 );
4205
4206 while ($seqrow = $ilDB->fetchAssoc($seqresult)) {
4207 $questionsequence = unserialize($seqrow["sequence"]);
4208
4209 foreach ($questionsequence as $sidx => $seq) {
4210 $data->getParticipant($active_id)->addQuestion(
4211 $questionsbysequence[$seq]["original_id"],
4212 $questionsbysequence[$seq]["question_fi"],
4213 $questionsbysequence[$seq]["points"],
4214 $sidx + 1,
4215 $seqrow["pass"]
4216 );
4217
4218 $data->addQuestionTitle(
4219 $questionsbysequence[$seq]["question_fi"],
4220 $questionsbysequence[$seq]["title"]
4221 );
4222 }
4223 }
4224 }
4225 }
4226 }
4227
4228 if ($this->getECTSOutput()) {
4229 $passed_array = &$this->getTotalPointsPassedArray();
4230 }
4231
4232 foreach (array_keys($data->getParticipants()) as $active_id) {
4233 $tstUserData = $data->getParticipant($active_id);
4234
4235 $percentage = $tstUserData->getReachedPointsInPercent();
4236
4237 $obligationsAnswered = $tstUserData->areObligationsAnswered();
4238
4239 $mark = $this->mark_schema->getMatchingMark($percentage);
4240
4241 if (is_object($mark)) {
4242 $tstUserData->setMark($mark->getShortName());
4243 $tstUserData->setMarkOfficial($mark->getOfficialName());
4244
4245 $tstUserData->setPassed(
4246 $mark->getPassed() && $tstUserData->areObligationsAnswered()
4247 );
4248 }
4249
4250 if ($this->getECTSOutput()) {
4251 $ects_mark = $this->getECTSGrade(
4252 $passed_array,
4253 $tstUserData->getReached(),
4254 $tstUserData->getMaxPoints()
4255 );
4256
4257 $tstUserData->setECTSMark($ects_mark);
4258 }
4259
4260 $visitingTime = $this->getVisitTimeOfParticipant($active_id);
4261
4262 $tstUserData->setFirstVisit($visitingTime["firstvisit"]);
4263 $tstUserData->setLastVisit($visitingTime["lastvisit"]);
4264 }
4265
4266 return $data;
4267 }
4268
4269 public static function _getQuestionCountAndPointsForPassOfParticipant($active_id, $pass): array
4270 {
4271 global $DIC;
4272 $ilDB = $DIC['ilDB'];
4273
4274 $questionSetType = ilObjTest::lookupQuestionSetTypeByActiveId($active_id);
4275
4276 switch ($questionSetType) {
4278
4279 $res = $ilDB->queryF(
4280 "
4281 SELECT COUNT(qpl_questions.question_id) qcount,
4282 SUM(qpl_questions.points) qsum
4283 FROM tst_active
4284 INNER JOIN tst_tests
4285 ON tst_tests.test_id = tst_active.test_fi
4286 INNER JOIN tst_dyn_quest_set_cfg
4287 ON tst_dyn_quest_set_cfg.test_fi = tst_tests.test_id
4288 INNER JOIN qpl_questions
4289 ON qpl_questions.obj_fi = tst_dyn_quest_set_cfg.source_qpl_fi
4290 AND qpl_questions.original_id IS NULL
4291 AND qpl_questions.complete = %s
4292 WHERE tst_active.active_id = %s
4293 ",
4294 array('integer', 'integer'),
4295 array(1, $active_id)
4296 );
4297
4298 break;
4299
4301
4302 $res = $ilDB->queryF(
4303 "
4304 SELECT tst_test_rnd_qst.pass,
4305 COUNT(tst_test_rnd_qst.question_fi) qcount,
4306 SUM(qpl_questions.points) qsum
4307
4308 FROM tst_test_rnd_qst,
4309 qpl_questions
4310
4311 WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id
4312 AND tst_test_rnd_qst.active_fi = %s
4313 AND pass = %s
4314
4315 GROUP BY tst_test_rnd_qst.active_fi,
4316 tst_test_rnd_qst.pass
4317 ",
4318 array('integer', 'integer'),
4319 array($active_id, $pass)
4320 );
4321
4322 break;
4323
4325
4326 $res = $ilDB->queryF(
4327 "
4328 SELECT COUNT(tst_test_question.question_fi) qcount,
4329 SUM(qpl_questions.points) qsum
4330
4331 FROM tst_test_question,
4332 qpl_questions,
4333 tst_active
4334
4335 WHERE tst_test_question.question_fi = qpl_questions.question_id
4336 AND tst_test_question.test_fi = tst_active.test_fi
4337 AND tst_active.active_id = %s
4338
4339 GROUP BY tst_test_question.test_fi
4340 ",
4341 array('integer'),
4342 array($active_id)
4343 );
4344
4345 break;
4346
4347 default:
4348
4349 throw new ilTestException("not supported question set type: $questionSetType");
4350 }
4351
4352 $row = $ilDB->fetchAssoc($res);
4353
4354 if (is_array($row)) {
4355 return array("count" => $row["qcount"], "points" => $row["qsum"]);
4356 }
4357
4358 return array("count" => 0, "points" => 0);
4359 }
4360
4361 public function &getCompleteEvaluationData($withStatistics = true, $filterby = "", $filtertext = ""): ilTestEvaluationData
4362 {
4363 $data = $this->getUnfilteredEvaluationData();
4364 if ($withStatistics) {
4365 $data->calculateStatistics();
4366 }
4367 $data->setFilter($filterby, $filtertext);
4368 return $data;
4369 }
4370
4377 public function &evalResultsOverview(): array
4378 {
4379 return $this->_evalResultsOverview($this->getTestId());
4380 }
4381
4388 public function &_evalResultsOverview($test_id): array
4389 {
4390 global $DIC;
4391 $ilDB = $DIC['ilDB'];
4392
4393 $result = $ilDB->queryF(
4394 "SELECT usr_data.usr_id, usr_data.firstname, usr_data.lastname, usr_data.title, usr_data.login, " .
4395 "tst_test_result.*, qpl_questions.original_id, qpl_questions.title questiontitle, " .
4396 "qpl_questions.points maxpoints " .
4397 "FROM tst_test_result, qpl_questions, tst_active " .
4398 "LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id " .
4399 "WHERE tst_active.active_id = tst_test_result.active_fi " .
4400 "AND qpl_questions.question_id = tst_test_result.question_fi " .
4401 "AND tst_active.test_fi = %s " .
4402 "ORDER BY tst_active.active_id, tst_test_result.pass, tst_test_result.tstamp",
4403 array('integer'),
4404 array($test_id)
4405 );
4406 $overview = array();
4407 while ($row = $ilDB->fetchAssoc($result)) {
4408 if (!array_key_exists($row["active_fi"], $overview)) {
4409 $overview[$row["active_fi"]] = array();
4410 $overview[$row["active_fi"]]["firstname"] = $row["firstname"];
4411 $overview[$row["active_fi"]]["lastname"] = $row["lastname"];
4412 $overview[$row["active_fi"]]["title"] = $row["title"];
4413 $overview[$row["active_fi"]]["login"] = $row["login"];
4414 $overview[$row["active_fi"]]["usr_id"] = $row["usr_id"];
4415 $overview[$row["active_fi"]]["started"] = $row["started"];
4416 $overview[$row["active_fi"]]["finished"] = $row["finished"];
4417 }
4418 if (!array_key_exists($row["pass"], $overview[$row["active_fi"]])) {
4419 $overview[$row["active_fi"]][$row["pass"]] = array();
4420 $overview[$row["active_fi"]][$row["pass"]]["reached"] = 0;
4421 $overview[$row["active_fi"]][$row["pass"]]["maxpoints"] = $row["maxpoints"];
4422 }
4423 array_push($overview[$row["active_fi"]][$row["pass"]], $row);
4424 $overview[$row["active_fi"]][$row["pass"]]["reached"] += $row["points"];
4425 }
4426 return $overview;
4427 }
4428
4436 public function &evalResultsOverviewOfParticipant($active_id): array
4437 {
4438 global $DIC;
4439 $ilDB = $DIC['ilDB'];
4440
4441 $result = $ilDB->queryF(
4442 "SELECT usr_data.usr_id, usr_data.firstname, usr_data.lastname, usr_data.title, usr_data.login, " .
4443 "tst_test_result.*, qpl_questions.original_id, qpl_questions.title questiontitle, " .
4444 "qpl_questions.points maxpoints " .
4445 "FROM tst_test_result, qpl_questions, tst_active " .
4446 "LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id " .
4447 "WHERE tst_active.active_id = tst_test_result.active_fi " .
4448 "AND qpl_questions.question_id = tst_test_result.question_fi " .
4449 "AND tst_active.test_fi = %s AND tst_active.active_id = %s" .
4450 "ORDER BY tst_active.active_id, tst_test_result.pass, tst_test_result.tstamp",
4451 array('integer', 'integer'),
4452 array($this->getTestId(), $active_id)
4453 );
4454 $overview = array();
4455 while ($row = $ilDB->fetchAssoc($result)) {
4456 if (!array_key_exists($row["active_fi"], $overview)) {
4457 $overview[$row["active_fi"]] = array();
4458 $overview[$row["active_fi"]]["firstname"] = $row["firstname"];
4459 $overview[$row["active_fi"]]["lastname"] = $row["lastname"];
4460 $overview[$row["active_fi"]]["title"] = $row["title"];
4461 $overview[$row["active_fi"]]["login"] = $row["login"];
4462 $overview[$row["active_fi"]]["usr_id"] = $row["usr_id"];
4463 $overview[$row["active_fi"]]["started"] = $row["started"];
4464 $overview[$row["active_fi"]]["finished"] = $row["finished"];
4465 }
4466 if (!array_key_exists($row["pass"], $overview[$row["active_fi"]])) {
4467 $overview[$row["active_fi"]][$row["pass"]] = array();
4468 $overview[$row["active_fi"]][$row["pass"]]["reached"] = 0;
4469 $overview[$row["active_fi"]][$row["pass"]]["maxpoints"] = $row["maxpoints"];
4470 }
4471 array_push($overview[$row["active_fi"]][$row["pass"]], $row);
4472 $overview[$row["active_fi"]][$row["pass"]]["reached"] += $row["points"];
4473 }
4474 return $overview;
4475 }
4476
4488 public function buildName($user_id, $firstname, $lastname, $title): string
4489 {
4490 $name = "";
4491 if (strlen($firstname . $lastname . $title) == 0) {
4492 $name = $this->lng->txt("deleted_user");
4493 } else {
4494 if ($user_id == ANONYMOUS_USER_ID) {
4495 $name = $lastname;
4496 } else {
4497 $name = trim($lastname . ", " . $firstname . " " . $title);
4498 }
4499 if ($this->getAnonymity()) {
4500 $name = $this->lng->txt("anonymous");
4501 }
4502 }
4503 return $name;
4504 }
4505
4518 public function _buildName($is_anonymous, $user_id, $firstname, $lastname, $title): string
4519 {
4520 global $DIC;
4521 $lng = $DIC['lng'];
4522 $name = "";
4523 if (strlen($firstname . $lastname . $title) == 0) {
4524 $name = $lng->txt("deleted_user");
4525 } else {
4526 if ($user_id == ANONYMOUS_USER_ID) {
4527 $name = $lastname;
4528 } else {
4529 $name = trim($lastname . ", " . $firstname . " " . $title);
4530 }
4531 if ($is_anonymous) {
4532 $name = $lng->txt("anonymous");
4533 }
4534 }
4535 return $name;
4536 }
4537
4544 public function evalTotalStartedAverageTime($activeIdsFilter = null): int
4545 {
4546 global $DIC; /* @var ILIAS\DI\Container $DIC */
4547
4548 $query = "SELECT tst_times.* FROM tst_active, tst_times WHERE tst_active.test_fi = %s AND tst_active.active_id = tst_times.active_fi";
4549
4550 if (is_array($activeIdsFilter) && count($activeIdsFilter)) {
4551 $query .= " AND " . $DIC->database()->in('active_id', $activeIdsFilter, false, 'integer');
4552 }
4553
4554 $result = $DIC->database()->queryF($query, array('integer'), array($this->getTestId()));
4555 $times = array();
4556 while ($row = $DIC->database()->fetchObject($result)) {
4557 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row->started, $matches);
4558 $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4559 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row->finished, $matches);
4560 $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4561 if (isset($times[$row->active_fi])) {
4562 $times[$row->active_fi] += ($epoch_2 - $epoch_1);
4563 } else {
4564 $times[$row->active_fi] = ($epoch_2 - $epoch_1);
4565 }
4566 }
4567 $max_time = 0;
4568 $counter = 0;
4569 foreach ($times as $key => $value) {
4570 $max_time += $value;
4571 $counter++;
4572 }
4573 if ($counter) {
4574 $average_time = round($max_time / $counter);
4575 } else {
4576 $average_time = 0;
4577 }
4578 return $average_time;
4579 }
4580
4587 public function getAvailableQuestionpools($use_object_id = false, $equal_points = false, $could_be_offline = false, $show_path = false, $with_questioncount = false, $permission = "read"): array
4588 {
4589 return ilObjQuestionPool::_getAvailableQuestionpools($use_object_id, $equal_points, $could_be_offline, $show_path, $with_questioncount, $permission);
4590 }
4591
4598 public function getEstimatedWorkingTime(): array
4599 {
4600 $time_in_seconds = 0;
4601 foreach ($this->questions as $question_id) {
4602 $question = ilObjTest::_instanciateQuestion($question_id);
4603 $est_time = $question->getEstimatedWorkingTime();
4604 $time_in_seconds += $est_time["h"] * 3600 + $est_time["m"] * 60 + $est_time["s"];
4605 }
4606 $hours = (int) ($time_in_seconds / 3600) ;
4607 $time_in_seconds = $time_in_seconds - ($hours * 3600);
4608 $minutes = (int) ($time_in_seconds / 60);
4609 $time_in_seconds = $time_in_seconds - ($minutes * 60);
4610 $result = array("hh" => $hours, "mm" => $minutes, "ss" => $time_in_seconds);
4611 return $result;
4612 }
4613
4620 public function getImagePath(): string
4621 {
4622 return CLIENT_WEB_DIR . "/assessment/" . $this->getId() . "/images/";
4623 }
4624
4631 public function getImagePathWeb()
4632 {
4633 $webdir = ilFileUtils::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/" . $this->getId() . "/images/";
4634 return str_replace(
4635 ilFileUtils::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH),
4637 $webdir
4638 );
4639 }
4640
4649 public function createQuestionGUI($question_type, $question_id = -1): ?assQuestionGUI
4650 {
4651 if ((!$question_type) and ($question_id > 0)) {
4652 $question_type = $this->getQuestionType($question_id);
4653 }
4654
4655 if (!strlen($question_type)) {
4656 return null;
4657 }
4658
4659 assQuestion::_includeClass($question_type, 1);
4660
4661 $question_type_gui = $question_type . 'GUI';
4662 $question = new $question_type_gui();
4663
4664 if ($question_id > 0) {
4665 $question->object->loadFromDb($question_id);
4666
4667 global $DIC;
4668 $ilCtrl = $DIC['ilCtrl'];
4669 $ilDB = $DIC['ilDB'];
4670 $ilUser = $DIC['ilUser'];
4671 $lng = $DIC['lng'];
4672
4673 $feedbackObjectClassname = assQuestion::getFeedbackClassNameByQuestionType($question_type);
4674 $question->object->feedbackOBJ = new $feedbackObjectClassname($question->object, $ilCtrl, $ilDB, $lng);
4675
4676 $assSettings = new ilSetting('assessment');
4677 $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
4678 $processLockerFactory->setQuestionId($question->object->getId());
4679 $processLockerFactory->setUserId($ilUser->getId());
4680 $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
4681 $question->object->setProcessLocker($processLockerFactory->getLocker());
4682 }
4683
4684 return $question;
4685 }
4686
4693 public static function _instanciateQuestion($question_id): ?assQuestion
4694 {
4695 if (strcmp((string) $question_id, "") !== 0) {
4696 return assQuestion::instantiateQuestion($question_id);
4697 }
4698
4699 return null;
4700 }
4701
4710 public function moveQuestions($move_questions, $target_index, $insert_mode)
4711 {
4712 $this->questions = array_values($this->questions);
4713 $array_pos = array_search($target_index, $this->questions);
4714 if ($insert_mode == 0) {
4715 $part1 = array_slice($this->questions, 0, $array_pos);
4716 $part2 = array_slice($this->questions, $array_pos);
4717 } elseif ($insert_mode == 1) {
4718 $part1 = array_slice($this->questions, 0, $array_pos + 1);
4719 $part2 = array_slice($this->questions, $array_pos + 1);
4720 }
4721 foreach ($move_questions as $question_id) {
4722 if (!(array_search($question_id, $part1) === false)) {
4723 unset($part1[array_search($question_id, $part1)]);
4724 }
4725 if (!(array_search($question_id, $part2) === false)) {
4726 unset($part2[array_search($question_id, $part2)]);
4727 }
4728 }
4729 $part1 = array_values($part1);
4730 $part2 = array_values($part2);
4731 $new_array = array_values(array_merge($part1, $move_questions, $part2));
4732 $this->questions = array();
4733 $counter = 1;
4734 foreach ($new_array as $question_id) {
4735 $this->questions[$counter] = $question_id;
4736 $counter++;
4737 }
4738 $this->saveQuestionsToDb();
4739 }
4740
4741
4749 public function startingTimeReached(): bool
4750 {
4751 if ($this->isStartingTimeEnabled() && $this->getStartingTime() != 0) {
4752 $now = time();
4753 if ($now < $this->getStartingTime()) {
4754 return false;
4755 }
4756 }
4757 return true;
4758 }
4759
4767 public function endingTimeReached(): bool
4768 {
4769 if ($this->isEndingTimeEnabled() && $this->getEndingTime() != 0) {
4770 $now = time();
4771 if ($now > $this->getEndingTime()) {
4772 return true;
4773 }
4774 }
4775 return false;
4776 }
4777
4783 public function getAvailableQuestions($arrFilter, $completeonly = 0): array
4784 {
4785 global $DIC;
4786 $component_repository = $DIC['component.repository'];
4787 $component_factory = $DIC['component.factory'];
4788 $lng = $DIC['lng'];
4789 $ilUser = $DIC['ilUser'];
4790 $ilDB = $DIC['ilDB'];
4791
4792 $available_pools = array_keys(ilObjQuestionPool::_getAvailableQuestionpools($use_object_id = true, $equal_points = false, $could_be_offline = false, $showPath = false, $with_questioncount = false));
4793 $available = "";
4794 if (count($available_pools)) {
4795 $available = " AND " . $ilDB->in('qpl_questions.obj_fi', $available_pools, false, 'integer');
4796 } else {
4797 return array();
4798 }
4799 if ($completeonly) {
4800 $available .= " AND qpl_questions.complete = " . $ilDB->quote("1", 'text');
4801 }
4802
4803 $where = "";
4804 if (is_array($arrFilter)) {
4805 if (array_key_exists('title', $arrFilter) && strlen($arrFilter['title'])) {
4806 $where .= " AND " . $ilDB->like('qpl_questions.title', 'text', "%%" . $arrFilter['title'] . "%%");
4807 }
4808 if (array_key_exists('description', $arrFilter) && strlen($arrFilter['description'])) {
4809 $where .= " AND " . $ilDB->like('qpl_questions.description', 'text', "%%" . $arrFilter['description'] . "%%");
4810 }
4811 if (array_key_exists('author', $arrFilter) && strlen($arrFilter['author'])) {
4812 $where .= " AND " . $ilDB->like('qpl_questions.author', 'text', "%%" . $arrFilter['author'] . "%%");
4813 }
4814 if (array_key_exists('type', $arrFilter) && strlen($arrFilter['type'])) {
4815 $where .= " AND qpl_qst_type.type_tag = " . $ilDB->quote($arrFilter['type'], 'text');
4816 }
4817 if (array_key_exists('qpl', $arrFilter) && strlen($arrFilter['qpl'])) {
4818 $where .= " AND " . $ilDB->like('object_data.title', 'text', "%%" . $arrFilter['qpl'] . "%%");
4819 }
4820 }
4821
4822 $original_ids = &$this->getExistingQuestions();
4823 $original_clause = " qpl_questions.original_id IS NULL";
4824 if (count($original_ids)) {
4825 $original_clause = " qpl_questions.original_id IS NULL AND " . $ilDB->in('qpl_questions.question_id', $original_ids, true, 'integer');
4826 }
4827
4828 $query_result = $ilDB->query("
4829 SELECT qpl_questions.*, qpl_questions.tstamp,
4830 qpl_qst_type.type_tag, qpl_qst_type.plugin, qpl_qst_type.plugin_name,
4831 object_data.title parent_title
4832 FROM qpl_questions, qpl_qst_type, object_data
4833 WHERE $original_clause $available
4834 AND object_data.obj_id = qpl_questions.obj_fi
4835 AND qpl_questions.tstamp > 0
4836 AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id
4837 $where
4838 ");
4839 $rows = array();
4840
4841 if ($query_result->numRows()) {
4842 while ($row = $ilDB->fetchAssoc($query_result)) {
4844
4845 if (!$row['plugin']) {
4846 $row[ 'ttype' ] = $lng->txt($row[ "type_tag" ]);
4847
4848 $rows[] = $row;
4849 continue;
4850 }
4851
4852 $plugin = $component_repository->getPluginByName($row['plugin_name']);
4853 if (!$plugin->isActive()) {
4854 continue;
4855 }
4856
4857 $pl = $component_factory->getPlugin($plugin->getId());
4858 $row[ 'ttype' ] = $pl->getQuestionTypeTranslation();
4859
4860 $rows[] = $row;
4861 }
4862 }
4863 return $rows;
4864 }
4865
4870 public function fromXML(ilQTIAssessment $assessment)
4871 {
4872 ilSession::clear('import_mob_xhtml');
4873
4874 $this->setDescription($assessment->getComment());
4875 $this->setTitle($assessment->getTitle());
4876
4877 $this->setIntroductionEnabled(false);
4878 foreach ($assessment->objectives as $objectives) {
4879 foreach ($objectives->materials as $material) {
4880 $intro = $this->QTIMaterialToString($material);
4881 $this->setIntroduction($intro);
4882 $this->setIntroductionEnabled(strlen($intro) > 0);
4883 }
4884 }
4885
4886 if (
4887 $assessment->getPresentationMaterial() &&
4888 $assessment->getPresentationMaterial()->getFlowMat(0) &&
4889 $assessment->getPresentationMaterial()->getFlowMat(0)->getMaterial(0)
4890 ) {
4891 $this->setFinalStatement($this->QTIMaterialToString($assessment->getPresentationMaterial()->getFlowMat(0)->getMaterial(0)));
4892 }
4893
4894 foreach ($assessment->assessmentcontrol as $assessmentcontrol) {
4895 switch ($assessmentcontrol->getSolutionswitch()) {
4896 case "Yes":
4897 $this->setInstantFeedbackSolution(1);
4898 break;
4899 default:
4900 $this->setInstantFeedbackSolution(0);
4901 break;
4902 }
4903 }
4904
4905 $this->setStartingTimeEnabled(false);
4906 $this->setEndingTimeEnabled(false);
4907 $this->setPasswordEnabled(false);
4908 $this->setLimitUsersEnabled(false);
4909
4910 $this->saveToDb();
4911 $score_settings = $this->getScoreSettings();
4912 $scoring_settings = $score_settings->getScoringSettings();
4913 $gamification_settings = $score_settings->getGamificationSettings();
4914 $result_summary_settings = $score_settings->getResultSummarySettings();
4915 $result_details_settings = $score_settings->getResultDetailsSettings();
4916 foreach ($assessment->qtimetadata as $metadata) {
4917 switch ($metadata["label"]) {
4918 case "test_type":
4919 // for old tests with a test type
4920 $type = $metadata["entry"];
4921 switch ($type) {
4922 case 1:
4923 // assessment
4924 $this->setAnonymity(1);
4925 break;
4926 case 2:
4927 // self assessment
4928 break;
4929 case 4:
4930 // online exam
4931 $this->setFixedParticipants(1);
4932 $this->setListOfQuestionsSettings(7);
4933 break;
4934 case 5:
4935 // varying random test
4936 break;
4937 }
4938 break;
4939 case "sequence_settings":
4940 $this->setSequenceSettings((int) $metadata["entry"]);
4941 break;
4942 case "solution_details":
4943 $result_details_settings = $result_details_settings->withShowSolutionDetails((bool) $metadata["entry"]);
4944 break;
4945 case "print_bs_with_res":
4946 $result_details_settings = $result_details_settings->withPrintBestSolutionWithResult((bool) $metadata["entry"]);
4947 break;
4948 case "author":
4949 $this->setAuthor($metadata["entry"]);
4950 break;
4951 case "nr_of_tries":
4952 $this->setNrOfTries($metadata["entry"]);
4953 break;
4954 case 'block_after_passed':
4955 $this->setBlockPassesAfterPassedEnabled((bool) $metadata['entry']);
4956 break;
4957 case "pass_waiting":
4958 $this->setPassWaiting($metadata["entry"]);
4959 break;
4960 case "kiosk":
4961 $this->setKiosk($metadata["entry"]);
4962 break;
4963 case "showfinalstatement":
4964 $this->setShowFinalStatement($metadata["entry"]);
4965 break;
4966 case "showinfo":
4967 $this->setShowInfo($metadata["entry"]);
4968 break;
4969 case "forcejs":
4970 $this->setForceJS($metadata["entry"]);
4971 break;
4972 case "customstyle":
4973 $this->setCustomStyle($metadata["entry"]);
4974 break;
4975
4976 case "highscore_enabled":
4977 $gamification_settings = $gamification_settings->withHighscoreEnabled((bool) $metadata["entry"]);
4978 break;
4979
4980 case "highscore_anon":
4981 $gamification_settings = $gamification_settings->withHighscoreAnon((bool) $metadata["entry"]);
4982 break;
4983
4984 case "highscore_achieved_ts":
4985 $gamification_settings = $gamification_settings->withHighscoreAchievedTS((bool) $metadata["entry"]);
4986 break;
4987
4988 case "highscore_score":
4989 $gamification_settings = $gamification_settings->withHighscoreScore((bool) $metadata["entry"]);
4990 break;
4991
4992 case "highscore_percentage":
4993 $gamification_settings = $gamification_settings->withHighscorePercentage((bool) $metadata["entry"]);
4994 break;
4995
4996 case "highscore_hints":
4997 $gamification_settings = $gamification_settings->withHighscoreHints((bool) $metadata["entry"]);
4998 break;
4999
5000 case "highscore_wtime":
5001 $gamification_settings = $gamification_settings->withHighscoreWTime((bool) $metadata["entry"]);
5002 break;
5003
5004 case "highscore_own_table":
5005 $gamification_settings = $gamification_settings->withHighscoreOwnTable((bool) $metadata["entry"]);
5006 break;
5007
5008 case "highscore_top_table":
5009 $gamification_settings = $gamification_settings->withHighscoreTopTable((bool) $metadata["entry"]);
5010 break;
5011
5012 case "highscore_top_num":
5013 $gamification_settings = $gamification_settings->withHighscoreTopNum((int) $metadata["entry"]);
5014 break;
5015
5016 case "hide_previous_results":
5017 if ($metadata["entry"] == 0) {
5018 $this->setUsePreviousAnswers(1);
5019 } else {
5020 $this->setUsePreviousAnswers(0);
5021 }
5022 break;
5023 case "use_previous_answers":
5024 $this->setUsePreviousAnswers($metadata["entry"]);
5025 break;
5026 case "answer_feedback":
5027 $this->setAnswerFeedback($metadata["entry"]);
5028 break;
5029 case "title_output":
5030 case "hide_title_points":
5031 $this->setTitleOutput($metadata["entry"]);
5032 break;
5033 case "question_set_type":
5034 $this->setQuestionSetType($metadata["entry"]);
5035 break;
5036 case "random_test":
5037 if ($metadata["entry"]) {
5038 $this->setQuestionSetType(self::QUESTION_SET_TYPE_RANDOM);
5039 } else {
5040 $this->setQuestionSetType(self::QUESTION_SET_TYPE_FIXED);
5041 }
5042 break;
5043 case "results_presentation":
5044 $result_details_settings = $result_details_settings->withResultsPresentation((int) $metadata["entry"]);
5045 break;
5046 case "reset_processing_time":
5047 $this->setResetProcessingTime($metadata["entry"]);
5048 break;
5049 case "instant_verification":
5050 $this->setInstantFeedbackSolution($metadata["entry"]);
5051 break;
5052 case "follow_qst_answer_fixation":
5053 $this->setFollowupQuestionAnswerFixationEnabled((bool) $metadata["entry"]);
5054 break;
5055 case "instant_feedback_answer_fixation":
5056 $this->setInstantFeedbackAnswerFixationEnabled((bool) $metadata["entry"]);
5057 break;
5058 case "force_instant_feedback":
5059 $this->setForceInstantFeedbackEnabled((bool) $metadata["entry"]);
5060 break;
5061 case "answer_feedback_points":
5062 $this->setAnswerFeedbackPoints($metadata["entry"]);
5063 break;
5064 case "anonymity":
5065 $this->setAnonymity($metadata["entry"]);
5066 break;
5067 case "show_cancel":
5068 $this->setShowCancel($metadata["entry"]);
5069 break;
5070 case "show_marker":
5071 $this->setShowMarker($metadata["entry"]);
5072 break;
5073 case "fixed_participants":
5074 $this->setFixedParticipants($metadata["entry"]);
5075 break;
5076 case "score_reporting":
5077 $result_summary_settings = $result_summary_settings->withScoreReporting((int) $metadata["entry"]);
5078 break;
5079 case "shuffle_questions":
5080 $this->setShuffleQuestions($metadata["entry"]);
5081 break;
5082 case "count_system":
5083 $scoring_settings = $scoring_settings->withCountSystem((int) $metadata["entry"]);
5084 break;
5085 case "mailnotification":
5086 $this->setMailNotification($metadata["entry"]);
5087 break;
5088 case "mailnottype":
5089 $this->setMailNotificationType($metadata["entry"]);
5090 break;
5091 case "exportsettings":
5092 $result_details_settings = $result_details_settings->withExportSettings((int) $metadata["entry"]);
5093 break;
5094 case "score_cutting":
5095 $scoring_settings = $scoring_settings->withScoreCutting((int) $metadata["entry"]);
5096 break;
5097 case "password":
5098 $this->setPassword($metadata["entry"]);
5099 $this->setPasswordEnabled(strlen($metadata["entry"]) > 0);
5100 break;
5101 case "allowedUsers":
5102 $this->setAllowedUsers($metadata["entry"]);
5103 $this->setLimitUsersEnabled((int) $metadata["entry"] > 0);
5104 break;
5105 case "allowedUsersTimeGap":
5106 $this->setAllowedUsersTimeGap($metadata["entry"]);
5107 break;
5108 case "pass_scoring":
5109 $scoring_settings = $scoring_settings->withPassScoring((int) $metadata["entry"]);
5110 break;
5111 case 'pass_deletion_allowed':
5112 $result_summary_settings = $result_summary_settings->withPassDeletionAllowed((bool) $metadata["entry"]);
5113 break;
5114 case "show_summary":
5115 $this->setListOfQuestionsSettings($metadata["entry"]);
5116 break;
5117 case "reporting_date":
5118 $iso8601period = $metadata["entry"];
5119 if (preg_match("/P(\d+)Y(\d+)M(\d+)DT(\d+)H(\d+)M(\d+)S/", $iso8601period, $matches)) {
5120 $this->setReportingDate(sprintf("%02d%02d%02d%02d%02d%02d", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]));
5121 }
5122 break;
5123 case 'enable_processing_time':
5124 $this->setEnableProcessingTime($metadata['entry']);
5125 break;
5126 case "processing_time":
5127 $this->setProcessingTime($metadata['entry']);
5128 break;
5129 case "starting_time":
5130 $iso8601period = $metadata["entry"];
5131 if (preg_match("/P(\d+)Y(\d+)M(\d+)DT(\d+)H(\d+)M(\d+)S/", $iso8601period, $matches)) {
5132 $date_time = new ilDateTime(sprintf("%02d-%02d-%02d %02d:%02d:%02d", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]), IL_CAL_DATETIME);
5133 $this->setStartingTime($date_time->get(IL_CAL_UNIX));
5134 $this->setStartingTimeEnabled(true);
5135 }
5136 break;
5137 case "ending_time":
5138 $iso8601period = $metadata["entry"];
5139 if (preg_match("/P(\d+)Y(\d+)M(\d+)DT(\d+)H(\d+)M(\d+)S/", $iso8601period, $matches)) {
5140 $date_time = new ilDateTime(sprintf("%02d-%02d-%02d %02d:%02d:%02d", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]), IL_CAL_DATETIME);
5141 $this->setEndingTime($date_time->get(IL_CAL_UNIX));
5142 $this->setEndingTimeEnabled(true);
5143 }
5144 break;
5145 case "enable_examview":
5146 $this->setEnableExamview($metadata["entry"]);
5147 break;
5148 case 'show_examview_html':
5149 $this->setShowExamviewHtml($metadata['entry']);
5150 break;
5151 case 'show_examview_pdf':
5152 $this->setShowExamviewPdf($metadata['entry']);
5153 break;
5154 case 'redirection_mode':
5155 $this->setRedirectionMode($metadata['entry']);
5156 break;
5157 case 'redirection_url':
5158 $this->setRedirectionUrl($metadata['entry']);
5159 break;
5160 case 'examid_in_kiosk':
5161 case 'examid_in_test_pass':
5162 $this->setShowExamIdInTestPassEnabled($metadata['entry']);
5163 break;
5164 case 'show_exam_id':
5165 case 'examid_in_test_res':
5166 $result_details_settings = $result_details_settings->withShowExamIdInTestResults((bool) $metadata["entry"]);
5167 break;
5168 case 'enable_archiving':
5169 $this->setEnableArchiving($metadata['entry']);
5170 break;
5171 case 'sign_submission':
5172 $this->setSignSubmission($metadata['entry']);
5173 break;
5174 case 'char_selector_availability':
5175 $this->setCharSelectorAvailability($metadata['entry']);
5176 break;
5177 case 'char_selector_definition':
5178 $this->setCharSelectorDefinition($metadata['entry']);
5179 break;
5180 case 'skill_service':
5181 $this->setSkillServiceEnabled((bool) $metadata['entry']);
5182 break;
5183 case 'result_tax_filters':
5184 $tax_ids = strlen($metadata['entry']) ? unserialize($metadata['entry']) : [];
5185 $result_details_settings = $result_details_settings->withTaxonomyFilterIds($tax_ids);
5186 break;
5187 case 'show_grading_status':
5188 $result_summary_settings = $result_summary_settings->withShowGradingStatusEnabled((bool) $metadata["entry"]);
5189 break;
5190 case 'show_grading_mark':
5191 $result_summary_settings = $result_summary_settings->withShowGradingMarkEnabled((bool) $metadata["entry"]);
5192 break;
5193 case 'activation_limited':
5194 $this->setActivationLimited($metadata['entry']);
5195 break;
5196 case 'activation_start_time':
5197 $this->setActivationStartingTime($metadata['entry']);
5198 break;
5199 case 'activation_end_time':
5200 $this->setActivationEndingTime($metadata['entry']);
5201 break;
5202 case 'activation_visibility':
5203 $this->setActivationVisibility($metadata['entry']);
5204 break;
5205 case 'autosave':
5206 $this->setAutosave($metadata['entry']);
5207 break;
5208 case 'autosave_ival':
5209 $this->setAutosaveIval($metadata['entry']);
5210 break;
5211 case 'offer_question_hints':
5212 $this->setOfferingQuestionHintsEnabled($metadata['entry']);
5213 break;
5214 case 'instant_feedback_specific':
5215 $this->setSpecificAnswerFeedback($metadata['entry']);
5216 break;
5217 case 'obligations_enabled':
5218 $this->setObligationsEnabled($metadata['entry']);
5219 break;
5220 }
5221 if (preg_match("/mark_step_\d+/", $metadata["label"])) {
5222 $xmlmark = $metadata["entry"];
5223 preg_match("/<short>(.*?)<\/short>/", $xmlmark, $matches);
5224 $mark_short = $matches[1];
5225 preg_match("/<official>(.*?)<\/official>/", $xmlmark, $matches);
5226 $mark_official = $matches[1];
5227 preg_match("/<percentage>(.*?)<\/percentage>/", $xmlmark, $matches);
5228 $mark_percentage = $matches[1];
5229 preg_match("/<passed>(.*?)<\/passed>/", $xmlmark, $matches);
5230 $mark_passed = $matches[1];
5231 $this->mark_schema->addMarkStep($mark_short, $mark_official, $mark_percentage, $mark_passed);
5232 }
5233 }
5234
5235 $this->saveToDb();
5236 $result_summary_settings = $result_summary_settings->withShowPassDetails($result_details_settings->getShowPassDetails());
5237 $score_settings = $score_settings
5238 ->withGamificationSettings($gamification_settings)
5239 ->withScoringSettings($scoring_settings)
5240 ->withResultDetailsSettings($result_details_settings)
5241 ->withResultSummarySettings($result_summary_settings);
5242 $this->getScoreSettingsRepository()->store($score_settings);
5243 $this->score_settings = $score_settings;
5244 $this->loadFromDb();
5245
5246 // handle the import of media objects in XHTML code
5247 if (is_array(ilSession::get("import_mob_xhtml"))) {
5248 foreach (ilSession::get("import_mob_xhtml") as $mob) {
5249 $importfile = ilObjTest::_getImportDirectory() . '/' . ilSession::get('tst_import_subdir') . '/' . $mob["uri"];
5250 if (file_exists($importfile)) {
5251 $media_object = ilObjMediaObject::_saveTempFileAsMediaObject(basename($importfile), $importfile, false);
5252 ilObjMediaObject::_saveUsage($media_object->getId(), "tst:html", $this->getId());
5253 $this->setIntroduction(ilRTE::_replaceMediaObjectImageSrc(str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $this->getIntroduction()), 1));
5254 $this->setFinalStatement(ilRTE::_replaceMediaObjectImageSrc(str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $this->getFinalStatement()), 1));
5255 } else {
5256 global $DIC;
5257 $ilLog = $DIC['ilLog'];
5258 $ilLog->write("Error: Could not open XHTML mob file for test introduction during test import. File $importfile does not exist!");
5259 }
5260 }
5261 $this->saveToDb();
5262 }
5263 }
5264
5270 public function toXML(): string
5271 {
5272 $a_xml_writer = new ilXmlWriter();
5273 // set xml header
5274 $a_xml_writer->xmlHeader();
5275 $a_xml_writer->xmlSetDtdDef("<!DOCTYPE questestinterop SYSTEM \"ims_qtiasiv1p2p1.dtd\">");
5276 $a_xml_writer->xmlStartTag("questestinterop");
5277
5278 $attrs = array(
5279 "ident" => "il_" . IL_INST_ID . "_tst_" . $this->getTestId(),
5280 "title" => $this->getTitle()
5281 );
5282 $a_xml_writer->xmlStartTag("assessment", $attrs);
5283 // add qti comment
5284 $a_xml_writer->xmlElement("qticomment", null, $this->getDescription());
5285
5286 // add qti duration
5287 if ($this->enable_processing_time) {
5288 preg_match("/(\d+):(\d+):(\d+)/", $this->processing_time, $matches);
5289 $a_xml_writer->xmlElement("duration", null, sprintf("P0Y0M0DT%dH%dM%dS", $matches[1], $matches[2], $matches[3]));
5290 }
5291
5292 // add the rest of the preferences in qtimetadata tags, because there is no correspondent definition in QTI
5293 $a_xml_writer->xmlStartTag("qtimetadata");
5294 $a_xml_writer->xmlStartTag("qtimetadatafield");
5295 $a_xml_writer->xmlElement("fieldlabel", null, "ILIAS_VERSION");
5296 $a_xml_writer->xmlElement("fieldentry", null, ILIAS_VERSION);
5297 $a_xml_writer->xmlEndTag("qtimetadatafield");
5298
5299 // anonymity
5300 $a_xml_writer->xmlStartTag("qtimetadatafield");
5301 $a_xml_writer->xmlElement("fieldlabel", null, "anonymity");
5302 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getAnonymity()));
5303 $a_xml_writer->xmlEndTag("qtimetadatafield");
5304
5305 // question set type (fixed, random, dynamic, ...)
5306 $a_xml_writer->xmlStartTag("qtimetadatafield");
5307 $a_xml_writer->xmlElement("fieldlabel", null, "question_set_type");
5308 $a_xml_writer->xmlElement("fieldentry", null, $this->getQuestionSetType());
5309 $a_xml_writer->xmlEndTag("qtimetadatafield");
5310
5311 // sequence settings
5312 $a_xml_writer->xmlStartTag("qtimetadatafield");
5313 $a_xml_writer->xmlElement("fieldlabel", null, "sequence_settings");
5314 $a_xml_writer->xmlElement("fieldentry", null, $this->getSequenceSettings());
5315 $a_xml_writer->xmlEndTag("qtimetadatafield");
5316
5317 // author
5318 $a_xml_writer->xmlStartTag("qtimetadatafield");
5319 $a_xml_writer->xmlElement("fieldlabel", null, "author");
5320 $a_xml_writer->xmlElement("fieldentry", null, $this->getAuthor());
5321 $a_xml_writer->xmlEndTag("qtimetadatafield");
5322
5323 // reset processing time
5324 $a_xml_writer->xmlStartTag("qtimetadatafield");
5325 $a_xml_writer->xmlElement("fieldlabel", null, "reset_processing_time");
5326 $a_xml_writer->xmlElement("fieldentry", null, $this->getResetProcessingTime());
5327 $a_xml_writer->xmlEndTag("qtimetadatafield");
5328
5329 // count system
5330 $a_xml_writer->xmlStartTag("qtimetadatafield");
5331 $a_xml_writer->xmlElement("fieldlabel", null, "count_system");
5332 $a_xml_writer->xmlElement("fieldentry", null, $this->getCountSystem());
5333 $a_xml_writer->xmlEndTag("qtimetadatafield");
5334
5335 // multiple choice scoring
5336 $a_xml_writer->xmlStartTag("qtimetadatafield");
5337 $a_xml_writer->xmlElement("fieldlabel", null, "score_cutting");
5338 $a_xml_writer->xmlElement("fieldentry", null, $this->getScoreCutting());
5339 $a_xml_writer->xmlEndTag("qtimetadatafield");
5340
5341 // multiple choice scoring
5342 $a_xml_writer->xmlStartTag("qtimetadatafield");
5343 $a_xml_writer->xmlElement("fieldlabel", null, "password");
5344 $a_xml_writer->xmlElement("fieldentry", null, $this->getPassword());
5345 $a_xml_writer->xmlEndTag("qtimetadatafield");
5346
5347 // allowed users
5348 $a_xml_writer->xmlStartTag("qtimetadatafield");
5349 $a_xml_writer->xmlElement("fieldlabel", null, "allowedUsers");
5350 $a_xml_writer->xmlElement("fieldentry", null, $this->getAllowedUsers());
5351 $a_xml_writer->xmlEndTag("qtimetadatafield");
5352
5353 // allowed users time gap
5354 $a_xml_writer->xmlStartTag("qtimetadatafield");
5355 $a_xml_writer->xmlElement("fieldlabel", null, "allowedUsersTimeGap");
5356 $a_xml_writer->xmlElement("fieldentry", null, $this->getAllowedUsersTimeGap());
5357 $a_xml_writer->xmlEndTag("qtimetadatafield");
5358
5359 // pass scoring
5360 $a_xml_writer->xmlStartTag("qtimetadatafield");
5361 $a_xml_writer->xmlElement("fieldlabel", null, "pass_scoring");
5362 $a_xml_writer->xmlElement("fieldentry", null, $this->getPassScoring());
5363 $a_xml_writer->xmlEndTag("qtimetadatafield");
5364
5365 $a_xml_writer->xmlStartTag('qtimetadatafield');
5366 $a_xml_writer->xmlElement('fieldlabel', null, 'pass_deletion_allowed');
5367 $a_xml_writer->xmlElement('fieldentry', null, (int) $this->isPassDeletionAllowed());
5368 $a_xml_writer->xmlEndTag('qtimetadatafield');
5369
5370 // score reporting date
5371 if ($this->getReportingDate()) {
5372 $a_xml_writer->xmlStartTag("qtimetadatafield");
5373 $a_xml_writer->xmlElement("fieldlabel", null, "reporting_date");
5374 preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->reporting_date, $matches);
5375 $a_xml_writer->xmlElement("fieldentry", null, sprintf("P%dY%dM%dDT%dH%dM%dS", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]));
5376 $a_xml_writer->xmlEndTag("qtimetadatafield");
5377 }
5378 // number of tries
5379 $a_xml_writer->xmlStartTag("qtimetadatafield");
5380 $a_xml_writer->xmlElement("fieldlabel", null, "nr_of_tries");
5381 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getNrOfTries()));
5382 $a_xml_writer->xmlEndTag("qtimetadatafield");
5383
5384 // number of tries
5385 $a_xml_writer->xmlStartTag('qtimetadatafield');
5386 $a_xml_writer->xmlElement('fieldlabel', null, 'block_after_passed');
5387 $a_xml_writer->xmlElement('fieldentry', null, (int) $this->isBlockPassesAfterPassedEnabled());
5388 $a_xml_writer->xmlEndTag('qtimetadatafield');
5389
5390 // pass_waiting
5391 $a_xml_writer->xmlStartTag("qtimetadatafield");
5392 $a_xml_writer->xmlElement("fieldlabel", null, "pass_waiting");
5393 $a_xml_writer->xmlElement("fieldentry", null, $this->getPassWaiting());
5394 $a_xml_writer->xmlEndTag("qtimetadatafield");
5395
5396 // kiosk
5397 $a_xml_writer->xmlStartTag("qtimetadatafield");
5398 $a_xml_writer->xmlElement("fieldlabel", null, "kiosk");
5399 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getKiosk()));
5400 $a_xml_writer->xmlEndTag("qtimetadatafield");
5401
5402
5403 //redirection_mode
5404 $a_xml_writer->xmlStartTag('qtimetadatafield');
5405 $a_xml_writer->xmlElement("fieldlabel", null, "redirection_mode");
5406 $a_xml_writer->xmlElement("fieldentry", null, $this->getRedirectionMode());
5407 $a_xml_writer->xmlEndTag("qtimetadatafield");
5408
5409 //redirection_url
5410 $a_xml_writer->xmlStartTag('qtimetadatafield');
5411 $a_xml_writer->xmlElement("fieldlabel", null, "redirection_url");
5412 $a_xml_writer->xmlElement("fieldentry", null, $this->getRedirectionUrl());
5413 $a_xml_writer->xmlEndTag("qtimetadatafield");
5414
5415 // use previous answers
5416 $a_xml_writer->xmlStartTag("qtimetadatafield");
5417 $a_xml_writer->xmlElement("fieldlabel", null, "use_previous_answers");
5418 $a_xml_writer->xmlElement("fieldentry", null, $this->getUsePreviousAnswers());
5419 $a_xml_writer->xmlEndTag("qtimetadatafield");
5420
5421 // hide title points
5422 $a_xml_writer->xmlStartTag("qtimetadatafield");
5423 $a_xml_writer->xmlElement("fieldlabel", null, "title_output");
5424 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getTitleOutput()));
5425 $a_xml_writer->xmlEndTag("qtimetadatafield");
5426
5427 // results presentation
5428 $a_xml_writer->xmlStartTag("qtimetadatafield");
5429 $a_xml_writer->xmlElement("fieldlabel", null, "results_presentation");
5430 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getResultsPresentation()));
5431 $a_xml_writer->xmlEndTag("qtimetadatafield");
5432
5433 // examid in test pass
5434 $a_xml_writer->xmlStartTag("qtimetadatafield");
5435 $a_xml_writer->xmlElement("fieldlabel", null, "examid_in_test_pass");
5436 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->isShowExamIdInTestPassEnabled()));
5437 $a_xml_writer->xmlEndTag("qtimetadatafield");
5438
5439 // examid in kiosk
5440 $a_xml_writer->xmlStartTag("qtimetadatafield");
5441 $a_xml_writer->xmlElement("fieldlabel", null, "examid_in_test_res");
5442 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->isShowExamIdInTestResultsEnabled()));
5443 $a_xml_writer->xmlEndTag("qtimetadatafield");
5444
5445 // solution details
5446 $a_xml_writer->xmlStartTag("qtimetadatafield");
5447 $a_xml_writer->xmlElement("fieldlabel", null, "show_summary");
5448 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getListOfQuestionsSettings()));
5449 $a_xml_writer->xmlEndTag("qtimetadatafield");
5450
5451 // solution details
5452 $a_xml_writer->xmlStartTag("qtimetadatafield");
5453 $a_xml_writer->xmlElement("fieldlabel", null, "score_reporting");
5454 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getScoreReporting()));
5455 $a_xml_writer->xmlEndTag("qtimetadatafield");
5456
5457 $a_xml_writer->xmlStartTag("qtimetadatafield");
5458 $a_xml_writer->xmlElement("fieldlabel", null, "solution_details");
5459 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getShowSolutionDetails());
5460 $a_xml_writer->xmlEndTag("qtimetadatafield");
5461 $a_xml_writer->xmlStartTag("qtimetadatafield");
5462 $a_xml_writer->xmlElement("fieldlabel", null, "print_bs_with_res");
5463 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getShowSolutionDetails() ? (int) $this->isBestSolutionPrintedWithResult() : 0);
5464 $a_xml_writer->xmlEndTag("qtimetadatafield");
5465
5466 // solution details
5467 $a_xml_writer->xmlStartTag("qtimetadatafield");
5468 $a_xml_writer->xmlElement("fieldlabel", null, "instant_verification");
5469 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getInstantFeedbackSolution()));
5470 $a_xml_writer->xmlEndTag("qtimetadatafield");
5471
5472 // answer specific feedback
5473 $a_xml_writer->xmlStartTag("qtimetadatafield");
5474 $a_xml_writer->xmlElement("fieldlabel", null, "answer_feedback");
5475 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getAnswerFeedback()));
5476 $a_xml_writer->xmlEndTag("qtimetadatafield");
5477
5478 // answer specific feedback of reached points
5479 $a_xml_writer->xmlStartTag("qtimetadatafield");
5480 $a_xml_writer->xmlElement("fieldlabel", null, "answer_feedback_points");
5481 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getAnswerFeedbackPoints()));
5482 $a_xml_writer->xmlEndTag("qtimetadatafield");
5483
5484 // followup question previous answer freezing
5485 $a_xml_writer->xmlStartTag("qtimetadatafield");
5486 $a_xml_writer->xmlElement("fieldlabel", null, "follow_qst_answer_fixation");
5487 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isFollowupQuestionAnswerFixationEnabled());
5488 $a_xml_writer->xmlEndTag("qtimetadatafield");
5489
5490 // instant response answer freezing
5491 $a_xml_writer->xmlStartTag("qtimetadatafield");
5492 $a_xml_writer->xmlElement("fieldlabel", null, "instant_feedback_answer_fixation");
5493 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isInstantFeedbackAnswerFixationEnabled());
5494 $a_xml_writer->xmlEndTag("qtimetadatafield");
5495
5496 // instant response forced
5497 $a_xml_writer->xmlStartTag("qtimetadatafield");
5498 $a_xml_writer->xmlElement("fieldlabel", null, "force_instant_feedback");
5499 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isForceInstantFeedbackEnabled());
5500 $a_xml_writer->xmlEndTag("qtimetadatafield");
5501
5502
5503 // highscore
5504 $highscore_metadata = array(
5505 'highscore_enabled' => array('value' => $this->getHighscoreEnabled()),
5506 'highscore_anon' => array('value' => $this->getHighscoreAnon()),
5507 'highscore_achieved_ts' => array('value' => $this->getHighscoreAchievedTS()),
5508 'highscore_score' => array('value' => $this->getHighscoreScore()),
5509 'highscore_percentage' => array('value' => $this->getHighscorePercentage()),
5510 'highscore_hints' => array('value' => $this->getHighscoreHints()),
5511 'highscore_wtime' => array('value' => $this->getHighscoreWTime()),
5512 'highscore_own_table' => array('value' => $this->getHighscoreOwnTable()),
5513 'highscore_top_table' => array('value' => $this->getHighscoreTopTable()),
5514 'highscore_top_num' => array('value' => $this->getHighscoreTopNum()),
5515 );
5516 foreach ($highscore_metadata as $label => $data) {
5517 $a_xml_writer->xmlStartTag("qtimetadatafield");
5518 $a_xml_writer->xmlElement("fieldlabel", null, $label);
5519 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $data['value']));
5520 $a_xml_writer->xmlEndTag("qtimetadatafield");
5521 }
5522
5523 // show cancel
5524 $a_xml_writer->xmlStartTag("qtimetadatafield");
5525 $a_xml_writer->xmlElement("fieldlabel", null, "show_cancel");
5526 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getShowCancel()));
5527 $a_xml_writer->xmlEndTag("qtimetadatafield");
5528
5529 // show marker
5530 $a_xml_writer->xmlStartTag("qtimetadatafield");
5531 $a_xml_writer->xmlElement("fieldlabel", null, "show_marker");
5532 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getShowMarker()));
5533 $a_xml_writer->xmlEndTag("qtimetadatafield");
5534
5535 // fixed participants
5536 $a_xml_writer->xmlStartTag("qtimetadatafield");
5537 $a_xml_writer->xmlElement("fieldlabel", null, "fixed_participants");
5538 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getFixedParticipants()));
5539 $a_xml_writer->xmlEndTag("qtimetadatafield");
5540
5541 // show final statement
5542 $a_xml_writer->xmlStartTag("qtimetadatafield");
5543 $a_xml_writer->xmlElement("fieldlabel", null, "showfinalstatement");
5544 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (($this->getShowFinalStatement()) ? "1" : "0")));
5545 $a_xml_writer->xmlEndTag("qtimetadatafield");
5546
5547 // show introduction only
5548 $a_xml_writer->xmlStartTag("qtimetadatafield");
5549 $a_xml_writer->xmlElement("fieldlabel", null, "showinfo");
5550 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (($this->getShowInfo()) ? "1" : "0")));
5551 $a_xml_writer->xmlEndTag("qtimetadatafield");
5552
5553 // mail notification
5554 $a_xml_writer->xmlStartTag("qtimetadatafield");
5555 $a_xml_writer->xmlElement("fieldlabel", null, "mailnotification");
5556 $a_xml_writer->xmlElement("fieldentry", null, $this->getMailNotification());
5557 $a_xml_writer->xmlEndTag("qtimetadatafield");
5558
5559 // mail notification type
5560 $a_xml_writer->xmlStartTag("qtimetadatafield");
5561 $a_xml_writer->xmlElement("fieldlabel", null, "mailnottype");
5562 $a_xml_writer->xmlElement("fieldentry", null, $this->getMailNotificationType());
5563 $a_xml_writer->xmlEndTag("qtimetadatafield");
5564
5565 // export settings
5566 $a_xml_writer->xmlStartTag("qtimetadatafield");
5567 $a_xml_writer->xmlElement("fieldlabel", null, "exportsettings");
5568 $a_xml_writer->xmlElement("fieldentry", null, $this->getExportSettings());
5569 $a_xml_writer->xmlEndTag("qtimetadatafield");
5570
5571 // force JavaScript
5572 $a_xml_writer->xmlStartTag("qtimetadatafield");
5573 $a_xml_writer->xmlElement("fieldlabel", null, "forcejs");
5574 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (($this->getForceJS()) ? "1" : "0")));
5575 $a_xml_writer->xmlEndTag("qtimetadatafield");
5576
5577 // custom style
5578 $a_xml_writer->xmlStartTag("qtimetadatafield");
5579 $a_xml_writer->xmlElement("fieldlabel", null, "customstyle");
5580 $a_xml_writer->xmlElement("fieldentry", null, $this->getCustomStyle());
5581 $a_xml_writer->xmlEndTag("qtimetadatafield");
5582
5583 // shuffle questions
5584 $a_xml_writer->xmlStartTag("qtimetadatafield");
5585 $a_xml_writer->xmlElement("fieldlabel", null, "shuffle_questions");
5586 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getShuffleQuestions()));
5587 $a_xml_writer->xmlEndTag("qtimetadatafield");
5588
5589 // processing time
5590 $a_xml_writer->xmlStartTag("qtimetadatafield");
5591 $a_xml_writer->xmlElement("fieldlabel", null, "processing_time");
5592 $a_xml_writer->xmlElement("fieldentry", null, $this->getProcessingTime());
5593 $a_xml_writer->xmlEndTag("qtimetadatafield");
5594
5595 // enable_examview
5596 $a_xml_writer->xmlStartTag("qtimetadatafield");
5597 $a_xml_writer->xmlElement("fieldlabel", null, "enable_examview");
5598 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getEnableExamview());
5599 $a_xml_writer->xmlEndTag("qtimetadatafield");
5600
5601 // show_examview_html
5602 $a_xml_writer->xmlStartTag("qtimetadatafield");
5603 $a_xml_writer->xmlElement("fieldlabel", null, "show_examview_html");
5604 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getShowExamviewHtml());
5605 $a_xml_writer->xmlEndTag("qtimetadatafield");
5606
5607 // show_examview_pdf
5608 $a_xml_writer->xmlStartTag("qtimetadatafield");
5609 $a_xml_writer->xmlElement("fieldlabel", null, "show_examview_pdf");
5610 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getShowExamviewPdf());
5611 $a_xml_writer->xmlEndTag("qtimetadatafield");
5612
5613 // enable_archiving
5614 $a_xml_writer->xmlStartTag("qtimetadatafield");
5615 $a_xml_writer->xmlElement("fieldlabel", null, "enable_archiving");
5616 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getEnableArchiving());
5617 $a_xml_writer->xmlEndTag("qtimetadatafield");
5618
5619 // sign_submission
5620 $a_xml_writer->xmlStartTag("qtimetadatafield");
5621 $a_xml_writer->xmlElement("fieldlabel", null, "sign_submission");
5622 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getSignSubmission());
5623 $a_xml_writer->xmlEndTag("qtimetadatafield");
5624
5625 // char_selector_availability
5626 $a_xml_writer->xmlStartTag("qtimetadatafield");
5627 $a_xml_writer->xmlElement("fieldlabel", null, "char_selector_availability");
5628 $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getCharSelectorAvailability()));
5629 $a_xml_writer->xmlEndTag("qtimetadatafield");
5630
5631 // char_selector_definition
5632 $a_xml_writer->xmlStartTag("qtimetadatafield");
5633 $a_xml_writer->xmlElement("fieldlabel", null, "char_selector_definition");
5634 $a_xml_writer->xmlElement("fieldentry", null, $this->getCharSelectorDefinition());
5635 $a_xml_writer->xmlEndTag("qtimetadatafield");
5636
5637 // skill_service
5638 $a_xml_writer->xmlStartTag("qtimetadatafield");
5639 $a_xml_writer->xmlElement("fieldlabel", null, "skill_service");
5640 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isSkillServiceEnabled());
5641 $a_xml_writer->xmlEndTag("qtimetadatafield");
5642
5643 // result_tax_filters
5644 $a_xml_writer->xmlStartTag("qtimetadatafield");
5645 $a_xml_writer->xmlElement("fieldlabel", null, "result_tax_filters");
5646 $a_xml_writer->xmlElement("fieldentry", null, serialize($this->getResultFilterTaxIds()));
5647 $a_xml_writer->xmlEndTag("qtimetadatafield");
5648
5649 // show_grading_status
5650 $a_xml_writer->xmlStartTag("qtimetadatafield");
5651 $a_xml_writer->xmlElement("fieldlabel", null, "show_grading_status");
5652 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isShowGradingStatusEnabled());
5653 $a_xml_writer->xmlEndTag("qtimetadatafield");
5654
5655 // show_grading_mark
5656 $a_xml_writer->xmlStartTag("qtimetadatafield");
5657 $a_xml_writer->xmlElement("fieldlabel", null, "show_grading_mark");
5658 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isShowGradingMarkEnabled());
5659 $a_xml_writer->xmlEndTag("qtimetadatafield");
5660
5661
5662 // starting time
5663 if ($this->getStartingTime()) {
5664 $a_xml_writer->xmlStartTag("qtimetadatafield");
5665 $a_xml_writer->xmlElement("fieldlabel", null, "starting_time");
5666 $backward_compatibility_format = $this->buildIso8601PeriodFromUnixtimeForExportCompatibility($this->starting_time);
5667 $a_xml_writer->xmlElement("fieldentry", null, $backward_compatibility_format);
5668 $a_xml_writer->xmlEndTag("qtimetadatafield");
5669 }
5670 // ending time
5671 if ($this->getEndingTime()) {
5672 $a_xml_writer->xmlStartTag("qtimetadatafield");
5673 $a_xml_writer->xmlElement("fieldlabel", null, "ending_time");
5674 $backward_compatibility_format = $this->buildIso8601PeriodFromUnixtimeForExportCompatibility($this->ending_time);
5675 $a_xml_writer->xmlElement("fieldentry", null, $backward_compatibility_format);
5676 $a_xml_writer->xmlEndTag("qtimetadatafield");
5677 }
5678
5679
5680 //activation_limited
5681 $a_xml_writer->xmlStartTag("qtimetadatafield");
5682 $a_xml_writer->xmlElement("fieldlabel", null, "activation_limited");
5683 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isActivationLimited());
5684 $a_xml_writer->xmlEndTag("qtimetadatafield");
5685
5686 //activation_start_time
5687 $a_xml_writer->xmlStartTag("qtimetadatafield");
5688 $a_xml_writer->xmlElement("fieldlabel", null, "activation_start_time");
5689 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getActivationStartingTime());
5690 $a_xml_writer->xmlEndTag("qtimetadatafield");
5691
5692 //activation_end_time
5693 $a_xml_writer->xmlStartTag("qtimetadatafield");
5694 $a_xml_writer->xmlElement("fieldlabel", null, "activation_end_time");
5695 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getActivationEndingTime());
5696 $a_xml_writer->xmlEndTag("qtimetadatafield");
5697
5698 //activation_visibility
5699 $a_xml_writer->xmlStartTag("qtimetadatafield");
5700 $a_xml_writer->xmlElement("fieldlabel", null, "activation_visibility");
5701 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getActivationVisibility());
5702 $a_xml_writer->xmlEndTag("qtimetadatafield");
5703
5704 // autosave
5705 $a_xml_writer->xmlStartTag("qtimetadatafield");
5706 $a_xml_writer->xmlElement("fieldlabel", null, "autosave");
5707 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getAutosave());
5708 $a_xml_writer->xmlEndTag("qtimetadatafield");
5709
5710 // autosave_ival
5711 $a_xml_writer->xmlStartTag("qtimetadatafield");
5712 $a_xml_writer->xmlElement("fieldlabel", null, "autosave_ival");
5713 $a_xml_writer->xmlElement("fieldentry", null, $this->getAutosaveIval());
5714 $a_xml_writer->xmlEndTag("qtimetadatafield");
5715
5716 //offer_question_hints
5717 $a_xml_writer->xmlStartTag("qtimetadatafield");
5718 $a_xml_writer->xmlElement("fieldlabel", null, "offer_question_hints");
5719 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isOfferingQuestionHintsEnabled());
5720 $a_xml_writer->xmlEndTag("qtimetadatafield");
5721
5722 //instant_feedback_specific
5723 $a_xml_writer->xmlStartTag("qtimetadatafield");
5724 $a_xml_writer->xmlElement("fieldlabel", null, "instant_feedback_specific");
5725 $a_xml_writer->xmlElement("fieldentry", null, $this->getSpecificAnswerFeedback());
5726 $a_xml_writer->xmlEndTag("qtimetadatafield");
5727
5728 //instant_feedback_answer_fixation
5729 $a_xml_writer->xmlStartTag("qtimetadatafield");
5730 $a_xml_writer->xmlElement("fieldlabel", null, "instant_feedback_answer_fixation");
5731 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isInstantFeedbackAnswerFixationEnabled());
5732 $a_xml_writer->xmlEndTag("qtimetadatafield");
5733
5734 //obligations_enabled
5735 $a_xml_writer->xmlStartTag("qtimetadatafield");
5736 $a_xml_writer->xmlElement("fieldlabel", null, "obligations_enabled");
5737 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->areObligationsEnabled());
5738 $a_xml_writer->xmlEndTag("qtimetadatafield");
5739
5740 //enable_processing_time
5741 $a_xml_writer->xmlStartTag("qtimetadatafield");
5742 $a_xml_writer->xmlElement("fieldlabel", null, "enable_processing_time");
5743 $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getEnableProcessingTime());
5744 $a_xml_writer->xmlEndTag("qtimetadatafield");
5745
5746 foreach ($this->mark_schema->mark_steps as $index => $mark) {
5747 // mark steps
5748 $a_xml_writer->xmlStartTag("qtimetadatafield");
5749 $a_xml_writer->xmlElement("fieldlabel", null, "mark_step_$index");
5750 $a_xml_writer->xmlElement("fieldentry", null, sprintf(
5751 "<short>%s</short><official>%s</official><percentage>%.2f</percentage><passed>%d</passed>",
5752 $mark->getShortName(),
5753 $mark->getOfficialName(),
5754 $mark->getMinimumLevel(),
5755 $mark->getPassed()
5756 ));
5757 $a_xml_writer->xmlEndTag("qtimetadatafield");
5758 }
5759 $a_xml_writer->xmlEndTag("qtimetadata");
5760
5761 // add qti objectives
5762 $a_xml_writer->xmlStartTag("objectives");
5763 $this->addQTIMaterial($a_xml_writer, $this->getIntroduction());
5764 $a_xml_writer->xmlEndTag("objectives");
5765
5766 // add qti assessmentcontrol
5767 if ($this->getInstantFeedbackSolution() == 1) {
5768 $attrs = array(
5769 "solutionswitch" => "Yes"
5770 );
5771 } else {
5772 $attrs = null;
5773 }
5774 $a_xml_writer->xmlElement("assessmentcontrol", $attrs, null);
5775
5776 if (strlen($this->getFinalStatement())) {
5777 // add qti presentation_material
5778 $a_xml_writer->xmlStartTag("presentation_material");
5779 $a_xml_writer->xmlStartTag("flow_mat");
5780 $this->addQTIMaterial($a_xml_writer, $this->getFinalStatement());
5781 $a_xml_writer->xmlEndTag("flow_mat");
5782 $a_xml_writer->xmlEndTag("presentation_material");
5783 }
5784
5785 $attrs = array(
5786 "ident" => "1"
5787 );
5788 $a_xml_writer->xmlElement("section", $attrs, null);
5789 $a_xml_writer->xmlEndTag("assessment");
5790 $a_xml_writer->xmlEndTag("questestinterop");
5791
5792 $xml = $a_xml_writer->xmlDumpMem(false);
5793 return $xml;
5794 }
5795
5800 protected function buildIso8601PeriodFromUnixtimeForExportCompatibility($unix_timestamp): string
5801 {
5802 $date_time_unix = new ilDateTime($unix_timestamp, IL_CAL_UNIX);
5803 $date_time = $date_time_unix->get(IL_CAL_DATETIME);
5804 preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $date_time, $matches);
5805 $iso8601_period = sprintf("P%dY%dM%dDT%dH%dM%dS", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]);
5806 return $iso8601_period;
5807 }
5808
5815 public function exportPagesXML(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog)
5816 {
5817 global $DIC;
5818 $ilBench = $DIC['ilBench'];
5819
5820 $this->mob_ids = array();
5821 $this->file_ids = array();
5822
5823 // MetaData
5824 $this->exportXMLMetaData($a_xml_writer);
5825
5826 // PageObjects
5827 $expLog->write(date("[y-m-d H:i:s] ") . "Start Export Page Objects");
5828 $ilBench->start("ContentObjectExport", "exportPageObjects");
5829 $this->exportXMLPageObjects($a_xml_writer, $a_inst, $expLog);
5830 $ilBench->stop("ContentObjectExport", "exportPageObjects");
5831 $expLog->write(date("[y-m-d H:i:s] ") . "Finished Export Page Objects");
5832
5833 // MediaObjects
5834 $expLog->write(date("[y-m-d H:i:s] ") . "Start Export Media Objects");
5835 $ilBench->start("ContentObjectExport", "exportMediaObjects");
5836 $this->exportXMLMediaObjects($a_xml_writer, $a_inst, $a_target_dir, $expLog);
5837 $ilBench->stop("ContentObjectExport", "exportMediaObjects");
5838 $expLog->write(date("[y-m-d H:i:s] ") . "Finished Export Media Objects");
5839
5840 // FileItems
5841 $expLog->write(date("[y-m-d H:i:s] ") . "Start Export File Items");
5842 $ilBench->start("ContentObjectExport", "exportFileItems");
5843 $this->exportFileItems($a_target_dir, $expLog);
5844 $ilBench->stop("ContentObjectExport", "exportFileItems");
5845 $expLog->write(date("[y-m-d H:i:s] ") . "Finished Export File Items");
5846 }
5847
5854 public function exportXMLMetaData(&$a_xml_writer)
5855 {
5856 $md2xml = new ilMD2XML($this->getId(), 0, $this->getType());
5857 $md2xml->setExportMode(true);
5858 $md2xml->startExport();
5859 $a_xml_writer->appendXML($md2xml->getXML());
5860 }
5861
5867 public function modifyExportIdentifier($a_tag, $a_param, $a_value)
5868 {
5869 if ($a_tag == "Identifier" && $a_param == "Entry") {
5870 $a_value = ilUtil::insertInstIntoID($a_value);
5871 }
5872
5873 return $a_value;
5874 }
5875
5876
5883 public function exportXMLPageObjects(&$a_xml_writer, $a_inst, &$expLog)
5884 {
5885 global $DIC;
5886 $ilBench = $DIC['ilBench'];
5887
5888 foreach ($this->questions as $question_id) {
5889 $ilBench->start("ContentObjectExport", "exportPageObject");
5890 $expLog->write(date("[y-m-d H:i:s] ") . "Page Object " . $question_id);
5891
5892 $attrs = array();
5893 $a_xml_writer->xmlStartTag("PageObject", $attrs);
5894
5895
5896 // export xml to writer object
5897 $ilBench->start("ContentObjectExport", "exportPageObject_XML");
5898 $page_object = new ilAssQuestionPage($question_id);
5899 $page_object->buildDom();
5900 $page_object->insertInstIntoIDs($a_inst);
5901 $mob_ids = $page_object->collectMediaObjects(false);
5902 $file_ids = ilPCFileList::collectFileItems($page_object, $page_object->getDomDoc());
5903 $xml = $page_object->getXMLFromDom(false, false, false, "", true);
5904 $xml = str_replace("&", "&amp;", $xml);
5905 $a_xml_writer->appendXML($xml);
5906 $page_object->freeDom();
5907 unset($page_object);
5908
5909 $ilBench->stop("ContentObjectExport", "exportPageObject_XML");
5910
5911 // collect media objects
5912 $ilBench->start("ContentObjectExport", "exportPageObject_CollectMedia");
5913 //$mob_ids = $page_obj->getMediaObjectIDs();
5914 foreach ($mob_ids as $mob_id) {
5915 $this->mob_ids[$mob_id] = $mob_id;
5916 }
5917 $ilBench->stop("ContentObjectExport", "exportPageObject_CollectMedia");
5918
5919 // collect all file items
5920 $ilBench->start("ContentObjectExport", "exportPageObject_CollectFileItems");
5921 //$file_ids = $page_obj->getFileItemIds();
5922 foreach ($file_ids as $file_id) {
5923 $this->file_ids[$file_id] = $file_id;
5924 }
5925 $ilBench->stop("ContentObjectExport", "exportPageObject_CollectFileItems");
5926
5927 $a_xml_writer->xmlEndTag("PageObject");
5928 //unset($page_obj);
5929
5930 $ilBench->stop("ContentObjectExport", "exportPageObject");
5931 }
5932 }
5933
5937 public function exportXMLMediaObjects(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog)
5938 {
5939 foreach ($this->mob_ids as $mob_id) {
5940 $expLog->write(date("[y-m-d H:i:s] ") . "Media Object " . $mob_id);
5941 if (ilObjMediaObject::_exists($mob_id)) {
5942 $media_obj = new ilObjMediaObject($mob_id);
5943 $media_obj->exportXML($a_xml_writer, $a_inst);
5944 $media_obj->exportFiles($a_target_dir);
5945 unset($media_obj);
5946 }
5947 }
5948 }
5949
5954 public function exportFileItems($target_dir, &$expLog)
5955 {
5956 foreach ($this->file_ids as $file_id) {
5957 $expLog->write(date("[y-m-d H:i:s] ") . "File Item " . $file_id);
5958 $file_dir = $target_dir . '/objects/il_' . IL_INST_ID . '_file_' . $file_id;
5959 ilFileUtils::makeDir($file_dir);
5960 $file_obj = new ilObjFile($file_id, false);
5961 $source_file = $file_obj->getFile($file_obj->getVersion());
5962 if (!is_file($source_file)) {
5963 $source_file = $file_obj->getFile();
5964 }
5965 if (is_file($source_file)) {
5966 copy($source_file, $file_dir . '/' . $file_obj->getFileName());
5967 }
5968 unset($file_obj);
5969 }
5970 }
5971
5976 public function getImportMapping(): array
5977 {
5978 return array();
5979 }
5980
5984 public function canEditEctsGrades(): bool
5985 {
5986 return $this->canShowEctsGrades() && $this->canEditMarks();
5987 }
5988
5992 public function canShowEctsGrades(): bool
5993 {
5994 return (bool) $this->getReportingDate();
5995 }
5996
6000 public function getECTSGrade($passed_array, $reached_points, $max_points): string
6001 {
6002 return self::_getECTSGrade($passed_array, $reached_points, $max_points, $this->ects_grades["A"], $this->ects_grades["B"], $this->ects_grades["C"], $this->ects_grades["D"], $this->ects_grades["E"], $this->ects_fx);
6003 }
6004
6008 public static function _getECTSGrade($points_passed, $reached_points, $max_points, $a, $b, $c, $d, $e, $fx): string
6009 {
6010 // calculate the median
6011 $passed_statistics = new ilStatistics();
6012 $passed_statistics->setData($points_passed);
6013 $ects_percentiles = array(
6014 "A" => $passed_statistics->quantile($a),
6015 "B" => $passed_statistics->quantile($b),
6016 "C" => $passed_statistics->quantile($c),
6017 "D" => $passed_statistics->quantile($d),
6018 "E" => $passed_statistics->quantile($e)
6019 );
6020 if (count($points_passed) && ($reached_points >= $ects_percentiles["A"])) {
6021 return "A";
6022 } elseif (count($points_passed) && ($reached_points >= $ects_percentiles["B"])) {
6023 return "B";
6024 } elseif (count($points_passed) && ($reached_points >= $ects_percentiles["C"])) {
6025 return "C";
6026 } elseif (count($points_passed) && ($reached_points >= $ects_percentiles["D"])) {
6027 return "D";
6028 } elseif (count($points_passed) && ($reached_points >= $ects_percentiles["E"])) {
6029 return "E";
6030 } elseif (strcmp($fx, "") != 0) {
6031 if ($max_points > 0) {
6032 $percentage = ($reached_points / $max_points) * 100.0;
6033 if ($percentage < 0) {
6034 $percentage = 0.0;
6035 }
6036 } else {
6037 $percentage = 0.0;
6038 }
6039 if ($percentage >= $fx) {
6040 return "FX";
6041 } else {
6042 return "F";
6043 }
6044 } else {
6045 return "F";
6046 }
6047 }
6048
6052 public function checkMarks()
6053 {
6054 return $this->mark_schema->checkMarks();
6055 }
6056
6061 {
6062 return $this->mark_schema;
6063 }
6064
6068 public function getMarkSchemaForeignId(): int
6069 {
6070 return $this->getTestId();
6071 }
6072
6075 public function onMarkSchemaSaved()
6076 {
6081 global $DIC;
6082 $ilDB = $DIC['ilDB'];
6083 $component_repository = $DIC['component.repository'];
6084 $tree = $DIC['tree'];
6085
6086 $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $component_repository, $this);
6087 $this->saveCompleteStatus($testQuestionSetConfigFactory->getQuestionSetConfig());
6088
6089 if ($this->participantDataExist()) {
6090 $this->recalculateScores(true);
6091 }
6092 }
6093
6097 public function canEditMarks(): bool
6098 {
6099 $total = $this->evalTotalPersons();
6100 if ($total > 0) {
6101 $reporting_date = $this->getScoreSettings()->getResultSummarySettings()->getReportingDate();
6102 if ($reporting_date !== null) {
6103 return $reporting_date <= new DateTimeImmutable('now', new DateTimeZone('UTC'));
6104 }
6105 return false;
6106 } else {
6107 return true;
6108 }
6109 }
6110
6118 public function setAuthor(string $author = "")
6119 {
6120 $this->author = $author;
6121 }
6122
6132 public function saveAuthorToMetadata(string $a_author = "")
6133 {
6134 $md = new ilMD($this->getId(), 0, $this->getType());
6135 $md_life = $md->getLifecycle();
6136 if (!$md_life) {
6137 if (strlen($a_author) == 0) {
6138 global $DIC;
6139 $ilUser = $DIC['ilUser'];
6140 $a_author = $ilUser->getFullname();
6141 }
6142
6143 $md_life = $md->addLifecycle();
6144 $md_life->save();
6145 $con = $md_life->addContribute();
6146 $con->setRole("Author");
6147 $con->save();
6148 $ent = $con->addEntity();
6149 $ent->setEntity($a_author);
6150 $ent->save();
6151 }
6152 }
6153
6157 protected function doCreateMetaData(): void
6158 {
6159 $this->saveAuthorToMetadata();
6160 }
6161
6169 public function getAuthor(): string
6170 {
6171 $author = array();
6172 $md = new ilMD($this->getId(), 0, $this->getType());
6173 $md_life = $md->getLifecycle();
6174 if ($md_life) {
6175 $ids = $md_life->getContributeIds();
6176 foreach ($ids as $id) {
6177 $md_cont = $md_life->getContribute($id);
6178 if (strcmp($md_cont->getRole(), "Author") == 0) {
6179 $entids = $md_cont->getEntityIds();
6180 foreach ($entids as $entid) {
6181 $md_ent = $md_cont->getEntity($entid);
6182 array_push($author, $md_ent->getEntity());
6183 }
6184 }
6185 }
6186 }
6187 return join(",", $author);
6188 }
6189
6197 public static function _lookupAuthor($obj_id): string
6198 {
6199 $author = array();
6200 $md = new ilMD($obj_id, 0, "tst");
6201 $md_life = $md->getLifecycle();
6202 if ($md_life) {
6203 $ids = $md_life->getContributeIds();
6204 foreach ($ids as $id) {
6205 $md_cont = $md_life->getContribute($id);
6206 if (strcmp($md_cont->getRole(), "Author") == 0) {
6207 $entids = $md_cont->getEntityIds();
6208 foreach ($entids as $entid) {
6209 $md_ent = $md_cont->getEntity($entid);
6210 array_push($author, $md_ent->getEntity());
6211 }
6212 }
6213 }
6214 }
6215 return join(",", $author);
6216 }
6217
6224 public static function _getAvailableTests($use_object_id = false): array
6225 {
6226 global $DIC;
6227 $ilUser = $DIC['ilUser'];
6228 $ilDB = $DIC['ilDB'];
6229
6230 $result_array = array();
6231 $tests = array_slice(
6232 array_reverse(
6233 ilUtil::_getObjectsByOperations("tst", "write", $ilUser->getId(), PHP_INT_MAX)
6234 ),
6235 0,
6236 10000
6237 );
6238
6239 if (count($tests)) {
6240 $titles = ilObject::_prepareCloneSelection($tests, "tst");
6241 foreach ($tests as $ref_id) {
6242 if ($use_object_id) {
6244 $result_array[$obj_id] = $titles[$ref_id];
6245 } else {
6246 $result_array[$ref_id] = $titles[$ref_id];
6247 }
6248 }
6249 }
6250 return $result_array;
6251 }
6252
6261 public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = false): ?ilObject
6262 {
6263 global $DIC;
6264
6266 $certificateLogger = $DIC->logger()->cert();
6267 $tree = $DIC['tree'];
6268 $ilDB = $DIC->database();
6269 $component_repository = $DIC['component.repository'];
6270
6271 $this->loadFromDb();
6272
6273 // Copy settings
6275 $newObj = parent::cloneObject($target_id, $copy_id, $omit_tree);
6276 $newObj->setTmpCopyWizardCopyId($copy_id);
6277 $this->cloneMetaData($newObj);
6278
6279 //copy online status if object is not the root copy object
6280 $cp_options = ilCopyWizardOptions::_getInstance($copy_id);
6281 if ($cp_options->isRootNode($this->getRefId())) {
6282 $newObj->setOfflineStatus(true);
6283 } else {
6284 $newObj->setOfflineStatus($this->getOfflineStatus());
6285 }
6286 $newObj->update();
6287
6288 $newObj->setAnonymity($this->getAnonymity());
6289 $newObj->setAnswerFeedback($this->getAnswerFeedback());
6290 $newObj->setAnswerFeedbackPoints($this->getAnswerFeedbackPoints());
6291 $newObj->setAuthor($this->getAuthor());
6292 $newObj->setLimitUsersEnabled($this->isLimitUsersEnabled());
6293 $newObj->setAllowedUsers($this->getAllowedUsers());
6294 $newObj->setAllowedUsersTimeGap($this->getAllowedUsersTimeGap());
6295 $newObj->setECTSFX($this->getECTSFX());
6296 $newObj->setECTSGrades($this->getECTSGrades());
6297 $newObj->setECTSOutput($this->getECTSOutput());
6298 $newObj->setEnableProcessingTime($this->getEnableProcessingTime());
6299 $newObj->setEndingTimeEnabled($this->isEndingTimeEnabled());
6300 $newObj->setEndingTime($this->getEndingTime());
6301 $newObj->setFixedParticipants($this->getFixedParticipants());
6302 $newObj->setInstantFeedbackSolution($this->getInstantFeedbackSolution());
6303 $newObj->setIntroductionEnabled($this->isIntroductionEnabled());
6304 $newObj->setIntroduction($this->getIntroduction());
6305 $newObj->setFinalStatement($this->getFinalStatement());
6306 $newObj->setShowInfo($this->getShowInfo());
6307 $newObj->setForceJS($this->getForceJS());
6308 $newObj->setCustomStyle($this->getCustomStyle());
6309 $newObj->setKiosk($this->getKiosk());
6310 $newObj->setShowFinalStatement($this->getShowFinalStatement());
6311 $newObj->setListOfQuestionsSettings($this->getListOfQuestionsSettings());
6312 $newObj->setMailNotification($this->getMailNotification());
6313 $newObj->setMailNotificationType($this->getMailNotificationType());
6314 $newObj->setNrOfTries($this->getNrOfTries());
6315 $newObj->setBlockPassesAfterPassedEnabled($this->isBlockPassesAfterPassedEnabled());
6316 $newObj->setPasswordEnabled($this->isPasswordEnabled());
6317 $newObj->setPassword($this->getPassword());
6318 $newObj->setProcessingTime($this->getProcessingTime());
6319 $newObj->setQuestionSetType($this->getQuestionSetType());
6320 $newObj->setReportingDate($this->getReportingDate());
6321 $newObj->setResetProcessingTime($this->getResetProcessingTime());
6322 $newObj->setShowGradingStatusEnabled($this->isShowGradingStatusEnabled());
6323 $newObj->setShowGradingMarkEnabled($this->isShowGradingMarkEnabled());
6324 $newObj->setSequenceSettings($this->getSequenceSettings());
6325 $newObj->setShowCancel($this->getShowCancel());
6326 $newObj->setShowMarker($this->getShowMarker());
6327 $newObj->setShuffleQuestions($this->getShuffleQuestions());
6328 $newObj->setStartingTimeEnabled($this->isStartingTimeEnabled());
6329 $newObj->setStartingTime($this->getStartingTime());
6330 $newObj->setTitleOutput($this->getTitleOutput());
6331 $newObj->setUsePreviousAnswers($this->getUsePreviousAnswers());
6332 $newObj->setRedirectionMode($this->getRedirectionMode());
6333 $newObj->setRedirectionUrl($this->getRedirectionUrl());
6334 $newObj->mark_schema = clone $this->mark_schema;
6335 $newObj->setEnabledViewMode($this->getEnabledViewMode());
6336 $newObj->setTemplate($this->getTemplate());
6337 $newObj->setShowExamIdInTestPassEnabled($this->isShowExamIdInTestPassEnabled());
6338 $newObj->setEnableExamView($this->getEnableExamview());
6339 $newObj->setShowExamViewHtml($this->getShowExamviewHtml());
6340 $newObj->setShowExamViewPdf($this->getShowExamviewPdf());
6341 $newObj->setEnableArchiving($this->getEnableArchiving());
6342 $newObj->setSignSubmission($this->getSignSubmission());
6343 $newObj->setCharSelectorAvailability($this->getCharSelectorAvailability());
6344 $newObj->setCharSelectorDefinition($this->getCharSelectorDefinition());
6345 $newObj->setSkillServiceEnabled($this->isSkillServiceEnabled());
6346 $newObj->setFollowupQuestionAnswerFixationEnabled($this->isFollowupQuestionAnswerFixationEnabled());
6347 $newObj->setInstantFeedbackAnswerFixationEnabled($this->isInstantFeedbackAnswerFixationEnabled());
6348 $newObj->setForceInstantFeedbackEnabled($this->isForceInstantFeedbackEnabled());
6349 $newObj->setAutosave($this->getAutosave());
6350 $newObj->setAutosaveIval($this->getAutosaveIval());
6351 $newObj->setOfferingQuestionHintsEnabled($this->isOfferingQuestionHintsEnabled());
6352 $newObj->setSpecificAnswerFeedback($this->getSpecificAnswerFeedback());
6353 if ($this->isPassWaitingEnabled()) {
6354 $newObj->setPassWaiting($this->getPassWaiting());
6355 }
6356 $newObj->setObligationsEnabled($this->areObligationsEnabled());
6357 $newObj->saveToDb();
6358
6359 // clone certificate
6360 $pathFactory = new ilCertificatePathFactory();
6361 $templateRepository = new ilCertificateTemplateDatabaseRepository($ilDB);
6362
6363 $cloneAction = new ilCertificateCloneAction(
6364 $ilDB,
6365 $pathFactory,
6366 $templateRepository,
6367 $DIC->filesystem()->web(),
6368 $certificateLogger,
6370 );
6371
6372 $cloneAction->cloneCertificate($this, $newObj);
6373
6374 $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $component_repository, $this);
6375 $testQuestionSetConfigFactory->getQuestionSetConfig()->cloneQuestionSetRelatedData($newObj);
6376 $newObj->saveQuestionsToDb();
6377
6378 $skillLevelThresholdList = new ilTestSkillLevelThresholdList($ilDB);
6379 $skillLevelThresholdList->setTestId($this->getTestId());
6380 $skillLevelThresholdList->loadFromDb();
6381 $skillLevelThresholdList->cloneListForTest($newObj->getTestId());
6382
6383 $newObj->saveToDb();
6384 $newObj->updateMetaData();// #14467
6385
6386 $score_settings = $this->getScoreSettingsRepository()->getForObjFi($this->getId());
6387 $this->getScoreSettingsRepository()->store(
6388 $score_settings->withTestId($newObj->getTestId())
6389 );
6390
6391 $obj_settings = new ilLPObjSettings($this->getId());
6392 $obj_settings->cloneSettings($newObj->getId());
6393
6394 return $newObj;
6395 }
6396
6397 public function getQuestionCount(): int
6398 {
6399 $num = 0;
6400
6401 if ($this->isRandomTest()) {
6402 global $DIC;
6403 $tree = $DIC['tree'];
6404 $ilDB = $DIC['ilDB'];
6405 $component_repository = $DIC['component.repository'];
6406
6407 $questionSetConfig = new ilTestRandomQuestionSetConfig(
6408 $tree,
6409 $ilDB,
6410 $component_repository,
6411 $this
6412 );
6413
6414 $questionSetConfig->loadFromDb();
6415
6416 if ($questionSetConfig->isQuestionAmountConfigurationModePerPool()) {
6417 $sourcePoolDefinitionList = new ilTestRandomQuestionSetSourcePoolDefinitionList(
6418 $ilDB,
6419 $this,
6421 );
6422
6423 $sourcePoolDefinitionList->loadDefinitions();
6424
6425 if (is_int($sourcePoolDefinitionList->getQuestionAmount())) {
6426 $num = $sourcePoolDefinitionList->getQuestionAmount();
6427 }
6428 } elseif (is_int($questionSetConfig->getQuestionAmountPerTest())) {
6429 $num = $questionSetConfig->getQuestionAmountPerTest();
6430 }
6431 } else {
6432 $this->loadQuestions();
6433 $num = count($this->questions);
6434 }
6435
6436 return $num;
6437 }
6438
6440 {
6441 if ($this->isRandomTest()) {
6442 return $this->getQuestionCount();
6443 }
6444 return count($this->questions);
6445 }
6446
6454 public function logAction($logtext = "", $question_id = "")
6455 {
6456 global $DIC;
6457 $ilUser = $DIC['ilUser'];
6458
6459 $original_id = "";
6460 if (strcmp($question_id, "") != 0) {
6461 $original_id = assQuestion::_getOriginalId($question_id);
6462 }
6463 ilObjAssessmentFolder::_addLog($ilUser->getId(), $this->getId(), $logtext, $question_id, $original_id, true, $this->getRefId());
6464 }
6465
6473 public static function _getObjectIDFromTestID($test_id)
6474 {
6475 global $DIC;
6476 $ilDB = $DIC['ilDB'];
6477 $object_id = false;
6478 $result = $ilDB->queryF(
6479 "SELECT obj_fi FROM tst_tests WHERE test_id = %s",
6480 array('integer'),
6481 array($test_id)
6482 );
6483 if ($result->numRows()) {
6484 $row = $ilDB->fetchAssoc($result);
6485 $object_id = $row["obj_fi"];
6486 }
6487 return $object_id;
6488 }
6489
6497 public static function _getObjectIDFromActiveID($active_id)
6498 {
6499 global $DIC;
6500 $ilDB = $DIC['ilDB'];
6501 $object_id = false;
6502 $result = $ilDB->queryF(
6503 "SELECT tst_tests.obj_fi FROM tst_tests, tst_active WHERE tst_tests.test_id = tst_active.test_fi AND tst_active.active_id = %s",
6504 array('integer'),
6505 array($active_id)
6506 );
6507 if ($result->numRows()) {
6508 $row = $ilDB->fetchAssoc($result);
6509 $object_id = $row["obj_fi"];
6510 }
6511 return $object_id;
6512 }
6513
6521 public static function _getTestIDFromObjectID($object_id)
6522 {
6523 global $DIC;
6524 $ilDB = $DIC['ilDB'];
6525 $test_id = false;
6526 $result = $ilDB->queryF(
6527 "SELECT test_id FROM tst_tests WHERE obj_fi = %s",
6528 array('integer'),
6529 array($object_id)
6530 );
6531 if ($result->numRows()) {
6532 $row = $ilDB->fetchAssoc($result);
6533 $test_id = $row["test_id"];
6534 }
6535 return $test_id;
6536 }
6537
6546 public function getTextAnswer($active_id, $question_id, $pass = null): string
6547 {
6548 global $DIC;
6549 $ilDB = $DIC['ilDB'];
6550
6551 if (!$active_id || !$question_id) {
6552 if ($pass === null) {
6553 $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
6554 }
6555 if ($pass === null) {
6556 return '';
6557 }
6558 $result = $ilDB->queryF(
6559 "SELECT value1 FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
6560 array('integer', 'integer', 'integer'),
6561 array($active_id, $question_id, $pass)
6562 );
6563 if ($result->numRows() == 1) {
6564 $row = $ilDB->fetchAssoc($result);
6565 return $row["value1"];
6566 }
6567 }
6568 return '';
6569 }
6570
6578 public function getQuestiontext($question_id): string
6579 {
6580 global $DIC;
6581 $ilDB = $DIC['ilDB'];
6582
6583 $res = "";
6584 if ($question_id) {
6585 $result = $ilDB->queryF(
6586 "SELECT question_text FROM qpl_questions WHERE question_id = %s",
6587 array('integer'),
6588 array($question_id)
6589 );
6590 if ($result->numRows() == 1) {
6591 $row = $ilDB->fetchAssoc($result);
6592 $res = $row["question_text"];
6593 }
6594 }
6595 return $res;
6596 }
6597
6602 {
6603 $participantList = new ilTestParticipantList($this);
6604 $participantList->initializeFromDbRows($this->getInvitedUsers());
6605
6606 return $participantList;
6607 }
6608
6613 {
6614 $participantList = new ilTestParticipantList($this);
6615 $participantList->initializeFromDbRows($this->getTestParticipants());
6616
6617 return $participantList;
6618 }
6619
6626 public function &getInvitedUsers($user_id = "", $order = "login, lastname, firstname"): array
6627 {
6628 global $DIC;
6629 $ilDB = $DIC['ilDB'];
6630
6631 $result_array = array();
6632
6633 if ($this->getAnonymity()) {
6634 if (is_numeric($user_id)) {
6635 $result = $ilDB->queryF(
6636 "SELECT tst_active.active_id, tst_active.tries, usr_id, %s login, %s lastname, %s firstname, tst_invited_user.clientip, " .
6637 "tst_active.submitted test_finished, matriculation, COALESCE(tst_active.last_finished_pass, -1) <> tst_active.last_started_pass unfinished_passes FROM usr_data, tst_invited_user " .
6638 "LEFT JOIN tst_active ON tst_active.user_fi = tst_invited_user.user_fi AND tst_active.test_fi = tst_invited_user.test_fi " .
6639 "WHERE tst_invited_user.test_fi = %s and tst_invited_user.user_fi=usr_data.usr_id AND usr_data.usr_id=%s " .
6640 "ORDER BY $order",
6641 array('text', 'text', 'text', 'integer', 'integer'),
6642 array("", $this->lng->txt("anonymous"), "", $this->getTestId(), $user_id)
6643 );
6644 } else {
6645 $result = $ilDB->queryF(
6646 "SELECT tst_active.active_id, tst_active.tries, usr_id, %s login, %s lastname, %s firstname, tst_invited_user.clientip, " .
6647 "tst_active.submitted test_finished, matriculation, COALESCE(tst_active.last_finished_pass, -1) <> tst_active.last_started_pass unfinished_passes FROM usr_data, tst_invited_user " .
6648 "LEFT JOIN tst_active ON tst_active.user_fi = tst_invited_user.user_fi AND tst_active.test_fi = tst_invited_user.test_fi " .
6649 "WHERE tst_invited_user.test_fi = %s and tst_invited_user.user_fi=usr_data.usr_id " .
6650 "ORDER BY $order",
6651 array('text', 'text', 'text', 'integer'),
6652 array("", $this->lng->txt("anonymous"), "", $this->getTestId())
6653 );
6654 }
6655 } else {
6656 if (is_numeric($user_id)) {
6657 $result = $ilDB->queryF(
6658 "SELECT tst_active.active_id, tst_active.tries, usr_id, login, lastname, firstname, tst_invited_user.clientip, " .
6659 "tst_active.submitted test_finished, matriculation, COALESCE(tst_active.last_finished_pass, -1) <> tst_active.last_started_pass unfinished_passes FROM usr_data, tst_invited_user " .
6660 "LEFT JOIN tst_active ON tst_active.user_fi = tst_invited_user.user_fi AND tst_active.test_fi = tst_invited_user.test_fi " .
6661 "WHERE tst_invited_user.test_fi = %s and tst_invited_user.user_fi=usr_data.usr_id AND usr_data.usr_id=%s " .
6662 "ORDER BY $order",
6663 array('integer', 'integer'),
6664 array($this->getTestId(), $user_id)
6665 );
6666 } else {
6667 $result = $ilDB->queryF(
6668 "SELECT tst_active.active_id, tst_active.tries, usr_id, login, lastname, firstname, tst_invited_user.clientip, " .
6669 "tst_active.submitted test_finished, matriculation, COALESCE(tst_active.last_finished_pass, -1) <> tst_active.last_started_pass unfinished_passes FROM usr_data, tst_invited_user " .
6670 "LEFT JOIN tst_active ON tst_active.user_fi = tst_invited_user.user_fi AND tst_active.test_fi = tst_invited_user.test_fi " .
6671 "WHERE tst_invited_user.test_fi = %s and tst_invited_user.user_fi=usr_data.usr_id " .
6672 "ORDER BY $order",
6673 array('integer'),
6674 array($this->getTestId())
6675 );
6676 }
6677 }
6678 $result_array = array();
6679 while ($row = $ilDB->fetchAssoc($result)) {
6680 $result_array[$row['usr_id']] = $row;
6681 }
6682 return $result_array;
6683 }
6684
6691 public function &getTestParticipants(): array
6692 {
6693 global $DIC;
6694 $ilDB = $DIC['ilDB'];
6695
6696 if ($this->getAnonymity()) {
6697 $query = "
6698 SELECT tst_active.active_id,
6699 tst_active.tries,
6700 tst_active.user_fi usr_id,
6701 %s login,
6702 %s lastname,
6703 %s firstname,
6704 tst_active.submitted test_finished,
6705 usr_data.matriculation,
6706 usr_data.active,
6707 tst_active.lastindex,
6708 COALESCE(tst_active.last_finished_pass, -1) <> tst_active.last_started_pass unfinished_passes
6709 FROM tst_active
6710 LEFT JOIN usr_data
6711 ON tst_active.user_fi = usr_data.usr_id
6712 WHERE tst_active.test_fi = %s
6713 ORDER BY usr_data.lastname
6714 ";
6715 $result = $ilDB->queryF(
6716 $query,
6717 array('text', 'text', 'text', 'integer'),
6718 array("", $this->lng->txt("anonymous"), "", $this->getTestId())
6719 );
6720 } else {
6721 $query = "
6722 SELECT tst_active.active_id,
6723 tst_active.tries,
6724 tst_active.user_fi usr_id,
6725 usr_data.login,
6726 usr_data.lastname,
6727 usr_data.firstname,
6728 tst_active.submitted test_finished,
6729 usr_data.matriculation,
6730 usr_data.active,
6731 tst_active.lastindex,
6732 COALESCE(tst_active.last_finished_pass, -1) <> tst_active.last_started_pass unfinished_passes
6733 FROM tst_active
6734 LEFT JOIN usr_data
6735 ON tst_active.user_fi = usr_data.usr_id
6736 WHERE tst_active.test_fi = %s
6737 ORDER BY usr_data.lastname
6738 ";
6739 $result = $ilDB->queryF(
6740 $query,
6741 array('integer'),
6742 array($this->getTestId())
6743 );
6744 }
6745 $data = array();
6746 while ($row = $ilDB->fetchAssoc($result)) {
6747 $data[$row['active_id']] = $row;
6748 }
6749 foreach ($data as $index => $participant) {
6750 if (strlen(trim($participant["firstname"] . $participant["lastname"])) == 0) {
6751 $data[$index]["lastname"] = $this->lng->txt("deleted_user");
6752 }
6753 }
6754 return $data;
6755 }
6756
6757 public function getTestParticipantsForManualScoring($filter = null): array
6758 {
6759 global $DIC;
6760 $ilDB = $DIC['ilDB'];
6761
6763 if (count($scoring) == 0) {
6764 return array();
6765 }
6766
6767 $participants = &$this->getTestParticipants();
6768 $filtered_participants = array();
6769 foreach ($participants as $active_id => $participant) {
6770 $qstType_IN_manScoreableQstTypes = $ilDB->in('qpl_questions.question_type_fi', $scoring, false, 'integer');
6771
6772 $queryString = "
6773 SELECT tst_test_result.manual
6774
6775 FROM tst_test_result
6776
6777 INNER JOIN qpl_questions
6778 ON tst_test_result.question_fi = qpl_questions.question_id
6779
6780 WHERE tst_test_result.active_fi = %s
6781 AND $qstType_IN_manScoreableQstTypes
6782 ";
6783
6784 $result = $ilDB->queryF(
6785 $queryString,
6786 array("integer"),
6787 array($active_id)
6788 );
6789
6790 $count = $result->numRows();
6791
6792 if ($count > 0) {
6793 switch ($filter) {
6794 case 1: // only active users
6795 if ($participant['active']) {
6796 $filtered_participants[$active_id] = $participant;
6797 }
6798 break;
6799 case 2: // only inactive users
6800 if (!$participant['active']) {
6801 $filtered_participants[$active_id] = $participant;
6802 }
6803 break;
6804 case 3: // all users
6805 $filtered_participants[$active_id] = $participant;
6806 break;
6807 case 4:
6808 $assessmentSetting = new ilSetting("assessment");
6809 $manscoring_done = $assessmentSetting->get("manscoring_done_" . $active_id);
6810 if ($manscoring_done) {
6811 $filtered_participants[$active_id] = $participant;
6812 }
6813 break;
6814 case 5:
6815 $assessmentSetting = new ilSetting("assessment");
6816 $manscoring_done = $assessmentSetting->get("manscoring_done_" . $active_id);
6817 if (!$manscoring_done) {
6818 $filtered_participants[$active_id] = $participant;
6819 }
6820 break;
6821 case 6:
6822 // partially scored participants
6823 $found = 0;
6824 while ($row = $ilDB->fetchAssoc($result)) {
6825 if ($row["manual"]) {
6826 $found++;
6827 }
6828 }
6829 if (($found > 0) && ($found < $count)) {
6830 $filtered_participants[$active_id] = $participant;
6831 }
6832 break;
6833 default:
6834 $filtered_participants[$active_id] = $participant;
6835 break;
6836 }
6837 }
6838 }
6839 return $filtered_participants;
6840 }
6841
6848 public function getUserData($ids): array
6849 {
6850 global $DIC;
6851 $ilDB = $DIC['ilDB'];
6852
6853 if (!is_array($ids) || count($ids) == 0) {
6854 return array();
6855 }
6856
6857 if ($this->getAnonymity()) {
6858 $result = $ilDB->queryF(
6859 "SELECT usr_id, %s login, %s lastname, %s firstname, client_ip clientip FROM usr_data WHERE " . $ilDB->in('usr_id', $ids, false, 'integer') . " ORDER BY login",
6860 array('text', 'text', 'text'),
6861 array("", $this->lng->txt("anonymous"), "")
6862 );
6863 } else {
6864 $result = $ilDB->query("SELECT usr_id, login, lastname, firstname, client_ip clientip FROM usr_data WHERE " . $ilDB->in('usr_id', $ids, false, 'integer') . " ORDER BY login");
6865 }
6866
6867 $result_array = array();
6868 while ($row = $ilDB->fetchAssoc($result)) {
6869 $result_array[$row["usr_id"]] = $row;
6870 }
6871 return $result_array;
6872 }
6873
6874 public function getGroupData($ids): array
6875 {
6876 if (!is_array($ids) || count($ids) == 0) {
6877 return array();
6878 }
6879 $result = array();
6880 foreach ($ids as $ref_id) {
6882 $result[$ref_id] = array("ref_id" => $ref_id, "title" => ilObject::_lookupTitle($obj_id), "description" => ilObject::_lookupDescription($obj_id));
6883 }
6884 return $result;
6885 }
6886
6887 public function getRoleData($ids): array
6888 {
6889 if (!is_array($ids) || count($ids) == 0) {
6890 return array();
6891 }
6892 $result = array();
6893 foreach ($ids as $obj_id) {
6894 $result[$obj_id] = array("obj_id" => $obj_id, "title" => ilObject::_lookupTitle($obj_id), "description" => ilObject::_lookupDescription($obj_id));
6895 }
6896 return $result;
6897 }
6898
6899
6906 public function inviteGroup($group_id)
6907 {
6908 $group = new ilObjGroup($group_id);
6909 $members = $group->getGroupMemberIds();
6910 foreach ($members as $user_id) {
6911 $this->inviteUser($user_id, ilObjUser::_lookupClientIP($user_id));
6912 }
6913 }
6914
6921 public function inviteRole($role_id)
6922 {
6923 global $DIC;
6924 $rbacreview = $DIC['rbacreview'];
6925 $members = $rbacreview->assignedUsers($role_id);
6926 foreach ($members as $user_id) {
6927 $this->inviteUser($user_id, ilObjUser::_lookupClientIP($user_id));
6928 }
6929 }
6930
6931
6932
6939 public function disinviteUser($user_id)
6940 {
6941 global $DIC;
6942 $ilDB = $DIC['ilDB'];
6943
6944 $affectedRows = $ilDB->manipulateF(
6945 "DELETE FROM tst_invited_user WHERE test_fi = %s AND user_fi = %s",
6946 array('integer', 'integer'),
6947 array($this->getTestId(), $user_id)
6948 );
6949 }
6950
6957 public function inviteUser($user_id, $client_ip = "")
6958 {
6959 global $DIC;
6960 $ilDB = $DIC['ilDB'];
6961
6962 $ilDB->manipulateF(
6963 "DELETE FROM tst_invited_user WHERE test_fi = %s AND user_fi = %s",
6964 array('integer', 'integer'),
6965 array($this->getTestId(), $user_id)
6966 );
6967 $ilDB->manipulateF(
6968 "INSERT INTO tst_invited_user (test_fi, user_fi, clientip, tstamp) VALUES (%s, %s, %s, %s)",
6969 array('integer', 'integer', 'text', 'integer'),
6970 array($this->getTestId(), $user_id, (strlen($client_ip)) ? $client_ip : null, time())
6971 );
6972 }
6973
6974
6975 public function setClientIP($user_id, $client_ip)
6976 {
6977 global $DIC;
6978 $ilDB = $DIC['ilDB'];
6979
6980 $affectedRows = $ilDB->manipulateF(
6981 "UPDATE tst_invited_user SET clientip = %s, tstamp = %s WHERE test_fi=%s and user_fi=%s",
6982 array('text', 'integer', 'integer', 'integer'),
6983 array((strlen($client_ip)) ? $client_ip : null, time(), $this->getTestId(), $user_id)
6984 );
6985 }
6986
6992 public static function _getSolvedQuestions($active_id, $question_fi = null): array
6993 {
6994 global $DIC;
6995 $ilDB = $DIC['ilDB'];
6996 if (is_numeric($question_fi)) {
6997 $result = $ilDB->queryF(
6998 "SELECT question_fi, solved FROM tst_qst_solved WHERE active_fi = %s AND question_fi=%s",
6999 array('integer', 'integer'),
7000 array($active_id, $question_fi)
7001 );
7002 } else {
7003 $result = $ilDB->queryF(
7004 "SELECT question_fi, solved FROM tst_qst_solved WHERE active_fi = %s",
7005 array('integer'),
7006 array($active_id)
7007 );
7008 }
7009 $result_array = array();
7010 while ($row = $ilDB->fetchAssoc($result)) {
7011 $result_array[$row["question_fi"]] = $row;
7012 }
7013 return $result_array;
7014 }
7015
7016
7020 public function setQuestionSetSolved($value, $question_id, $user_id)
7021 {
7022 global $DIC;
7023 $ilDB = $DIC['ilDB'];
7024
7025 $active_id = $this->getActiveIdOfUser($user_id);
7026 $ilDB->manipulateF(
7027 "DELETE FROM tst_qst_solved WHERE active_fi = %s AND question_fi = %s",
7028 array('integer', 'integer'),
7029 array($active_id, $question_id)
7030 );
7031 $ilDB->manipulateF(
7032 "INSERT INTO tst_qst_solved (solved, question_fi, active_fi) VALUES (%s, %s, %s)",
7033 array('integer', 'integer', 'integer'),
7034 array($value, $question_id, $active_id)
7035 );
7036 }
7037
7041 public function isTestFinished($active_id): bool
7042 {
7043 global $DIC;
7044 $ilDB = $DIC['ilDB'];
7045
7046 $result = $ilDB->queryF(
7047 "SELECT submitted FROM tst_active WHERE active_id=%s AND submitted=%s",
7048 array('integer', 'integer'),
7049 array($active_id, 1)
7050 );
7051 return $result->numRows() == 1;
7052 }
7053
7057 public function isActiveTestSubmitted($user_id = null): bool
7058 {
7059 global $DIC;
7060 $ilUser = $DIC['ilUser'];
7061 $ilDB = $DIC['ilDB'];
7062
7063 if (!is_numeric($user_id)) {
7064 $user_id = $ilUser->getId();
7065 }
7066
7067 $result = $ilDB->queryF(
7068 "SELECT submitted FROM tst_active WHERE test_fi=%s AND user_fi=%s AND submitted=%s",
7069 array('integer', 'integer', 'integer'),
7070 array($this->getTestId(), $user_id, 1)
7071 );
7072 return $result->numRows() == 1;
7073 }
7074
7078 public function hasNrOfTriesRestriction(): bool
7079 {
7080 return $this->getNrOfTries() != 0;
7081 }
7082
7083
7089 public function isNrOfTriesReached($tries): bool
7090 {
7091 return $tries >= $this->getNrOfTries();
7092 }
7093
7094
7103 public function getAllTestResults($participants, $prepareForCSV = true): array
7104 {
7105 $results = array();
7106 $row = array(
7107 "user_id" => $this->lng->txt("user_id"),
7108 "matriculation" => $this->lng->txt("matriculation"),
7109 "lastname" => $this->lng->txt("lastname"),
7110 "firstname" => $this->lng->txt("firstname"),
7111 "login" => $this->lng->txt("login"),
7112 "reached_points" => $this->lng->txt("tst_reached_points"),
7113 "max_points" => $this->lng->txt("tst_maximum_points"),
7114 "percent_value" => $this->lng->txt("tst_percent_solved"),
7115 "mark" => $this->lng->txt("tst_mark"),
7116 "ects" => $this->lng->txt("ects_grade"),
7117 "passed" => $this->lng->txt("tst_mark_passed"),
7118 );
7119 $results[] = $row;
7120 if (count($participants)) {
7121 if ($this->getECTSOutput()) {
7122 $passed_array = &$this->getTotalPointsPassedArray();
7123 }
7124 foreach ($participants as $active_id => $user_rec) {
7125 $mark = $ects_mark = '';
7126 $row = array();
7127 $reached_points = 0;
7128 $max_points = 0;
7129 $pass = ilObjTest::_getResultPass($active_id);
7130 // abort if no valid pass can be found
7131 if (!is_int($pass)) {
7132 continue;
7133 }
7134 foreach ($this->questions as $value) {
7135 $question = ilObjTest::_instanciateQuestion($value);
7136 if (is_object($question)) {
7137 $max_points += $question->getMaximumPoints();
7138 $reached_points += $question->getReachedPoints($active_id, $pass);
7139 }
7140 }
7141 if ($max_points > 0) {
7142 $percentvalue = $reached_points / $max_points;
7143 if ($percentvalue < 0) {
7144 $percentvalue = 0.0;
7145 }
7146 } else {
7147 $percentvalue = 0;
7148 }
7149 $mark_obj = $this->mark_schema->getMatchingMark($percentvalue * 100);
7150 $passed = "";
7151 if ($mark_obj) {
7152 $mark = $mark_obj->getOfficialName();
7153 if ($this->getECTSOutput()) {
7154 $ects_mark = $this->getECTSGrade($passed_array, $reached_points, $max_points);
7155 }
7156 }
7157 if ($this->getAnonymity()) {
7158 $user_rec['firstname'] = "";
7159 $user_rec['lastname'] = $this->lng->txt("anonymous");
7160 }
7161 $row = array(
7162 "user_id" => $user_rec['usr_id'],
7163 "matriculation" => $user_rec['matriculation'],
7164 "lastname" => $user_rec['lastname'],
7165 "firstname" => $user_rec['firstname'],
7166 "login" => $user_rec['login'],
7167 "reached_points" => $reached_points,
7168 "max_points" => $max_points,
7169 "percent_value" => $percentvalue,
7170 "mark" => $mark,
7171 "ects" => $ects_mark,
7172 "passed" => $user_rec['passed'] ? '1' : '0',
7173 );
7174 $results[] = $prepareForCSV ? $this->processCSVRow($row, true) : $row;
7175 }
7176 }
7177 return $results;
7178 }
7179
7190 public function &processCSVRow($row, $quoteAll = false, $separator = ";"): array
7191 {
7192 $resultarray = array();
7193 foreach ($row as $rowindex => $entry) {
7194 $surround = false;
7195 if ($quoteAll) {
7196 $surround = true;
7197 }
7198 if (strpos($entry, "\"") !== false) {
7199 $entry = str_replace("\"", "\"\"", $entry);
7200 $surround = true;
7201 }
7202 if (strpos($entry, $separator) !== false) {
7203 $surround = true;
7204 }
7205 // replace all CR LF with LF (for Excel for Windows compatibility
7206 $entry = str_replace(chr(13) . chr(10), chr(10), $entry);
7207
7208 if ($surround) {
7209 $entry = "\"" . $entry . "\"";
7210 }
7211
7212 $resultarray[$rowindex] = $entry;
7213 }
7214 return $resultarray;
7215 }
7216
7225 public static function _getPass($active_id): int
7226 {
7227 global $DIC;
7228 $ilDB = $DIC['ilDB'];
7229 $result = $ilDB->queryF(
7230 "SELECT tries FROM tst_active WHERE active_id = %s",
7231 array('integer'),
7232 array($active_id)
7233 );
7234 if ($result->numRows()) {
7235 $row = $ilDB->fetchAssoc($result);
7236 return $row["tries"];
7237 } else {
7238 return 0;
7239 }
7240 }
7241
7251 public static function _getMaxPass($active_id): ?int
7252 {
7253 global $DIC;
7254 $ilDB = $DIC['ilDB'];
7255 $result = $ilDB->queryF(
7256 "SELECT MAX(pass) maxpass FROM tst_pass_result WHERE active_fi = %s",
7257 array('integer'),
7258 array($active_id)
7259 );
7260
7261 if ($result->numRows()) {
7262 $row = $ilDB->fetchAssoc($result);
7263 return $row["maxpass"];
7264 }
7265
7266 return null;
7267 }
7268
7274 public static function _getBestPass($active_id): ?int
7275 {
7276 global $DIC;
7277 $ilDB = $DIC['ilDB'];
7278
7279 $result = $ilDB->queryF(
7280 "SELECT * FROM tst_pass_result WHERE active_fi = %s",
7281 array('integer'),
7282 array($active_id)
7283 );
7284
7285 if (!$result->numRows()) {
7286 return null;
7287 }
7288
7289 $bestrow = null;
7290 $bestfactor = 0.0;
7291 while ($row = $ilDB->fetchAssoc($result)) {
7292 if ($row["maxpoints"] > 0.0) {
7293 $factor = (float) ($row["points"] / $row["maxpoints"]);
7294 } else {
7295 $factor = 0.0;
7296 }
7297 if ($factor === 0.0 && $bestfactor === 0.0
7298 || $factor > $bestfactor) {
7299 $bestrow = $row;
7300 $bestfactor = $factor;
7301 }
7302 }
7303
7304 if (is_array($bestrow)) {
7305 return $bestrow["pass"];
7306 }
7307
7308 return null;
7309 }
7310
7319 public static function _getResultPass($active_id): ?int
7320 {
7321 $counted_pass = null;
7322 if (ilObjTest::_getPassScoring($active_id) == SCORE_BEST_PASS) {
7323 $counted_pass = ilObjTest::_getBestPass($active_id);
7324 } else {
7325 $counted_pass = ilObjTest::_getMaxPass($active_id);
7326 }
7327 return $counted_pass;
7328 }
7329
7339 public function getAnsweredQuestionCount($active_id, $pass = null): int
7340 {
7341 if ($this->isDynamicTest()) {
7342 global $DIC;
7343 $tree = $DIC['tree'];
7344 $ilDB = $DIC['ilDB'];
7345 $lng = $DIC['lng'];
7346 $refinery = $DIC['refinery'];
7347 $component_repository = $DIC['component.repository'];
7348
7349 $testSessionFactory = new ilTestSessionFactory($this);
7350 $testSession = $testSessionFactory->getSession($active_id);
7351
7352 $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $refinery, $component_repository, $this);
7353 $testSequence = $testSequenceFactory->getSequenceByTestSession($testSession);
7354
7355 $dynamicQuestionSetConfig = new ilObjTestDynamicQuestionSetConfig($tree, $ilDB, $component_repository, $this);
7356 $dynamicQuestionSetConfig->loadFromDb();
7357
7358 $testSequence->loadFromDb($dynamicQuestionSetConfig);
7359 $testSequence->loadQuestions($dynamicQuestionSetConfig, new ilTestDynamicQuestionSetFilterSelection());
7360
7361 return $testSequence->getTrackedQuestionCount();
7362 }
7363
7364 if ($this->isRandomTest()) {
7365 $this->loadQuestions($active_id, $pass);
7366 }
7367 $workedthrough = 0;
7368 foreach ($this->questions as $value) {
7369 if (assQuestion::_isWorkedThrough($active_id, $value, $pass)) {
7370 $workedthrough += 1;
7371 }
7372 }
7373 return $workedthrough;
7374 }
7375
7382 public static function lookupPassResultsUpdateTimestamp($active_id, $pass): int
7383 {
7384 global $DIC;
7385 $ilDB = $DIC['ilDB'];
7386
7387 if (is_null($pass)) {
7388 $pass = 0;
7389 }
7390
7391 $query = "
7392 SELECT tst_pass_result.tstamp pass_res_tstamp,
7393 tst_test_result.tstamp quest_res_tstamp
7394
7395 FROM tst_pass_result
7396
7397 LEFT JOIN tst_test_result
7398 ON tst_test_result.active_fi = tst_pass_result.active_fi
7399 AND tst_test_result.pass = tst_pass_result.pass
7400
7401 WHERE tst_pass_result.active_fi = %s
7402 AND tst_pass_result.pass = %s
7403
7404 ORDER BY tst_test_result.tstamp DESC
7405 ";
7406
7407 $result = $ilDB->queryF(
7408 $query,
7409 array('integer', 'integer'),
7410 array($active_id, $pass)
7411 );
7412
7413 while ($row = $ilDB->fetchAssoc($result)) {
7414 if ($row['quest_res_tstamp']) {
7415 return $row['quest_res_tstamp'];
7416 }
7417
7418 return $row['pass_res_tstamp'];
7419 }
7420
7421 return 0;
7422 }
7423
7432 public function isExecutable($testSession, $user_id, $allowPassIncrease = false): array
7433 {
7434 $result = array(
7435 "executable" => true,
7436 "errormessage" => ""
7437 );
7438 if ($this->isDynamicTest()) {
7439 $result["executable"] = false;
7440 $result["errormessage"] = ($this->lng->txt("ctm_cannot_be_started"));
7441 return $result;
7442 }
7443 if (!$this->startingTimeReached()) {
7444 $result["executable"] = false;
7445 $result["errormessage"] = sprintf($this->lng->txt("detail_starting_time_not_reached"), ilDatePresentation::formatDate(new ilDateTime($this->getStartingTime(), IL_CAL_UNIX)));
7446 return $result;
7447 }
7448 if ($this->endingTimeReached()) {
7449 $result["executable"] = false;
7450 $result["errormessage"] = sprintf($this->lng->txt("detail_ending_time_reached"), ilDatePresentation::formatDate(new ilDateTime($this->getEndingTime(), IL_CAL_UNIX)));
7451 return $result;
7452 }
7453
7454 $active_id = $this->getActiveIdOfUser($user_id);
7455
7456 if ($this->getEnableProcessingTime()) {
7457 if ($active_id > 0) {
7458 $starting_time = $this->getStartingTimeOfUser($active_id);
7459 if ($starting_time !== false) {
7460 if ($this->isMaxProcessingTimeReached($starting_time, $active_id)) {
7461 if ($allowPassIncrease && $this->getResetProcessingTime() && (($this->getNrOfTries() == 0) || ($this->getNrOfTries() > (self::_getPass($active_id) + 1)))) {
7462 // a test pass was quitted because the maximum processing time was reached, but the time
7463 // will be resetted for future passes, so if there are more passes allowed, the participant may
7464 // start the test again.
7465 // This code block is only called when $allowPassIncrease is TRUE which only happens when
7466 // the test info page is opened. Otherwise this will lead to unexpected results!
7467 $testSession->increasePass();
7468 $testSession->setLastSequence(0);
7469 $testSession->saveToDb();
7470 } else {
7471 $result["executable"] = false;
7472 $result["errormessage"] = $this->lng->txt("detail_max_processing_time_reached");
7473 }
7474 return $result;
7475 }
7476 }
7477 }
7478 }
7479
7480 $testPassesSelector = new ilTestPassesSelector($this->db, $this);
7481 $testPassesSelector->setActiveId($active_id);
7482 $testPassesSelector->setLastFinishedPass($testSession->getLastFinishedPass());
7483
7484 if ($this->hasNrOfTriesRestriction() && ($active_id > 0)) {
7485 $closedPasses = $testPassesSelector->getClosedPasses();
7486
7487 if (count($closedPasses) >= $this->getNrOfTries()) {
7488 $result["executable"] = false;
7489 $result["errormessage"] = $this->lng->txt("maximum_nr_of_tries_reached");
7490 return $result;
7491 }
7492
7493 if ($this->isBlockPassesAfterPassedEnabled() && !$testPassesSelector->openPassExists()) {
7494 if (ilObjTestAccess::_isPassed($user_id, $this->getId())) {
7495 $result['executable'] = false;
7496 $result['errormessage'] = $this->lng->txt("tst_addit_passes_blocked_after_passed_msg");
7497 return $result;
7498 }
7499 }
7500 }
7501 if ($this->isPassWaitingEnabled() && $testPassesSelector->getLastFinishedPass() !== null) {
7502 $lastPass = $testPassesSelector->getLastFinishedPassTimestamp();
7503 if ($lastPass && strlen($this->getPassWaiting())) {
7504 $pass_waiting_string = $this->getPassWaiting();
7505 $time_values = explode(":", $pass_waiting_string);
7506 $next_pass_allowed = strtotime('+ ' . $time_values[0] . ' Months + ' . $time_values[1] . ' Days + ' . $time_values[2] . ' Hours' . $time_values[3] . ' Minutes', $lastPass);
7507
7508 if (time() < $next_pass_allowed) {
7509 $date = ilDatePresentation::formatDate(new ilDateTime($next_pass_allowed, IL_CAL_UNIX));
7510
7511 $result["executable"] = false;
7512 $result["errormessage"] = sprintf($this->lng->txt('wait_for_next_pass_hint_msg'), $date);
7513 return $result;
7514 }
7515 }
7516 }
7517 return $result;
7518 }
7519
7520
7521 public function canShowTestResults(ilTestSession $testSession): bool
7522 {
7523 $passSelector = new ilTestPassesSelector($this->db, $this);
7524
7525 $passSelector->setActiveId($testSession->getActiveId());
7526 $passSelector->setLastFinishedPass($testSession->getLastFinishedPass());
7527
7528 return $passSelector->hasReportablePasses();
7529 }
7530
7531 public function hasAnyTestResult(ilTestSession $testSession): bool
7532 {
7533 $passSelector = new ilTestPassesSelector($this->db, $this);
7534
7535 $passSelector->setActiveId($testSession->getActiveId());
7536 $passSelector->setLastFinishedPass($testSession->getLastFinishedPass());
7537
7538 return $passSelector->hasExistingPasses();
7539 }
7540
7548 public function getStartingTimeOfUser($active_id, $pass = null)
7549 {
7550 $ilDB = $this->db;
7551
7552 if ($active_id < 1) {
7553 return false;
7554 }
7555 if ($pass === null) {
7556 $pass = ($this->getResetProcessingTime()) ? self::_getPass($active_id) : 0;
7557 }
7558 $result = $ilDB->queryF(
7559 "SELECT tst_times.started FROM tst_times WHERE tst_times.active_fi = %s AND tst_times.pass = %s ORDER BY tst_times.started",
7560 array('integer', 'integer'),
7561 array($active_id, $pass)
7562 );
7563 if ($result->numRows()) {
7564 $row = $ilDB->fetchAssoc($result);
7565 if (preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches)) {
7566 return mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
7567 } else {
7568 return time();
7569 }
7570 } else {
7571 return time();
7572 }
7573 }
7574
7581 public function isMaxProcessingTimeReached(int $starting_time, int $active_id): bool
7582 {
7583 if ($this->getEnableProcessingTime()) {
7584 $processing_time = $this->getProcessingTimeInSeconds($active_id);
7585 $now = time();
7586 if ($now > ($starting_time + $processing_time)) {
7587 return true;
7588 } else {
7589 return false;
7590 }
7591 } else {
7592 return false;
7593 }
7594 }
7595
7596 public function &getTestQuestions(): array
7597 {
7598 global $DIC;
7599 $ilDB = $DIC['ilDB'];
7600
7601 $tags_trafo = $this->refinery->string()->stripTags();
7602
7603 $query = "
7604 SELECT questions.*,
7605 questtypes.type_tag,
7606 tstquest.sequence,
7607 tstquest.obligatory,
7608 origquest.obj_fi orig_obj_fi
7609
7610 FROM qpl_questions questions
7611
7612 INNER JOIN qpl_qst_type questtypes
7613 ON questtypes.question_type_id = questions.question_type_fi
7614
7615 INNER JOIN tst_test_question tstquest
7616 ON tstquest.question_fi = questions.question_id
7617
7618 LEFT JOIN qpl_questions origquest
7619 ON origquest.question_id = questions.original_id
7620
7621 WHERE tstquest.test_fi = %s
7622
7623 ORDER BY tstquest.sequence
7624 ";
7625
7626 $query_result = $ilDB->queryF(
7627 $query,
7628 array('integer'),
7629 array($this->getTestId())
7630 );
7631
7632 $questions = array();
7633
7634 while ($row = $ilDB->fetchAssoc($query_result)) {
7635 $row['title'] = $tags_trafo->transform($row['title']);
7636 $row['description'] = $tags_trafo->transform($row['description'] !== '' && $row['description'] !== null ? $row['description'] : '&nbsp;');
7637 $row['author'] = $tags_trafo->transform($row['author']);
7638 $row['obligationPossible'] = self::isQuestionObligationPossible($row['question_id']);
7639
7640 $questions[] = $row;
7641 }
7642
7643 return $questions;
7644 }
7645
7650 public function isTestQuestion($questionId): bool
7651 {
7652 foreach ($this->getTestQuestions() as $questionData) {
7653 if ($questionData['question_id'] != $questionId) {
7654 continue;
7655 }
7656
7657 return true;
7658 }
7659
7660 return false;
7661 }
7662
7663 public function checkQuestionParent($questionId): bool
7664 {
7665 global $DIC; /* @var ILIAS\DI\Container $DIC */
7666
7667 $row = $DIC->database()->fetchAssoc($DIC->database()->queryF(
7668 "SELECT COUNT(question_id) cnt FROM qpl_questions WHERE question_id = %s AND obj_fi = %s",
7669 array('integer', 'integer'),
7670 array($questionId, $this->getId())
7671 ));
7672
7673 return (bool) $row['cnt'];
7674 }
7675
7680 {
7681 $points = 0;
7682
7683 foreach ($this->getTestQuestions() as $questionData) {
7684 $points += $questionData['points'];
7685 }
7686
7687 return $points;
7688 }
7689
7693 public function getPotentialRandomTestQuestions(): array
7694 {
7698 global $DIC;
7699 $ilDB = $DIC['ilDB'];
7700
7701 $query = "
7702 SELECT questions.*,
7703 questtypes.type_tag,
7704 origquest.obj_fi orig_obj_fi
7705
7706 FROM qpl_questions questions
7707
7708 INNER JOIN qpl_qst_type questtypes
7709 ON questtypes.question_type_id = questions.question_type_fi
7710
7711 INNER JOIN tst_rnd_cpy tstquest
7712 ON tstquest.qst_fi = questions.question_id
7713
7714 LEFT JOIN qpl_questions origquest
7715 ON origquest.question_id = questions.original_id
7716
7717 WHERE tstquest.tst_fi = %s
7718 ";
7719
7720 $query_result = $ilDB->queryF(
7721 $query,
7722 array('integer'),
7723 array($this->getTestId())
7724 );
7725
7726 $questions = array();
7727
7728 while ($row = $ilDB->fetchAssoc($query_result)) {
7729 $question = $row;
7730
7731 $question['obligationPossible'] = self::isQuestionObligationPossible($row['question_id']);
7732
7733 $questions[] = $question;
7734 }
7735
7736 return $questions;
7737 }
7738
7745 public function getShuffleQuestions(): int
7746 {
7747 return ($this->shuffle_questions) ? 1 : 0;
7748 }
7749
7756 public function setShuffleQuestions($a_shuffle)
7757 {
7758 $this->shuffle_questions = ($a_shuffle) ? 1 : 0;
7759 }
7760
7774 {
7775 return ($this->show_summary) ? $this->show_summary : 0;
7776 }
7777
7790 public function setListOfQuestionsSettings($a_value = 0)
7791 {
7792 $this->show_summary = $a_value;
7793 }
7794
7801 public function getListOfQuestions(): bool
7802 {
7803 if (($this->show_summary & 1) > 0) {
7804 return true;
7805 } else {
7806 return false;
7807 }
7808 }
7809
7816 public function setListOfQuestions($a_value = true)
7817 {
7818 if ($a_value) {
7819 $this->show_summary = 1;
7820 } else {
7821 $this->show_summary = 0;
7822 }
7823 }
7824
7831 public function getListOfQuestionsStart(): bool
7832 {
7833 if (($this->show_summary & 2) > 0) {
7834 return true;
7835 } else {
7836 return false;
7837 }
7838 }
7839
7846 public function setListOfQuestionsStart($a_value = true)
7847 {
7848 if ($a_value && $this->getListOfQuestions()) {
7849 $this->show_summary = $this->show_summary | 2;
7850 }
7851 if (!$a_value && $this->getListOfQuestions()) {
7852 if ($this->getListOfQuestionsStart()) {
7853 $this->show_summary = $this->show_summary ^ 2;
7854 }
7855 }
7856 }
7857
7864 public function getListOfQuestionsEnd(): bool
7865 {
7866 if (($this->show_summary & 4) > 0) {
7867 return true;
7868 } else {
7869 return false;
7870 }
7871 }
7872
7879 public function setListOfQuestionsEnd($a_value = true)
7880 {
7881 if ($a_value && $this->getListOfQuestions()) {
7882 $this->show_summary = $this->show_summary | 4;
7883 }
7884 if (!$a_value && $this->getListOfQuestions()) {
7885 if ($this->getListOfQuestionsEnd()) {
7886 $this->show_summary = $this->show_summary ^ 4;
7887 }
7888 }
7889 }
7890
7894 public function getListOfQuestionsDescription(): bool
7895 {
7896 if (($this->show_summary & 8) > 0) {
7897 return true;
7898 } else {
7899 return false;
7900 }
7901 }
7902
7909 public function setListOfQuestionsDescription($a_value = true)
7910 {
7911 if ($a_value && $this->getListOfQuestions()) {
7912 $this->show_summary = $this->show_summary | 8;
7913 }
7914 if (!$a_value && $this->getListOfQuestions()) {
7915 if ($this->getListOfQuestionsDescription()) {
7916 $this->show_summary = $this->show_summary ^ 8;
7917 }
7918 }
7919 }
7920
7927 public function getResultsPresentation(): int
7928 {
7929 return $this->getScoreSettings()->getResultDetailsSettings()->getResultsPresentation();
7930 }
7931
7935 public function getShowPassDetails(): bool
7936 {
7937 return $this->getScoreSettings()->getResultDetailsSettings()->getShowPassDetails();
7938 }
7939
7943 public function getShowSolutionDetails(): bool
7944 {
7945 return $this->getScoreSettings()->getResultDetailsSettings()->getShowSolutionDetails();
7946 }
7947
7951 public function getShowSolutionPrintview(): bool
7952 {
7953 return $this->getScoreSettings()->getResultDetailsSettings()->getShowSolutionPrintview();
7954 }
7958 public function canShowSolutionPrintview($user_id = null): bool
7959 {
7960 return $this->getShowSolutionPrintview();
7961 }
7962
7966 public function getShowSolutionFeedback(): bool
7967 {
7968 return $this->getScoreSettings()->getResultDetailsSettings()->getShowSolutionFeedback();
7969 }
7970
7974 public function getShowSolutionAnswersOnly(): bool
7975 {
7976 return $this->getScoreSettings()->getResultDetailsSettings()->getShowSolutionAnswersOnly();
7977 }
7978
7982 public function getShowSolutionSignature(): bool
7983 {
7984 return $this->getScoreSettings()->getResultDetailsSettings()->getShowSolutionSignature();
7985 }
7986
7990 public function getShowSolutionSuggested(): bool
7991 {
7992 return $this->getScoreSettings()->getResultDetailsSettings()->getShowSolutionSuggested();
7993 }
7994
7999 public function getShowSolutionListComparison(): bool
8000 {
8001 return $this->getScoreSettings()->getResultDetailsSettings()->getShowSolutionListComparison();
8002 }
8003
8004 public function getShowSolutionListOwnAnswers($user_id = null): bool
8005 {
8006 return $this->getScoreSettings()->getResultDetailsSettings()->getShowSolutionListOwnAnswers();
8007 }
8008
8016 public function setShowSolutionPrintview(int $a_printview = 1): void
8017 {
8018 $this->getScoreSettings()->getResultDetailsSettings()->withShowSolutionPrintview($a_printview === 1);
8019 }
8020
8029 public static function _getUserIdFromActiveId($active_id)
8030 {
8031 global $DIC;
8032 $ilDB = $DIC['ilDB'];
8033 $result = $ilDB->queryF(
8034 "SELECT user_fi FROM tst_active WHERE active_id = %s",
8035 array('integer'),
8036 array($active_id)
8037 );
8038 if ($result->numRows()) {
8039 $row = $ilDB->fetchAssoc($result);
8040 return $row["user_fi"];
8041 } else {
8042 return -1;
8043 }
8044 }
8045
8046 public function isLimitUsersEnabled(): ?bool
8047 {
8048 return $this->limitUsersEnabled;
8049 }
8050
8051 public function setLimitUsersEnabled(bool $limitUsersEnabled): void
8052 {
8053 $this->limitUsersEnabled = $limitUsersEnabled;
8054 }
8055
8056 public function getAllowedUsers()
8057 {
8058 return ($this->allowedUsers) ? $this->allowedUsers : 0;
8059 }
8060
8061 public function setAllowedUsers($a_allowed_users)
8062 {
8063 $this->allowedUsers = $a_allowed_users;
8064 }
8065
8066 public function getAllowedUsersTimeGap()
8067 {
8068 return ($this->allowedUsersTimeGap) ? $this->allowedUsersTimeGap : 0;
8069 }
8070
8071 public function setAllowedUsersTimeGap($a_allowed_users_time_gap)
8072 {
8073 $this->allowedUsersTimeGap = $a_allowed_users_time_gap;
8074 }
8075
8076 public function checkMaximumAllowedUsers(): bool
8077 {
8078 global $DIC;
8079 $ilDB = $DIC['ilDB'];
8080
8081 $nr_of_users = $this->getAllowedUsers();
8082 $time_gap = ($this->getAllowedUsersTimeGap()) ? $this->getAllowedUsersTimeGap() : 60;
8083 if (($nr_of_users > 0) && ($time_gap > 0)) {
8084 $now = time();
8085 $time_border = $now - $time_gap;
8086 $str_time_border = date("YmdHis", $time_border);
8087 $query = "
8088 SELECT DISTINCT tst_times.active_fi
8089 FROM tst_times
8090 INNER JOIN tst_active
8091 ON tst_times.active_fi = tst_active.active_id
8092 AND (
8093 tst_times.pass > tst_active.last_finished_pass OR tst_active.last_finished_pass IS NULL
8094 )
8095 WHERE tst_times.tstamp > %s
8096 AND tst_active.test_fi = %s
8097 ";
8098 $result = $ilDB->queryF($query, array('integer', 'integer'), array($time_border, $this->getTestId()));
8099 if ($result->numRows() >= $nr_of_users) {
8101 $this->logAction($this->lng->txtlng("assessment", "log_could_not_enter_test_due_to_simultaneous_users", ilObjAssessmentFolder::_getLogLanguage()));
8102 }
8103 return false;
8104 } else {
8105 return true;
8106 }
8107 }
8108 return true;
8109 }
8110
8111 public function _getLastAccess($active_id)
8112 {
8113 global $DIC;
8114 $ilDB = $DIC['ilDB'];
8115
8116 $result = $ilDB->queryF(
8117 "SELECT finished FROM tst_times WHERE active_fi = %s ORDER BY finished DESC",
8118 array('integer'),
8119 array($active_id)
8120 );
8121 if ($result->numRows()) {
8122 $row = $ilDB->fetchAssoc($result);
8123 return $row["finished"];
8124 }
8125 return "";
8126 }
8127
8128 public static function lookupLastTestPassAccess($activeId, $passIndex)
8129 {
8130 global $DIC; /* @var \ILIAS\DI\Container $DIC */
8131
8132 $query = "
8133 SELECT MAX(tst_times.tstamp) as last_pass_access
8134 FROM tst_times
8135 WHERE active_fi = %s
8136 AND pass = %s
8137 ";
8138
8139 $res = $DIC->database()->queryF(
8140 $query,
8141 array('integer', 'integer'),
8142 array($activeId, $passIndex)
8143 );
8144
8145 while ($row = $DIC->database()->fetchAssoc($res)) {
8146 return $row['last_pass_access'];
8147 }
8148
8149 return null;
8150 }
8151
8159 public function isHTML($a_text): bool
8160 {
8161 if (preg_match("/<[^>]*?>/", $a_text)) {
8162 return true;
8163 } else {
8164 return false;
8165 }
8166 }
8167
8174 public function QTIMaterialToString($a_material): string
8175 {
8176 $result = "";
8177 for ($i = 0; $i < $a_material->getMaterialCount(); $i++) {
8178 $material = $a_material->getMaterial($i);
8179 if (strcmp($material["type"], "mattext") == 0) {
8180 $result .= $material["material"]->getContent();
8181 }
8182 if (strcmp($material["type"], "matimage") == 0) {
8183 $matimage = $material["material"];
8184 if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches)) {
8185 // import an mediaobject which was inserted using tiny mce
8186 if (!is_array(ilSession::get("import_mob_xhtml"))) {
8187 ilSession::set("import_mob_xhtml", array());
8188 }
8189 $import_mob_xhtml = ilSession::get("import_mob_xhtml");
8190 array_push($import_mob_xhtml, array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
8191 }
8192 }
8193 }
8194 global $DIC;
8195 $ilLog = $DIC['ilLog'];
8196 $ilLog->write(print_r(ilSession::get("import_mob_xhtml"), true));
8197 return $result;
8198 }
8199
8206 public function addQTIMaterial(&$a_xml_writer, $a_material = '')
8207 {
8208 $a_xml_writer->xmlStartTag("material");
8209 $txt = $a_material;
8210 $attrs = array(
8211 "texttype" => "text/plain"
8212 );
8213 if ($this->isHTML($a_material)) {
8214 $attrs["texttype"] = "text/xhtml";
8215 $txt = ilRTE::_replaceMediaObjectImageSrc($a_material, 0);
8216 }
8217
8218 $a_xml_writer->xmlElement("mattext", $attrs, $txt);
8219
8220 $mobs = ilObjMediaObject::_getMobsOfObject("tst:html", $this->getId());
8221 foreach ($mobs as $mob) {
8222 $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
8223 if (strpos($a_material, "mm_$mob") !== false) {
8224 if (ilObjMediaObject::_exists($mob)) {
8225 $mob_obj = new ilObjMediaObject($mob);
8226 $imgattrs = array(
8227 "label" => $moblabel,
8228 "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
8229 );
8230 }
8231 $a_xml_writer->xmlElement("matimage", $imgattrs, null);
8232 }
8233 }
8234 $a_xml_writer->xmlEndTag("material");
8235 }
8236
8243 public function prepareTextareaOutput($txt_output, $prepare_for_latex_output = false, $omitNl2BrWhenTextArea = false)
8244 {
8245 if ($txt_output == null) {
8246 $txt_output = '';
8247 }
8249 $txt_output,
8250 $prepare_for_latex_output,
8251 $omitNl2BrWhenTextArea
8252 );
8253 }
8254
8261 public function getAnonymity(): int
8262 {
8263 return ($this->anonymity) ? 1 : 0;
8264 }
8265
8272 public function setAnonymity($a_value = 0)
8273 {
8274 switch ($a_value) {
8275 case 1:
8276 $this->anonymity = 1;
8277 break;
8278 default:
8279 $this->anonymity = 0;
8280 break;
8281 }
8282 }
8283
8290 public function getShowCancel(): int
8291 {
8292 return ($this->show_cancel) ? 1 : 0;
8293 }
8294
8301 public function setShowCancel($a_value = 1)
8302 {
8303 switch ($a_value) {
8304 case 1:
8305 $this->show_cancel = 1;
8306 break;
8307 default:
8308 $this->show_cancel = 0;
8309 break;
8310 }
8311 }
8312
8319 public function getShowMarker(): int
8320 {
8321 return ($this->show_marker) ? 1 : 0;
8322 }
8323
8330 public function setShowMarker($a_value = 1)
8331 {
8332 switch ($a_value) {
8333 case 1:
8334 $this->show_marker = 1;
8335 break;
8336 default:
8337 $this->show_marker = 0;
8338 break;
8339 }
8340 }
8341
8348 public function getFixedParticipants(): int
8349 {
8350 return ($this->fixed_participants) ? 1 : 0;
8351 }
8352
8359 public function setFixedParticipants($a_value = 1)
8360 {
8361 switch ($a_value) {
8362 case 1:
8363 $this->fixed_participants = 1;
8364 break;
8365 default:
8366 $this->fixed_participants = 0;
8367 break;
8368 }
8369 }
8370
8378 public static function _lookupAnonymity($a_obj_id): int
8379 {
8380 global $DIC;
8381 $ilDB = $DIC['ilDB'];
8382
8383 $result = $ilDB->queryF(
8384 "SELECT anonymity FROM tst_tests WHERE obj_fi = %s",
8385 array('integer'),
8386 array($a_obj_id)
8387 );
8388 while ($row = $ilDB->fetchAssoc($result)) {
8389 return $row['anonymity'];
8390 }
8391 return 0;
8392 }
8393
8400 public static function lookupQuestionSetTypeByActiveId($active_id): ?string
8401 {
8402 global $DIC;
8403 $ilDB = $DIC['ilDB'];
8404
8405 $query = "
8406 SELECT tst_tests.question_set_type
8407 FROM tst_active
8408 INNER JOIN tst_tests
8409 ON tst_active.test_fi = tst_tests.test_id
8410 WHERE tst_active.active_id = %s
8411 ";
8412
8413 $res = $ilDB->queryF($query, array('integer'), array($active_id));
8414
8415 while ($row = $ilDB->fetchAssoc($res)) {
8416 return $row['question_set_type'];
8417 }
8418
8419 return null;
8420 }
8421
8430 public function _lookupRandomTestFromActiveId($active_id): int
8431 {
8432 throw new Exception(__METHOD__ . ' is deprecated ... use ilObjTest::lookupQuestionSetTypeByActiveId() instead!');
8433
8434 global $DIC;
8435 $ilDB = $DIC['ilDB'];
8436
8437 $result = $ilDB->queryF(
8438 "SELECT tst_tests.random_test FROM tst_tests, tst_active WHERE tst_active.active_id = %s AND tst_active.test_fi = tst_tests.test_id",
8439 array('integer'),
8440 array($active_id)
8441 );
8442 while ($row = $ilDB->fetchAssoc($result)) {
8443 return $row['random_test'];
8444 }
8445 return 0;
8446 }
8447
8458 public function userLookupFullName($user_id, $overwrite_anonymity = false, $sorted_order = false, $suffix = ""): string
8459 {
8460 if ($this->getAnonymity() && !$overwrite_anonymity) {
8461 return $this->lng->txt("anonymous") . $suffix;
8462 } else {
8463 $uname = ilObjUser::_lookupName($user_id);
8464 if (strlen($uname["firstname"] . $uname["lastname"]) == 0) {
8465 $uname["firstname"] = $this->lng->txt("deleted_user");
8466 }
8467 if ($sorted_order) {
8468 return trim($uname["lastname"] . ", " . $uname["firstname"]) . $suffix;
8469 } else {
8470 return trim($uname["firstname"] . " " . $uname["lastname"]) . $suffix;
8471 }
8472 }
8473 }
8474
8482 public function getStartTestLabel($active_id): string
8483 {
8484 if ($this->getNrOfTries() == 1) {
8485 return $this->lng->txt("tst_start_test");
8486 }
8487 $active_pass = self::_getPass($active_id);
8488 $res = $this->getNrOfResultsForPass($active_id, $active_pass);
8489 if ($res == 0) {
8490 if ($active_pass == 0) {
8491 return $this->lng->txt("tst_start_test");
8492 } else {
8493 return $this->lng->txt("tst_start_new_test_pass");
8494 }
8495 } else {
8496 return $this->lng->txt("tst_resume_test");
8497 }
8498 }
8499
8505 public function getAvailableDefaults(): array
8506 {
8511 global $DIC;
8512 $ilDB = $DIC['ilDB'];
8513 $ilUser = $DIC['ilUser'];
8514
8515 $result = $ilDB->queryF(
8516 "SELECT * FROM tst_test_defaults WHERE user_fi = %s ORDER BY name ASC",
8517 array('integer'),
8518 array($ilUser->getId())
8519 );
8520 $defaults = array();
8521 while ($row = $ilDB->fetchAssoc($result)) {
8522 $defaults[$row["test_defaults_id"]] = $row;
8523 }
8524 return $defaults;
8525 }
8526
8534 public function getTestDefaults($test_defaults_id): ?array
8535 {
8536 return self::_getTestDefaults($test_defaults_id);
8537 }
8538
8539 public static function _getTestDefaults($test_defaults_id)
8540 {
8541 global $DIC;
8542 $ilDB = $DIC['ilDB'];
8543
8544 $result = $ilDB->queryF(
8545 "SELECT * FROM tst_test_defaults WHERE test_defaults_id = %s",
8546 array('integer'),
8547 array($test_defaults_id)
8548 );
8549 if ($result->numRows() == 1) {
8550 $row = $ilDB->fetchAssoc($result);
8551 return $row;
8552 } else {
8553 return null;
8554 }
8555 }
8556
8563 public function deleteDefaults($test_default_id)
8564 {
8565 global $DIC;
8566 $ilDB = $DIC['ilDB'];
8567 $affectedRows = $ilDB->manipulateF(
8568 "DELETE FROM tst_test_defaults WHERE test_defaults_id = %s",
8569 array('integer'),
8570 array($test_default_id)
8571 );
8572 }
8573
8580 public function addDefaults($a_name)
8581 {
8582 global $DIC;
8583 $ilDB = $DIC['ilDB'];
8584 $ilUser = $DIC['ilUser'];
8585 $testsettings = array(
8586 "TitleOutput" => $this->getTitleOutput(),
8587 "PassScoring" => $this->getPassScoring(),
8588 "IntroEnabled" => $this->isIntroductionEnabled(),
8589 "Introduction" => $this->getIntroduction(),
8590 "FinalStatement" => $this->getFinalStatement(),
8591 "ShowInfo" => $this->getShowInfo(),
8592 "ForceJS" => $this->getForceJS(),
8593 "CustomStyle" => $this->getCustomStyle(),
8594 "ShowFinalStatement" => $this->getShowFinalStatement(),
8595 "SequenceSettings" => $this->getSequenceSettings(),
8596 "ScoreReporting" => $this->getScoreReporting(),
8597 "ScoreCutting" => $this->getScoreCutting(),
8598 'SpecificAnswerFeedback' => $this->getSpecificAnswerFeedback(),
8599 'PrintBsWithRes' => (int) $this->isBestSolutionPrintedWithResult(),
8600 "InstantFeedbackSolution" => $this->getInstantFeedbackSolution(),
8601 "AnswerFeedback" => $this->getAnswerFeedback(),
8602 "AnswerFeedbackPoints" => $this->getAnswerFeedbackPoints(),
8603 "ResultsPresentation" => $this->getResultsPresentation(),
8604 "Anonymity" => $this->getAnonymity(),
8605 "ShowCancel" => $this->getShowCancel(),
8606 "ShowMarker" => $this->getShowMarker(),
8607 "ReportingDate" => $this->getReportingDate(),
8608 "NrOfTries" => $this->getNrOfTries(),
8609 'BlockAfterPassed' => (int) $this->isBlockPassesAfterPassedEnabled(),
8610 "Shuffle" => $this->getShuffleQuestions(),
8611 "Kiosk" => $this->getKiosk(),
8612 "UsePreviousAnswers" => $this->getUsePreviousAnswers(),
8613 "ProcessingTime" => $this->getProcessingTime(),
8614 "EnableProcessingTime" => $this->getEnableProcessingTime(),
8615 "ResetProcessingTime" => $this->getResetProcessingTime(),
8616 "StartingTimeEnabled" => $this->isStartingTimeEnabled(),
8617 "StartingTime" => $this->getStartingTime(),
8618 "EndingTimeEnabled" => $this->isEndingTimeEnabled(),
8619 "EndingTime" => $this->getEndingTime(),
8620 "ECTSOutput" => $this->getECTSOutput(),
8621 "ECTSFX" => $this->getECTSFX(),
8622 "ECTSGrades" => $this->getECTSGrades(),
8623 "questionSetType" => $this->getQuestionSetType(),
8624 "CountSystem" => $this->getCountSystem(),
8625 "mailnotification" => $this->getMailNotification(),
8626 "mailnottype" => $this->getMailNotificationType(),
8627 "exportsettings" => $this->getExportSettings(),
8628 "ListOfQuestionsSettings" => $this->getListOfQuestionsSettings(),
8629 'obligations_enabled' => (int) $this->areObligationsEnabled(),
8630 'offer_question_hints' => (int) $this->isOfferingQuestionHintsEnabled(),
8631 'pass_deletion_allowed' => (int) $this->isPassDeletionAllowed(),
8632 'enable_examview' => $this->getEnableExamview(),
8633 'show_examview_html' => $this->getShowExamviewHtml(),
8634 'show_examview_pdf' => $this->getShowExamviewPdf(),
8635 'char_selector_availability' => $this->getCharSelectorAvailability(),
8636 'char_selector_definition' => $this->getCharSelectorDefinition(),
8637 'skill_service' => (int) $this->isSkillServiceEnabled(),
8638 'result_tax_filters' => $this->getResultFilterTaxIds(),
8639 'show_grading_status' => (int) $this->isShowGradingStatusEnabled(),
8640 'show_grading_mark' => (int) $this->isShowGradingMarkEnabled(),
8641
8642 'follow_qst_answer_fixation' => $this->isFollowupQuestionAnswerFixationEnabled(),
8643 'inst_fb_answer_fixation' => $this->isInstantFeedbackAnswerFixationEnabled(),
8644 'force_inst_fb' => $this->isForceInstantFeedbackEnabled(),
8645 'redirection_mode' => $this->getRedirectionMode(),
8646 'redirection_url' => $this->getRedirectionUrl(),
8647 'sign_submission' => $this->getSignSubmission(),
8648 'autosave' => (int) $this->getAutosave(),
8649 'autosave_ival' => $this->getAutosaveIval(),
8650 'examid_in_test_pass' => (int) $this->isShowExamIdInTestPassEnabled(),
8651 'examid_in_test_res' => (int) $this->isShowExamIdInTestResultsEnabled(),
8652
8653 'enable_archiving' => (int) $this->getEnableArchiving(),
8654 'password_enabled' => (int) $this->isPasswordEnabled(),
8655 'password' => (string) $this->getPassword(),
8656 'fixed_participants' => $this->getFixedParticipants(),
8657 'limit_users_enabled' => $this->isLimitUsersEnabled(),
8658 'allowedusers' => $this->getAllowedUsers(),
8659 'alloweduserstimegap' => $this->getAllowedUsersTimeGap(),
8660 'activation_limited' => $this->isActivationLimited(),
8661 'activation_start_time' => $this->getActivationStartingTime(),
8662 'activation_end_time' => $this->getActivationEndingTime(),
8663 'activation_visibility' => $this->getActivationVisibility(),
8664 'highscore_enabled' => $this->getHighscoreEnabled(),
8665 'highscore_anon' => $this->getHighscoreAnon(),
8666 'highscore_achieved_ts' => $this->getHighscoreAchievedTS(),
8667 'highscore_score' => $this->getHighscoreScore(),
8668 'highscore_percentage' => $this->getHighscorePercentage(),
8669 'highscore_hints' => $this->getHighscoreHints(),
8670 'highscore_wtime' => $this->getHighscoreWTime(),
8671 'highscore_own_table' => $this->getHighscoreOwnTable(),
8672 'highscore_top_table' => $this->getHighscoreTopTable(),
8673 'highscore_top_num' => $this->getHighscoreTopNum(),
8674 'use_previous_answers' => (string) $this->getUsePreviousAnswers(),
8675 'pass_waiting' => $this->getPassWaiting()
8676 );
8677
8678 $next_id = $ilDB->nextId('tst_test_defaults');
8679 $ilDB->insert(
8680 'tst_test_defaults',
8681 array(
8682 'test_defaults_id' => array('integer', $next_id),
8683 'name' => array('text', $a_name),
8684 'user_fi' => array('integer', $ilUser->getId()),
8685 'defaults' => array('clob', serialize($testsettings)),
8686 'marks' => array('clob', serialize($this->mark_schema)),
8687 'tstamp' => array('integer', time())
8688 )
8689 );
8690 }
8691
8699 public function applyDefaults($test_defaults): bool
8700 {
8701 $testsettings = unserialize($test_defaults["defaults"]);
8702 $this->mark_schema = unserialize($test_defaults["marks"]);
8703
8704 if (array_key_exists('TitleOutput', $testsettings)) {
8705 $this->setTitleOutput($testsettings['TitleOutput']);
8706 }
8707
8708 if (array_key_exists('IntroEnabled', $testsettings)) {
8709 $this->setIntroductionEnabled($testsettings['IntroEnabled']);
8710 }
8711
8712 if (array_key_exists('Introduction', $testsettings)) {
8713 $this->setIntroduction($testsettings['Introduction'] ?? '');
8714 }
8715
8716 if (array_key_exists('FinalStatement', $testsettings)) {
8717 $this->setFinalStatement($testsettings['FinalStatement'] ?? '');
8718 }
8719
8720 if (array_key_exists('ShowInfo', $testsettings)) {
8721 $this->setShowInfo($testsettings['ShowInfo']);
8722 }
8723
8724 if (array_key_exists('ForceJS', $testsettings)) {
8725 $this->setForceJS($testsettings['ForceJS']);
8726 }
8727
8728 if (array_key_exists('CustomStyle', $testsettings)) {
8729 $this->setCustomStyle($testsettings['CustomStyle']);
8730 }
8731
8732 if (array_key_exists('ShowFinalStatement', $testsettings)) {
8733 $this->setShowFinalStatement($testsettings['ShowFinalStatement']);
8734 }
8735
8736 if (array_key_exists('SequenceSettings', $testsettings)) {
8737 $this->setSequenceSettings($testsettings['SequenceSettings']);
8738 }
8739
8740 if (array_key_exists('SpecificAnswerFeedback', $testsettings)) {
8741 $this->setSpecificAnswerFeedback($testsettings['SpecificAnswerFeedback']);
8742 }
8743
8744 if (array_key_exists('InstantFeedbackSolution', $testsettings)) {
8745 $this->setInstantFeedbackSolution($testsettings['InstantFeedbackSolution']);
8746 }
8747
8748 if (array_key_exists('AnswerFeedback', $testsettings)) {
8749 $this->setAnswerFeedback($testsettings['AnswerFeedback']);
8750 }
8751
8752 if (array_key_exists('AnswerFeedbackPoints', $testsettings)) {
8753 $this->setAnswerFeedbackPoints($testsettings['AnswerFeedbackPoints']);
8754 }
8755
8756 if (array_key_exists('Anonymity', $testsettings)) {
8757 $this->setAnonymity($testsettings['Anonymity']);
8758 }
8759
8760 if (array_key_exists('ShowCancel', $testsettings)) {
8761 $this->setShowCancel($testsettings['ShowCancel']);
8762 }
8763
8764 if (array_key_exists('Shuffle', $testsettings)) {
8765 $this->setShuffleQuestions($testsettings['Shuffle']);
8766 }
8767
8768 if (array_key_exists('ShowMarker', $testsettings)) {
8769 $this->setShowMarker($testsettings['ShowMarker']);
8770 }
8771
8772 if (array_key_exists('ReportingDate', $testsettings)) {
8773 $this->setReportingDate($testsettings['ReportingDate']);
8774 }
8775
8776 if (array_key_exists('NrOfTries', $testsettings)) {
8777 $this->setNrOfTries($testsettings['NrOfTries']);
8778 }
8779
8780 if (array_key_exists('BlockAfterPassed', $testsettings)) {
8781 $this->setBlockPassesAfterPassedEnabled($testsettings['BlockAfterPassed']);
8782 }
8783
8784 if (array_key_exists('UsePreviousAnswers', $testsettings)) {
8785 $this->setUsePreviousAnswers($testsettings['UsePreviousAnswers']);
8786 }
8787
8788 if (array_key_exists('redirection_mode', $testsettings)) {
8789 $this->setRedirectionMode($testsettings['redirection_mode']);
8790 }
8791
8792 if (array_key_exists('redirection_url', $testsettings)) {
8793 $this->setRedirectionUrl($testsettings['redirection_url']);
8794 }
8795
8796 if (array_key_exists('ProcessingTime', $testsettings)) {
8797 $this->setProcessingTime($testsettings['ProcessingTime']);
8798 }
8799
8800 if (array_key_exists('ResetProcessingTime', $testsettings)) {
8801 $this->setResetProcessingTime($testsettings['ResetProcessingTime']);
8802 }
8803
8804 if (array_key_exists('EnableProcessingTime', $testsettings)) {
8805 $this->setEnableProcessingTime($testsettings['EnableProcessingTime']);
8806 }
8807
8808 if (array_key_exists('StartingTimeEnabled', $testsettings)) {
8809 $this->setStartingTimeEnabled($testsettings['StartingTimeEnabled']);
8810 }
8811
8812 if (array_key_exists('StartingTime', $testsettings)) {
8813 $this->setStartingTime($testsettings['StartingTime']);
8814 }
8815
8816 if (array_key_exists('Kiosk', $testsettings)) {
8817 $this->setKiosk($testsettings['Kiosk']);
8818 }
8819
8820 if (array_key_exists('EndingTimeEnabled', $testsettings)) {
8821 $this->setEndingTimeEnabled($testsettings['EndingTimeEnabled']);
8822 }
8823
8824 if (array_key_exists('EndingTime', $testsettings)) {
8825 $this->setEndingTime($testsettings['EndingTime']);
8826 }
8827
8828 if (array_key_exists('ECTSOutput', $testsettings)) {
8829 $this->setECTSOutput($testsettings['ECTSOutput']);
8830 }
8831
8832 if (array_key_exists('ECTSFX', $testsettings)) {
8833 $this->setECTSFX($testsettings['ECTSFX']);
8834 }
8835
8836 if (array_key_exists('ECTSGrades', $testsettings)) {
8837 $this->setECTSGrades($testsettings['ECTSGrades']);
8838 }
8839
8840 if (isset($testsettings["isRandomTest"])) {
8841 if ($testsettings["isRandomTest"]) {
8842 $this->setQuestionSetType(self::QUESTION_SET_TYPE_RANDOM);
8843 } else {
8844 $this->setQuestionSetType(self::QUESTION_SET_TYPE_FIXED);
8845 }
8846 } elseif (isset($testsettings["questionSetType"])) {
8847 $this->setQuestionSetType($testsettings["questionSetType"]);
8848 }
8849
8850 if (array_key_exists('mailnotification', $testsettings)) {
8851 $this->setMailNotification($testsettings['mailnotification']);
8852 }
8853
8854 if (array_key_exists('mailnottype', $testsettings)) {
8855 $this->setMailNotificationType($testsettings['mailnottype']);
8856 }
8857
8858 if (array_key_exists('exportsettings', $testsettings)) {
8859 $this->setExportSettings($testsettings['exportsettings']);
8860 }
8861
8862 if (array_key_exists('ListOfQuestionsSettings', $testsettings)) {
8863 $this->setListOfQuestionsSettings($testsettings['ListOfQuestionsSettings']);
8864 }
8865
8866 if (array_key_exists('obligations_enabled', $testsettings)) {
8867 $this->setObligationsEnabled($testsettings['obligations_enabled']);
8868 }
8869
8870 if (array_key_exists('offer_question_hints', $testsettings)) {
8871 $this->setOfferingQuestionHintsEnabled($testsettings['offer_question_hints']);
8872 }
8873
8874 if (isset($testsettings['examid_in_kiosk'])) {
8875 $this->setShowExamIdInTestPassEnabled($testsettings['examid_in_kiosk']);
8876 } else {
8877 $this->setShowExamIdInTestPassEnabled($testsettings['examid_in_test_pass']);
8878 }
8879
8880 if (array_key_exists('enable_examview', $testsettings)) {
8881 $this->setEnableExamview($testsettings['enable_examview']);
8882 }
8883
8884 if (array_key_exists('show_examview_html', $testsettings)) {
8885 $this->setShowExamviewHtml($testsettings['show_examview_html']);
8886 }
8887
8888 if (array_key_exists('show_examview_pdf', $testsettings)) {
8889 $this->setShowExamviewPdf($testsettings['show_examview_pdf']);
8890 }
8891
8892 if (array_key_exists('enable_archiving', $testsettings)) {
8893 $this->setEnableArchiving($testsettings['enable_archiving']);
8894 }
8895
8896 if (array_key_exists('sign_submission', $testsettings)) {
8897 $this->setSignSubmission($testsettings['sign_submission']);
8898 }
8899
8900 if (array_key_exists('char_selector_availability', $testsettings)) {
8901 $this->setCharSelectorAvailability($testsettings['char_selector_availability']);
8902 }
8903
8904 if (array_key_exists('char_selector_definition', $testsettings)) {
8905 $this->setCharSelectorDefinition($testsettings['char_selector_definition']);
8906 }
8907
8908 if (array_key_exists('skill_service', $testsettings)) {
8909 $this->setSkillServiceEnabled((bool) $testsettings['skill_service']);
8910 }
8911
8912 if (array_key_exists('show_grading_status', $testsettings)) {
8913 $this->setShowGradingStatusEnabled((bool) $testsettings['show_grading_status']);
8914 }
8915
8916 if (array_key_exists('show_grading_mark', $testsettings)) {
8917 $this->setShowGradingMarkEnabled((bool) $testsettings['show_grading_mark']);
8918 }
8919
8920 if (array_key_exists('follow_qst_answer_fixation', $testsettings)) {
8921 $this->setFollowupQuestionAnswerFixationEnabled($testsettings['follow_qst_answer_fixation']);
8922 }
8923
8924 if (array_key_exists('inst_fb_answer_fixation', $testsettings)) {
8925 $this->setInstantFeedbackAnswerFixationEnabled($testsettings['inst_fb_answer_fixation']);
8926 }
8927
8928 if (array_key_exists('force_inst_fb', $testsettings)) {
8929 $this->setForceInstantFeedbackEnabled($testsettings['force_inst_fb']);
8930 }
8931
8932 if (array_key_exists('redirection_mode', $testsettings)) {
8933 $this->setRedirectionMode($testsettings['redirection_mode']);
8934 }
8935
8936 if (array_key_exists('redirection_url', $testsettings)) {
8937 $this->setRedirectionUrl($testsettings['redirection_url']);
8938 }
8939
8940 if (array_key_exists('autosave', $testsettings)) {
8941 $this->setAutosave($testsettings['autosave']);
8942 }
8943
8944 if (array_key_exists('autosave_ival', $testsettings)) {
8945 $this->setAutosaveIval($testsettings['autosave_ival']);
8946 }
8947
8948 if (array_key_exists('password_enabled', $testsettings)) {
8949 $this->setPasswordEnabled($testsettings['password_enabled']);
8950 }
8951
8952 if (array_key_exists('password', $testsettings)) {
8953 $this->setPassword($testsettings['password']);
8954 }
8955
8956 if (array_key_exists('fixed_participants', $testsettings)) {
8957 $this->setFixedParticipants($testsettings['fixed_participants']);
8958 }
8959
8960 if (array_key_exists('limit_users_enabled', $testsettings)) {
8961 $this->setLimitUsersEnabled($testsettings['limit_users_enabled']);
8962 }
8963
8964 if (array_key_exists('allowedusers', $testsettings)) {
8965 $this->setAllowedUsers($testsettings['allowedusers']);
8966 }
8967
8968 if (array_key_exists('alloweduserstimegap', $testsettings)) {
8969 $this->setAllowedUsersTimeGap($testsettings['alloweduserstimegap']);
8970 }
8971
8972 if (array_key_exists('use_previous_answers', $testsettings)) {
8973 $this->setUsePreviousAnswers($testsettings['use_previous_answers']);
8974 }
8975
8976 if (array_key_exists('activation_limited', $testsettings)) {
8977 $this->setActivationLimited($testsettings['activation_limited']);
8978 }
8979
8980 if (array_key_exists('activation_start_time', $testsettings)) {
8981 $this->setActivationStartingTime($testsettings['activation_start_time']);
8982 }
8983
8984 if (array_key_exists('activation_end_time', $testsettings)) {
8985 $this->setActivationEndingTime($testsettings['activation_end_time']);
8986 }
8987
8988 if (array_key_exists('activation_visibility', $testsettings)) {
8989 $this->setActivationVisibility($testsettings['activation_visibility']);
8990 }
8991
8992 if (array_key_exists('pass_waiting', $testsettings)) {
8993 $this->setPassWaiting($testsettings['pass_waiting']);
8994 }
8995
8996 $settings = $this->getScoreSettings();
8997 $exam_id_in_results = false;
8998 if (array_key_exists('show_exam_id', $testsettings)) {
8999 $exam_id_in_results = (bool) $testsettings['show_exam_id'];
9000 } elseif (array_key_exists('examid_in_test_res', $testsettings)) {
9001 $exam_id_in_results = (bool) $testsettings['examid_in_test_res'];
9002 }
9003
9005 ->withScoringSettings(
9006 $settings->getScoringSettings()
9007 ->withPassScoring((bool) $testsettings["PassScoring"])
9008 ->withScoreCutting((bool) $testsettings['ScoreCutting'])
9009 ->withCountSystem((bool) $testsettings["CountSystem"])
9010 )
9011 ->withResultSummarySettings(
9012 $settings->getResultSummarySettings()
9013 ->withPassDeletionAllowed($testsettings['pass_deletion_allowed'])
9014 )
9015 ->withResultDetailsSettings(
9016 $settings->getResultDetailsSettings()
9017 ->withPrintBestSolutionWithResult((bool) $testsettings['PrintBsWithRes'])
9018 ->withShowExamIdInTestResults($exam_id_in_results)
9019 ->withTaxonomyFilterIds((array) $testsettings['result_tax_filters'])
9020 )
9021 ->withGamificationSettings(
9022 $settings->getGamificationSettings()
9023 ->withHighscoreEnabled($testsettings['highscore_enabled'])
9024 ->withHighscoreAnon($testsettings['highscore_anon'])
9025 ->withHighscoreAchievedTS($testsettings['highscore_achieved_ts'])
9026 ->withHighscoreScore($testsettings['highscore_score'])
9027 ->withHighscorePercentage($testsettings['highscore_percentage'])
9028 ->withHighscoreHints($testsettings['highscore_hints'])
9029 ->withHighscoreWTime($testsettings['highscore_wtime'])
9030 ->withHighscoreOwnTable($testsettings['highscore_own_table'])
9031 ->withHighscoreTopTable($testsettings['highscore_top_table'])
9032 ->withHighscoreTopNum($testsettings['highscore_top_num'])
9033 )
9034 ;
9035 $this->getScoreSettingsRepository()->store($settings);
9036 $this->saveToDb();
9037
9038 return true;
9039 }
9040
9048 public function processPrintoutput2FO($print_output): string
9049 {
9050 if (extension_loaded("tidy")) {
9051 $config = array(
9052 "indent" => false,
9053 "output-xml" => true,
9054 "numeric-entities" => true
9055 );
9056 $tidy = new tidy();
9057 $tidy->parseString($print_output, $config, 'utf8');
9058 $tidy->cleanRepair();
9059 $print_output = tidy_get_output($tidy);
9060 $print_output = preg_replace("/^.*?(<html)/", "\\1", $print_output);
9061 } else {
9062 $print_output = str_replace("&nbsp;", "&#160;", $print_output);
9063 $print_output = str_replace("&otimes;", "X", $print_output);
9064 }
9065 $xsl = file_get_contents("./Modules/Test/xml/question2fo.xsl");
9066
9067 // additional font support
9068 global $DIC;
9069 $xsl = str_replace(
9070 'font-family="Helvetica, unifont"',
9071 'font-family="' . $DIC['ilSetting']->get('rpc_pdf_font', 'Helvetica, unifont') . '"',
9072 $xsl
9073 );
9074
9075 $args = array( '/_xml' => $print_output, '/_xsl' => $xsl );
9076 $xh = xslt_create();
9077 $params = array();
9078 $output = xslt_process($xh, "arg:/_xml", "arg:/_xsl", null, $args, $params);
9079 xslt_error($xh);
9080 xslt_free($xh);
9081 return $output;
9082 }
9083
9090 public function deliverPDFfromHTML($content, $title = null)
9091 {
9092 $content = preg_replace("/href=\".*?\"/", "", $content);
9093 $printbody = new ilTemplate("tpl.il_as_tst_print_body.html", true, true, "Modules/Test");
9094 $printbody->setVariable("TITLE", ilLegacyFormElementsUtil::prepareFormOutput($this->getTitle()));
9095 $printbody->setVariable("ADM_CONTENT", $content);
9096 $printbody->setCurrentBlock("css_file");
9097 $printbody->setVariable("CSS_FILE", ilUtil::getStyleSheetLocation("filesystem", "delos.css"));
9098 $printbody->parseCurrentBlock();
9099 $printoutput = $printbody->get();
9100 $html = str_replace("href=\"./", "href=\"" . ILIAS_HTTP_PATH . "/", $printoutput);
9101 $html = preg_replace("/<div id=\"dontprint\">.*?<\\/div>/ims", "", $html);
9102 if (extension_loaded("tidy")) {
9103 $config = array(
9104 "indent" => false,
9105 "output-xml" => true,
9106 "numeric-entities" => true
9107 );
9108 $tidy = new tidy();
9109 $tidy->parseString($html, $config, 'utf8');
9110 $tidy->cleanRepair();
9111 $html = tidy_get_output($tidy);
9112 $html = preg_replace("/^.*?(<html)/", "\\1", $html);
9113 } else {
9114 $html = str_replace("&nbsp;", "&#160;", $html);
9115 $html = str_replace("&otimes;", "X", $html);
9116 }
9117 $html = preg_replace("/src=\".\\//ims", "src=\"" . ILIAS_HTTP_PATH . "/", $html);
9118 $this->deliverPDFfromFO($this->processPrintoutput2FO($html), $title);
9119 }
9120
9126 public function deliverPDFfromFO($fo, $title = null): bool
9127 {
9128 global $DIC;
9129 $ilLog = $DIC['ilLog'];
9130
9131 $fo_file = ilFileUtils::ilTempnam() . ".fo";
9132 $fp = fopen($fo_file, "w");
9133 fwrite($fp, $fo);
9134 fclose($fp);
9135
9136 try {
9137 $pdf_base64 = ilRpcClientFactory::factory('RPCTransformationHandler')->ilFO2PDF($fo);
9138 $filename = (strlen($title)) ? $title : $this->getTitle();
9141 $pdf_base64->scalar,
9143 "application/pdf"
9144 );
9145 return true;
9146 } catch (Exception $e) {
9147 $ilLog->write(__METHOD__ . ': ' . $e->getMessage());
9148 return false;
9149 }
9150 }
9151
9161 public static function getManualFeedback($active_id, $question_id, $pass): string
9162 {
9163 $feedback = "";
9164 $row = self::getSingleManualFeedback((int) $active_id, (int) $question_id, (int) $pass);
9165
9166 if ($row !== [] && ($row['finalized_evaluation'] || \ilTestService::isManScoringDone($active_id))) {
9167 $feedback = $row['feedback'] ?? '';
9168 }
9169
9170 return $feedback;
9171 }
9172
9173 public static function getSingleManualFeedback(int $active_id, int $question_id, int $pass): array
9174 {
9175 global $DIC;
9176
9177 $ilDB = $DIC->database();
9178 $row = [];
9179 $result = $ilDB->queryF(
9180 "SELECT * FROM tst_manual_fb WHERE active_fi = %s AND question_fi = %s AND pass = %s",
9181 ['integer', 'integer', 'integer'],
9182 [$active_id, $question_id, $pass]
9183 );
9184
9185 if ($ilDB->numRows($result) === 1) {
9186 $row = $ilDB->fetchAssoc($result);
9187 $row['feedback'] = ilRTE::_replaceMediaObjectImageSrc($row['feedback'] ?? '', 1);
9188 } elseif ($ilDB->numRows($result) > 1) {
9189 $DIC->logger()->root()->warning(
9190 "WARNING: Multiple feedback entries on tst_manual_fb for " .
9191 "active_fi = $active_id , question_fi = $question_id and pass = $pass"
9192 );
9193 }
9194
9195 return $row;
9196 }
9197
9205 public static function getCompleteManualFeedback(int $question_id): array
9206 {
9207 global $DIC;
9208
9209 $ilDB = $DIC->database();
9210 $feedback = array();
9211 $result = $ilDB->queryF(
9212 "SELECT * FROM tst_manual_fb WHERE question_fi = %s",
9213 array('integer'),
9214 array($question_id)
9215 );
9216
9217 while ($row = $ilDB->fetchAssoc($result)) {
9218 $active = $row['active_fi'];
9219 $pass = $row['pass'];
9220 $question = $row['question_fi'];
9221
9222 $row['feedback'] = ilRTE::_replaceMediaObjectImageSrc($row['feedback'] ?? '', 1);
9223
9224 $feedback[$active][$pass][$question] = $row;
9225 }
9226
9227 return $feedback;
9228 }
9229
9230 public function saveManualFeedback(
9231 int $active_id,
9232 int $question_id,
9233 int $pass,
9234 ?string $feedback,
9235 bool $finalized = false,
9236 bool $is_single_feedback = false
9237 ): bool {
9238 global $DIC;
9239
9240 $feedback_old = self::getSingleManualFeedback($active_id, $question_id, $pass);
9241
9242 $finalized_record = (int) ($feedback_old['finalized_evaluation'] ?? 0);
9243 if ($finalized_record === 0 || ($is_single_feedback && $finalized_record === 1)) {
9244 $DIC->database()->manipulateF(
9245 "DELETE FROM tst_manual_fb WHERE active_fi = %s AND question_fi = %s AND pass = %s",
9246 ['integer', 'integer', 'integer'],
9247 [$active_id, $question_id, $pass]
9248 );
9249
9250 $this->insertManualFeedback($active_id, $question_id, $pass, $feedback, $finalized, $feedback_old);
9251
9253 $this->logManualFeedback($active_id, $question_id, $feedback);
9254 }
9255 }
9256
9257 return true;
9258 }
9259
9260 private function insertManualFeedback(
9261 int $active_id,
9262 int $question_id,
9263 int $pass,
9264 ?string $feedback,
9265 bool $finalized,
9266 array $feedback_old
9267 ): void {
9268 global $DIC;
9269
9270 $ilDB = $DIC->database();
9271 $ilUser = $DIC->user();
9272 $next_id = $ilDB->nextId('tst_manual_fb');
9273 $user = $ilUser->getId();
9274 $finalized_time = time();
9275
9276 $update_default = [
9277 'manual_feedback_id' => [ 'integer', $next_id],
9278 'active_fi' => [ 'integer', $active_id],
9279 'question_fi' => [ 'integer', $question_id],
9280 'pass' => [ 'integer', $pass],
9281 'feedback' => [ 'clob', $feedback ? ilRTE::_replaceMediaObjectImageSrc($feedback, 0) : null],
9282 'tstamp' => [ 'integer', time()]
9283 ];
9284
9285 if ($feedback_old !== [] && (int) $feedback_old['finalized_evaluation'] === 1) {
9286 $user = $feedback_old['finalized_by_usr_id'];
9287 $finalized_time = $feedback_old['finalized_tstamp'];
9288 }
9289
9290 if ($finalized === false) {
9291 $update_default['finalized_evaluation'] = ['integer', 0];
9292 $update_default['finalized_by_usr_id'] = ['integer', 0];
9293 $update_default['finalized_tstamp'] = ['integer', 0];
9294 } elseif ($finalized === true) {
9295 $update_default['finalized_evaluation'] = ['integer', 1];
9296 $update_default['finalized_by_usr_id'] = ['integer', $user];
9297 $update_default['finalized_tstamp'] = ['integer', $finalized_time];
9298 }
9299
9300 $ilDB->insert('tst_manual_fb', $update_default);
9301 }
9302
9310 private function logManualFeedback($active_id, $question_id, $feedback)
9311 {
9312 global $DIC;
9313
9314 $ilUser = $DIC->user();
9315 $lng = $DIC->language();
9316 $username = ilObjTestAccess::_getParticipantData($active_id);
9317
9318 $this->logAction(
9319 sprintf(
9320 $lng->txtlng('assessment', 'log_manual_feedback', ilObjAssessmentFolder::_getLogLanguage()),
9321 $ilUser->getFullname() . ' (' . $ilUser->getLogin() . ')',
9322 $username,
9323 assQuestion::_getQuestionTitle($question_id),
9324 $feedback
9325 )
9326 );
9327 }
9328
9336 public function getJavaScriptOutput(): bool
9337 {
9338 return true;
9339 }
9340
9341 public function &createTestSequence($active_id, $pass, $shuffle)
9342 {
9343 $this->testSequence = new ilTestSequence($active_id, $pass, $this->isRandomTest());
9344 }
9345
9351 public function setTestId($a_id)
9352 {
9353 $this->test_id = $a_id;
9354 }
9355
9364 public function getDetailedTestResults($participants): array
9365 {
9366 $results = array();
9367 if (count($participants)) {
9368 foreach ($participants as $active_id => $user_rec) {
9369 $row = array();
9370 $reached_points = 0;
9371 $max_points = 0;
9372 $pass = ilObjTest::_getResultPass($active_id);
9373 foreach ($this->questions as $value) {
9374 $question = ilObjTest::_instanciateQuestion($value);
9375 if (is_object($question)) {
9376 $max_points += $question->getMaximumPoints();
9377 $reached_points += $question->getReachedPoints($active_id, $pass);
9378 if ($max_points > 0) {
9379 $percentvalue = $reached_points / $max_points;
9380 if ($percentvalue < 0) {
9381 $percentvalue = 0.0;
9382 }
9383 } else {
9384 $percentvalue = 0;
9385 }
9386 if ($this->getAnonymity()) {
9387 $user_rec['firstname'] = "";
9388 $user_rec['lastname'] = $this->lng->txt("anonymous");
9389 }
9390 $row = array(
9391 "user_id" => $user_rec['usr_id'],
9392 "matriculation" => $user_rec['matriculation'],
9393 "lastname" => $user_rec['lastname'],
9394 "firstname" => $user_rec['firstname'],
9395 "login" => $user_rec['login'],
9396 "question_id" => $question->getId(),
9397 "question_title" => $question->getTitle(),
9398 "reached_points" => $reached_points,
9399 "max_points" => $max_points,
9400 "passed" => $user_rec['passed'] ? '1' : '0',
9401 );
9402 $results[] = $row;
9403 }
9404 }
9405 }
9406 }
9407 return $results;
9408 }
9409
9413 public static function _lookupTestObjIdForQuestionId($a_q_id)
9414 {
9415 global $DIC;
9416 $ilDB = $DIC['ilDB'];
9417
9418 $result = $ilDB->queryF(
9419 "SELECT t.obj_fi obj_id FROM tst_test_question q, tst_tests t WHERE q.test_fi = t.test_id AND q.question_fi = %s",
9420 array('integer'),
9421 array($a_q_id)
9422 );
9423 $rec = $ilDB->fetchAssoc($result);
9424 return $rec["obj_id"] ?? null;
9425 }
9426
9433 public function isPluginActive($a_pname): bool
9434 {
9435 global $DIC;
9436 $component_repository = $DIC['component.repository'];
9437
9438 if (!$component_repository->getComponentByTypeAndName(
9440 'TestQuestionPool'
9441 )->getPluginSlotById('qst')->hasPluginName($a_pname)) {
9442 return false;
9443 }
9444
9445 return $component_repository
9446 ->getComponentByTypeAndName(
9448 'TestQuestionPool'
9449 )
9450 ->getPluginSlotById(
9451 'qst'
9452 )
9453 ->getPluginByName(
9454 $a_pname
9455 )->isActive();
9456 }
9457
9458 public function getPassed($active_id)
9459 {
9460 global $DIC;
9461 $ilDB = $DIC['ilDB'];
9462
9463 $result = $ilDB->queryF(
9464 "SELECT passed FROM tst_result_cache WHERE active_fi = %s",
9465 array('integer'),
9466 array($active_id)
9467 );
9468 if ($result->numRows()) {
9469 $row = $ilDB->fetchAssoc($result);
9470 return $row['passed'];
9471 } else {
9472 $counted_pass = ilObjTest::_getResultPass($active_id);
9473 $result_array = &$this->getTestResult($active_id, $counted_pass);
9474 return $result_array["test"]["passed"];
9475 }
9476 }
9477
9481 public function getParticipantsForTestAndQuestion($test_id, $question_id): array
9482 {
9484 global $DIC;
9485 $ilDB = $DIC['ilDB'];
9486
9487 $query = "
9488 SELECT tst_test_result.active_fi, tst_test_result.question_fi, tst_test_result.pass
9489 FROM tst_test_result
9490 INNER JOIN tst_active ON tst_active.active_id = tst_test_result.active_fi AND tst_active.test_fi = %s
9491 INNER JOIN qpl_questions ON qpl_questions.question_id = tst_test_result.question_fi
9492 LEFT JOIN usr_data ON usr_data.usr_id = tst_active.user_fi
9493 WHERE tst_test_result.question_fi = %s
9494 ORDER BY usr_data.lastname ASC, usr_data.firstname ASC
9495 ";
9496
9497 $result = $ilDB->queryF(
9498 $query,
9499 array('integer', 'integer'),
9500 array($test_id, $question_id)
9501 );
9502 $foundusers = array();
9504 while ($row = $ilDB->fetchAssoc($result)) {
9505 if ($this->getAccessFilteredParticipantList() && !$this->getAccessFilteredParticipantList()->isActiveIdInList($row["active_fi"])) {
9506 continue;
9507 }
9508
9509 if (!array_key_exists($row["active_fi"], $foundusers)) {
9510 $foundusers[$row["active_fi"]] = array();
9511 }
9512 array_push($foundusers[$row["active_fi"]], array("pass" => $row["pass"], "qid" => $row["question_fi"]));
9513 }
9514 return $foundusers;
9515 }
9516
9522 public function getAggregatedResultsData(): array
9523 {
9524 $data = &$this->getCompleteEvaluationData();
9525 $foundParticipants = $data->getParticipants();
9526 $results = array("overview" => array(), "questions" => array());
9527 if (count($foundParticipants)) {
9528 $results["overview"][$this->lng->txt("tst_eval_total_persons")] = count($foundParticipants);
9529 $total_finished = $data->getTotalFinishedParticipants();
9530 $results["overview"][$this->lng->txt("tst_eval_total_finished")] = $total_finished;
9531 $average_time = $this->evalTotalStartedAverageTime($data->getParticipantIds());
9532 $diff_seconds = $average_time;
9533 $diff_hours = floor($diff_seconds / 3600);
9534 $diff_seconds -= $diff_hours * 3600;
9535 $diff_minutes = floor($diff_seconds / 60);
9536 $diff_seconds -= $diff_minutes * 60;
9537 $results["overview"][$this->lng->txt("tst_eval_total_finished_average_time")] = sprintf("%02d:%02d:%02d", $diff_hours, $diff_minutes, $diff_seconds);
9538 $total_passed = 0;
9539 $total_passed_reached = 0;
9540 $total_passed_max = 0;
9541 $total_passed_time = 0;
9542 foreach ($foundParticipants as $userdata) {
9543 if ($userdata->getPassed()) {
9544 $total_passed++;
9545 $total_passed_reached += $userdata->getReached();
9546 $total_passed_max += $userdata->getMaxpoints();
9547 $total_passed_time += $userdata->getTimeOfWork();
9548 }
9549 }
9550 $average_passed_reached = $total_passed ? $total_passed_reached / $total_passed : 0;
9551 $average_passed_max = $total_passed ? $total_passed_max / $total_passed : 0;
9552 $average_passed_time = $total_passed ? $total_passed_time / $total_passed : 0;
9553 $results["overview"][$this->lng->txt("tst_eval_total_passed")] = $total_passed;
9554 $results["overview"][$this->lng->txt("tst_eval_total_passed_average_points")] = sprintf("%2.2f", $average_passed_reached) . " " . strtolower($this->lng->txt("of")) . " " . sprintf("%2.2f", $average_passed_max);
9555 $average_time = $average_passed_time;
9556 $diff_seconds = $average_time;
9557 $diff_hours = floor($diff_seconds / 3600);
9558 $diff_seconds -= $diff_hours * 3600;
9559 $diff_minutes = floor($diff_seconds / 60);
9560 $diff_seconds -= $diff_minutes * 60;
9561 $results["overview"][$this->lng->txt("tst_eval_total_passed_average_time")] = sprintf("%02d:%02d:%02d", $diff_hours, $diff_minutes, $diff_seconds);
9562 }
9563
9564 foreach ($data->getQuestionTitles() as $question_id => $question_title) {
9565 $answered = 0;
9566 $reached = 0;
9567 $max = 0;
9568 foreach ($foundParticipants as $userdata) {
9569 for ($i = 0; $i <= $userdata->getLastPass(); $i++) {
9570 if (is_object($userdata->getPass($i))) {
9571 $question = $userdata->getPass($i)->getAnsweredQuestionByQuestionId($question_id);
9572 if (is_array($question)) {
9573 $answered++;
9574 $reached += $question["reached"];
9575 $max += $question["points"];
9576 }
9577 }
9578 }
9579 }
9580 $percent = $max ? $reached / $max * 100.0 : 0;
9581 $results["questions"][$question_id] = array(
9582 $question_title,
9583 sprintf("%.2f", $answered ? $reached / $answered : 0) . " " . strtolower($this->lng->txt("of")) . " " . sprintf("%.2f", $answered ? $max / $answered : 0),
9584 sprintf("%.2f", $percent) . "%",
9585 $answered,
9586 sprintf("%.2f", $answered ? $reached / $answered : 0),
9587 sprintf("%.2f", $answered ? $max / $answered : 0),
9588 $percent / 100.0
9589 );
9590 }
9591 return $results;
9592 }
9593
9597 public function getXMLZip(): string
9598 {
9599 $expFactory = new ilTestExportFactory($this);
9600 $test_exp = $expFactory->getExporter('xml');
9601 return $test_exp->buildExportFile();
9602 }
9603
9604 public function getMailNotification(): int
9605 {
9606 return $this->mailnotification;
9607 }
9608
9609 public function setMailNotification($a_notification)
9610 {
9611 $this->mailnotification = $a_notification;
9612 }
9613
9614 public function sendSimpleNotification($active_id)
9615 {
9616 $mail = new ilTestMailNotification();
9617 $owner_id = $this->getOwner();
9618 $usr_data = $this->userLookupFullName(ilObjTest::_getUserIdFromActiveId($active_id));
9619 $mail->sendSimpleNotification($owner_id, $this->getTitle(), $usr_data);
9620 }
9621
9627 public function getEvaluationAdditionalFields(): array
9628 {
9629 $table_gui = new ilEvaluationAllTableGUI(new ilObjTestGUI($this->getRefId()), 'outEvaluation', $this->getAnonymity());
9630 return $table_gui->getSelectedColumns();
9631 }
9632
9633 public function sendAdvancedNotification($active_id)
9634 {
9635 $mail = new ilTestMailNotification();
9636 $owner_id = $this->getOwner();
9637 $usr_data = $this->userLookupFullName(ilObjTest::_getUserIdFromActiveId($active_id));
9638
9639 $participantList = new ilTestParticipantList($this);
9640 $participantList->initializeFromDbRows($this->getTestParticipants());
9641
9642 $expFactory = new ilTestExportFactory($this);
9643 $exportObj = $expFactory->getExporter('results');
9644 $exportObj->setForcedAccessFilteredParticipantList($participantList);
9645 $file = $exportObj->exportToExcel($deliver = false, 'active_id', $active_id, $passedonly = false);
9647 $fd->copyAttachmentFile($file, "result_" . $active_id . ".xlsx");
9648 $file_names[] = "result_" . $active_id . ".xlsx";
9649
9650 $mail->sendAdvancedNotification($owner_id, $this->getTitle(), $usr_data, $file_names);
9651
9652 if (count($file_names)) {
9653 $fd->unlinkFiles($file_names);
9654 unset($fd);
9655 @unlink($file);
9656 }
9657 }
9658
9659 public function getResultsForActiveId($active_id)
9660 {
9661 global $DIC;
9662 $ilDB = $DIC['ilDB'];
9663
9664 $query = "
9665 SELECT *
9666 FROM tst_result_cache
9667 WHERE active_fi = %s
9668 ";
9669
9670 $result = $ilDB->queryF(
9671 $query,
9672 array('integer'),
9673 array($active_id)
9674 );
9675
9676 if (!$result->numRows()) {
9678
9679 $query = "
9680 SELECT *
9681 FROM tst_result_cache
9682 WHERE active_fi = %s
9683 ";
9684
9685 $result = $ilDB->queryF(
9686 $query,
9687 array('integer'),
9688 array($active_id)
9689 );
9690 }
9691
9692 $row = $ilDB->fetchAssoc($result);
9693
9694 return $row;
9695 }
9696
9697 public function getMailNotificationType(): int
9698 {
9699 if ($this->mailnottype == 1) {
9700 return $this->mailnottype;
9701 } else {
9702 return 0;
9703 }
9704 }
9705
9706 public function setMailNotificationType($a_type)
9707 {
9708 if ($a_type == 1) {
9709 $this->mailnottype = 1;
9710 } else {
9711 $this->mailnottype = 0;
9712 }
9713 }
9714
9715 public function getExportSettings(): int
9716 {
9717 return $this->getScoreSettings()->getResultDetailsSettings()->getExportSettings();
9718 }
9719
9720 public function setExportSettings($a_settings)
9721 {
9722 if ($a_settings) {
9723 $this->exportsettings = $a_settings;
9724 } else {
9725 $this->exportsettings = 0;
9726 }
9727 }
9728
9730 {
9731 return $this->getScoreSettings()->getResultDetailsSettings()->getExportSettingsSingleChoiceShort();
9732 }
9733
9734 public function setExportSettingsSingleChoiceShort($a_settings)
9735 {
9736 if ($a_settings) {
9737 $this->exportsettings = $this->exportsettings | 1;
9738 } else {
9739 if ($this->getExportSettingsSingleChoiceShort()) {
9740 $this->exportsettings = $this->exportsettings ^ 1;
9741 }
9742 }
9743 }
9744
9745 public function getEnabledViewMode()
9746 {
9747 return $this->enabled_view_mode;
9748 }
9749
9750 public function setEnabledViewMode($mode)
9751 {
9752 $this->enabled_view_mode = $mode;
9753 }
9754
9755 public function setTemplate($template_id)
9756 {
9757 $this->template_id = (int) $template_id;
9758 }
9759
9760 public function getTemplate(): string
9761 {
9762 return $this->template_id;
9763 }
9764
9766 {
9767 return (
9768 $this->getSpecificAnswerFeedback() || $this->getGenericAnswerFeedback() ||
9769 $this->getAnswerFeedbackPoints() || $this->getInstantFeedbackSolution()
9770 );
9771 }
9772
9773 public function getInstantFeedbackOptionsAsArray(): array
9774 {
9775 $values = array();
9776
9777 if ($this->getSpecificAnswerFeedback()) {
9778 $values[] = 'instant_feedback_specific';
9779 }
9780 if ($this->getGenericAnswerFeedback()) {
9781 $values[] = 'instant_feedback_generic';
9782 }
9783 if ($this->getAnswerFeedbackPoints()) {
9784 $values[] = 'instant_feedback_points';
9785 }
9786 if ($this->getInstantFeedbackSolution()) {
9787 $values[] = 'instant_feedback_solution';
9788 }
9789
9790 return $values;
9791 }
9792
9793 public function setInstantFeedbackOptionsByArray($options)
9794 {
9795 if (is_array($options)) {
9796 $this->setGenericAnswerFeedback(in_array('instant_feedback_generic', $options) ? 1 : 0);
9797 $this->setSpecificAnswerFeedback(in_array('instant_feedback_specific', $options) ? 1 : 0);
9798 $this->setAnswerFeedbackPoints(in_array('instant_feedback_points', $options) ? 1 : 0);
9799 $this->setInstantFeedbackSolution(in_array('instant_feedback_solution', $options) ? 1 : 0);
9800 } else {
9801 $this->setGenericAnswerFeedback(0);
9802 $this->setSpecificAnswerFeedback(0);
9803 $this->setAnswerFeedbackPoints(0);
9804 $this->setInstantFeedbackSolution(0);
9805 }
9806 }
9807
9812 {
9813 global $DIC;
9814 $tree = $DIC['tree'];
9815 $db = $DIC['ilDB'];
9816 $component_repository = $DIC['component.repository'];
9817
9818 $qscFactory = new ilTestQuestionSetConfigFactory($tree, $db, $component_repository, $this);
9819 $questionSetConfig = $qscFactory->getQuestionSetConfig();
9820
9821 /* @var ilTestFixedQuestionSetConfig $questionSetConfig */
9822 $reindexedSequencePositionMap = $questionSetConfig->reindexQuestionOrdering();
9823
9824 $this->loadQuestions();
9825
9826 return $reindexedSequencePositionMap;
9827 }
9828
9829 public function setQuestionOrderAndObligations($orders, $obligations)
9830 {
9831 global $DIC;
9832 $ilDB = $DIC['ilDB'];
9833
9834 asort($orders);
9835
9836 $i = 0;
9837
9838 foreach ($orders as $id => $position) {
9839 $i++;
9840
9841 $obligatory = (
9842 isset($obligations[$id]) && $obligations[$id] ? 1 : 0
9843 );
9844
9845 $query = "
9846 UPDATE tst_test_question
9847 SET sequence = %s,
9848 obligatory = %s
9849 WHERE question_fi = %s
9850 ";
9851
9852 $ilDB->manipulateF(
9853 $query,
9854 array('integer', 'integer', 'integer'),
9855 array($i, $obligatory, $id)
9856 );
9857 }
9858
9859 $this->loadQuestions();
9860 }
9861
9862 public function moveQuestionAfter($question_to_move, $question_before)
9863 {
9864 global $DIC;
9865 $ilDB = $DIC['ilDB'];
9866 if ($question_before) {
9867 $query = 'SELECT sequence, test_fi FROM tst_test_question WHERE question_fi = %s';
9868 $types = array('integer');
9869 $values = array($question_before);
9870 $rset = $ilDB->queryF($query, $types, $values);
9871 }
9872
9873 if (!$question_before || ($rset && !($row = $ilDB->fetchAssoc($rset)))) {
9874 $row = array(
9875 'sequence' => 0,
9876 'test_fi' => $this->getTestId(),
9877 );
9878 }
9879
9880 $update = 'UPDATE tst_test_question SET sequence = sequence + 1 WHERE sequence > %s AND test_fi = %s';
9881 $types = array('integer', 'integer');
9882 $values = array($row['sequence'], $row['test_fi']);
9883 $ilDB->manipulateF($update, $types, $values);
9884
9885 $update = 'UPDATE tst_test_question SET sequence = %s WHERE question_fi = %s';
9886 $types = array('integer', 'integer');
9887 $values = array($row['sequence'] + 1, $question_to_move);
9888 $ilDB->manipulateF($update, $types, $values);
9889
9890 $this->reindexFixedQuestionOrdering();
9891 }
9892
9893 public function hasQuestionsWithoutQuestionpool(): bool
9894 {
9895 global $DIC;
9896 $ilDB = $DIC['ilDB'];
9897
9898 $questions = $this->getQuestionTitlesAndIndexes();
9899
9900 $IN_questions = $ilDB->in('q1.question_id', array_keys($questions), false, 'integer');
9901
9902 $query = "
9903 SELECT count(q1.question_id) cnt
9904
9905 FROM qpl_questions q1
9906
9907 INNER JOIN qpl_questions q2
9908 ON q2.question_id = q1.original_id
9909
9910 WHERE $IN_questions
9911 AND q1.obj_fi = q2.obj_fi
9912 ";
9913
9914 $rset = $ilDB->query($query);
9915
9916 $row = $ilDB->fetchAssoc($rset);
9917
9918 return $row['cnt'] > 0;
9919 }
9920
9927 public static function _lookupFinishedUserTests($a_user_id): array
9928 {
9929 global $DIC;
9930 $ilDB = $DIC['ilDB'];
9931
9932 $result = $ilDB->queryF(
9933 "SELECT test_fi,MAX(pass) AS pass FROM tst_active" .
9934 " JOIN tst_pass_result ON (tst_pass_result.active_fi = tst_active.active_id)" .
9935 " WHERE user_fi=%s" .
9936 " GROUP BY test_fi",
9937 array('integer', 'integer'),
9938 array($a_user_id, 1)
9939 );
9940 $all = array();
9941 while ($row = $ilDB->fetchAssoc($result)) {
9942 $obj_id = self::_getObjectIDFromTestID($row["test_fi"]);
9943 $all[$obj_id] = (bool) $row["pass"];
9944 }
9945 return $all;
9946 }
9947 public function getQuestions(): array
9948 {
9949 return $this->questions;
9950 }
9951
9952 public function isOnline(): bool
9953 {
9954 return $this->online;
9955 }
9956
9957 public function setOnline($a_online = true)
9958 {
9959 $this->online = (bool) $a_online;
9960 }
9961
9965 public function getOldOnlineStatus()
9966 {
9967 return $this->oldOnlineStatus;
9968 }
9969
9970 public function setOldOnlineStatus($oldOnlineStatus): void
9971 {
9972 $this->oldOnlineStatus = $oldOnlineStatus;
9973 }
9974
9975 public function isBestSolutionPrintedWithResult(): bool
9976 {
9977 return $this->getScoreSettings()->getResultDetailsSettings()->getPrintBestSolutionWithResult();
9978 }
9979
9985 public function isOfferingQuestionHintsEnabled(): bool
9986 {
9987 return $this->offeringQuestionHintsEnabled ?: false;
9988 }
9989
9995 public function setOfferingQuestionHintsEnabled($offeringQuestionHintsEnabled)
9996 {
9997 $this->offeringQuestionHintsEnabled = (bool) $offeringQuestionHintsEnabled;
9998 }
9999
10000 public function setActivationVisibility($a_value)
10001 {
10002 $this->activation_visibility = (bool) $a_value;
10003 }
10004
10005 public function getActivationVisibility()
10006 {
10007 return $this->activation_visibility;
10008 }
10009
10010 public function isActivationLimited(): ?bool
10011 {
10012 return $this->activation_limited;
10013 }
10014
10015 public function setActivationLimited($a_value)
10016 {
10017 $this->activation_limited = (bool) $a_value;
10018 }
10019
10020 /* GET/SET for highscore feature */
10021 public function getHighscoreEnabled(): bool
10022 {
10023 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreEnabled();
10024 }
10025
10035 public function getHighscoreAnon(): bool
10036 {
10037 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreAnon();
10038 }
10039
10048 public function isHighscoreAnon(): bool
10049 {
10050 return $this->getAnonymity() == 1 || $this->getHighscoreAnon();
10051 }
10052
10056 public function getHighscoreAchievedTS(): bool
10057 {
10058 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreAchievedTS();
10059 }
10060
10064 public function getHighscoreScore(): bool
10065 {
10066 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreScore();
10067 }
10068
10072 public function getHighscorePercentage(): bool
10073 {
10074 return $this->getScoreSettings()->getGamificationSettings()->getHighscorePercentage();
10075 }
10076
10080 public function getHighscoreHints(): bool
10081 {
10082 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreHints();
10083 }
10084
10088 public function getHighscoreWTime(): bool
10089 {
10090 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreWTime();
10091 }
10092
10096 public function getHighscoreOwnTable(): bool
10097 {
10098 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreOwnTable();
10099 }
10100
10104 public function getHighscoreTopTable(): bool
10105 {
10106 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreTopTable();
10107 }
10108
10113 public function getHighscoreTopNum(int $a_retval = 10): int
10114 {
10115 return $this->getScoreSettings()->getGamificationSettings()->getHighscoreTopNum();
10116 }
10117
10118 public function getHighscoreMode(): int
10119 {
10120 return $this->getScoreSettings()->getGamificationSettings()->getHighScoreMode();
10121 }
10122 /* End GET/SET for highscore feature*/
10123
10124 public function setSpecificAnswerFeedback($specific_answer_feedback)
10125 {
10126 switch ($specific_answer_feedback) {
10127 case 1:
10128 $this->specific_answer_feedback = 1;
10129 break;
10130 default:
10131 $this->specific_answer_feedback = 0;
10132 break;
10133 }
10134 }
10135
10137 {
10138 switch ($this->specific_answer_feedback) {
10139 case 1:
10140 return 1;
10141 default:
10142 return 0;
10143 }
10144 }
10145
10151 public function setObligationsEnabled($obligationsEnabled = true)
10152 {
10153 $this->obligationsEnabled = (bool) $obligationsEnabled;
10154 }
10155
10161 public function areObligationsEnabled(): bool
10162 {
10163 return (bool) $this->obligationsEnabled;
10164 }
10165
10172 public static function isQuestionObligationPossible($questionId): bool
10173 {
10174 $classConcreteQuestion = assQuestion::_getQuestionType($questionId);
10175
10176 assQuestion::_includeClass($classConcreteQuestion, 0);
10177
10178 // static binder is not at work yet (in PHP < 5.3)
10179 //$obligationPossible = $classConcreteQuestion::isObligationPossible();
10180 $obligationPossible = call_user_func(array($classConcreteQuestion, 'isObligationPossible'), $questionId);
10181
10182 return $obligationPossible;
10183 }
10184
10191 public static function isQuestionObligatory($question_id): bool
10192 {
10193 global $DIC;
10194 $ilDB = $DIC['ilDB'];
10195
10196 $rset = $ilDB->queryF('SELECT obligatory FROM tst_test_question WHERE question_fi = %s', array('integer'), array($question_id));
10197
10198 if ($row = $ilDB->fetchAssoc($rset)) {
10199 return (bool) $row['obligatory'];
10200 }
10201
10202 return false;
10203 }
10204
10217 public static function allObligationsAnswered($test_id, $active_id, $pass): bool
10218 {
10219 global $DIC;
10220 $ilDB = $DIC['ilDB'];
10221
10222 $rset = $ilDB->queryF(
10223 'SELECT obligations_answered FROM tst_pass_result WHERE active_fi = %s AND pass = %s',
10224 array('integer', 'integer'),
10225 array($active_id, $pass)
10226 );
10227
10228 if ($row = $ilDB->fetchAssoc($rset)) {
10229 return (bool) $row['obligations_answered'];
10230 }
10231
10232 return !self::hasObligations($test_id);
10233 }
10234
10243 public static function hasObligations($test_id): bool
10244 {
10245 global $DIC;
10246 $ilDB = $DIC['ilDB'];
10247
10248 $rset = $ilDB->queryF(
10249 'SELECT count(*) cnt FROM tst_test_question WHERE test_fi = %s AND obligatory = 1',
10250 array('integer'),
10251 array($test_id)
10252 );
10253
10254 $row = $ilDB->fetchAssoc($rset);
10255
10256 return (bool) $row['cnt'] > 0;
10257 }
10258
10259 public function setAutosave($autosave)
10260 {
10261 $this->autosave = $autosave;
10262 }
10263
10264 public function getAutosave(): bool
10265 {
10266 return $this->autosave;
10267 }
10268
10269 public function setAutosaveIval($autosave_ival)
10270 {
10271 $this->autosave_ival = $autosave_ival;
10272 }
10273
10274 public function getAutosaveIval(): int
10275 {
10276 return $this->autosave_ival;
10277 }
10278
10279 public function isPassDeletionAllowed(): bool
10280 {
10281 return $this->getScoreSettings()->getResultSummarySettings()->getPassDeletionAllowed();
10282 }
10283
10284 #region Examview / PDF Examview
10288 public function setShowExamviewHtml($show_examview_html): void
10289 {
10290 $this->show_examview_html = $show_examview_html;
10291 }
10292
10296 public function getShowExamviewHtml(): bool
10297 {
10298 return $this->show_examview_html;
10299 }
10300
10304 public function setShowExamviewPdf($show_examview_pdf): void
10305 {
10306 $this->show_examview_pdf = $show_examview_pdf;
10307 }
10308
10312 public function getShowExamviewPdf(): bool
10313 {
10314 return $this->show_examview_pdf;
10315 }
10316
10320 public function setEnableExamview($enable_examview): void
10321 {
10322 $this->enable_examview = $enable_examview;
10323 }
10324
10328 public function getEnableExamview(): bool
10329 {
10330 return $this->enable_examview;
10331 }
10332
10333 #endregion
10334
10335 public function setActivationStartingTime($starting_time = null)
10336 {
10337 $this->activation_starting_time = $starting_time;
10338 }
10339
10340 public function setActivationEndingTime($ending_time = null)
10341 {
10342 $this->activation_ending_time = $ending_time;
10343 }
10344
10346 {
10347 return (strlen($this->activation_starting_time)) ? $this->activation_starting_time : null;
10348 }
10349
10350 public function getActivationEndingTime()
10351 {
10352 return (strlen($this->activation_ending_time)) ? $this->activation_ending_time : null;
10353 }
10354
10361 public function getStartingTimeOfParticipants(): array
10362 {
10363 global $DIC;
10364 $ilDB = $DIC['ilDB'];
10365
10366 $times = array();
10367 $result = $ilDB->queryF(
10368 "SELECT tst_times.active_fi, tst_times.started FROM tst_times, tst_active WHERE tst_times.active_fi = tst_active.active_id AND tst_active.test_fi = %s ORDER BY tst_times.tstamp DESC",
10369 array('integer'),
10370 array($this->getTestId())
10371 );
10372 while ($row = $ilDB->fetchAssoc($result)) {
10373 $times[$row['active_fi']] = $row['started'];
10374 }
10375 return $times;
10376 }
10377
10378 public function getTimeExtensionsOfParticipants(): array
10379 {
10380 global $DIC;
10381 $ilDB = $DIC['ilDB'];
10382
10383 $times = array();
10384 $result = $ilDB->queryF(
10385 "SELECT tst_addtime.active_fi, tst_addtime.additionaltime FROM tst_addtime, tst_active WHERE tst_addtime.active_fi = tst_active.active_id AND tst_active.test_fi = %s",
10386 array('integer'),
10387 array($this->getTestId())
10388 );
10389 while ($row = $ilDB->fetchAssoc($result)) {
10390 $times[$row['active_fi']] = $row['additionaltime'];
10391 }
10392 return $times;
10393 }
10394
10395 public function getExtraTime($active_id)
10396 {
10397 global $DIC;
10398 $ilDB = $DIC['ilDB'];
10399
10400 $result = $ilDB->queryF(
10401 "SELECT additionaltime FROM tst_addtime WHERE active_fi = %s",
10402 array('integer'),
10403 array($active_id)
10404 );
10405 if ($result->numRows() > 0) {
10406 $row = $ilDB->fetchAssoc($result);
10407 return $row['additionaltime'];
10408 }
10409 return 0;
10410 }
10411
10412 public function addExtraTime($active_id, $minutes)
10413 {
10414 $participantData = new ilTestParticipantData($this->db, $this->lng);
10415
10416 $participantData->setParticipantAccessFilter(
10418 );
10419
10420 if ($active_id) {
10421 $participantData->setActiveIdsFilter(array($active_id));
10422 }
10423
10424 $participantData->load($this->getTestId());
10425
10426 foreach ($participantData->getActiveIds() as $active_fi) {
10427 $result = $this->db->queryF(
10428 "SELECT active_fi FROM tst_addtime WHERE active_fi = %s",
10429 array('integer'),
10430 array($active_fi)
10431 );
10432
10433 if ($result->numRows() > 0) {
10434 $this->db->manipulateF(
10435 "DELETE FROM tst_addtime WHERE active_fi = %s",
10436 array('integer'),
10437 array($active_fi)
10438 );
10439 }
10440
10441 $this->db->manipulateF(
10442 "UPDATE tst_active SET tries = %s, submitted = %s, submittimestamp = %s WHERE active_id = %s",
10443 array('integer','integer','timestamp','integer'),
10444 array(0, 0, null, $active_fi)
10445 );
10446
10447 $this->db->manipulateF(
10448 "INSERT INTO tst_addtime (active_fi, additionaltime, tstamp) VALUES (%s, %s, %s)",
10449 array('integer','integer','integer'),
10450 array($active_fi, $minutes, time())
10451 );
10452
10454 $this->logAction(sprintf($this->lng->txtlng("assessment", "log_added_extratime", ilObjAssessmentFolder::_getLogLanguage()), $minutes, $active_id));
10455 }
10456 }
10457 }
10458
10464 public function setEnableArchiving($enable_archiving): ilObjTest
10465 {
10466 $this->enable_archiving = $enable_archiving;
10467 return $this;
10468 }
10469
10473 public function getEnableArchiving(): bool
10474 {
10475 return $this->enable_archiving;
10476 }
10477
10478 public function getMaxPassOfTest(): int
10479 {
10483 global $DIC;
10484 $ilDB = $DIC['ilDB'];
10485
10486 $query = '
10487 SELECT MAX(tst_pass_result.pass) + 1 max_res
10488 FROM tst_pass_result
10489 INNER JOIN tst_active ON tst_active.active_id = tst_pass_result.active_fi
10490 WHERE test_fi = ' . $ilDB->quote($this->getTestId(), 'integer') . '
10491 ';
10492 $res = $ilDB->query($query);
10493 $data = $ilDB->fetchAssoc($res);
10494 return (int) $data['max_res'];
10495 }
10496
10497 public static function lookupExamId($active_id, $pass)
10498 {
10499 global $DIC;
10500 $ilDB = $DIC['ilDB'];
10501
10502 $exam_id_query = 'SELECT exam_id FROM tst_pass_result WHERE active_fi = %s AND pass = %s';
10503 $exam_id_result = $ilDB->queryF($exam_id_query, array( 'integer', 'integer' ), array( $active_id, $pass ));
10504 if ($ilDB->numRows($exam_id_result) == 1) {
10505 $exam_id_row = $ilDB->fetchAssoc($exam_id_result);
10506
10507 if ($exam_id_row['exam_id'] != null) {
10508 return $exam_id_row['exam_id'];
10509 }
10510 }
10511
10512 return null;
10513 }
10514
10515 public static function buildExamId($active_id, $pass, $test_obj_id = null): string
10516 {
10517 global $DIC;
10518 $ilSetting = $DIC['ilSetting'];
10519
10520 $inst_id = $ilSetting->get('inst_id', null);
10521
10522 if ($test_obj_id === null) {
10523 $obj_id = self::_getObjectIDFromActiveID($active_id);
10524 } else {
10525 $obj_id = $test_obj_id;
10526 }
10527
10528 $examId = 'I' . $inst_id . '_T' . $obj_id . '_A' . $active_id . '_P' . $pass;
10529
10530 return $examId;
10531 }
10532
10533 public function setShowExamIdInTestPassEnabled($show_exam_id_in_test_pass_enabled)
10534 {
10535 $this->show_exam_id_in_test_pass_enabled = $show_exam_id_in_test_pass_enabled;
10536 }
10537
10538 public function isShowExamIdInTestPassEnabled(): bool
10539 {
10540 return $this->show_exam_id_in_test_pass_enabled;
10541 }
10542
10546 public function isShowExamIdInTestResultsEnabled(): bool
10547 {
10548 return $this->getScoreSettings()->getResultDetailsSettings()->getShowExamIdInTestResults();
10549 }
10550
10554 public function setSignSubmission($sign_submission)
10555 {
10556 $this->sign_submission = $sign_submission;
10557 }
10558
10562 public function getSignSubmission(): bool
10563 {
10564 return $this->sign_submission;
10565 }
10566
10567 public function setCharSelectorAvailability($availability)
10568 {
10569 $this->char_selector_availability = (int) $availability;
10570 }
10571
10576 {
10577 return (int) $this->char_selector_availability;
10578 }
10579
10583 public function setCharSelectorDefinition($definition = '')
10584 {
10585 $this->char_selector_definition = $definition;
10586 }
10587
10591 public function getCharSelectorDefinition(): ?string
10592 {
10593 return $this->char_selector_definition;
10594 }
10595
10596
10602 public function setQuestionSetType($questionSetType)
10603 {
10604 $this->questionSetType = $questionSetType;
10605 }
10606
10612 public function getQuestionSetType(): string
10613 {
10614 return $this->questionSetType;
10615 }
10616
10624 public static function lookupQuestionSetType($objId): ?string
10625 {
10626 global $DIC;
10627 $ilDB = $DIC['ilDB'];
10628
10629 $query = "SELECT question_set_type FROM tst_tests WHERE obj_fi = %s";
10630
10631 $res = $ilDB->queryF($query, array('integer'), array($objId));
10632
10633 $questionSetType = null;
10634
10635 while ($row = $ilDB->fetchAssoc($res)) {
10636 $questionSetType = $row['question_set_type'];
10637 }
10638
10639 return $questionSetType;
10640 }
10641
10647 public function isFixedTest(): bool
10648 {
10649 return $this->getQuestionSetType() == self::QUESTION_SET_TYPE_FIXED;
10650 }
10651
10657 public function isRandomTest(): bool
10658 {
10659 return $this->getQuestionSetType() == self::QUESTION_SET_TYPE_RANDOM;
10660 }
10661
10667 public function isDynamicTest(): bool
10668 {
10669 return $this->getQuestionSetType() == self::QUESTION_SET_TYPE_DYNAMIC;
10670 }
10671
10679 public static function _lookupRandomTest($a_obj_id): bool
10680 {
10681 return self::lookupQuestionSetType($a_obj_id) == self::QUESTION_SET_TYPE_RANDOM;
10682 }
10683
10684 public function getQuestionSetTypeTranslation(ilLanguage $lng, $questionSetType): string
10685 {
10686 switch ($questionSetType) {
10688 return $lng->txt('tst_question_set_type_fixed');
10689
10691 return $lng->txt('tst_question_set_type_random');
10692 }
10693
10694 throw new ilTestException('invalid question set type value given: ' . $questionSetType);
10695 }
10696
10697 public function participantDataExist(): bool
10698 {
10699 if ($this->participantDataExist === null) {
10700 $this->participantDataExist = (bool) $this->evalTotalPersons();
10701 }
10702
10703 return $this->participantDataExist;
10704 }
10705
10706 public function recalculateScores($preserve_manscoring = false)
10707 {
10708 $scoring = new ilTestScoring($this);
10709 $scoring->setPreserveManualScores($preserve_manscoring);
10710 $scoring->recalculateSolutions();
10711 ilLPStatusWrapper::_updateStatus($this->getId(), $this->user->getId());
10712 }
10713
10714 public static function getTestObjIdsWithActiveForUserId($userId): array
10715 {
10716 global $DIC;
10717 $ilDB = $DIC['ilDB'];
10718
10719 $query = "
10720 SELECT obj_fi
10721 FROM tst_active
10722 INNER JOIN tst_tests
10723 ON test_id = test_fi
10724 WHERE user_fi = %s
10725 ";
10726
10727 $res = $ilDB->queryF($query, array('integer'), array($userId));
10728
10729 $objIds = array();
10730
10731 while ($row = $ilDB->fetchAssoc($res)) {
10732 $objIds[] = (int) $row['obj_fi'];
10733 }
10734
10735 return $objIds;
10736 }
10737
10738 public function setSkillServiceEnabled($skillServiceEnabled)
10739 {
10740 $this->skillServiceEnabled = $skillServiceEnabled;
10741 }
10742
10743 public function isSkillServiceEnabled(): bool
10744 {
10745 return $this->skillServiceEnabled;
10746 }
10747
10748 public function getResultFilterTaxIds(): array
10749 {
10750 if ($this->getTestId() != -1) {
10751 return $this->getScoreSettings()->getResultDetailsSettings()->getTaxonomyFilterIds();
10752 }
10753 return [];
10754 }
10755
10767 public function isSkillServiceToBeConsidered(): bool
10768 {
10769 if (!$this->isSkillServiceEnabled()) {
10770 return false;
10771 }
10772
10773 if (!self::isSkillManagementGloballyActivated()) {
10774 return false;
10775 }
10776
10777 return true;
10778 }
10779
10780 private static $isSkillManagementGloballyActivated = null;
10781
10782 public static function isSkillManagementGloballyActivated(): ?bool
10783 {
10784 if (self::$isSkillManagementGloballyActivated === null) {
10785 $skmgSet = new ilSkillManagementSettings();
10786
10787 self::$isSkillManagementGloballyActivated = $skmgSet->isActivated();
10788 }
10789
10790 return self::$isSkillManagementGloballyActivated;
10791 }
10792
10793 public function setShowGradingStatusEnabled($showGradingStatusEnabled)
10794 {
10795 $this->showGradingStatusEnabled = $showGradingStatusEnabled;
10796 }
10797
10798 public function isShowGradingStatusEnabled(): bool
10799 {
10800 return $this->showGradingStatusEnabled;
10801 }
10802
10803 public function setShowGradingMarkEnabled($showGradingMarkEnabled)
10804 {
10805 $this->showGradingMarkEnabled = $showGradingMarkEnabled;
10806 }
10807
10808
10809 public function isShowGradingMarkEnabled(): bool
10810 {
10811 return $this->showGradingMarkEnabled;
10812 }
10813
10814 public function setFollowupQuestionAnswerFixationEnabled($followupQuestionAnswerFixationEnabled)
10815 {
10816 $this->followupQuestionAnswerFixationEnabled = $followupQuestionAnswerFixationEnabled;
10817 }
10818
10820 {
10821 return $this->followupQuestionAnswerFixationEnabled;
10822 }
10823
10824 public function setInstantFeedbackAnswerFixationEnabled($instantFeedbackAnswerFixationEnabled)
10825 {
10826 $this->instantFeedbackAnswerFixationEnabled = $instantFeedbackAnswerFixationEnabled;
10827 }
10828
10830 {
10831 return $this->instantFeedbackAnswerFixationEnabled;
10832 }
10833
10834 public function isForceInstantFeedbackEnabled(): ?bool
10835 {
10836 return $this->forceInstantFeedbackEnabled;
10837 }
10838
10842 public function setForceInstantFeedbackEnabled($forceInstantFeedbackEnabled): void
10843 {
10844 $this->forceInstantFeedbackEnabled = $forceInstantFeedbackEnabled;
10845 }
10846
10847 public static function ensureParticipantsLastActivePassFinished($testObjId, $userId, $a_force_new_run = false): void
10848 {
10849 global $DIC;
10850 $ilDB = $DIC['ilDB'];
10851 $lng = $DIC['lng'];
10852 $refinery = $DIC['refinery'];
10853 $component_repository = $DIC['component.repository'];
10854
10855 /* @var ilObjTest $testOBJ */
10856
10857 $testOBJ = ilObjectFactory::getInstanceByRefId($testObjId, false);
10858
10859 $activeId = $testOBJ->getActiveIdOfUser($userId);
10860
10861 $testSessionFactory = new ilTestSessionFactory($testOBJ);
10862
10863 $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $refinery, $component_repository, $testOBJ);
10864
10865 $testSession = $testSessionFactory->getSession($activeId);
10866 $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($activeId, $testSession->getPass());
10867 $testSequence->loadFromDb();
10868
10869 // begin-patch lok changed smeyer
10870 if ($a_force_new_run) {
10871 if ($testSequence->hasSequence()) {
10872 $testSession->increasePass();
10873 }
10874 $testSession->setLastSequence(0);
10875 $testSession->saveToDb();
10876 }
10877 // end-patch lok
10878 }
10879
10880 public static function isParticipantsLastPassActive($testRefId, $userId): bool
10881 {
10882 global $DIC;
10883 $ilDB = $DIC['ilDB'];
10884 $lng = $DIC['lng'];
10885 $refinery = $DIC['refinery'];
10886 $component_repository = $DIC['component.repository'];
10887
10888 /* @var ilObjTest $testOBJ */
10889
10890 $testOBJ = ilObjectFactory::getInstanceByRefId($testRefId, false);
10891
10892
10893 $activeId = $testOBJ->getActiveIdOfUser($userId);
10894
10895 $testSessionFactory = new ilTestSessionFactory($testOBJ);
10896 // Added temporarily bugfix smeyer
10897 $testSessionFactory->reset();
10898
10899 $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $refinery, $component_repository, $testOBJ);
10900
10901 $testSession = $testSessionFactory->getSession($activeId);
10902 $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($activeId, $testSession->getPass());
10903 $testSequence->loadFromDb();
10904
10905 return $testSequence->hasSequence();
10906 }
10907
10911 public function isTestFinalBroken(): bool
10912 {
10913 return $this->testFinalBroken;
10914 }
10915
10919 public function setTestFinalBroken($testFinalBroken)
10920 {
10921 $this->testFinalBroken = $testFinalBroken;
10922 }
10923
10924 public function adjustTestSequence()
10925 {
10926 global $DIC;
10927 $ilDB = $DIC['ilDB'];
10928
10929 $query = "
10930 SELECT COUNT(test_question_id) cnt
10931 FROM tst_test_question
10932 WHERE test_fi = %s
10933 ORDER BY sequence
10934 ";
10935
10936 $questRes = $ilDB->queryF($query, array('integer'), array($this->getTestId()));
10937
10938 $row = $ilDB->fetchAssoc($questRes);
10939 $questCount = $row['cnt'];
10940
10941 if ($this->getShuffleQuestions()) {
10942 $query = "
10943 SELECT tseq.*
10944 FROM tst_active tac
10945 INNER JOIN tst_sequence tseq
10946 ON tseq.active_fi = tac.active_id
10947 WHERE tac.test_fi = %s
10948 ";
10949
10950 $partRes = $ilDB->queryF(
10951 $query,
10952 array('integer'),
10953 array($this->getTestId())
10954 );
10955
10956 while ($row = $ilDB->fetchAssoc($partRes)) {
10957 $sequence = @unserialize($row['sequence']);
10958
10959 if (!$sequence) {
10960 $sequence = array();
10961 }
10962
10963 $sequence = array_filter($sequence, function ($value) use ($questCount) {
10964 return $value <= $questCount;
10965 });
10966
10967 $num_seq = count($sequence);
10968 if ($questCount > $num_seq) {
10969 $diff = $questCount - $num_seq;
10970 for ($i = 1; $i <= $diff; $i++) {
10971 $sequence[$num_seq + $i - 1] = $num_seq + $i;
10972 }
10973 }
10974
10975 $new_sequence = serialize($sequence);
10976
10977 $ilDB->update('tst_sequence', array(
10978 'sequence' => array('clob', $new_sequence)
10979 ), array(
10980 'active_fi' => array('integer', $row['active_fi']),
10981 'pass' => array('integer', $row['pass'])
10982 ));
10983 }
10984 } else {
10985 $new_sequence = serialize($questCount > 0 ? range(1, $questCount) : array());
10986
10987 $query = "
10988 SELECT tseq.*
10989 FROM tst_active tac
10990 INNER JOIN tst_sequence tseq
10991 ON tseq.active_fi = tac.active_id
10992 WHERE tac.test_fi = %s
10993 ";
10994
10995 $part_rest = $ilDB->queryF(
10996 $query,
10997 array('integer'),
10998 array($this->getTestId())
10999 );
11000
11001 while ($row = $ilDB->fetchAssoc($part_rest)) {
11002 $ilDB->update('tst_sequence', array(
11003 'sequence' => array('clob', $new_sequence)
11004 ), array(
11005 'active_fi' => array('integer', $row['active_fi']),
11006 'pass' => array('integer', $row['pass'])
11007 ));
11008 }
11009 }
11010 }
11011
11016 {
11017 return ilHtmlPurifierFactory::getInstanceByType('qpl_usersolution');
11018 }
11019
11021 {
11022 if (!$this->score_settings) {
11023 $this->score_settings = $this->getScoreSettingsRepository()
11024 ->getFor($this->getTestId());
11025 }
11026 return $this->score_settings;
11027 }
11028
11030 {
11031 if (!$this->score_settings_repo) {
11032 $this->score_settings_repo = new ilObjTestScoreSettingsDatabaseRepository($this->db);
11033 }
11034 return $this->score_settings_repo;
11035 }
11036}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$filename
Definition: buildRTE.php:78
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Builds data types.
Definition: Factory.php:21
raiseError(string $a_msg, int $a_err_obj)
wrapper for downward compability
const IL_CAL_UNIX
const IL_CAL_DATETIME
return true
const NEWS_USERS
const NEWS_NOTICE
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Abstract basic class which is to be extended by the concrete assessment question type classes.
static _isWorkedThrough(int $active_id, int $question_id, int $pass)
Returns true if the question was worked through in the given pass Worked through means that the user ...
static _getQuestionType(int $question_id)
static _getSolutionMaxPass(int $question_id, int $active_id)
Returns the maximum pass a users question solution.
static _getOriginalId(int $question_id)
static _includeClass(string $question_type, int $gui=0)
static _getSuggestedSolutionOutput(int $question_id)
static getFeedbackClassNameByQuestionType(string $questionType)
static _getQuestionTitle(int $question_id)
static instantiateQuestion(int $question_id)
static _updateTestResultCache(int $active_id, ilAssQuestionProcessLocker $processLocker=null)
@TODO Move this to a proper place.
static deleteRequestsByActiveIds($activeIds)
Deletes all hint requests relating to a testactive included in given active ids.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static completeMissingPluginName($questionTypeData)
static _getInstance(int $a_copy_id)
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false)
@classDescription Date and time handling
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This class handles all operations on files (attachments) in directory ilias_data/mail.
static getASCIIFilename(string $a_filename)
static makeDir(string $a_dir)
creates a new directory and inherits all filesystem permissions of the parent directory You may pass ...
static ilTempnam(?string $a_temp_path=null)
Returns a unique and non existing Path for e temporary file or directory.
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 removeTrailingPathSeparators(string $path)
static getInstanceByType(string $type)
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)
language handling
loadLanguageModule(string $a_module)
Load language module.
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...
static prepareTextareaOutput(string $txt_output, bool $prepare_for_latex_output=false, bool $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output where latex code may be in it If the text is HTML-free,...
static prepareFormOutput($a_str, bool $a_strip=false)
A news item can be created by different sources.
static getFirstNewsIdForContext(int $a_context_obj_id, string $a_context_obj_type, int $a_context_sub_obj_id=0, string $a_context_sub_obj_type="")
Get first new id of news set related to a certain context.
static deleteNewsOfContext(int $a_context_obj_id, string $a_context_obj_type, int $a_context_sub_obj_id=0, string $a_context_sub_obj_type="")
Delete all news of a context.
static _addLog( $user_id, $object_id, $logtext, $question_id=0, $original_id=0, $test_only=false, $test_ref_id=0)
Add an assessment log entry.
static _getManualScoring()
Retrieve the manual scoring settings.
Class ilObjFile.
Class ilObjGroup.
static _exists(int $id, bool $reference=false, ?string $type=null)
checks if an object exists in object_data
static _getMobsOfObject(string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
static _saveTempFileAsMediaObject(string $name, string $tmp_name, bool $upload=true)
Create new media object and update page in db and return new media object.
static _removeUsage(int $a_mob_id, string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
Remove usage of mob in another container.
static _saveUsage(int $a_mob_id, string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
Save usage of mob within another container (e.g.
static _getAvailableQuestionpools($use_object_id=false, $equal_points=false, $could_be_offline=false, $showPath=false, $with_questioncount=false, $permission="read", $usr_id="")
Returns the available question pools for the active user.
static _getParticipantData($active_id)
Retrieves a participant name from active id.
static _isPassed($user_id, $a_obj_id)
Returns TRUE if the user with the user id $user_id passed the test with the object id $a_obj_id.
Class ilObjTestGUI.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
withGamificationSettings(ilObjTestSettingsGamification $settings)
static _getECTSGrade($points_passed, $reached_points, $max_points, $a, $b, $c, $d, $e, $fx)
{Returns the ECTS grade for a number of reached points.string The ECTS grade short description}
getResetProcessingTime()
Returns wheather the processing time should be reset or not.
_buildName($is_anonymous, $user_id, $firstname, $lastname, $title)
Builds a user name for the output depending on test type and existence of the user.
static _getResultPass($active_id)
Retrieves the pass number that should be counted for a given user.
setAnswerFeedback($answer_feedback=0)
Sets the generic feedback for the test @deprecate Use setGenericAnswerFeedback instead.
getPassed($active_id)
exportXMLPageObjects(&$a_xml_writer, $a_inst, &$expLog)
export page objects to xml (see ilias_co.dtd)
setECTSFX($a_ects_fx)
static _getObjectIDFromActiveID($active_id)
Returns the ILIAS test object id for a given active id.
getProcessingTimeAsMinutes()
static _instanciateQuestion($question_id)
Creates an instance of a question with a given question id.
getUserData($ids)
Returns a data of all users specified by id list.
getEstimatedWorkingTime()
Returns the estimated working time for the test calculated from the working time of the contained que...
setShowInfo($a_info=1)
Set whether the complete information page is shown or the required data only.
const QUESTION_SET_TYPE_DYNAMIC
getTestId()
Gets the database id of the additional test data.
endingTimeReached()
Returns true if the ending time of a test is reached An ending time is not available for self assessm...
setClientIP($user_id, $client_ip)
setAllowedUsersTimeGap($a_allowed_users_time_gap)
getQuestionSetType()
getter for question set type
setTestId($a_id)
Sets the test ID.
getShowKioskModeParticipant()
Returns the status of the kiosk mode participant.
setKiosk($kiosk=0)
Sets the kiosk mode for the test.
getQuestionTitle($title, $nr=null)
Returns the title of a test question and checks if the title output is allowed.
getHighscoreWTime()
Gets if the column with the workingtime should be shown.
int $fixed_participants
getHighscoreOwnTable()
Gets if the own rankings table should be shown.
getAnsweredQuestionCount($active_id, $pass=null)
Retrieves the number of answered questions for a given user in a given test.
ILIAS Test InternalRequestService $testrequest
getShowKioskModeTitle()
Returns the status of the kiosk mode title.
getHighscoreTopTable()
Gets, if the top-rankings table should be shown.
getCountSystem()
Gets the count system for the calculation of points.
isShowExamIdInTestPassEnabled()
setPostponingEnabled($postponingEnabled)
setInstantFeedbackSolution($instant_feedback=0)
Sets the instant feedback for the solution.
canShowSolutionPrintview($user_id=null)
getEnableProcessingTime()
Returns the state of the processing time (enabled/disabled)
setShowKioskModeParticipant($a_participant=false)
Set to true, if the participant's name should be shown in kiosk mode.
getListOfQuestionsStart()
Returns if the list of questions should be presented as the first page of the test.
& getExistingQuestions($pass=null)
Get the id's of the questions which are already part of the test.
saveToDb(bool $properties_only=false)
string $_finalstatement
static _getTestIDFromObjectID($object_id)
Returns the ILIAS test id for a given object id.
pcArrayShuffle($array)
Shuffles the values of a given array.
getShowSolutionListOwnAnswers($user_id=null)
_getTitleOutput($active_id)
Returns the value of the title_output status.
isAnyInstantFeedbackOptionEnabled()
isComplete(ilTestQuestionSetConfig $testQuestionSetConfig)
canShowTestResults(ilTestSession $testSession)
getInstantFeedbackSolution()
Returns 1 if the correct solution will be shown after answering a question.
setFixedParticipants($a_value=1)
Sets the fixed participants status.
getStartingTimeOfUser($active_id, $pass=null)
Returns the unix timestamp of the time a user started a test.
_getLastAccess($active_id)
getStartTestLabel($active_id)
Returns the "Start the Test" label for the Info page.
static _getTestDefaults($test_defaults_id)
setOnline($a_online=true)
& getParticipants()
Returns all persons who started the test.
setProcessingTime($processing_time="00:00:00")
Sets the processing time for the test.
int $reset_processing_time
getDetailedTestResults($participants)
returns all test results for all participants
setAllowedUsers($a_allowed_users)
isNrOfTriesReached($tries)
returns if number of tries are reached
reindexFixedQuestionOrdering()
static getTestObjIdsWithActiveForUserId($userId)
setKioskMode($a_kiosk=false)
Sets the kiosk mode for the test.
setTemplate($template_id)
setShowExamviewPdf($show_examview_pdf)
setShowExamIdInTestPassEnabled($show_exam_id_in_test_pass_enabled)
isPreviousSolutionReuseEnabled($active_id)
inviteUser($user_id, $client_ip="")
Invites a user to a test.
static _getCountSystem($active_id)
Gets the count system for the calculation of points.
setAuthor(string $author="")
Sets the authors name of the ilObjTest object.
evalTotalPersonsArray($name_sort_order="asc")
Returns all persons who started the test.
evalTotalStartedAverageTime($activeIdsFilter=null)
Returns the average processing time for all started tests.
setFollowupQuestionAnswerFixationEnabled($followupQuestionAnswerFixationEnabled)
getQuestiontext($question_id)
Returns the question text for a given question.
QTIMaterialToString($a_material)
Reads an QTI material tag an creates a text string.
const QUESTION_SET_TYPE_RANDOM
setOldOnlineStatus($oldOnlineStatus)
processPrintoutput2FO($print_output)
Convert a print output to XSL-FO.
sendSimpleNotification($active_id)
getXMLZip()
Get zipped xml file for test.
getShowSolutionListComparison()
static lookupExamId($active_id, $pass)
createExportDirectory()
creates data directory for export files (data_dir/tst_data/tst_<id>/export, depending on data directo...
bool $followupQuestionAnswerFixationEnabled
ilObjTestScoreSettings $score_settings
& getTestResult( $active_id, $pass=null, bool $ordered_sequence=false, bool $considerHiddenQuestions=true, bool $considerOptionalQuestions=true)
Calculates the results of a test for a given user and returns an array with all test results.
setShowGradingStatusEnabled($showGradingStatusEnabled)
static _getActiveIdOfUser($user_id="", $test_id="")
getExtraTime($active_id)
getFixedParticipants()
Returns the fixed participants status.
isBestSolutionPrintedWithResult()
_lookupRandomTestFromActiveId($active_id)
Returns the random status of a test with a given object id.
setPassword($a_password=null)
Sets the password for test access.
& createTestSequence($active_id, $pass, $shuffle)
createQuestionGUI($question_type, $question_id=-1)
Creates a question GUI instance of a given question type.
bool $show_exam_id_in_test_results_enabled
string $starting_time
questionMoveDown($question_id)
Moves a question down in order.
setShowFinalStatement($show=0)
Sets whether the final statement should be shown or not.
bool $blockPassesAfterPassedEnabled
isMaxProcessingTimeReached(int $starting_time, int $active_id)
Returns whether the maximum processing time for a test is reached or not.
logAction($logtext="", $question_id="")
Logs an action into the Test&Assessment log.
$metadata
A reference to an IMS compatible matadata set.
bool $activation_limited
getEvaluationAdditionalFields()
Gets additional user fields that should be shown in the user evaluation.
string $char_selector_definition
setEndingTimeEnabled($ending_time_enabled)
loadQuestions($active_id="", $pass=null)
Load the test question id's from the database.
& getCompleteEvaluationData($withStatistics=true, $filterby="", $filtertext="")
insertQuestion(ilTestQuestionSetConfig $testQuestionSetConfig, $question_id, $linkOnly=false)
Insert a question in the list of questions.
buildName($user_id, $firstname, $lastname, $title)
Builds a user name for the output depending on test type and existence of the user.
hasRandomQuestionsForPass(int $active_id, int $pass)
Checkes wheather a random test has already created questions for a given pass or not.
bool $shuffle_questions
setRedirectionUrl($redirection_url=null)
hasQuestionsWithoutQuestionpool()
buildIso8601PeriodFromUnixtimeForExportCompatibility($unix_timestamp)
exportFileItems($target_dir, &$expLog)
export files of file itmes
removeQuestionFromSequences($questionId, $activeIds, ilTestReindexedSequencePositionMap $reindexedSequencePositionMap)
$mark_schema
Defines the mark schema ASS_MarkSchema ?
getImagePath()
Returns the image path for web accessable images of a test The image path is under the CLIENT_WEB_DIR...
getShowSolutionSignature()
Returns if the signature field should be shown in the test results.
getHighscoreAnon()
Gets if the highscores should be anonymized per setting.
exportXMLMediaObjects(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog)
export media objects to xml (see ilias_co.dtd)
getPassScoring()
Gets the pass scoring type.
setInstantFeedbackAnswerFixationEnabled($instantFeedbackAnswerFixationEnabled)
ScoreSettingsRepository $score_settings_repo
setPasswordEnabled($passwordEnabled)
isHighscoreAnon()
Gets if the highscores should be displayed anonymized.
setOfferingQuestionHintsEnabled($offeringQuestionHintsEnabled)
sets offering question hints enabled/disabled
getShowSolutionAnswersOnly()
Returns if the full solution (including ILIAS content) should be presented to the solution or not.
isExecutable($testSession, $user_id, $allowPassIncrease=false)
Checks if the test is executable by the given user.
& getTestParticipants()
Returns a list of all participants in a test.
getStartingTime()
Returns the starting time of the test.
startingTimeReached()
Returns true if the starting time of a test is reached A starting time is not available for self asse...
setAutosave($autosave)
bool $offeringQuestionHintsEnabled
setListOfQuestions($a_value=true)
Sets if the the list of questions should be presented to the user or not.
& getTotalPointsPassedArray()
Returns an array with the total points of all users who passed the test This array could be used for ...
getShowPassDetails()
Returns if the pass details should be shown when a test is not finished.
getQuestionSetTypeTranslation(ilLanguage $lng, $questionSetType)
& evalResultsOverview()
Creates an associated array with the results of all participants of a test.
getResultsPresentation()
Returns the combined results presentation value.
bool $show_exam_id_in_test_pass_enabled
static _lookupAuthor($obj_id)
Gets the authors name of the ilObjTest object.
saveCompleteStatus(ilTestQuestionSetConfig $testQuestionSetConfig)
getKioskMode()
Returns the kiosk mode.
getSequenceSettings()
SEQUENCE SETTING = POSTPONING ENABLED !!
static allObligationsAnswered($test_id, $active_id, $pass)
checks wether all questions marked as obligatory were answered within the test pass with given testId...
getShowSolutionFeedback()
Returns if the feedback should be presented to the solution or not.
static _getPassScoring($active_id)
Gets the pass scoring type.
getTimeExtensionsOfParticipants()
setShowSolutionPrintview(int $a_printview=1)
Sets if the the solution printview should be presented to the user or not.
int $tmpCopyWizardCopyId
static _getQuestionCountAndPointsForPassOfParticipant($active_id, $pass)
const QUESTION_SET_TYPE_FIXED
duplicateQuestionForTest($question_id)
Takes a question and creates a copy of the question for use in the test.
setRedirectionMode($redirection_mode=0)
hasSingleChoiceQuestions()
setMailNotification($a_notification)
isActiveTestSubmitted($user_id=null)
returns if the active for user_id has been submitted
setQuestionSetType($questionSetType)
setter for question set type
setReportingDate($reporting_date)
Sets the reporting date of the ilObjTest object when the score reporting is available.
static lookupQuestionSetType($objId)
lookup-er for question set type
evalStatistical($active_id)
Returns the statistical evaluation of the test for a specified user.
setCharSelectorDefinition($definition='')
string $introduction
getKiosk()
Returns the kiosk mode.
getResultsForActiveId($active_id)
getShowSolutionDetails()
Returns if the solution details should be presented to the user or not.
setQuestionOrderAndObligations($orders, $obligations)
isTestQuestion($questionId)
static _getPass($active_id)
Retrieves the actual pass of a given user for a given test.
getAuthor()
Gets the authors name of the ilObjTest object.
getAnswerFeedbackPoints()
Returns 1 if answer specific feedback as reached points is activated.
setIntroductionEnabled($introductionEnabled)
getProcessingTimeInSeconds($active_id="")
Returns the processing time for the test in seconds.
saveManualFeedback(int $active_id, int $question_id, int $pass, ?string $feedback, bool $finalized=false, bool $is_single_feedback=false)
getFixedQuestionSetTotalPoints()
bool $show_examview_pdf
static lookupLastTestPassAccess($activeId, $passIndex)
inviteGroup($group_id)
Invites all users of a group to a test.
getScoreCutting()
Determines if the score of a question should be cut at 0 points or the score of the whole test.
getHighscorePercentage()
Gets if the percentage column should be shown.
getQuestionCountWithoutReloading()
addDefaults($a_name)
Adds the defaults of this test to the test defaults.
prepareTextareaOutput($txt_output, $prepare_for_latex_output=false, $omitNl2BrWhenTextArea=false)
Prepares a string for a text area output in tests.
setListOfQuestionsEnd($a_value=true)
Sets if the the list of questions as the end page of the test.
getTestParticipantsForManualScoring($filter=null)
getHtmlQuestionContentPurifier()
setResetProcessingTime($reset=0)
Sets wheather the processing time should be reset or not.
moveQuestions($move_questions, $target_index, $insert_mode)
Move questions to another position.
static _getObjectIDFromTestID($test_id)
Returns the ILIAS test object id for a given test id.
setListOfQuestionsSettings($a_value=0)
Sets the settings for the list of questions options in the test properties This could contain one of ...
isSkillServiceToBeConsidered()
Returns whether this test must consider skills, usually by providing appropriate extensions in the us...
sendAdvancedNotification($active_id)
modifyExportIdentifier($a_tag, $a_param, $a_value)
Returns the installation id for a given identifier.
saveAuthorToMetadata(string $a_author="")
Saves an authors name into the lifecycle metadata if no lifecycle metadata exists This will only be c...
string $redirection_url
updateWorkingTime($times_id)
Update the working time of a test when a question is answered.
setActivationStartingTime($starting_time=null)
removeQuestion(int $question_id)
static isSkillManagementGloballyActivated()
getHighscoreHints()
Gets, if the column with the number of requested hints should be shown.
buildStatisticsAccessFilteredParticipantList()
$evaluation_data
Contains the evaluation data settings the tutor defines for the user.
getGenericAnswerFeedback()
Returns 1 if generic answer feedback is to be shown.
doCreateMetaData()
@inheritDoc
setObligationsEnabled($obligationsEnabled=true)
sets obligations enabled/disabled
setSkillServiceEnabled($skillServiceEnabled)
int $instant_verification
fromXML(ilQTIAssessment $assessment)
Receives parameters from a QTI parser and creates a valid ILIAS test object.
static _setImportDirectory($a_import_dir=null)
static _getMaxPass($active_id)
Retrieves the maximum pass of a given user for a given test in which the user answered at least one q...
isRandomTest()
Returns the fact wether this test is a random questions test or not.
getQuestionType($question_id)
Returns the question type of a question with a given id.
getShowSolutionPrintview()
Returns if the solution printview should be presented to the user or not.
exportXMLMetaData(&$a_xml_writer)
export content objects meta data to xml (see ilias_co.dtd)
static _getImportDirectory()
Get the import directory location of the test.
getShowInfo()
Gets whether the complete information page is shown or the required data only.
setActivationVisibility($a_value)
setQuestionSetSolved($value, $question_id, $user_id)
sets question solved state to value for given user_id
bool $skillServiceEnabled
isFixedTest()
Returns the fact wether this test is a fixed question set test or not.
getMarkSchema()
{ASS_MarkSchema}
& _getCompleteWorkingTimeOfParticipants($test_id)
Returns the complete working time in seconds for all test participants.
getTestDefaults($test_defaults_id)
Returns the test defaults for a given id.
deleteDefaults($test_default_id)
Deletes the defaults for a test.
getListOfQuestionsEnd()
Returns if the list of questions should be presented as the last page of the test.
getReportingDate()
Gets the reporting date of the ilObjTest object.
setForceJS($a_js=1)
Set whether JavaScript should be forced for tests.
isTestFinished($active_id)
returns if the active for user_id has been submitted
static _getBestPass($active_id)
Retrieves the best pass of a given user for a given test.
static getSingleManualFeedback(int $active_id, int $question_id, int $pass)
static _lookupRandomTest($a_obj_id)
Returns the fact wether the test with passed obj id is a random questions test or not.
bool $show_examview_html
getQuestionDataset($question_id)
Returns the dataset for a given question id.
getTitleOutput()
Returns the value of the title_output status.
getAggregatedResultsData()
Returns the aggregated test results.
isDynamicTest()
Returns the fact wether this test is a dynamic question set test or not.
string $author
int $use_previous_answers
getAvailableQuestionpools($use_object_id=false, $equal_points=false, $could_be_offline=false, $show_path=false, $with_questioncount=false, $permission="read")
Returns the available question pools for the active user.
checkQuestionParent($questionId)
setInstantFeedbackOptionsByArray($options)
setFinalStatement(string $a_statement)
getHighscoreAchievedTS()
Returns if date and time of the scores achievement should be displayed.
setActivationEndingTime($ending_time=null)
getHighscoreTopNum(int $a_retval=10)
Gets the number of entries which are to be shown in the top-rankings table.
static getCompleteManualFeedback(int $question_id)
Retrieves the manual feedback for a question in a test.
& getQuestionTitlesAndIndexes()
Returns the titles of the test questions in question sequence.
getAllTestResults($participants, $prepareForCSV=true)
returns all test results for all participants
randomSelectQuestions(int $nr_of_questions, int $questionpool, $use_obj_id=0, $qpls="", $pass=null)
Returns a random selection of questions.
getListOfQuestions()
Returns if the list of questions should be presented to the user or not.
ilDBInterface $db
isHTML($a_text)
Checks if a given string contains HTML or not.
moveQuestionAfter($question_to_move, $question_before)
logManualFeedback($active_id, $question_id, $feedback)
Creates a log for the manual feedback.
string $questionSetType
setShuffleQuestions($a_shuffle)
Sets the status of the shuffle_questions variable.
getUsePreviousAnswers()
Returns if the previous answers should be shown for a learner.
setLimitUsersEnabled(bool $limitUsersEnabled)
& getWorkedQuestions($active_id, $pass=null)
Gets the id's of all questions a user already worked through.
bool $introductionEnabled
string $password
setForceInstantFeedbackEnabled($forceInstantFeedbackEnabled)
getAvailableQuestions($arrFilter, $completeonly=0)
Calculates the available questions for a test.
getListOfQuestionsSettings()
Returns the settings for the list of questions options in the test properties This could contain one ...
removeTestResultsByActiveIds($activeIds)
startWorkingTime($active_id, $pass)
Write the initial entry for the tests working time to the database.
isFollowupQuestionAnswerFixationEnabled()
setBlockPassesAfterPassedEnabled($blockPassesAfterPassedEnabled)
setSpecificAnswerFeedback($specific_answer_feedback)
bool $obligationsEnabled
& processCSVRow($row, $quoteAll=false, $separator=";")
Processes an array as a CSV row and converts the array values to correct CSV values.
setMailNotificationType($a_type)
setTitleOutput($title_output=0)
Sets the status of the title output.
Refinery $refinery
isOfferingQuestionHintsEnabled()
returns the fact wether offering hints is enabled or not
getCompleteWorkingTime($user_id)
Returns the complete working time in seconds a user worked on the test.
static buildExamId($active_id, $pass, $test_obj_id=null)
& getQuestionsOfTest($active_id)
Retrieves all the assigned questions for all test passes of a test participant.
setPassWaiting($pass_waiting)
array $resultFilterTaxIds
getForceJS()
Gets whether JavaScript should be forced for tests.
int $sequence_settings
removeTestResults(ilTestParticipantData $participantData)
applyDefaults($test_defaults)
Applies given test defaults to this test.
disinviteUser($user_id)
Disinvites a user from a test.
setAnswerFeedbackPoints($answer_feedback_points=0)
Sets the answer specific feedback of reached points for the test.
getInstantFeedbackOptionsAsArray()
setStartingTimeEnabled($starting_time_enabled)
setIntroduction(string $introduction)
setECTSGrades(array $a_ects_grades)
__construct($a_id=0, bool $a_call_by_reference=true)
Constructor.
static lookupQuestionSetTypeByActiveId($active_id)
returns the question set type of test relating to passed active id
& getQuestionsOfPass($active_id, $pass)
Retrieves all the assigned questions for a test participant in a given test pass.
deliverPDFfromHTML($content, $title=null)
Delivers a PDF file from XHTML.
getImagePathWeb()
Returns the web image path for web accessable images of a test The image path is under the web access...
addQTIMaterial(&$a_xml_writer, $a_material='')
Creates a QTI material tag from a plain text or xhtml text.
static _getWorkingTimeOfParticipantForPass($active_id, $pass)
Returns the complete working time in seconds for a test participant.
static isQuestionObligationPossible($questionId)
checks wether the obligation for question with given id is possible or not
setActivationLimited($a_value)
_isComplete($obj_id)
isShowExamIdInTestResultsEnabled()
areObligationsEnabled()
returns the fact wether obligations are enabled or not
const DEFAULT_PROCESSING_TIME_MINUTES
static _createImportDirectory()
creates data directory for import files (data_dir/tst_data/tst_<id>/import, depending on data directo...
create()
note: title, description and type should be set when this function is called
$limitUsersEnabled
bool?
getEndingTime()
Returns the ending time of the test.
static _getAvailableTests($use_object_id=false)
Returns the available tests for the active user.
float $ects_fx
Contains the percentage of maximum points a failed user needs to get the FX ECTS grade.
setShowKioskModeTitle($a_title=false)
Set to true, if the full test title should be shown in kiosk mode.
setShowGradingMarkEnabled($showGradingMarkEnabled)
getCompleteWorkingTimeOfParticipant($active_id)
Returns the complete working time in seconds for a test participant.
setStartingTime($starting_time=null)
Sets the starting time in database timestamp format for the test.
evalTotalPersons()
Returns the number of persons who started the test.
setEnableExamview($enable_examview)
setSignSubmission($sign_submission)
setEnableArchiving($enable_archiving)
& _evalResultsOverview($test_id)
Creates an associated array with the results of all participants of a test.
$ending_time_enabled
bool?
isInstantFeedbackAnswerFixationEnabled()
& getQuestionTitles()
Returns the titles of the test questions in question sequence.
getAllRTEContent()
Returns the content of all RTE enabled text areas in the test.
_getVisitTimeOfParticipant($test_id, $active_id)
Returns the first and last visit of a participant.
bool $testFinalBroken
getExportSettingsSingleChoiceShort()
array $questions
setShowCancel($a_value=1)
Sets the cancel test button status.
bool $print_best_solution_with_result
removeQuestions(array $removeQuestionIds)
getMarkSchemaForeignId()
{int}
& getAllQuestions($pass=null)
Returns all questions of a test in test order.
static _getUserIdFromActiveId($active_id)
isPluginActive($a_pname)
Checks wheather or not a question plugin with a given name is active.
setAutosaveIval($autosave_ival)
$passwordEnabled
bool?
getAnonymity()
Returns the anonymity status of the test.
deliverPDFfromFO($fo, $title=null)
Delivers a PDF file from a XSL-FO string.
static hasObligations($test_id)
returns the fact wether the test with given test id contains questions markes as obligatory or not
setTestFinalBroken($testFinalBroken)
string $ending_time
toXML()
Returns a QTI xml representation of the test.
static lookupPassResultsUpdateTimestamp($active_id, $pass)
hasAnyTestResult(ilTestSession $testSession)
array $ects_grades
& evalResultsOverviewOfParticipant($active_id)
Creates an associated array with the results for a given participant of a test.
bool $sign_submission
getShowCancel()
Returns wheather the cancel test button is shown or not.
setTmpCopyWizardCopyId(int $tmpCopyWizardCopyId)
setNrOfTries($nr_of_tries=0)
Sets the nr of tries for the test.
$participantDataExist
holds the fact wether participant data exists or not DO NOT USE TIS PROPERTY DRIRECTLY ALWAYS USE ilO...
getHighscoreScore()
Gets if the score column should be shown.
isBlockPassesAfterPassedEnabled()
setECTSOutput($a_ects_output)
& getInvitedUsers($user_id="", $order="login, lastname, firstname")
Returns a list of all invited users in a test.
setProcessingTimeByMinutes($minutes)
removeTestResultsFromSoapLpAdministration($userIds)
bool $instantFeedbackAnswerFixationEnabled
static _lookupFinishedUserTests($a_user_id)
Gather all finished tests for user.
static _getScoreCutting($active_id)
Determines if the score of a question should be cut at 0 points or the score of the whole test.
setListOfQuestionsStart($a_value=true)
Sets if the the list of questions as the start page of the test.
removeTestActives($activeIds)
getShowMarker()
Returns wheather the marker button is shown or not.
getImportMapping()
get array of (two) new created questions for import id
isSingleChoiceTestWithoutShuffle()
setCharSelectorAvailability($availability)
setAnonymity($a_value=0)
Sets the anonymity status of the test.
bool $showGradingMarkEnabled
insertManualFeedback(int $active_id, int $question_id, int $pass, ?string $feedback, bool $finalized, array $feedback_old)
getShuffleQuestions()
Returns the status of the shuffle_questions variable.
setExportSettingsSingleChoiceShort($a_settings)
removeTestResultsByUserIds($userIds)
isNewRandomTest()
Checks wheather the test is a new random test (using tst_rnd_cpy) or an old one.
getListOfQuestionsDescription()
Returns TRUE if the list of questions should be presented with the question descriptions.
getECTSGrade($passed_array, $reached_points, $max_points)
{Returns the ECTS grade for a number of reached points.string The ECTS grade short description}
getActiveIdOfUser($user_id="", $anonymous_id="")
Gets the active id of a given user.
bool $enable_examview
checkMarks()
{boolean|string True or an error string which can be used for display purposes}
recalculateScores($preserve_manscoring=false)
setEnabledViewMode($mode)
static isParticipantsLastPassActive($testRefId, $userId)
getSecondsUntilEndingTime()
Returns the seconds left from the actual time until the ending time.
getNrOfTries()
Returns the nr of tries for the test.
setShowExamviewHtml($show_examview_html)
setGenericAnswerFeedback(int $generic_answer_feedback=0)
static _getSolvedQuestions($active_id, $question_fi=null)
get solved questions
static _lookupAnonymity($a_obj_id)
Returns the anonymity status of a test with a given object id.
setEnableProcessingTime($enable=0)
Sets the processing time enabled or disabled.
setUsePreviousAnswers($use_previous_answers=1)
Sets the status of the visibility of previous learner answers.
static getManualFeedback($active_id, $question_id, $pass)
Retrieves the feedback comment for a question in a test if it is finalized.
inviteRole($role_id)
Invites all users of a role to a test.
int $answer_feedback_points
getJavaScriptOutput()
Returns if Javascript should be chosen for drag & drop actions for the active user.
setListOfQuestionsDescription($a_value=true)
Sets the show_summary attribute to TRUE if the list of questions should be presented with the questio...
static isQuestionObligatory($question_id)
checks wether the question with given id is marked as obligatory or not
$testSequence
contains the test sequence data
evalTotalParticipantsArray($name_sort_order="asc")
Returns all participants who started the test.
bool $enable_archiving
getTextAnswer($active_id, $question_id, $pass=null)
Returns the text answer of a given user for a given question.
userLookupFullName($user_id, $overwrite_anonymity=false, $sorted_order=false, $suffix="")
Returns the full name of a test user according to the anonymity status.
getNrOfResultsForPass($active_id, $pass)
Calculates the number of user results for a specific test pass.
setExportSettings($a_settings)
setEndingTime($ending_time=null)
Sets the ending time in database timestamp format for the test.
getStartingTimeOfParticipants()
Note, this function should only be used if absolutely necessary, since it perform joins on tables tha...
addExtraTime($active_id, $minutes)
getAccessFilteredParticipantList()
cleanupMediaobjectUsage()
Cleans up the media objects for all text fields in a test which are using an RTE field.
isTestFinishedToViewResults($active_id, $currentpass)
Returns true if an active user completed a test pass and did not start a new pass.
static ensureParticipantsLastActivePassFinished($testObjId, $userId, $a_force_new_run=false)
hasNrOfTriesRestriction()
returns if the numbers of tries have to be checked
static _lookupTestObjIdForQuestionId($a_q_id)
Get test Object ID for question ID.
setCustomStyle($a_customStyle=null)
Set the custom style.
bool $showGradingStatusEnabled
getVisitTimeOfParticipant($active_id)
Returns the first and last visit of a participant.
setAccessFilteredParticipantList($accessFilteredParticipantList)
getTitleFilenameCompliant()
returns the object title prepared to be used as a filename
int $specific_answer_feedback
getShowFinalStatement()
Returns whether the final statement should be shown or not.
questionMoveUp($question_id)
Moves a question up in order.
getProcessingTime()
Returns the processing time for the test.
setSequenceSettings($sequence_settings=0)
SEQUENCE SETTING = POSTPONING ENABLED !!
isForceInstantFeedbackEnabled()
$starting_time_enabled
bool?
& getCompleteWorkingTimeOfParticipants()
Returns the complete working time in seconds for all test participants.
getAnswerFeedback()
Returns 1 if generic answer feedback is activated.
$testSession
contains the test session data
exportPagesXML(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog)
export pages of test to xml (see ilias_co.dtd)
$_customStyle
Name of a custom style sheet for the test string?
setShowMarker($a_value=1)
Sets the marker button status.
static _lookupName(int $a_user_id)
lookup user name
static _lookupClientIP(int $a_user_id)
Class ilObjectActivation.
static getItem(int $ref_id)
static getInstanceByRefId(int $ref_id, bool $stop_on_error=true)
get an instance of an Ilias object by reference id
static getInstance(int $obj_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _lookupOwner(int $obj_id)
Lookup owner user ID for object ID.
ilLanguage $lng
ilTree $tree
static _prepareCloneSelection(array $ref_ids, string $new_type, bool $show_path=true)
Prepare copy wizard object selection.
setOfflineStatus(bool $status)
ILIAS $ilias
static _lookupObjId(int $ref_id)
static _lookupTitle(int $obj_id)
static _lookupDescription(int $obj_id)
static collectFileItems(ilPageObject $a_page, DOMDocument $a_domdoc)
Get all file items that are used within the page.
static _cleanupMediaObjectUsage(string $a_text, string $a_usage_type, int $a_usage_id)
Synchronises appearances of media objects in $a_text with media object usage table.
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
static factory(string $a_package, int $a_timeout=0)
Creates an ilRpcClient instance to our ilServer.
static get(string $a_var)
static clear(string $a_var)
static set(string $a_var, $a_val)
Set a value.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This class provides mathematical functions for statistics.
special template class to simplify handling of ITX/PEAR
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static isManScoringDone($activeId)
reads the flag wether manscoring is done for the given test active or not from the global settings (s...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getStyleSheetLocation(string $mode="output", string $a_css_name="", string $a_css_location="")
get full style sheet file name (path inclusive) of current user
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,...
static deliverData(string $a_data, string $a_filename, string $mime="application/octet-stream")
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$c
Definition: cli.php:38
const CLIENT_WEB_DIR
Definition: constants.php:47
const IL_INST_ID
Definition: constants.php:40
const ANONYMOUS_USER_ID
Definition: constants.php:27
for( $i=6;$i< 13;$i++) for($i=1; $i< 13; $i++) $d
Definition: date.php:296
$txt
Definition: error.php:13
global $DIC
Definition: feed.php:28
$target_id
Definition: goto.php:52
$update
Definition: imgupload.php:92
$mobs
Definition: imgupload.php:70
$ilUser
Definition: imgupload.php:34
const SCORE_BEST_PASS
const TEST_FIXED_SEQUENCE
Test constants.
const INVITATION_OFF
const ILIAS_VERSION
xslt_error(&$proc)
xslt_free(&$proc)
xslt_create()
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Interface ilDBInterface.
Interface for html sanitizing functionality.
$ref_id
Definition: ltiauth.php:67
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
Definition: ltiregstart.php:33
$res
Definition: ltiservices.php:69
if($format !==null) $name
Definition: metadata.php:247
$index
Definition: metadata.php:145
if(!array_key_exists('PATH_INFO', $_SERVER)) $config
Definition: metadata.php:85
$i
Definition: metadata.php:41
$keys
Definition: metadata.php:204
$xml
Definition: metadata.php:351
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
string $key
Consumer key/client ID value.
Definition: System.php:193
array $settings
Setting values (LTI parameters, custom parameters and local parameters).
Definition: System.php:200
Refinery Factory $refinery
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
header include for all ilias files.
global $ilSetting
Definition: privfeed.php:17
$query
$type
$results
$lng
$objectives
$objId
Definition: xapitoken.php:57
$rows
Definition: xhr_table.php:10