1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once 'Services/Object/classes/class.ilObject.php';
5require_once 'Modules/Test/classes/inc.AssessmentConstants.php';
6require_once 'Modules/Test/interfaces/interface.ilMarkSchemaAware.php';
7require_once 'Modules/Test/interfaces/interface.ilEctsGradesEnabled.php';
8require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssQuestionType.php';
9
10/**
11 * Class ilObjTest
12 *
13 * @author		Helmut Schottmüller <helmut.schottmueller@mac.com>
14 * @author		Björn Heyser <bheyser@databay.de>
15 * @version		$Id$
16 *
17 * @defgroup ModulesTest Modules/Test
18 * @extends ilObject
19 */
20class ilObjTest extends ilObject implements ilMarkSchemaAware, ilEctsGradesEnabled
21{
22    const DEFAULT_PROCESSING_TIME_MINUTES = 90;
23
24    #region Properties
25
26    /**
27     * type setting value for fixed question set
28     */
29    const QUESTION_SET_TYPE_FIXED = 'FIXED_QUEST_SET';
30
31    /**
32     * type setting value for random question set
33     */
34    const QUESTION_SET_TYPE_RANDOM = 'RANDOM_QUEST_SET';
35
36    /**
37     * type setting value for dynamic question set (continues testing mode)
38     */
39    const QUESTION_SET_TYPE_DYNAMIC = 'DYNAMIC_QUEST_SET';
40
41    /**
42     *
43     */
44    const HIGHSCORE_SHOW_OWN_TABLE = 1;
45
46    /**
47     *
48     */
49    const HIGHSCORE_SHOW_TOP_TABLE = 2;
50
51    /**
52     *
53     */
54    const HIGHSCORE_SHOW_ALL_TABLES = 3;
55
56    /**
57     * question set type setting
58     *
59     * @var string
60     */
61    private $questionSetType = self::QUESTION_SET_TYPE_FIXED;
62
63    /**
64     * @var bool
65     */
66    private $skillServiceEnabled = false;
67
68    /**
69     * @var array
70     */
71    private $resultFilterTaxIds = array();
72
73    /**
74    * Kiosk mode
75    *
76    * Tells wheather the test runs in a kiosk mode or not
77    *
78    * @var integer
79    */
80    protected $_kiosk;
81
82    /**
83* The database id of the additional test data dataset
84*
85* @var integer
86*/
87    public $test_id;
88
89    /**
90* Defines if the test will be placed on users personal desktops
91*
92* @var integer
93*/
94    public $invitation = INVITATION_OFF;
95
96    /**
97* A text representation of the authors name. The name of the author must
98* not necessary be the name of the owner.
99*
100* @var string
101*/
102    public $author;
103
104    /**
105* A reference to an IMS compatible matadata set
106*
107* @var object
108*/
109    public $metadata;
110
111    /**
112* An array which contains all the test questions
113*
114* @var array
115*/
116    public $questions;
117
118    /**
119     * @var bool
120     */
121    protected $introductionEnabled;
122
123    /**
124     * An introduction text to give users more information
125     * on the test.
126     *
127     * @var string
128     */
129    protected $introduction;
130
131    /**
132* Defines the mark schema
133*
134* @var ASS_MarkSchema
135*/
136    public $mark_schema;
137
138    /**
139* Defines the sequence settings for the test user. There are two values:
140* TEST_FIXED_SEQUENCE (=0) and TEST_POSTPONE (=1). The default value is
141* TEST_FIXED_SEQUENCE.
142*
143* @var integer
144*/
145    public $sequence_settings;
146
147    /**
148* Defines the score reporting for the test. There are two values:
149* REPORT_AFTER_TEST (=1), REPORT_ALWAYS (=2) AND REPORT_AFTER_DATE (=3). The default
150* value is REPORT_AFTER_TEST. If the score reporting is set to
151* REPORT_AFTER_TEST, it is also possible to use the $reporting_date
152* attribute to set a time/date for the earliest reporting time.
153*
154* @var integer
155*/
156    public $score_reporting;
157
158    /**
159* Defines the question verification type for the test. When set to 1
160* a instant verification button will be offered during the test to verify
161* the question solution
162*
163* @var integer
164*/
165    public $instant_verification;
166
167    /**
168* Defines wheather or not the reached points are shown as answer feedback
169*
170* @var integer
171*/
172    public $answer_feedback_points;
173
174    /**
175* A time/date value to set the earliest reporting time for the test score.
176* If you set this attribute, the sequence settings will be set to REPORT_AFTER_TEST
177* automatically. If $reporting_date is not set, the user will get a direct feedback.
178* The reporting date is given in database TIMESTAMP notation (yyyymmddhhmmss).
179*
180* @var string
181*/
182    public $reporting_date;
183
184    /**
185* Contains the evaluation data settings the tutor defines for the user
186*
187* @var object
188*/
189    public $evaluation_data;
190
191    /**
192* Number of tries the user is allowed to do. If set to 0, the user has
193* infinite tries.
194*
195* @var integer
196*/
197    public $nr_of_tries;
198
199    protected $blockPassesAfterPassedEnabled = false;
200
201    /**
202* Tells ILIAS to use the previous answers of a learner in a later test pass
203* The default is 1 which shows the previous answers in the next pass.
204*
205* @var integer
206*/
207    public $use_previous_answers;
208
209    /**
210* Tells ILIAS how to deal with the test titles. The test title will be shown with
211* the full title and the points when title_output is 0. When title_output is 1,
212* the available points will be hidden and when title_output is 2, the full title
213* will be hidden.
214*
215* @var integer
216*/
217    public $title_output;
218
219    /**
220* The maximum processing time as hh:mm:ss string the user is allowed to do.
221*
222* @var integer
223*/
224    public $processing_time;
225
226    /**
227* Contains 0 if the processing time is disabled, 1 if the processing time is enabled
228*
229* @var integer
230*/
231    public $enable_processing_time;
232
233    /**
234* Contains 0 if the processing time should not be reset, 1 if the processing time should be reset
235*
236* @var integer
237*/
238    public $reset_processing_time;
239
240    /**
241     * @var bool
242     */
243    protected $starting_time_enabled;
244
245    /**
246     * The starting time in database timestamp format which defines the earliest starting time for the test
247     *
248     * @var string
249     */
250    protected $starting_time;
251
252    /**
253     * @var bool
254     */
255    protected $ending_time_enabled;
256
257    /**
258     * The ending time in database timestamp format which defines the latest ending time for the test
259     *
260     * @var string
261     */
262    protected $ending_time;
263
264    /**
265     * Indicates if ECTS grades will be used
266     * @var int|boolean
267     */
268    protected $ects_output = false;
269
270    /**
271     * Contains the percentage of maximum points a failed user needs to get the FX ECTS grade
272     * @var float|null
273     */
274    protected $ects_fx = null;
275
276    /**
277     * The percentiles of the ECTS grades for this test
278     * @var array
279     */
280    protected $ects_grades = array();
281
282
283    /**
284* Indicates if the points for answers are counted for partial solutions
285* or only for correct solutions
286*
287* @var integer
288*/
289    public $count_system;
290
291    /**
292* Indicates if the points unchecked multiple choice questions are given or not
293*
294* @var integer
295*/
296    public $mc_scoring;
297
298    /**
299* Defines which pass should be used for scoring
300*
301* @var integer
302*/
303    public $pass_scoring;
304
305    /**
306* Indicates if the questions in a test are shuffled before
307* a user accesses the test
308*
309* @var boolean
310*/
311    public $shuffle_questions;
312
313    /**
314* Contains the presentation settings for the test results
315*
316* @var integer
317*/
318    public $results_presentation;
319
320    /**
321* Determines wheather or not a question summary is shown to the users
322*
323* @var boolean
324*/
325    public $show_summary;
326
327    /**
328* Determines if the score of every question should be cut at 0 points or the score of the complete test
329*
330* @var boolean
331*/
332    public $score_cutting;
333
334    /**
335     * @var bool
336     */
337    protected $passwordEnabled;
338
339    /**
340     * Password access to enter the test
341     *
342     * @var string
343     */
344    protected $password;
345
346    /**
347     * @var bool
348     */
349    protected $limitUsersEnabled;
350
351    /**
352     * number of allowed users for the test
353     *
354     * @var int
355     */
356    protected $allowedUsers;
357
358    /**
359     * inactivity time gap of the allowed users to let new users into the test
360     *
361     * @var int
362     */
363    protected $allowedUsersTimeGap;
364
365    /**
366* visiblity settings for a test certificate
367*
368* @var int
369*/
370    public $certificate_visibility;
371
372    /**
373* Anonymity of the test users
374*
375* @var int
376*/
377    public $anonymity;
378
379    /**
380* determines wheather a cancel test button is shown or not
381*
382* @var int
383*/
384    public $show_cancel;
385
386    /**
387* determines wheather a marker button is shown or not
388*
389* @var int
390*/
391    public $show_marker;
392
393    /**
394* determines wheather a test may have fixed participants or not
395*
396* @var int
397*/
398    public $fixed_participants;
399
400    /**
401* determines wheather an answer specific feedback is shown or not
402*
403* @var int
404*/
405    public $answer_feedback;
406
407    /**
408    * contains the test session data
409    *
410    * @var object
411    */
412    public $testSession;
413
414    /**
415    * contains the test sequence data
416    *
417    * @var object
418    */
419    public $testSequence;
420
421    /**
422    * Determines whether or not a final statement should be shown on test completion
423    *
424    * @var integer
425    */
426    private $_showfinalstatement;
427
428    /**
429    * A final statement for test completion
430    *
431    * @var string
432    */
433    private $_finalstatement;
434
435    /**
436    * Show the complete data on the test information page
437    *
438    * @var boolean
439    */
440    private $_showinfo;
441
442    /**
443    * Force JavaScript for test questions
444    *
445    * @var boolean
446    */
447    private $_forcejs = true;
448
449    /**
450    * Name of a custom style sheet for the test
451    *
452    * @var string;
453    */
454    private $_customStyle;
455
456    protected $mailnotification;
457
458    protected $mailnottype;
459
460    protected $exportsettings;
461
462    protected $poolUsage;
463
464    private $template_id;
465
466    protected $oldOnlineStatus = null;
467
468    /**
469     * @var bool
470     */
471    protected $print_best_solution_with_result = true;
472
473    /**
474     * defines wether question specific hints are offered or not
475     *
476     * @var boolean
477     */
478    private $offeringQuestionHintsEnabled = null;
479
480    /**
481     * defines wether it is possible to define obligatory questions
482     *
483     * @var boolean
484     */
485    private $obligationsEnabled = null;
486
487    protected $activation_visibility;
488
489    protected $activation_starting_time;
490
491    protected $activation_ending_time;
492
493    protected $autosave;
494
495    protected $autosave_ival;
496
497    /**
498     * defines wether it is possible for users
499     * to delete their own test passes or not
500     *
501     * @var boolean
502     */
503    private $passDeletionAllowed = null;
504
505    /**
506     * holds the fact wether participant data exists or not
507     * DO NOT USE TIS PROPERTY DRIRECTLY
508     * ALWAYS USE ilObjTest::paricipantDataExist() since this method initialises this property
509     */
510    private $participantDataExist = null;
511
512    /** @var $enable_examview bool */
513    protected $enable_examview;
514
515    /** @var $show_examview_html bool */
516    protected $show_examview_html;
517
518    /** @var $show_examview_pdf bool */
519    protected $show_examview_pdf;
520
521    /** @var $enbale_archiving bool */
522    protected $enable_archiving;
523
524    /**
525     * @var int
526     */
527    private $redirection_mode = 0;
528
529    /**
530     * @var string null
531     */
532    private $redirection_url = null;
533
534    /** @var bool $show_exam_id_in_test_pass_enabled */
535    protected $show_exam_id_in_test_pass_enabled;
536
537    /** @var bool $show_exam_id_in_test_results_enabled */
538    protected $show_exam_id_in_test_results_enabled;
539
540    /** @var bool $sign_submission */
541    protected $sign_submission;
542
543    /** @var mixed availability of selector for special characters  */
544    protected $char_selector_availability;
545
546    /** @var string definition of selector for special characters  */
547    protected $char_selector_definition;
548
549    /**
550     * @var bool
551     */
552    protected $showGradingStatusEnabled;
553
554    /**
555     * @var bool
556     */
557    protected $showGradingMarkEnabled;
558
559    /**
560     * @var bool
561     */
562    protected $followupQuestionAnswerFixationEnabled;
563
564    /**
565     * @var bool
566     */
567    protected $instantFeedbackAnswerFixationEnabled;
568
569    /**
570     * @var bool
571     */
572    protected $forceInstantFeedbackEnabled;
573
574    /**
575     * @var bool
576     */
577    protected $testFinalBroken;
578
579    /**
580     * @var integer
581     */
582    private $tmpCopyWizardCopyId;
583
584    /**
585     * @var string mm:ddd:hh:ii:ss
586     */
587    protected $pass_waiting = "00:000:00:00:00";
588    #endregion
589
590    /**
591     * Constructor
592     *
593     * @param	$a_id 					integer		Reference_id or object_id.
594     * @param	$a_call_by_reference	boolean		Treat the id as reference_id (true) or object_id (false).
595     *
596     * @return \ilObjTest
597     */
598    public function __construct($a_id = 0, $a_call_by_reference = true)
599    {
600        global $DIC;
601        $ilUser = $DIC['ilUser'];
602        $lng = $DIC['lng'];
603        $this->type = "tst";
604
605        $lng->loadLanguageModule("assessment");
606        // Defaults:
607        include_once "./Modules/Test/classes/class.assMarkSchema.php";
608        $this->mark_schema = new ASS_MarkSchema();
609        $this->mark_schema->createSimpleSchema(
610            $lng->txt("failed_short"),
611            $lng->txt("failed_official"),
612            0,
613            0,
614            $lng->txt("passed_short"),
615            $lng->txt("passed_official"),
616            50,
617            1
618        );
619
620        $this->test_id = -1;
621        $this->author = $ilUser->fullname;
622        $this->introductionEnabled = false;
623        $this->introduction = "";
624        $this->questions = array();
625        $this->sequence_settings = TEST_FIXED_SEQUENCE;
626        $this->score_reporting = self::SCORE_REPORTING_FINISHED;
627        $this->instant_verification = 0;
628        $this->answer_feedback_points = 0;
629        $this->reporting_date = "";
630        $this->nr_of_tries = 0;
631        $this->_kiosk = 0;
632        $this->use_previous_answers = 1;
633        $this->title_output = 0;
634        $this->starting_time = "";
635        $this->ending_time = "";
636        $this->processing_time = "";
637        $this->enable_processing_time = "0";
638        $this->reset_processing_time = 0;
639        $this->ects_output = false;
640        $this->ects_fx = null;
641        $this->shuffle_questions = false;
642        $this->mailnottype = 0;
643        $this->exportsettings = 0;
644        $this->show_summary = 8;
645        $this->count_system = COUNT_PARTIAL_SOLUTIONS;
646        $this->mc_scoring = SCORE_ZERO_POINTS_WHEN_UNANSWERED;
647        $this->score_cutting = SCORE_CUT_QUESTION;
648        $this->pass_scoring = SCORE_LAST_PASS;
649        $this->answer_feedback = 0;
650        $this->password = "";
651        $this->certificate_visibility = 0;
652        $this->allowedUsers = "";
653        $this->_showfinalstatement = false;
654        $this->_finalstatement = "";
655        $this->_showinfo = true;
656        $this->_forcejs = true;
657        $this->_customStyle = "";
658        $this->allowedUsersTimeGap = "";
659        $this->anonymity = 0;
660        $this->show_cancel = 0;
661        $this->show_marker = 0;
662        $this->fixed_participants = 0;
663        $this->setShowPassDetails(true);
664        $this->setShowSolutionDetails(true);
665        $this->setShowSolutionAnswersOnly(false);
666        $this->setShowSolutionSignature(false);
667        $this->testSession = false;
668        $this->testSequence = false;
669        $this->mailnotification = 0;
670        $this->poolUsage = 1;
671
672        $this->ects_grades = array(
673            'A' => 90,
674            'B' => 65,
675            'C' => 35,
676            'D' => 10,
677            'E' => 0
678        );
679
680        $this->autosave = false;
681        $this->autosave_ival = 30000;
682
683        $this->enable_examview = false;
684        $this->show_examview_html = false;
685        $this->show_examview_pdf = false;
686        $this->enable_archiving = false;
687
688        $this->express_mode = false;
689        $this->template_id = '';
690        $this->redirection_mode = 0;
691        $this->redirection_url = null;
692        $this->show_exam_id_in_test_pass_enabled = false;
693        $this->show_exam_id_in_test_results_enabled = false;
694        $this->sign_submission = false;
695        $this->char_selector_availability = 0;
696        $this->char_selector_definition = null;
697
698        $this->showGradingStatusEnabled = true;
699        $this->showGradingMarkEnabled = true;
700
701        $this->followupQuestionAnswerFixationEnabled = false;
702        $this->instantFeedbackAnswerFixationEnabled = false;
703
704        $this->testFinalBroken = false;
705
706        $this->tmpCopyWizardCopyId = null;
707
708        parent::__construct($a_id, $a_call_by_reference);
709    }
710
711    /**
712     * returns the object title prepared to be used as a filename
713     *
714     * @return string
715     */
716    public function getTitleFilenameCompliant()
717    {
718        require_once 'Services/Utilities/classes/class.ilUtil.php';
719        return ilUtil::getASCIIFilename($this->getTitle());
720    }
721
722    /**
723     * @return int
724     */
725    public function getTmpCopyWizardCopyId()
726    {
727        return $this->tmpCopyWizardCopyId;
728    }
729
730    /**
731     * @param int $tmpCopyWizardCopyId
732     */
733    public function setTmpCopyWizardCopyId($tmpCopyWizardCopyId)
734    {
735        $this->tmpCopyWizardCopyId = $tmpCopyWizardCopyId;
736    }
737
738    /**
739    * create test object
740    */
741    public function create()
742    {
743        $this->setOfflineStatus(true);
744        parent::create();
745
746        // meta data will be created by
747        // import parser
748        if (!$a_upload) {
749            $this->createMetaData();
750        }
751    }
752
753    /**
754    * update object data
755    *
756    * @access	public
757    * @return	boolean
758    */
759    public function update()
760    {
761        if (!parent::update()) {
762            return false;
763        }
764
765        // put here object specific stuff
766        $this->updateMetaData();
767        return true;
768    }
769
770    /**
771        * read object data from db into object
772        * @param	boolean
773        * @access	public
774        */
775    public function read()
776    {
777        parent::read();
778        $this->loadFromDb();
779    }
780
781
782    /**
783    * delete object and all related data
784    *
785    * @access	public
786    * @return	boolean	true if all object data were removed; false if only a references were removed
787    */
788    public function delete()
789    {
790        // always call parent delete function first!!
791        if (!parent::delete()) {
792            return false;
793        }
794
795        // delet meta data
796        $this->deleteMetaData();
797
798        //put here your module specific stuff
799        $this->deleteTest();
800
801        require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssQuestionSkillAssignmentImportFails.php';
802        $qsaImportFails = new ilAssQuestionSkillAssignmentImportFails($this->getId());
803        $qsaImportFails->deleteRegisteredImportFails();
804        require_once 'Modules/Test/classes/class.ilTestSkillLevelThresholdImportFails.php';
805        $sltImportFails = new ilTestSkillLevelThresholdImportFails($this->getId());
806        $sltImportFails->deleteRegisteredImportFails();
807
808        return true;
809    }
810
811    /**
812    * Deletes the test and all related objects, files and database entries
813    *
814    * @access	public
815    */
816    public function deleteTest()
817    {
818        global $DIC;
819        $tree = $DIC['tree'];
820        $ilDB = $DIC['ilDB'];
821        $ilPluginAdmin = $DIC['ilPluginAdmin'];
822        $lng = $DIC['lng'];
823
824        require_once 'Modules/Test/classes/class.ilTestParticipantData.php';
825        $participantData = new ilTestParticipantData($ilDB, $lng);
826        $participantData->load($this->getTestId());
827        $this->removeTestResults($participantData);
828
829        $affectedRows = $ilDB->manipulateF(
830            "DELETE FROM tst_mark WHERE test_fi = %s",
831            array('integer'),
832            array($this->getTestId())
833        );
834
835        $affectedRows = $ilDB->manipulateF(
836            "DELETE FROM tst_tests WHERE test_id = %s",
837            array('integer'),
838            array($this->getTestId())
839        );
840
841        require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
842        $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $this);
843        $testQuestionSetConfigFactory->getQuestionSetConfig()->removeQuestionSetRelatedData();
844
845        // delete export files
846        include_once "./Services/Utilities/classes/class.ilUtil.php";
847        $tst_data_dir = ilUtil::getDataDir() . "/tst_data";
848        $directory = $tst_data_dir . "/tst_" . $this->getId();
849        if (is_dir($directory)) {
850            include_once "./Services/Utilities/classes/class.ilUtil.php";
851            ilUtil::delDir($directory);
852        }
853        include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
854        $mobs = ilObjMediaObject::_getMobsOfObject("tst:html", $this->getId());
855        // remaining usages are not in text anymore -> delete them
856        // and media objects (note: delete method of ilObjMediaObject
857        // checks whether object is used in another context; if yes,
858        // the object is not deleted!)
859        foreach ($mobs as $mob) {
860            ilObjMediaObject::_removeUsage($mob, "tst:html", $this->getId());
861            if (ilObjMediaObject::_exists($mob)) {
862                $mob_obj = new ilObjMediaObject($mob);
863                $mob_obj->delete();
864            }
865        }
866    }
867
868    /**
869    * creates data directory for export files
870    * (data_dir/tst_data/tst_<id>/export, depending on data
871    * directory that is set in ILIAS setup/ini)
872    */
873    public function createExportDirectory()
874    {
875        include_once "./Services/Utilities/classes/class.ilUtil.php";
876        $tst_data_dir = ilUtil::getDataDir() . "/tst_data";
877        ilUtil::makeDir($tst_data_dir);
878        if (!is_writable($tst_data_dir)) {
879            $this->ilias->raiseError("Test Data Directory (" . $tst_data_dir
880                . ") not writeable.", $this->ilias->error_obj->MESSAGE);
881        }
882
883        // create learning module directory (data_dir/lm_data/lm_<id>)
884        $tst_dir = $tst_data_dir . "/tst_" . $this->getId();
885        ilUtil::makeDir($tst_dir);
886        if (!@is_dir($tst_dir)) {
887            $this->ilias->raiseError("Creation of Test Directory failed.", $this->ilias->error_obj->MESSAGE);
888        }
889        // create Export subdirectory (data_dir/lm_data/lm_<id>/Export)
890        $export_dir = $tst_dir . "/export";
891        ilUtil::makeDir($export_dir);
892        if (!@is_dir($export_dir)) {
893            $this->ilias->raiseError("Creation of Export Directory failed.", $this->ilias->error_obj->MESSAGE);
894        }
895    }
896
897    /**
898    * Get the location of the export directory for the test
899    *
900    * @access	public
901    */
902    public function getExportDirectory()
903    {
904        include_once "./Services/Utilities/classes/class.ilUtil.php";
905        $export_dir = ilUtil::getDataDir() . "/tst_data" . "/tst_" . $this->getId() . "/export";
906        return $export_dir;
907    }
908
909    /**
910    * Get a list of the already exported files in the export directory
911    *
912    * @return array A list of file names
913    * @access	public
914    */
915    public function getExportFiles($dir)
916    {
917        // quit if import dir not available
918        if (!@is_dir($dir) || !is_writeable($dir)) {
919            return array();
920        }
921
922        $files = array();
923        foreach (new DirectoryIterator($dir) as $file) {
924            /**
925             * @var $file SplFileInfo
926             */
927            if ($file->isDir()) {
928                continue;
929            }
930
931            $files[] = $file->getBasename();
932        }
933
934        sort($files);
935
936        return $files;
937    }
938
939    /**
940    * set import directory
941    */
942    public static function _setImportDirectory($a_import_dir = null)
943    {
944        if (strlen($a_import_dir)) {
945            $_SESSION["tst_import_dir"] = $a_import_dir;
946        } else {
947            unset($_SESSION["tst_import_dir"]);
948        }
949    }
950
951    /**
952    * Get the import directory location of the test
953    *
954    * @return string The location of the import directory or false if the directory doesn't exist
955    * @access	public
956    */
957    public static function _getImportDirectory()
958    {
959        if (strlen($_SESSION["tst_import_dir"])) {
960            return $_SESSION["tst_import_dir"];
961        }
962        return null;
963    }
964
965    public function getImportDirectory()
966    {
967        return ilObjTest::_getImportDirectory();
968    }
969
970    /**
971    * creates data directory for import files
972    * (data_dir/tst_data/tst_<id>/import, depending on data
973    * directory that is set in ILIAS setup/ini)
974    */
975    public static function _createImportDirectory()
976    {
977        global $DIC;
978        $ilias = $DIC['ilias'];
979        include_once "./Services/Utilities/classes/class.ilUtil.php";
980        $tst_data_dir = ilUtil::getDataDir() . "/tst_data";
981        ilUtil::makeDir($tst_data_dir);
982
983        if (!is_writable($tst_data_dir)) {
984            $ilias->raiseError("Test Data Directory (" . $tst_data_dir
985                . ") not writeable.", $ilias->error_obj->FATAL);
986        }
987
988        // create test directory (data_dir/tst_data/tst_import)
989        $tst_dir = $tst_data_dir . "/tst_import";
990        ilUtil::makeDir($tst_dir);
991        if (!@is_dir($tst_dir)) {
992            $ilias->raiseError("Creation of test import directory failed.", $ilias->error_obj->FATAL);
993        }
994
995        // assert that this is empty and does not contain old data
996        ilUtil::delDir($tst_dir, true);
997
998        return $tst_dir;
999    }
1000
1001    /**
1002    * Returns TRUE if the test contains single choice results
1003    *
1004    * @return boolean
1005    * @access public
1006    */
1007    public function hasSingleChoiceQuestions()
1008    {
1009        global $DIC;
1010        $ilDB = $DIC['ilDB'];
1011
1012        $result = $ilDB->queryF(
1013            "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",
1014            array('integer'),
1015            array($this->getTestId())
1016        );
1017        $hasSC = false;
1018        while ($row = $ilDB->fetchAssoc($result)) {
1019            if (strcmp($row['foundtypes'], 'assSingleChoice') == 0) {
1020                $hasSC = true;
1021            }
1022        }
1023        return $hasSC;
1024    }
1025
1026    /**
1027    * Returns TRUE if the test contains single choice results only
1028    *
1029    * @return boolean
1030    * @access public
1031    */
1032    public function isSingleChoiceTest()
1033    {
1034        global $DIC;
1035        $ilDB = $DIC['ilDB'];
1036
1037        $result = $ilDB->queryF(
1038            "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",
1039            array('integer'),
1040            array($this->getTestId())
1041        );
1042        if ($result->numRows() == 1) {
1043            $row = $ilDB->fetchAssoc($result);
1044            if (strcmp($row['foundtypes'], 'assSingleChoice') == 0) {
1045                return true;
1046            } else {
1047                return false;
1048            }
1049        }
1050        return false;
1051    }
1052
1053    /**
1054    * Returns TRUE if the test contains single choice results and no shuffle only
1055    *
1056    * @return boolean
1057    * @access public
1058    */
1059    public function isSingleChoiceTestWithoutShuffle()
1060    {
1061        global $DIC;
1062        $ilDB = $DIC['ilDB'];
1063
1064        if (!$this->hasSingleChoiceQuestions()) {
1065            return false;
1066        }
1067
1068        $result = $ilDB->queryF(
1069            "
1070				SELECT	DISTINCT(qpl_qst_sc.shuffle) foundshuffles
1071				FROM	qpl_questions,
1072						qpl_qst_sc,
1073						tst_test_result,
1074						qpl_qst_type,
1075						tst_active
1076				WHERE	tst_test_result.question_fi = qpl_questions.question_id
1077				AND		qpl_questions.question_type_fi = qpl_qst_type.question_type_id
1078				AND		tst_test_result.active_fi = tst_active.active_id
1079				AND		qpl_questions.question_id = qpl_qst_sc.question_fi
1080				AND		tst_active.test_fi = %s
1081				AND		qpl_qst_type.type_tag = %s
1082			",
1083            array('integer', 'text'),
1084            array($this->getTestId(), 'assSingleChoice')
1085        );
1086        if ($result->numRows() == 1) {
1087            $row = $ilDB->fetchAssoc($result);
1088            return ($row['foundshuffles'] == 0);
1089        }
1090        return false;
1091    }
1092
1093    /**
1094     * Returns true, if a test is complete for use and can be set online
1095     *
1096     * @param ilTestQuestionSetConfig $testQuestionSetConfig
1097     * @return boolean
1098     */
1099    final public function isComplete(ilTestQuestionSetConfig $testQuestionSetConfig)
1100    {
1101        if (!count($this->mark_schema->mark_steps)) {
1102            return false;
1103        }
1104
1105        if (!$testQuestionSetConfig->isQuestionSetConfigured()) {
1106            return false;
1107        }
1108
1109        return true;
1110    }
1111
1112    /**
1113    * Returns true, if a test is complete for use
1114    *
1115    * @return boolean True, if the test is complete for use, otherwise false
1116    * @access public
1117    */
1118    public function _isComplete($obj_id)
1119    {
1120        global $DIC;
1121        $tree = $DIC['tree'];
1122        $ilDB = $DIC['ilDB'];
1123        $ilPluginAdmin = $DIC['ilPluginAdmin'];
1124
1125        $test = new ilObjTest($obj_id, false);
1126        $test->loadFromDb();
1127
1128        require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
1129        $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $test);
1130
1131        return $test->isComplete($testQuestionSetConfigFactory->getQuestionSetConfig());
1132    }
1133
1134    /**
1135     * Saves the ECTS status (output of ECTS grades in a test) to the database
1136     */
1137    public function saveECTSStatus()
1138    {
1139        /**
1140         * @var $ilDB ilDBInterface
1141         */
1142        global $DIC;
1143        $ilDB = $DIC['ilDB'];
1144
1145        if ($this->getTestId() > 0) {
1146            $this->setECTSFX(preg_replace('/,/', '.', $this->getECTSFX()));
1147            if (!preg_match('/\d+/', $this->getECTSFX())) {
1148                $this->setECTSFX(null);
1149            }
1150
1151            $grades = $this->getECTSGrades();
1152            $ilDB->manipulateF(
1153                "UPDATE tst_tests
1154				SET ects_output = %s, ects_a = %s, ects_b = %s, ects_c = %s, ects_d = %s, ects_e = %s, ects_fx = %s
1155				WHERE test_id = %s",
1156                array('text', 'float', 'float', 'float', 'float', 'float', 'float', 'integer'),
1157                array(
1158                    (int) $this->getECTSOutput(),
1159                    $grades['A'], $grades['B'], $grades['C'], $grades['D'], $grades['E'],
1160                    $this->getECTSFX(),
1161                    $this->getTestId()
1162                )
1163            );
1164        }
1165    }
1166
1167    /**
1168     * Checks if the test is complete and saves the status in the database
1169     * @param ilTestQuestionSetConfig $testQuestionSetConfig
1170     */
1171    public function saveCompleteStatus(ilTestQuestionSetConfig $testQuestionSetConfig)
1172    {
1173        global $DIC;
1174        $ilDB = $DIC['ilDB'];
1175
1176        $complete = 0;
1177        if ($this->isComplete($testQuestionSetConfig)) {
1178            $complete = 1;
1179        }
1180        if ($this->getTestId() > 0) {
1181            $ilDB->manipulateF(
1182                "UPDATE tst_tests SET complete = %s WHERE test_id = %s",
1183                array('text', 'integer'),
1184                array($complete, $this->test_id)
1185            );
1186        }
1187    }
1188
1189    /**
1190    * Returns the content of all RTE enabled text areas in the test
1191    *
1192    * @access private
1193    */
1194    public function getAllRTEContent()
1195    {
1196        $result = array();
1197        array_push($result, $this->getIntroduction());
1198        array_push($result, $this->getFinalStatement());
1199        return $result;
1200    }
1201
1202    /**
1203    * Cleans up the media objects for all text fields in a test which are using an RTE field
1204    *
1205    * @access private
1206    */
1207    public function cleanupMediaobjectUsage()
1208    {
1209        include_once("./Services/RTE/classes/class.ilRTE.php");
1210        $completecontent = "";
1211        foreach ($this->getAllRTEContent() as $content) {
1212            $completecontent .= $content;
1213        }
1214        ilRTE::_cleanupMediaObjectUsage(
1215            $completecontent,
1216            $this->getType() . ":html",
1217            $this->getId()
1218        );
1219    }
1220
1221    /**
1222     * Saves a ilObjTest object to a database
1223     *
1224     * @param bool $properties_only
1225     */
1226    public function saveToDb($properties_only = false)
1227    {
1228        global $DIC;
1229        $tree = $DIC['tree'];
1230        $ilDB = $DIC['ilDB'];
1231        $ilPluginAdmin = $DIC['ilPluginAdmin'];
1232
1233        // moved online_status to ilObjectActivation (see below)
1234
1235        // cleanup RTE images
1236        $this->cleanupMediaobjectUsage();
1237
1238        require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
1239        $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $this);
1240        $testQuestionSetConfig = $testQuestionSetConfigFactory->getQuestionSetConfig();
1241
1242        include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1243        if ($this->test_id == -1) {
1244            // Create new dataset
1245            $next_id = $ilDB->nextId('tst_tests');
1246
1247            $ilDB->insert('tst_tests', array(
1248                'test_id' => array('integer', $next_id),
1249                'obj_fi' => array('integer', $this->getId()),
1250                'author' => array('text', $this->getAuthor()),
1251                'intro_enabled' => array('integer', (int) $this->isIntroductionEnabled()),
1252                'introduction' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getIntroduction(), 0)),
1253                'finalstatement' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getFinalStatement(), 0)),
1254                'showinfo' => array('integer', $this->getShowInfo()),
1255                'forcejs' => array('integer', $this->getForceJS()),
1256                'customstyle' => array('text', $this->getCustomStyle()),
1257                'showfinalstatement' => array('integer', $this->getShowFinalStatement()),
1258                'sequence_settings' => array('integer', $this->getSequenceSettings()),
1259                'score_reporting' => array('integer', $this->getScoreReporting()),
1260                'instant_verification' => array('text', $this->getInstantFeedbackSolution()),
1261                'answer_feedback_points' => array('text', $this->getAnswerFeedbackPoints()),
1262                'answer_feedback' => array('text', $this->getAnswerFeedback()),
1263                'anonymity' => array('text', $this->getAnonymity()),
1264                'show_cancel' => array('text', $this->getShowCancel()),
1265                'show_marker' => array('integer', $this->getShowMarker()),
1266                'fixed_participants' => array('text', $this->getFixedParticipants()),
1267                'nr_of_tries' => array('integer', $this->getNrOfTries()),
1268                'block_after_passed' => array('integer', (int) $this->isBlockPassesAfterPassedEnabled()),
1269                'kiosk' => array('integer', $this->getKiosk()),
1270                'use_previous_answers' => array('text', $this->getUsePreviousAnswers()),
1271                'title_output' => array('text', $this->getTitleOutput()),
1272                'processing_time' => array('text', $this->getProcessingTime()),
1273                'enable_processing_time' => array('text', $this->getEnableProcessingTime()),
1274                'reset_processing_time' => array('integer', $this->getResetProcessingTime()),
1275                'reporting_date' => array('text', $this->getReportingDate()),
1276                'starting_time_enabled' => array('integer', $this->isStartingTimeEnabled()),
1277                'starting_time' => array('integer', $this->getStartingTime()),
1278                'ending_time_enabled' => array('integer', $this->isEndingTimeEnabled()),
1279                'ending_time' => array('integer', $this->getEndingTime()),
1280                'complete' => array('text', $this->isComplete($testQuestionSetConfig)),
1281                'ects_output' => array('text', $this->getECTSOutput()),
1282                'ects_a' => array('float', strlen($this->ects_grades["A"]) ? $this->ects_grades["A"] : null),
1283                'ects_b' => array('float', strlen($this->ects_grades["B"]) ? $this->ects_grades["B"] : null),
1284                'ects_c' => array('float', strlen($this->ects_grades["C"]) ? $this->ects_grades["C"] : null),
1285                'ects_d' => array('float', strlen($this->ects_grades["D"]) ? $this->ects_grades["D"] : null),
1286                'ects_e' => array('float', strlen($this->ects_grades["E"]) ? $this->ects_grades["E"] : null),
1287                'ects_fx' => array('float', $this->getECTSFX()),
1288                'count_system' => array('text', $this->getCountSystem()),
1289                'mc_scoring' => array('text', $this->getMCScoring()),
1290                'score_cutting' => array('text', $this->getScoreCutting()),
1291                'pass_scoring' => array('text', $this->getPassScoring()),
1292                'shuffle_questions' => array('text', $this->getShuffleQuestions()),
1293                'results_presentation' => array('integer', $this->getResultsPresentation()),
1294                'show_summary' => array('integer', $this->getListOfQuestionsSettings()),
1295                'password_enabled' => array('integer', (int) $this->isPasswordEnabled()),
1296                'password' => array('text', $this->getPassword()),
1297                'limit_users_enabled' => array('integer', (int) $this->isLimitUsersEnabled()),
1298                'allowedusers' => array('integer', $this->getAllowedUsers()),
1299                'alloweduserstimegap' => array('integer', $this->getAllowedUsersTimeGap()),
1300                'mailnottype' => array('integer', $this->getMailNotificationType()),
1301                'exportsettings' => array('integer', $this->getExportSettings()),
1302                'certificate_visibility' => array('text', $this->getCertificateVisibility()),
1303                'mailnotification' => array('integer', $this->getMailNotification()),
1304                'created' => array('integer', time()),
1305                'tstamp' => array('integer', time()),
1306                'enabled_view_mode' => array('text', $this->getEnabledViewMode()),
1307                'template_id' => array('integer', $this->getTemplate()),
1308                'pool_usage' => array('integer', $this->getPoolUsage()),
1309                'print_bs_with_res' => array('integer', (int) $this->isBestSolutionPrintedWithResult()),
1310                'obligations_enabled' => array('integer', (int) $this->areObligationsEnabled()),
1311                'offer_question_hints' => array('integer', (int) $this->isOfferingQuestionHintsEnabled()),
1312                'highscore_enabled' => array('integer', (int) $this->getHighscoreEnabled()),
1313                'highscore_anon' => array('integer', (int) $this->getHighscoreAnon()),
1314                'highscore_achieved_ts' => array('integer', (int) $this->getHighscoreAchievedTS()),
1315                'highscore_score' => array('integer', (int) $this->getHighscoreScore()),
1316                'highscore_percentage' => array('integer', (int) $this->getHighscorePercentage()),
1317                'highscore_hints' => array('integer', (int) $this->getHighscoreHints()),
1318                'highscore_wtime' => array('integer', (int) $this->getHighscoreWTime()),
1319                'highscore_own_table' => array('integer', (int) $this->getHighscoreOwnTable()),
1320                'highscore_top_table' => array('integer', (int) $this->getHighscoreTopTable()),
1321                'highscore_top_num' => array('integer', (int) $this->getHighscoreTopNum()),
1322                'specific_feedback' => array('integer', (int) $this->getSpecificAnswerFeedback()),
1323                'autosave' => array('integer', (int) $this->getAutosave()),
1324                'autosave_ival' => array('integer', (int) $this->getAutosaveIval()),
1325                'pass_deletion_allowed' => array('integer', (int) $this->isPassDeletionAllowed()),
1326                'enable_examview' => array('integer', (int) $this->getEnableExamview()),
1327                'show_examview_html' => array('integer', (int) $this->getShowExamviewHtml()),
1328                'show_examview_pdf' => array('integer', (int) $this->getShowExamviewPdf()),
1329                'redirection_mode' => array('integer', (int) $this->getRedirectionMode()),
1330                'redirection_url' => array('text', (string) $this->getRedirectionUrl()),
1331                'enable_archiving' => array('integer', (int) $this->getEnableArchiving()),
1332                'examid_in_test_pass' => array('integer', (int) $this->isShowExamIdInTestPassEnabled()),
1333                'examid_in_test_res' => array('integer', (int) $this->isShowExamIdInTestResultsEnabled()),
1334                'sign_submission' => array('integer', (int) $this->getSignSubmission()),
1335                'question_set_type' => array('text', $this->getQuestionSetType()),
1336                'char_selector_availability' => array('integer', (int) $this->getCharSelectorAvailability()),
1337                'char_selector_definition' => array('text', (string) $this->getCharSelectorDefinition()),
1338                'skill_service' => array('integer', (int) $this->isSkillServiceEnabled()),
1339                'result_tax_filters' => array('text', serialize((array) $this->getResultFilterTaxIds())),
1340                'show_grading_status' => array('integer', (int) $this->isShowGradingStatusEnabled()),
1341                'show_grading_mark' => array('integer', (int) $this->isShowGradingMarkEnabled()),
1342                'follow_qst_answer_fixation' => array('integer', (int) $this->isFollowupQuestionAnswerFixationEnabled()),
1343                'inst_fb_answer_fixation' => array('integer', (int) $this->isInstantFeedbackAnswerFixationEnabled()),
1344                'force_inst_fb' => array('integer', (int) $this->isForceInstantFeedbackEnabled()),
1345                'broken' => array('integer', (int) $this->isTestFinalBroken()),
1346                'pass_waiting' => array('text', (string) $this->getPassWaiting())
1347            ));
1348
1349            $this->test_id = $next_id;
1350
1351            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
1352                $this->logAction($this->lng->txtlng("assessment", "log_create_new_test", ilObjAssessmentFolder::_getLogLanguage()));
1353            }
1354        } else {
1355            // Modify existing dataset
1356            $oldrow = array();
1357            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
1358                $result = $ilDB->queryF(
1359                    "SELECT * FROM tst_tests WHERE test_id = %s",
1360                    array('integer'),
1361                    array($this->test_id)
1362                );
1363                if ($result->numRows() == 1) {
1364                    $oldrow = $ilDB->fetchAssoc($result);
1365                }
1366            }
1367
1368            $ilDB->update(
1369                'tst_tests',
1370                array(
1371                        'author' => array('text', $this->getAuthor()),
1372                        'intro_enabled' => array('integer', (int) $this->isIntroductionEnabled()),
1373                        'introduction' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getIntroduction(), 0)),
1374                        'finalstatement' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getFinalStatement(), 0)),
1375                        'showinfo' => array('integer', $this->getShowInfo()),
1376                        'forcejs' => array('integer', $this->getForceJS()),
1377                        'customstyle' => array('text', $this->getCustomStyle()),
1378                        'showfinalstatement' => array('integer', $this->getShowFinalStatement()),
1379                        'sequence_settings' => array('integer', $this->getSequenceSettings()),
1380                        'score_reporting' => array('integer', $this->getScoreReporting()),
1381                        'instant_verification' => array('text', $this->getInstantFeedbackSolution()),
1382                        'answer_feedback_points' => array('text', $this->getAnswerFeedbackPoints()),
1383                        'answer_feedback' => array('text', $this->getGenericAnswerFeedback()),
1384                        'anonymity' => array('text', $this->getAnonymity()),
1385                        'show_cancel' => array('text', $this->getShowCancel()),
1386                        'show_marker' => array('integer', $this->getShowMarker()),
1387                        'fixed_participants' => array('text', $this->getFixedParticipants()),
1388                        'nr_of_tries' => array('integer', $this->getNrOfTries()),
1389                        'block_after_passed' => array('integer', (int) $this->isBlockPassesAfterPassedEnabled()),
1390                        'kiosk' => array('integer', $this->getKiosk()),
1391                        'use_previous_answers' => array('text', $this->getUsePreviousAnswers()),
1392                        'title_output' => array('text', $this->getTitleOutput()),
1393                        'processing_time' => array('text', $this->getProcessingTime()),
1394                        'enable_processing_time' => array('text', $this->getEnableProcessingTime()),
1395                        'reset_processing_time' => array('integer', $this->getResetProcessingTime()),
1396                        'reporting_date' => array('text', $this->getReportingDate()),
1397                        'starting_time_enabled' => array('integer', $this->isStartingTimeEnabled()),
1398                        'starting_time' => array('integer', $this->getStartingTime()),
1399                        'ending_time_enabled' => array('integer', $this->isEndingTimeEnabled()),
1400                        'ending_time' => array('integer', $this->getEndingTime()),
1401                        'complete' => array('text', $this->isComplete($testQuestionSetConfig)),
1402                        'ects_output' => array('text', $this->getECTSOutput()),
1403                        'ects_a' => array('float', strlen($this->ects_grades["A"]) ? $this->ects_grades["A"] : null),
1404                        'ects_b' => array('float', strlen($this->ects_grades["B"]) ? $this->ects_grades["B"] : null),
1405                        'ects_c' => array('float', strlen($this->ects_grades["C"]) ? $this->ects_grades["C"] : null),
1406                        'ects_d' => array('float', strlen($this->ects_grades["D"]) ? $this->ects_grades["D"] : null),
1407                        'ects_e' => array('float', strlen($this->ects_grades["E"]) ? $this->ects_grades["E"] : null),
1408                        'ects_fx' => array('float', $this->getECTSFX()),
1409                        'count_system' => array('text', $this->getCountSystem()),
1410                        'mc_scoring' => array('text', $this->getMCScoring()),
1411                        'score_cutting' => array('text', $this->getScoreCutting()),
1412                        'pass_scoring' => array('text', $this->getPassScoring()),
1413                        'shuffle_questions' => array('text', $this->getShuffleQuestions()),
1414                        'results_presentation' => array('integer', $this->getResultsPresentation()),
1415                        'show_summary' => array('integer', $this->getListOfQuestionsSettings()),
1416                        'password_enabled' => array('integer', (int) $this->isPasswordEnabled()),
1417                        'password' => array('text', $this->getPassword()),
1418                        'limit_users_enabled' => array('integer', (int) $this->isLimitUsersEnabled()),
1419                        'allowedusers' => array('integer', $this->getAllowedUsers()),
1420                        'alloweduserstimegap' => array('integer', $this->getAllowedUsersTimeGap()),
1421                        'mailnottype' => array('integer', $this->getMailNotificationType()),
1422                        'exportsettings' => array('integer', $this->getExportSettings()),
1423                        'certificate_visibility' => array('text', $this->getCertificateVisibility()),
1424                        'mailnotification' => array('integer', $this->getMailNotification()),
1425                        'tstamp' => array('integer', time()),
1426                        'enabled_view_mode' => array('text', $this->getEnabledViewMode()),
1427                        'template_id' => array('integer', $this->getTemplate()),
1428                        'pool_usage' => array('integer', $this->getPoolUsage()),
1429                        'print_bs_with_res' => array('integer', (int) $this->isBestSolutionPrintedWithResult()),
1430                        'obligations_enabled' => array('integer', (int) $this->areObligationsEnabled()),
1431                        'offer_question_hints' => array('integer', (int) $this->isOfferingQuestionHintsEnabled()),
1432                        'highscore_enabled' => array('integer', (int) $this->getHighscoreEnabled()),
1433                        'highscore_anon' => array('integer', (int) $this->getHighscoreAnon()),
1434                        'highscore_achieved_ts' => array('integer', (int) $this->getHighscoreAchievedTS()),
1435                        'highscore_score' => array('integer', (int) $this->getHighscoreScore()),
1436                        'highscore_percentage' => array('integer', (int) $this->getHighscorePercentage()),
1437                        'highscore_hints' => array('integer', (int) $this->getHighscoreHints()),
1438                        'highscore_wtime' => array('integer', (int) $this->getHighscoreWTime()),
1439                        'highscore_own_table' => array('integer', (int) $this->getHighscoreOwnTable()),
1440                        'highscore_top_table' => array('integer', (int) $this->getHighscoreTopTable()),
1441                        'highscore_top_num' => array('integer', (int) $this->getHighscoreTopNum()),
1442                        'specific_feedback' => array('integer', (int) $this->getSpecificAnswerFeedback()),
1443                        'autosave' => array('integer', (int) $this->getAutosave()),
1444                        'autosave_ival' => array('integer', (int) $this->getAutosaveIval()),
1445                        'pass_deletion_allowed' => array('integer', (int) $this->isPassDeletionAllowed()),
1446                        'enable_examview' => array('integer', (int) $this->getEnableExamview()),
1447                        'show_examview_html' => array('integer', (int) $this->getShowExamviewHtml()),
1448                        'show_examview_pdf' => array('integer', (int) $this->getShowExamviewPdf()),
1449                        'redirection_mode' => array('integer', (int) $this->getRedirectionMode()),
1450                        'redirection_url' => array('text', (string) $this->getRedirectionUrl()),
1451                        'enable_archiving' => array('integer', (int) $this->getEnableArchiving()),
1452                        'examid_in_test_pass' => array('integer', (int) $this->isShowExamIdInTestPassEnabled()),
1453                        'examid_in_test_res' => array('integer', (int) $this->isShowExamIdInTestResultsEnabled()),
1454                        'sign_submission' => array('integer', (int) $this->getSignSubmission()),
1455                        'question_set_type' => array('text', $this->getQuestionSetType()),
1456                        'char_selector_availability' => array('integer', (int) $this->getCharSelectorAvailability()),
1457                        'char_selector_definition' => array('text', (string) $this->getCharSelectorDefinition()),
1458                        'skill_service' => array('integer', (int) $this->isSkillServiceEnabled()),
1459                        'result_tax_filters' => array('text', serialize((array) $this->getResultFilterTaxIds())),
1460                        'show_grading_status' => array('integer', (int) $this->isShowGradingStatusEnabled()),
1461                        'show_grading_mark' => array('integer', (int) $this->isShowGradingMarkEnabled()),
1462                        'follow_qst_answer_fixation' => array('integer', (int) $this->isFollowupQuestionAnswerFixationEnabled()),
1463                        'inst_fb_answer_fixation' => array('integer', (int) $this->isInstantFeedbackAnswerFixationEnabled()),
1464                        'force_inst_fb' => array('integer', (int) $this->isForceInstantFeedbackEnabled()),
1465                        'broken' => array('integer', (int) $this->isTestFinalBroken()),
1466                        'pass_waiting' => array('text', (string) $this->getPassWaiting())
1467                    ),
1468                array(
1469                        'test_id' => array('integer', (int) $this->getTestId())
1470                    )
1471            );
1472
1473            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1474            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
1475                $logresult = $ilDB->queryF(
1476                    "SELECT * FROM tst_tests WHERE test_id = %s",
1477                    array('integer'),
1478                    array($this->getTestId())
1479                );
1480                $newrow = array();
1481                if ($logresult->numRows() == 1) {
1482                    $newrow = $ilDB->fetchAssoc($logresult);
1483                }
1484                $changed_fields = array();
1485                foreach ($oldrow as $key => $value) {
1486                    if (strcmp($oldrow[$key], $newrow[$key]) != 0) {
1487                        array_push($changed_fields, "$key: " . $oldrow[$key] . " => " . $newrow[$key]);
1488                    }
1489                }
1490                $changes = join(", ", $changed_fields);
1491                if (count($changed_fields) > 0) {
1492                    $this->logAction($this->lng->txtlng("assessment", "log_modified_test", ilObjAssessmentFolder::_getLogLanguage()) . " [" . $changes . "]");
1493                }
1494            }
1495            if ($this->evalTotalPersons() > 0) {
1496                // reset the finished status of participants if the nr of test passes did change
1497                if ($this->getNrOfTries() > 0) {
1498                    // set all unfinished tests with nr of passes >= allowed passes finished
1499                    $aresult = $ilDB->queryF(
1500                        "SELECT active_id FROM tst_active WHERE test_fi = %s AND tries >= %s AND submitted = %s",
1501                        array('integer', 'integer', 'integer'),
1502                        array($this->getTestId(), $this->getNrOfTries(), 0)
1503                    );
1504                    while ($row = $ilDB->fetchAssoc($aresult)) {
1505                        $ilDB->manipulateF(
1506                            "UPDATE tst_active SET submitted = %s, submittimestamp = %s WHERE active_id = %s",
1507                            array('integer', 'timestamp', 'integer'),
1508                            array(1, date('Y-m-d H:i:s'), $row["active_id"])
1509                        );
1510                    }
1511
1512                    // set all finished tests with nr of passes < allowed passes not finished
1513                    $aresult = $ilDB->queryF(
1514                        "SELECT active_id FROM tst_active WHERE test_fi = %s AND tries < %s AND submitted = %s",
1515                        array('integer', 'integer', 'integer'),
1516                        array($this->getTestId(), $this->getNrOfTries() - 1, 1)
1517                    );
1518                    while ($row = $ilDB->fetchAssoc($aresult)) {
1519                        $ilDB->manipulateF(
1520                            "UPDATE tst_active SET submitted = %s, submittimestamp = %s WHERE active_id = %s",
1521                            array('integer', 'timestamp', 'integer'),
1522                            array(0, null, $row["active_id"])
1523                        );
1524                    }
1525                } else {
1526                    // set all finished tests with nr of passes >= allowed passes not finished
1527                    $aresult = $ilDB->queryF(
1528                        "SELECT active_id FROM tst_active WHERE test_fi = %s AND submitted = %s",
1529                        array('integer', 'integer'),
1530                        array($this->getTestId(), 1)
1531                    );
1532                    while ($row = $ilDB->fetchAssoc($aresult)) {
1533                        $ilDB->manipulateF(
1534                            "UPDATE tst_active SET submitted = %s, submittimestamp = %s WHERE active_id = %s",
1535                            array('integer', 'timestamp', 'integer'),
1536                            array(0, null, $row["active_id"])
1537                        );
1538                    }
1539                }
1540            }
1541        }
1542
1543        // news item creation/update/deletion
1544        include_once 'Services/News/classes/class.ilNewsItem.php';
1545        if (!$this->getOldOnlineStatus() && !$this->getOfflineStatus()) {
1546            global $DIC;
1547            $ilUser = $DIC['ilUser'];
1548            $newsItem = new ilNewsItem();
1549            $newsItem->setContext($this->getId(), 'tst');
1550            $newsItem->setPriority(NEWS_NOTICE);
1551            $newsItem->setTitle('new_test_online');
1552            $newsItem->setContentIsLangVar(true);
1553            $newsItem->setContent('');
1554            $newsItem->setUserId($ilUser->getId());
1555            $newsItem->setVisibility(NEWS_USERS);
1556            $newsItem->create();
1557        } elseif ($this->getOldOnlineStatus() && !$this->getOfflineStatus()) {
1558            ilNewsItem::deleteNewsOfContext($this->getId(), 'tst');
1559        } elseif (!$this->getOfflineStatus()) {
1560            $newsId = ilNewsItem::getFirstNewsIdForContext($this->getId(), 'tst');
1561            if ($newsId > 0) {
1562                $newsItem = new ilNewsItem($newsId);
1563                $newsItem->setTitle('new_test_online');
1564                $newsItem->setContentIsLangVar(true);
1565                $newsItem->setContent('');
1566                $newsItem->update();
1567            }
1568        }
1569
1570        // moved activation to ilObjectActivation
1571        if ($this->ref_id) {
1572            include_once "./Services/Object/classes/class.ilObjectActivation.php";
1573            ilObjectActivation::getItem($this->ref_id);
1574
1575            $item = new ilObjectActivation;
1576            if (!$this->isActivationLimited()) {
1577                $item->setTimingType(ilObjectActivation::TIMINGS_DEACTIVATED);
1578            } else {
1579                $item->setTimingType(ilObjectActivation::TIMINGS_ACTIVATION);
1580                $item->setTimingStart($this->getActivationStartingTime());
1581                $item->setTimingEnd($this->getActivationEndingTime());
1582                $item->toggleVisible($this->getActivationVisibility());
1583            }
1584
1585            $item->update($this->ref_id);
1586        }
1587
1588        if (!$properties_only) {
1589            if ($this->getQuestionSetType() == self::QUESTION_SET_TYPE_FIXED) {
1590                $this->saveQuestionsToDb();
1591            }
1592
1593            $this->mark_schema->saveToDb($this->test_id);
1594        }
1595    }
1596
1597    /**
1598    * Saves the test questions to the database
1599    *
1600    * @access public
1601    * @see $questions
1602    */
1603    public function saveQuestionsToDb()
1604    {
1605        global $DIC;
1606        $ilDB = $DIC['ilDB'];
1607
1608        $oldquestions = array();
1609        include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
1610        if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
1611            $result = $ilDB->queryF(
1612                "SELECT question_fi FROM tst_test_question WHERE test_fi = %s ORDER BY sequence",
1613                array('integer'),
1614                array($this->getTestId())
1615            );
1616            if ($result->numRows() > 0) {
1617                while ($row = $ilDB->fetchAssoc($result)) {
1618                    array_push($oldquestions, $row["question_fi"]);
1619                }
1620            }
1621        }
1622        // workaround for lost obligations
1623        // this method is called if a question is removed
1624        $currentQuestionsObligationsQuery = 'SELECT question_fi, obligatory FROM tst_test_question WHERE test_fi = %s';
1625        $rset = $ilDB->queryF($currentQuestionsObligationsQuery, array('integer'), array($this->getTestId()));
1626        while ($row = $ilDB->fetchAssoc($rset)) {
1627            $obligatoryQuestionState[$row['question_fi']] = $row['obligatory'];
1628        }
1629        // delete existing category relations
1630        $affectedRows = $ilDB->manipulateF(
1631            "DELETE FROM tst_test_question WHERE test_fi = %s",
1632            array('integer'),
1633            array($this->getTestId())
1634        );
1635        // create new category relations
1636        foreach ($this->questions as $key => $value) {
1637            // workaround for import witout obligations information
1638            if (!isset($obligatoryQuestionState[$value]) || is_null($obligatoryQuestionState[$value])) {
1639                $obligatoryQuestionState[$value] = 0;
1640            }
1641
1642            // insert question
1643            $next_id = $ilDB->nextId('tst_test_question');
1644            $ilDB->insert('tst_test_question', array(
1645                'test_question_id' => array('integer', $next_id),
1646                'test_fi' => array('integer', $this->getTestId()),
1647                'question_fi' => array('integer', $value),
1648                'sequence' => array('integer', $key),
1649                'obligatory' => array('integer', $obligatoryQuestionState[$value]),
1650                'tstamp' => array('integer', time())
1651            ));
1652        }
1653        include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1654        if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
1655            $result = $ilDB->queryF(
1656                "SELECT question_fi FROM tst_test_question WHERE test_fi = %s ORDER BY sequence",
1657                array('integer'),
1658                array($this->getTestId())
1659            );
1660            $newquestions = array();
1661            if ($result->numRows() > 0) {
1662                while ($row = $ilDB->fetchAssoc($result)) {
1663                    array_push($newquestions, $row["question_fi"]);
1664                }
1665            }
1666            foreach ($oldquestions as $index => $question_id) {
1667                if (strcmp($newquestions[$index], $question_id) != 0) {
1668                    $pos = array_search($question_id, $newquestions);
1669                    if ($pos === false) {
1670                        $this->logAction($this->lng->txtlng("assessment", "log_question_removed", ilObjAssessmentFolder::_getLogLanguage()), $question_id);
1671                    } else {
1672                        $this->logAction($this->lng->txtlng("assessment", "log_question_position_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . ($index + 1) . " => " . ($pos + 1), $question_id);
1673                    }
1674                }
1675            }
1676            foreach ($newquestions as $index => $question_id) {
1677                if (array_search($question_id, $oldquestions) === false) {
1678                    $this->logAction($this->lng->txtlng("assessment", "log_question_added", ilObjAssessmentFolder::_getLogLanguage()) . ": " . ($index + 1), $question_id);
1679                }
1680            }
1681        }
1682    }
1683
1684    /**
1685     * Checks wheather the test is a new random test (using tst_rnd_cpy) or an old one
1686     *
1687     * @deprecated --> old school random test
1688     */
1689    protected function isNewRandomTest()
1690    {
1691        global $DIC;
1692        $ilDB = $DIC['ilDB'];
1693        $result = $ilDB->queryF(
1694            'SELECT copy_id FROM tst_rnd_cpy WHERE tst_fi = %s',
1695            array('integer'),
1696            array($this->getTestId())
1697        );
1698        return $result->numRows() > 0;
1699    }
1700
1701    /**
1702     * Returns a random selection of questions
1703     *
1704     * @param integer $nr_of_questions Number of questions to return
1705     * @param integer $questionpool ID of questionpool to choose the questions from (0 = all available questionpools)
1706     * @param boolean $user_obj_id Use the object id instead of the reference id when set to true
1707     * @param array $qpls An array of questionpool id's if the random questions should only be chose from the contained questionpools
1708     * @return array A random selection of questions
1709     * @access public
1710     *
1711     * @deprecated --> old school random test
1712     */
1713    public function randomSelectQuestions($nr_of_questions, $questionpool, $use_obj_id = 0, $qpls = "", $pass = null)
1714    {
1715        global $DIC;
1716        $rbacsystem = $DIC['rbacsystem'];
1717        $ilDB = $DIC['ilDB'];
1718
1719        // retrieve object id instead of ref id if necessary
1720        if (($questionpool != 0) && (!$use_obj_id)) {
1721            $questionpool = ilObject::_lookupObjId($questionpool);
1722        }
1723
1724        // get original ids of all existing questions in the test
1725        $result = $ilDB->queryF(
1726            "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",
1727            array("integer"),
1728            array($this->getTestId())
1729        );
1730        $original_ids = array();
1731        $paramtypes = array();
1732        $paramvalues = array();
1733        while ($row = $ilDB->fetchAssoc($result)) {
1734            array_push($original_ids, $row['original_id']);
1735        }
1736
1737        $available = "";
1738        // get a list of all available questionpools
1739        if (($questionpool == 0) && (!is_array($qpls))) {
1740            include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
1741            $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())));
1742            if (count($available_pools)) {
1743                $available = " AND " . $ilDB->in('obj_fi', $available_pools, false, 'integer');
1744            } else {
1745                return array();
1746            }
1747        }
1748
1749        $constraint_qpls = "";
1750        $result_array = array();
1751        if ($questionpool == 0) {
1752            if (is_array($qpls)) {
1753                if (count($qpls) > 0) {
1754                    $constraint_qpls = " AND " . $ilDB->in('obj_fi', $qpls, false, 'integer');
1755                }
1756            }
1757        }
1758
1759        $original_clause = "";
1760        if (count($original_ids)) {
1761            $original_clause = " AND " . $ilDB->in('question_id', $original_ids, true, 'integer');
1762        }
1763
1764        if ($questionpool == 0) {
1765            $result = $ilDB->queryF(
1766                "SELECT question_id FROM qpl_questions WHERE original_id IS NULL $available $constraint_qpls AND owner > %s AND complete = %s $original_clause",
1767                array('integer', 'text'),
1768                array(0, "1")
1769            );
1770        } else {
1771            $result = $ilDB->queryF(
1772                "SELECT question_id FROM qpl_questions WHERE original_id IS NULL AND obj_fi = %s AND owner > %s AND complete = %s $original_clause",
1773                array('integer','integer', 'text'),
1774                array($questionpool, 0, "1")
1775            );
1776        }
1777        $found_ids = array();
1778        while ($row = $ilDB->fetchAssoc($result)) {
1779            array_push($found_ids, $row['question_id']);
1780        }
1781        $nr_of_questions = ($nr_of_questions > count($found_ids)) ? count($found_ids) : $nr_of_questions;
1782        if ($nr_of_questions == 0) {
1783            return array();
1784        }
1785        $rand_keys = array_rand($found_ids, $nr_of_questions);
1786        $result = array();
1787        if (is_array($rand_keys)) {
1788            foreach ($rand_keys as $key) {
1789                $result[$found_ids[$key]] = $found_ids[$key];
1790            }
1791        } else {
1792            $result[$found_ids[$rand_keys]] = $found_ids[$rand_keys];
1793        }
1794        return $result;
1795    }
1796
1797    /**
1798     * Calculates the number of user results for a specific test pass
1799     *
1800     * @access private
1801     *
1802     * @deprecated: still in use?
1803     */
1804    public function getNrOfResultsForPass($active_id, $pass)
1805    {
1806        global $DIC;
1807        $ilDB = $DIC['ilDB'];
1808
1809        $result = $ilDB->queryF(
1810            "SELECT test_result_id FROM tst_test_result WHERE active_fi = %s AND pass = %s",
1811            array('integer','integer'),
1812            array($active_id, $pass)
1813        );
1814        return $result->numRows();
1815    }
1816
1817    /**
1818     * Checkes wheather a random test has already created questions for a given pass or not
1819     *
1820     * @access private
1821     * @param $active_id Active id of the test
1822     * @param $pass Pass of the test
1823     * @return boolean TRUE if the test already contains questions, FALSE otherwise
1824     *
1825     * @deprecated: still in use?
1826     */
1827    public function hasRandomQuestionsForPass($active_id, $pass)
1828    {
1829        global $DIC;
1830        $ilDB = $DIC['ilDB'];
1831        $result = $ilDB->queryF(
1832            "SELECT test_random_question_id FROM tst_test_rnd_qst WHERE active_fi = %s AND pass = %s",
1833            array('integer','integer'),
1834            array($active_id, $pass)
1835        );
1836        return ($result->numRows() > 0) ? true : false;
1837    }
1838
1839    /**
1840     * Loads a ilObjTest object from a database
1841     */
1842    public function loadFromDb()
1843    {
1844        global $DIC;
1845        $ilDB = $DIC['ilDB'];
1846
1847        $result = $ilDB->queryF(
1848            "SELECT * FROM tst_tests WHERE obj_fi = %s",
1849            array('integer'),
1850            array($this->getId())
1851        );
1852        if ($result->numRows() == 1) {
1853            $data = $ilDB->fetchObject($result);
1854            $this->setTestId($data->test_id);
1855            if (strlen($this->getAuthor()) == 0) {
1856                $this->saveAuthorToMetadata($data->author);
1857            }
1858            $this->setAuthor($data->author);
1859            include_once("./Services/RTE/classes/class.ilRTE.php");
1860            $this->setIntroductionEnabled($data->intro_enabled);
1861            $this->setIntroduction(ilRTE::_replaceMediaObjectImageSrc($data->introduction, 1));
1862            $this->setShowInfo($data->showinfo);
1863            $this->setFinalStatement(ilRTE::_replaceMediaObjectImageSrc($data->finalstatement, 1));
1864            $this->setForceJS($data->forcejs);
1865            $this->setCustomStyle($data->customstyle);
1866            $this->setShowFinalStatement($data->showfinalstatement);
1867            $this->setSequenceSettings($data->sequence_settings);
1868            $this->setScoreReporting($data->score_reporting);
1869            $this->setInstantFeedbackSolution($data->instant_verification);
1870            $this->setAnswerFeedbackPoints($data->answer_feedback_points);
1871            $this->setAnswerFeedback($data->answer_feedback);
1872            $this->setAnonymity($data->anonymity);
1873            $this->setShowCancel($data->show_cancel);
1874            $this->setShowMarker($data->show_marker);
1875            $this->setFixedParticipants($data->fixed_participants);
1876            $this->setNrOfTries($data->nr_of_tries);
1877            $this->setBlockPassesAfterPassedEnabled((bool) $data->block_after_passed);
1878            $this->setKiosk($data->kiosk);
1879            $this->setUsePreviousAnswers($data->use_previous_answers);
1880            $this->setRedirectionMode($data->redirection_mode);
1881            $this->setRedirectionUrl($data->redirection_url);
1882            $this->setTitleOutput($data->title_output);
1883            $this->setProcessingTime($data->processing_time);
1884            $this->setEnableProcessingTime($data->enable_processing_time);
1885            $this->setResetProcessingTime($data->reset_processing_time);
1886            $this->setReportingDate($data->reporting_date);
1887            $this->setShuffleQuestions($data->shuffle_questions);
1888            $this->setResultsPresentation($data->results_presentation);
1889            $this->setStartingTimeEnabled($data->starting_time_enabled);
1890            $this->setStartingTime($data->starting_time);
1891            $this->setEndingTimeEnabled($data->ending_time_enabled);
1892            $this->setEndingTime($data->ending_time);
1893            $this->setListOfQuestionsSettings($data->show_summary);
1894            $this->setECTSOutput($data->ects_output);
1895            $this->setECTSGrades(
1896                array(
1897                    "A" => $data->ects_a,
1898                    "B" => $data->ects_b,
1899                    "C" => $data->ects_c,
1900                    "D" => $data->ects_d,
1901                    "E" => $data->ects_e
1902                )
1903            );
1904            $this->setECTSFX($data->ects_fx);
1905            $this->mark_schema->flush();
1906            $this->mark_schema->loadFromDb($this->getTestId());
1907            $this->setCountSystem($data->count_system);
1908            $this->setMCScoring($data->mc_scoring);
1909            $this->setMailNotification($data->mailnotification);
1910            $this->setMailNotificationType($data->mailnottype);
1911            $this->setExportSettings($data->exportsettings);
1912            $this->setScoreCutting($data->score_cutting);
1913            $this->setPasswordEnabled($data->password_enabled);
1914            $this->setPassword($data->password);
1915            $this->setLimitUsersEnabled($data->limit_users_enabled);
1916            $this->setAllowedUsers($data->allowedusers);
1917            $this->setAllowedUsersTimeGap($data->alloweduserstimegap);
1918            $this->setPassScoring($data->pass_scoring);
1919            $this->setObligationsEnabled($data->obligations_enabled);
1920            $this->setOfferingQuestionHintsEnabled($data->offer_question_hints);
1921            $this->setCertificateVisibility($data->certificate_visibility);
1922            $this->setEnabledViewMode($data->enabled_view_mode);
1923            $this->setTemplate($data->template_id);
1924            $this->setPoolUsage($data->pool_usage);
1925            $this->setPrintBestSolutionWithResult((bool) $data->print_bs_with_res);
1926            $this->setHighscoreEnabled((bool) $data->highscore_enabled);
1927            $this->setHighscoreAnon((bool) $data->highscore_anon);
1928            $this->setHighscoreAchievedTS((bool) $data->highscore_achieved_ts);
1929            $this->setHighscoreScore((bool) $data->highscore_score);
1930            $this->setHighscorePercentage((bool) $data->highscore_percentage);
1931            $this->setHighscoreHints((bool) $data->highscore_hints);
1932            $this->setHighscoreWTime((bool) $data->highscore_wtime);
1933            $this->setHighscoreOwnTable((bool) $data->highscore_own_table);
1934            $this->setHighscoreTopTable((bool) $data->highscore_top_table);
1935            $this->setHighscoreTopNum((int) $data->highscore_top_num);
1936            $this->setOldOnlineStatus((bool) !$this->getOfflineStatus());
1937            $this->setSpecificAnswerFeedback((int) $data->specific_feedback);
1938            $this->setAutosave((bool) $data->autosave);
1939            $this->setAutosaveIval((int) $data->autosave_ival);
1940            $this->setPassDeletionAllowed($data->pass_deletion_allowed);
1941            $this->setEnableExamview((bool) $data->enable_examview);
1942            $this->setShowExamviewHtml((bool) $data->show_examview_html);
1943            $this->setShowExamviewPdf((bool) $data->show_examview_pdf);
1944            $this->setEnableArchiving((bool) $data->enable_archiving);
1945            $this->setShowExamIdInTestPassEnabled((bool) $data->examid_in_test_pass);
1946            $this->setShowExamIdInTestResultsEnabled((bool) $data->examid_in_test_res);
1947            $this->setSignSubmission((bool) $data->sign_submission);
1948            $this->setQuestionSetType($data->question_set_type);
1949            $this->setCharSelectorAvailability((int) $data->char_selector_availability);
1950            $this->setCharSelectorDefinition($data->char_selector_definition);
1951            $this->setSkillServiceEnabled((bool) $data->skill_service);
1952            $this->setResultFilterTaxIds(strlen($data->result_tax_filters) ? unserialize($data->result_tax_filters) : array());
1953            $this->setShowGradingStatusEnabled((bool) $data->show_grading_status);
1954            $this->setShowGradingMarkEnabled((bool) $data->show_grading_mark);
1955            $this->setFollowupQuestionAnswerFixationEnabled((bool) $data->follow_qst_answer_fixation);
1956            $this->setInstantFeedbackAnswerFixationEnabled((bool) $data->inst_fb_answer_fixation);
1957            $this->setForceInstantFeedbackEnabled((bool) $data->force_inst_fb);
1958            $this->setTestFinalBroken((bool) $data->broken);
1959            $this->setPassWaiting($data->pass_waiting);
1960            $this->loadQuestions();
1961        }
1962
1963        // moved activation to ilObjectActivation
1964        if ($this->ref_id) {
1965            include_once "./Services/Object/classes/class.ilObjectActivation.php";
1966            $activation = ilObjectActivation::getItem($this->ref_id);
1967            switch ($activation["timing_type"]) {
1968                case ilObjectActivation::TIMINGS_ACTIVATION:
1969                    $this->setActivationLimited(true);
1970                    $this->setActivationStartingTime($activation["timing_start"]);
1971                    $this->setActivationEndingTime($activation["timing_end"]);
1972                    $this->setActivationVisibility($activation["visible"]);
1973                    break;
1974
1975                default:
1976                    $this->setActivationLimited(false);
1977                    break;
1978            }
1979        }
1980    }
1981
1982    /**
1983    * Load the test question id's from the database
1984    *
1985    * @param integer $user_id The user id of the test user (necessary for random tests)
1986    * @access	public
1987    */
1988    public function loadQuestions($active_id = "", $pass = null)
1989    {
1990        global $DIC;
1991        $ilUser = $DIC['ilUser'];
1992        $ilDB = $DIC['ilDB'];
1993
1994        $this->questions = array();
1995        if ($this->isRandomTest()) {
1996            if (strcmp($active_id, "") == 0) {
1997                $active_id = $this->getActiveIdOfUser($ilUser->getId());
1998            }
1999            if (is_null($pass)) {
2000                $pass = self::_getPass($active_id);
2001            }
2002            $result = $ilDB->queryF(
2003                "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",
2004                array('integer', 'integer'),
2005                array($active_id, $pass)
2006        );
2007            // The following is a fix for random tests prior to ILIAS 3.8. If someone started a random test in ILIAS < 3.8, there
2008            // is only one test pass (pass = 0) in tst_test_rnd_qst while with ILIAS 3.8 there are questions for every test pass.
2009            // To prevent problems with tests started in an older version and continued in ILIAS 3.8, the first pass should be taken if
2010            // no questions are present for a newer pass.
2011            if ($result->numRows() == 0) {
2012                $result = $ilDB->queryF(
2013                    "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",
2014                    array('integer'),
2015                    array($active_id)
2016            );
2017            }
2018        } else {
2019            $result = $ilDB->queryF(
2020                "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",
2021                array('integer'),
2022                array($this->test_id)
2023        );
2024        }
2025        $index = 1;
2026        while ($data = $ilDB->fetchAssoc($result)) {
2027            $this->questions[$index++] = $data["question_fi"];
2028        }
2029    }
2030
2031    /**
2032     * @return boolean
2033     */
2034    public function isIntroductionEnabled()
2035    {
2036        return $this->introductionEnabled;
2037    }
2038
2039    /**
2040     * @param boolean $introductionEnabled
2041     */
2042    public function setIntroductionEnabled($introductionEnabled)
2043    {
2044        $this->introductionEnabled = $introductionEnabled;
2045    }
2046
2047    /**
2048     * Gets the introduction text of the ilObjTest object
2049     *
2050     * @return mixed The introduction text of the test, NULL if empty
2051     * @see $introduction
2052     */
2053    public function getIntroduction()
2054    {
2055        return (strlen($this->introduction)) ? $this->introduction : null;
2056    }
2057
2058    /**
2059     * Sets the introduction text of the ilObjTest object
2060     *
2061     * @param string $introduction An introduction string for the test
2062     * @access public
2063     * @see $introduction
2064     */
2065    public function setIntroduction($introduction = "")
2066    {
2067        $this->introduction = $introduction;
2068    }
2069
2070
2071    /**
2072    * Sets the final statement text of the ilObjTest object
2073    *
2074    * @param string $a_statement A final statement
2075    * @access public
2076    * @see $_finalstatement
2077    */
2078    public function setFinalStatement($a_statement = "")
2079    {
2080        $this->_finalstatement = $a_statement;
2081    }
2082
2083    /**
2084    * Set whether the complete information page is shown or the required data only
2085    *
2086    * @param integer $a_info 1 for the complete information, 0 otherwise
2087    * @access public
2088    * @see $_showinfo
2089    */
2090    public function setShowInfo($a_info = 1)
2091    {
2092        $this->_showinfo = ($a_info) ? 1 : 0;
2093    }
2094
2095    /**
2096    * Set whether JavaScript should be forced for tests
2097    *
2098    * @param integer $a_js 1 to force JavaScript, 0 otherwise
2099    * @access public
2100    * @see $_forcejs
2101    */
2102    public function setForceJS($a_js = 1)
2103    {
2104        $this->_forcejs = ($a_js) ? 1 : 0;
2105    }
2106
2107    /**
2108    * Set the custom style
2109    *
2110    * @param string $a_customStyle The custom style
2111    * @access public
2112    * @see $_customStyle
2113    */
2114    public function setCustomStyle($a_customStyle = null)
2115    {
2116        $this->_customStyle = $a_customStyle;
2117    }
2118
2119    /**
2120    * Get the custom style
2121    *
2122    * @return mixed The custom style, NULL if empty
2123    * @access public
2124    * @see $_customStyle
2125    */
2126    public function getCustomStyle()
2127    {
2128        return (strlen($this->_customStyle)) ? $this->_customStyle : null;
2129    }
2130
2131    /**
2132    * Return the available custom styles
2133    *
2134    * @return array An array of strings containing the available custom styles
2135    * @access public
2136    * @see $_customStyle
2137    */
2138    public function getCustomStyles()
2139    {
2140        $css_path = ilUtil::getStyleSheetLocation("filesystem", "ta.css", "Modules/Test");
2141        $css_path = str_replace("ta.css", "customstyles", $css_path) . "/";
2142        $customstyles = array();
2143        if (is_dir($css_path)) {
2144            $results = array();
2145            include_once "./Services/Utilities/classes/class.ilFileUtils.php";
2146            ilFileUtils::recursive_dirscan($css_path, $results);
2147            if (is_array($results["file"])) {
2148                foreach ($results["file"] as $filename) {
2149                    if (strpos($filename, ".css")) {
2150                        array_push($customstyles, $filename);
2151                    }
2152                }
2153            }
2154        }
2155        return $customstyles;
2156    }
2157
2158    /**
2159    * get full style sheet file name (path inclusive) of current user
2160    *
2161    * @param $mode string Output mode of the style sheet ("output" or "filesystem"). !"filesystem" generates the ILIAS
2162    * version number as attribute to force the reload of the style sheet in a different ILIAS version
2163    * @access	public
2164    */
2165    public function getTestStyleLocation($mode = "output")
2166    {
2167        if (strlen($this->getCustomStyle())) {
2168            $default = ilUtil::getStyleSheetLocation("filesystem", "ta.css", "Modules/Test");
2169            $custom = str_replace("ta.css", "customstyles/" . $this->getCustomStyle(), $default);
2170            if (file_exists($custom)) {
2171                $custom = ilUtil::getStyleSheetLocation($mode, "ta.css", "Modules/Test");
2172                $custom = str_replace("ta.css", "customstyles/" . $this->getCustomStyle(), $custom);
2173                return $custom;
2174            } else {
2175                return ilUtil::getStyleSheetLocation($mode, "ta.css", "Modules/Test");
2176            }
2177        } else {
2178            return ilUtil::getStyleSheetLocation($mode, "ta.css", "Modules/Test");
2179        }
2180    }
2181
2182    /**
2183    * Sets whether the final statement should be shown or not
2184    *
2185    * @param integer $show 1 if TRUE or 0 if FALSE
2186    * @access public
2187    * @see $_finalstatement
2188    */
2189    public function setShowFinalStatement($show = 0)
2190    {
2191        $this->_showfinalstatement = ($show) ? 1 : 0;
2192    }
2193
2194    /**
2195    * Gets the final statement
2196    *
2197    * @return mixed The final statement, NULL if empty
2198    * @see $_finalstatement
2199    */
2200    public function getFinalStatement()
2201    {
2202        return (strlen($this->_finalstatement)) ? $this->_finalstatement : null;
2203    }
2204
2205    /**
2206    * Gets whether the complete information page is shown or the required data only
2207    *
2208    * @return integer 1 for the complete information, 0 otherwise
2209    * @access public
2210    * @see $_showinfo
2211    */
2212    public function getShowInfo()
2213    {
2214        return ($this->_showinfo) ? 1 : 0;
2215    }
2216
2217    /**
2218    * Gets whether JavaScript should be forced for tests
2219    *
2220    * @return integer 1 to force JavaScript, 0 otherwise
2221    * @access public
2222    * @see $_forcejs
2223    */
2224    public function getForceJS()
2225    {
2226        return ($this->_forcejs) ? 1 : 0;
2227    }
2228
2229    /**
2230    * Returns whether the final statement should be shown or not
2231    *
2232    * @return integer 0 if false, 1 if true
2233    * @access public
2234    * @see $_showfinalstatement
2235    */
2236    public function getShowFinalStatement()
2237    {
2238        return ($this->_showfinalstatement) ? 1 : 0;
2239    }
2240
2241    /**
2242    * Gets the database id of the additional test data
2243    *
2244    * @return integer The database id of the additional test data
2245    * @access public
2246    * @see $test_id
2247    */
2248    public function getTestId()
2249    {
2250        return $this->test_id;
2251    }
2252
2253    /**
2254     * {@inheritdoc}
2255     */
2256    public function getECTSOutput()
2257    {
2258        return ($this->ects_output) ? 1 : 0;
2259    }
2260
2261    /**
2262     * {@inheritdoc}
2263     */
2264    public function setECTSOutput($a_ects_output)
2265    {
2266        $this->ects_output = $a_ects_output ? 1 : 0;
2267    }
2268
2269    /**
2270     * {@inheritdoc}
2271     */
2272    public function getECTSFX()
2273    {
2274        return (strlen($this->ects_fx)) ? $this->ects_fx : null;
2275    }
2276
2277    /**
2278     * {@inheritdoc}
2279     */
2280    public function setECTSFX($a_ects_fx)
2281    {
2282        $this->ects_fx = $a_ects_fx;
2283    }
2284
2285    /**
2286     * {@inheritdoc}
2287     */
2288    public function getECTSGrades()
2289    {
2290        return $this->ects_grades;
2291    }
2292
2293    /**
2294     * {@inheritdoc}
2295     */
2296    public function setECTSGrades(array $a_ects_grades)
2297    {
2298        $this->ects_grades = $a_ects_grades;
2299    }
2300
2301    /**
2302     * SEQUENCE SETTING = POSTPONING ENABLED !!
2303     *
2304     * @return integer The POSTPONING ENABLED status
2305     */
2306    public function getSequenceSettings()
2307    {
2308        return ($this->sequence_settings) ? $this->sequence_settings : 0;
2309    }
2310
2311    /**
2312     * SEQUENCE SETTING = POSTPONING ENABLED !!
2313     *
2314     * @param integer $sequence_settings The POSTPONING ENABLED status
2315     */
2316    public function setSequenceSettings($sequence_settings = 0)
2317    {
2318        $this->sequence_settings = $sequence_settings;
2319    }
2320
2321    /**
2322     * @return bool $postponingEnabled
2323     */
2324    public function isPostponingEnabled()
2325    {
2326        return (bool) $this->getSequenceSettings();
2327    }
2328
2329    /**
2330     * @param bool $postponingEnabled
2331     */
2332    public function setPostponingEnabled($postponingEnabled)
2333    {
2334        $this->setSequenceSettings((int) $postponingEnabled);
2335    }
2336
2337    /**
2338* Sets the score reporting of the ilObjTest object
2339*
2340* @param integer $score_reporting The score reporting
2341* @access public
2342* @see $score_reporting
2343*/
2344    public function setScoreReporting($score_reporting = 0)
2345    {
2346        $this->score_reporting = $score_reporting;
2347    }
2348
2349    /**
2350    * Sets the instant feedback for the solution
2351    *
2352    * @param integer $instant_feedback If 1, the solution will be shown after answering a question
2353    * @access public
2354    * @see $instant_verification
2355    */
2356    public function setInstantFeedbackSolution($instant_feedback = 0)
2357    {
2358        switch ($instant_feedback) {
2359            case 1:
2360                $this->instant_verification = 1;
2361                break;
2362            default:
2363                $this->instant_verification = 0;
2364                break;
2365        }
2366    }
2367
2368    /**
2369    * Sets the generic feedback for the test
2370    * @deprecate Use setGenericAnswerFeedback instead.
2371    * @param integer $answer_feedback If 1, answer specific feedback will be shown after answering a question
2372    * @access public
2373    * @see $answer_feedback
2374    */
2375    public function setAnswerFeedback($answer_feedback = 0)
2376    {
2377        switch ($answer_feedback) {
2378        case 1:
2379            $this->answer_feedback = 1;
2380            break;
2381        default:
2382            $this->answer_feedback = 0;
2383            break;
2384    }
2385    }
2386
2387    /**
2388     * Sets if the generic feedback is to be shown in the test.
2389     *
2390     * @param int $generic_answer_feedback
2391     */
2392    public function setGenericAnswerFeedback($generic_answer_feedback = 0)
2393    {
2394        switch ($generic_answer_feedback) {
2395        case 1:
2396            $this->answer_feedback = 1;
2397            break;
2398        default:
2399            $this->answer_feedback = 0;
2400            break;
2401    }
2402    }
2403
2404    /**
2405    * Sets the answer specific feedback of reached points for the test
2406    *
2407    * @param integer $answer_feedback_points If 1, answer specific feedback will show the reached points after answering a question
2408    * @access public
2409    * @see $answer_feedback_points
2410    */
2411    public function setAnswerFeedbackPoints($answer_feedback_points = 0)
2412    {
2413        switch ($answer_feedback_points) {
2414            case 1:
2415                $this->answer_feedback_points = 1;
2416                break;
2417            default:
2418                $this->answer_feedback_points = 0;
2419                break;
2420        }
2421    }
2422
2423    /**
2424     * Sets the reporting date of the ilObjTest object
2425     * @param timestamp $reporting_date The date and time the score reporting is available
2426     */
2427    public function setReportingDate($reporting_date)
2428    {
2429        if (!$reporting_date) {
2430            $this->reporting_date = '';
2431            $this->setECTSOutput(false);
2432        } else {
2433            $this->reporting_date = $reporting_date;
2434        }
2435    }
2436
2437    const SCORE_REPORTING_DISABLED = 0;
2438    const SCORE_REPORTING_FINISHED = 1;
2439    const SCORE_REPORTING_IMMIDIATLY = 2;
2440    const SCORE_REPORTING_DATE = 3;
2441    const SCORE_REPORTING_AFTER_PASSED = 4;
2442
2443    /**
2444    * Gets the score reporting of the ilObjTest object
2445    *
2446    * @return integer The score reporting of the test
2447    * @access public
2448    * @see $score_reporting
2449    */
2450    public function getScoreReporting()
2451    {
2452        return ($this->score_reporting) ? $this->score_reporting : 0;
2453    }
2454
2455    public function isScoreReportingEnabled()
2456    {
2457        switch ($this->getScoreReporting()) {
2458            case self::SCORE_REPORTING_FINISHED:
2459            case self::SCORE_REPORTING_IMMIDIATLY:
2460            case self::SCORE_REPORTING_DATE:
2461            case self::SCORE_REPORTING_AFTER_PASSED:
2462
2463                return true;
2464
2465            case self::SCORE_REPORTING_DISABLED:
2466            default:
2467
2468                return false;
2469        }
2470    }
2471
2472    /**
2473    * Returns 1 if the correct solution will be shown after answering a question
2474    *
2475    * @return integer The status of the solution instant feedback
2476    * @access public
2477    * @see $instant_verification
2478    */
2479    public function getInstantFeedbackSolution()
2480    {
2481        return ($this->instant_verification) ? $this->instant_verification : 0;
2482    }
2483
2484    /**
2485     * Returns 1 if generic answer feedback is activated
2486     *
2487     * @deprecated Use getGenericAnswerFeedback instead.
2488     * @return integer The status of the answer specific feedback
2489     * @access     public
2490     * @see        $answer_feedback
2491     */
2492    public function getAnswerFeedback()
2493    {
2494        return ($this->answer_feedback) ? $this->answer_feedback : 0;
2495    }
2496
2497    /**
2498     * Returns 1 if generic answer feedback is to be shown.
2499     *
2500     * @return integer 1, if answer specific feedback is to be shown.
2501     * @access public
2502     */
2503    public function getGenericAnswerFeedback()
2504    {
2505        return ($this->answer_feedback) ? $this->answer_feedback : 0;
2506    }
2507
2508    /**
2509    * Returns 1 if answer specific feedback as reached points is activated
2510    *
2511    * @return integer The status of the answer specific feedback as reached points
2512    * @access public
2513    * @see $answer_feedback_points
2514    */
2515    public function getAnswerFeedbackPoints()
2516    {
2517        return ($this->answer_feedback_points) ? $this->answer_feedback_points : 0;
2518    }
2519
2520    /**
2521    * Gets the count system for the calculation of points
2522    *
2523    * @return integer The count system for the calculation of points
2524    * @access public
2525    * @see $count_system
2526    */
2527    public function getCountSystem()
2528    {
2529        return ($this->count_system) ? $this->count_system : 0;
2530    }
2531
2532    /**
2533    * Gets the count system for the calculation of points
2534    *
2535    * @return integer The count system for the calculation of points
2536    * @access public
2537    * @see $count_system
2538    */
2539    public static function _getCountSystem($active_id)
2540    {
2541        global $DIC;
2542        $ilDB = $DIC['ilDB'];
2543        $result = $ilDB->queryF(
2544            "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",
2545            array('integer'),
2546            array($active_id)
2547        );
2548        if ($result->numRows()) {
2549            $row = $ilDB->fetchAssoc($result);
2550            return $row["count_system"];
2551        }
2552        return false;
2553    }
2554
2555    /**
2556    * Gets the scoring type for multiple choice questions
2557    *
2558    * @return integer The scoring type for multiple choice questions
2559    * @access public
2560    * @see $mc_scoring
2561    */
2562    public function getMCScoring()
2563    {
2564        return ($this->mc_scoring) ? $this->mc_scoring : 0;
2565    }
2566
2567    /**
2568    * Determines if the score of a question should be cut at 0 points or the score of the whole test
2569    *
2570    * @return integer The score cutting type. 0 for question cutting, 1 for test cutting
2571    * @access public
2572    * @see $score_cutting
2573    */
2574    public function getScoreCutting()
2575    {
2576        return ($this->score_cutting) ? $this->score_cutting : 0;
2577    }
2578
2579    /**
2580    * Gets the pass scoring type
2581    *
2582    * @return integer The pass scoring type
2583    * @access public
2584    * @see $pass_scoring
2585    */
2586    public function getPassScoring()
2587    {
2588        return ($this->pass_scoring) ? $this->pass_scoring : 0;
2589    }
2590
2591    /**
2592    * Gets the pass scoring type
2593    *
2594    * @return integer The pass scoring type
2595    * @access public
2596    * @see $pass_scoring
2597    */
2598    public static function _getPassScoring($active_id)
2599    {
2600        global $DIC;
2601        $ilDB = $DIC['ilDB'];
2602        $result = $ilDB->queryF(
2603            "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",
2604            array('integer'),
2605            array($active_id)
2606        );
2607        if ($result->numRows()) {
2608            $row = $ilDB->fetchAssoc($result);
2609            return $row["pass_scoring"];
2610        }
2611        return 0;
2612    }
2613
2614    /**
2615    * Gets the scoring type for multiple choice questions
2616    *
2617    * @return mixed The scoring type for multiple choice questions
2618    * @access public
2619    * @see $mc_scoring
2620    */
2621    public static function _getMCScoring($active_id)
2622    {
2623        global $DIC;
2624        $ilDB = $DIC['ilDB'];
2625        $result = $ilDB->queryF(
2626            "SELECT tst_tests.mc_scoring FROM tst_tests, tst_active WHERE tst_active.active_id = %s AND tst_active.test_fi = tst_tests.test_id",
2627            array('integer'),
2628            array($active_id)
2629        );
2630        if ($result->numRows()) {
2631            $row = $ilDB->fetchAssoc($result);
2632            return $row["mc_scoring"];
2633        }
2634        return false;
2635    }
2636
2637    /**
2638    * Determines if the score of a question should be cut at 0 points or the score of the whole test
2639    *
2640    * @return boolean The score cutting type. 0 for question cutting, 1 for test cutting
2641    * @access public
2642    * @see $score_cutting
2643    */
2644    public static function _getScoreCutting($active_id)
2645    {
2646        global $DIC;
2647        $ilDB = $DIC['ilDB'];
2648        $result = $ilDB->queryF(
2649            "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",
2650            array('integer'),
2651            array($active_id)
2652        );
2653        if ($result->numRows()) {
2654            $row = $ilDB->fetchAssoc($result);
2655            return $row["score_cutting"];
2656        }
2657        return false;
2658    }
2659
2660    /**
2661    * Gets the reporting date of the ilObjTest object
2662    *
2663    * @return string The reporting date of the test of an empty string (=FALSE) if no reporting date is set
2664    * @access public
2665    * @see $reporting_date
2666    */
2667    public function getReportingDate()
2668    {
2669        return (strlen($this->reporting_date)) ? $this->reporting_date : null;
2670    }
2671
2672    /**
2673    * Returns the nr of tries for the test
2674    *
2675    * @return integer The maximum number of tries
2676    * @access public
2677    * @see $nr_of_tries
2678    */
2679    public function getNrOfTries()
2680    {
2681        return ($this->nr_of_tries) ? $this->nr_of_tries : 0;
2682    }
2683
2684    /**
2685     * @return bool
2686     */
2687    public function isBlockPassesAfterPassedEnabled()
2688    {
2689        return $this->blockPassesAfterPassedEnabled;
2690    }
2691
2692    /**
2693     * @param bool $blockPassesAfterPassedEnabled
2694     */
2695    public function setBlockPassesAfterPassedEnabled($blockPassesAfterPassedEnabled)
2696    {
2697        $this->blockPassesAfterPassedEnabled = $blockPassesAfterPassedEnabled;
2698    }
2699
2700    /**
2701    * Returns the kiosk mode
2702    *
2703    * @return integer Kiosk mode
2704    * @access public
2705    * @see $_kiosk
2706    */
2707    public function getKiosk()
2708    {
2709        return ($this->_kiosk) ? $this->_kiosk : 0;
2710    }
2711
2712
2713    /**
2714    * Sets the kiosk mode for the test
2715    *
2716    * @param integer $kiosk The value for the kiosk mode.
2717    * @access public
2718    * @see $_kiosk
2719    */
2720    public function setKiosk($kiosk = 0)
2721    {
2722        $this->_kiosk = $kiosk;
2723    }
2724
2725    /**
2726    * Returns the kiosk mode
2727    *
2728    * @return boolean Kiosk mode
2729    * @access public
2730    * @see $_kiosk
2731    */
2732    public function getKioskMode()
2733    {
2734        if (($this->_kiosk & 1) > 0) {
2735            return true;
2736        } else {
2737            return false;
2738        }
2739    }
2740
2741    /**
2742    * Sets the kiosk mode for the test
2743    *
2744    * @param boolean $kiosk The value for the kiosk mode
2745    * @access public
2746    * @see $_kiosk
2747    */
2748    public function setKioskMode($a_kiosk = false)
2749    {
2750        if ($a_kiosk) {
2751            $this->_kiosk = $this->_kiosk | 1;
2752        } else {
2753            if ($this->getKioskMode()) {
2754                $this->_kiosk = $this->_kiosk ^ 1;
2755            }
2756        }
2757    }
2758
2759    /**
2760    * Returns the status of the kiosk mode title
2761    *
2762    * @return boolean Kiosk mode title
2763    * @access public
2764    * @see $_kiosk
2765    */
2766    public function getShowKioskModeTitle()
2767    {
2768        if (($this->_kiosk & 2) > 0) {
2769            return true;
2770        } else {
2771            return false;
2772        }
2773    }
2774
2775    /**
2776    * Set to true, if the full test title should be shown in kiosk mode
2777    *
2778    * @param boolean $a_title TRUE if the test title should be shown in kiosk mode, FALSE otherwise
2779    * @access public
2780    */
2781    public function setShowKioskModeTitle($a_title = false)
2782    {
2783        if ($a_title) {
2784            $this->_kiosk = $this->_kiosk | 2;
2785        } else {
2786            if ($this->getShowKioskModeTitle()) {
2787                $this->_kiosk = $this->_kiosk ^ 2;
2788            }
2789        }
2790    }
2791
2792    /**
2793    * Returns the status of the kiosk mode participant
2794    *
2795    * @return boolean Kiosk mode participant
2796    * @access public
2797    * @see $_kiosk
2798    */
2799    public function getShowKioskModeParticipant()
2800    {
2801        if (($this->_kiosk & 4) > 0) {
2802            return true;
2803        } else {
2804            return false;
2805        }
2806    }
2807
2808    /**
2809    * Set to true, if the participant's name should be shown in kiosk mode
2810    *
2811    * @param boolean $a_title TRUE if the participant's name should be shown in kiosk mode, FALSE otherwise
2812    * @access public
2813    */
2814    public function setShowKioskModeParticipant($a_participant = false)
2815    {
2816        if ($a_participant) {
2817            $this->_kiosk = $this->_kiosk | 4;
2818        } else {
2819            if ($this->getShowKioskModeParticipant()) {
2820                $this->_kiosk = $this->_kiosk ^ 4;
2821            }
2822        }
2823    }
2824
2825    /**
2826    * Returns if the previous answers should be shown for a learner
2827    *
2828    * @return integer 1 if the previous answers should be shown, 0 otherwise
2829    * @access public
2830    * @see $use_previous_answers
2831    */
2832    public function getUsePreviousAnswers()
2833    {
2834        return ($this->use_previous_answers) ? $this->use_previous_answers : 0;
2835    }
2836
2837    /**
2838    * Returns the value of the title_output status
2839    *
2840    * @return integer 0 for full title, 1 for title without points, 2 for no title
2841    * @access public
2842    * @see $title_output
2843    */
2844    public function getTitleOutput()
2845    {
2846        return ($this->title_output) ? $this->title_output : 0;
2847    }
2848
2849    /**
2850    * Returns the value of the title_output status
2851    *
2852    * @param integer $active_id The active id of a user
2853    * @return integer 0 for full title, 1 for title without points, 2 for no title
2854    * @access public
2855    * @see $title_output
2856    */
2857    public function _getTitleOutput($active_id)
2858    {
2859        global $DIC;
2860        $ilDB = $DIC['ilDB'];
2861
2862        $result = $ilDB->queryF(
2863            "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",
2864            array('integer'),
2865            array($active_id)
2866        );
2867        if ($result->numRows()) {
2868            $row = $ilDB->fetchAssoc($result);
2869            return $row["title_output"];
2870        }
2871        return 0;
2872    }
2873
2874    // hey: prevPassSolutions - serious (nonstatic) identifier, for use in high level controller gui
2875    public function isPreviousSolutionReuseEnabled($activeId)
2876    {
2877        // checks if allowed in general and if enabled by participant
2878        return self::_getUsePreviousAnswers($activeId, true);
2879    }
2880    // hey.
2881
2882    /**
2883    * Returns if the previous results should be hidden for a learner
2884    *
2885    * @param integer $test_id The test id
2886    * @param boolean $use_active_user_setting If true, the tst_use_previous_answers- of the active user should be used as well
2887    * @return integer 1 if the previous results should be hidden, 0 otherwise
2888    * @access public
2889    * @see $use_previous_answers
2890    */
2891    public static function _getUsePreviousAnswers($active_id, $user_active_user_setting = false)
2892    {
2893        global $DIC;
2894        $ilDB = $DIC['ilDB'];
2895        $ilUser = $DIC['ilUser'];
2896
2897        $use_previous_answers = 1;
2898
2899        $result = $ilDB->queryF(
2900            "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",
2901            array("integer"),
2902            array($active_id)
2903        );
2904        if ($result->numRows()) {
2905            $row = $ilDB->fetchAssoc($result);
2906            $use_previous_answers = $row["use_previous_answers"];
2907        }
2908
2909        if ($use_previous_answers == 1) {
2910            if ($user_active_user_setting) {
2911                $res = $ilUser->getPref("tst_use_previous_answers");
2912                if ($res !== false) {
2913                    $use_previous_answers = $res;
2914                }
2915            }
2916        }
2917        return $use_previous_answers;
2918    }
2919
2920    /**
2921    * Returns the processing time for the test
2922    *
2923    * @return string The processing time for the test
2924    * @access public
2925    * @see $processing_time
2926    */
2927    public function getProcessingTime()
2928    {
2929        return (strlen($this->processing_time)) ? $this->processing_time : null;
2930    }
2931
2932    /**
2933    * Returns the processing time for the test
2934    *
2935    * @return string The processing time for the test
2936    * @see $processing_time
2937    */
2938    public function getProcessingTimeAsArray()
2939    {
2940        if (strlen($this->processing_time)) {
2941            if (preg_match("/(\d{2}):(\d{2}):(\d{2})/is", $this->processing_time, $matches)) {
2942                if ((int) $matches[1] + (int) $matches[2] + (int) $matches[3] == 0) {
2943                    return $this->getEstimatedWorkingTime();
2944                } else {
2945                    return array(
2946                        'hh' => $matches[1],
2947                        'mm' => $matches[2],
2948                        'ss' => $matches[3],
2949                    );
2950                }
2951            }
2952        }
2953        return $this->getEstimatedWorkingTime();
2954    }
2955
2956    public function getProcessingTimeAsMinutes()
2957    {
2958        if (strlen($this->processing_time)) {
2959            if (preg_match("/(\d{2}):(\d{2}):(\d{2})/is", $this->processing_time, $matches)) {
2960                return ($matches[1] * 60) + $matches[2];
2961            }
2962        }
2963
2964        return self::DEFAULT_PROCESSING_TIME_MINUTES;
2965    }
2966
2967    /**
2968    * Returns the processing time for the test in seconds
2969    *
2970    * @return integer The processing time for the test in seconds
2971    * @access public
2972    * @see $processing_time
2973    */
2974    public function getProcessingTimeInSeconds($active_id = "")
2975    {
2976        if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $this->getProcessingTime(), $matches)) {
2977            $extratime = $this->getExtraTime($active_id) * 60;
2978            return ($matches[1] * 3600) + ($matches[2] * 60) + $matches[3] + $extratime;
2979        } else {
2980            return 0;
2981        }
2982    }
2983
2984    /**
2985    * Returns the seconds left from the actual time until the ending time
2986    *
2987    * @return integer The seconds left until the ending time is reached
2988    * @access public
2989    * @see $ending_time
2990    */
2991    public function getSecondsUntilEndingTime()
2992    {
2993        if ($this->getEndingTime() != 0) {
2994            $ending = $this->getEndingTime();
2995            $now = time();
2996            return $ending - $now;
2997        } else {
2998            return 0;
2999        }
3000    }
3001
3002    /**
3003    * Returns the state of the processing time (enabled/disabled)
3004    *
3005    * @return integer The processing time state (0 for disabled, 1 for enabled)
3006    * @access public
3007    * @see $processing_time
3008    */
3009    public function getEnableProcessingTime()
3010    {
3011        return ($this->enable_processing_time) ? $this->enable_processing_time : 0;
3012    }
3013
3014    /**
3015    * Returns wheather the processing time should be reset or not
3016    *
3017    * @return integer 0 for no reset, 1 for a reset
3018    * @access public
3019    * @see $reset_processing_time
3020    */
3021    public function getResetProcessingTime()
3022    {
3023        return ($this->reset_processing_time) ? $this->reset_processing_time : 0;
3024    }
3025
3026    /**
3027     * @return boolean
3028     */
3029    public function isStartingTimeEnabled()
3030    {
3031        return $this->starting_time_enabled;
3032    }
3033
3034    /**
3035     * @param boolean $starting_time_enabled
3036     */
3037    public function setStartingTimeEnabled($starting_time_enabled)
3038    {
3039        $this->starting_time_enabled = $starting_time_enabled;
3040    }
3041
3042    /**
3043     * Returns the starting time of the test
3044     *
3045     * @return string The starting time of the test
3046     * @access public
3047     * @see $starting_time
3048     */
3049    public function getStartingTime()
3050    {
3051        return ($this->starting_time != 0) ? $this->starting_time : 0;
3052    }
3053
3054    /**
3055     * Sets the starting time in database timestamp format for the test
3056     *
3057     * @param string $starting_time The starting time for the test. Empty string for no starting time.
3058     * @access public
3059     * @see $starting_time
3060     */
3061    public function setStartingTime($starting_time = null)
3062    {
3063        $this->starting_time = $starting_time;
3064    }
3065
3066    /**
3067     * @return boolean
3068     */
3069    public function isEndingTimeEnabled()
3070    {
3071        return $this->ending_time_enabled;
3072    }
3073
3074    /**
3075     * @param boolean $ending_time_enabled
3076     */
3077    public function setEndingTimeEnabled($ending_time_enabled)
3078    {
3079        $this->ending_time_enabled = $ending_time_enabled;
3080    }
3081
3082    /**
3083     * Returns the ending time of the test
3084     *
3085     * @return string The ending time of the test
3086     * @access public
3087     * @see $ending_time
3088     */
3089    public function getEndingTime()
3090    {
3091        return ($this->ending_time != 0) ? $this->ending_time : 0;
3092    }
3093
3094    /**
3095     * Sets the ending time in database timestamp format for the test
3096     *
3097     * @param string $ending_time The ending time for the test. Empty string for no ending time.
3098     * @access public
3099     * @see $ending_time
3100     */
3101    public function setEndingTime($ending_time = null)
3102    {
3103        $this->ending_time = $ending_time;
3104    }
3105
3106    /**
3107    * Sets the nr of tries for the test
3108    *
3109    * @param integer $nr_of_tries The maximum number of tries for the test. 0 for infinite tries.
3110    * @access public
3111    * @see $nr_of_tries
3112    */
3113    public function setNrOfTries($nr_of_tries = 0)
3114    {
3115        $this->nr_of_tries = $nr_of_tries;
3116    }
3117
3118    /**
3119    * Sets the status of the visibility of previous learner answers
3120    **
3121    * @param integer $use_previous_answers 1 if the previous answers should be shown
3122    * @access public
3123    * @see $use_previous_answers
3124    */
3125    public function setUsePreviousAnswers($use_previous_answers = 1)
3126    {
3127        if ($use_previous_answers) {
3128            $this->use_previous_answers = 1;
3129        } else {
3130            $this->use_previous_answers = 0;
3131        }
3132    }
3133
3134    public function setRedirectionMode($redirection_mode = 0)
3135    {
3136        $this->redirection_mode = $redirection_mode;
3137    }
3138    public function getRedirectionMode()
3139    {
3140        return $this->redirection_mode;
3141    }
3142    public function setRedirectionUrl($redirection_url = null)
3143    {
3144        $this->redirection_url = $redirection_url;
3145    }
3146    public function getRedirectionUrl()
3147    {
3148        return $this->redirection_url;
3149    }
3150
3151    /**
3152* Sets the status of the title output
3153**
3154* @param integer $title_output 0 for full title, 1 for title without points, 2 for no title
3155* @access public
3156* @see $title_output
3157*/
3158    public function setTitleOutput($title_output = 0)
3159    {
3160        switch ($title_output) {
3161            case 1:
3162                $this->title_output = 1;
3163                break;
3164            case 2:
3165                $this->title_output = 2;
3166                break;
3167            default:
3168                $this->title_output = 0;
3169                break;
3170        }
3171    }
3172
3173    /**
3174    * Sets the processing time for the test
3175    *
3176    * @param string $processing_time The maximum processing time for the test given in hh:mm:ss
3177    * @access public
3178    * @see $processing_time
3179    */
3180    public function setProcessingTime($processing_time = "00:00:00")
3181    {
3182        $this->processing_time = $processing_time;
3183    }
3184
3185    public function setProcessingTimeByMinutes($minutes)
3186    {
3187        $this->processing_time = sprintf("%02d:%02d:00", floor($minutes / 60), $minutes % 60);
3188    }
3189
3190    /**
3191* Sets the processing time enabled or disabled
3192*
3193* @param integer $enable 0 to disable the processing time, 1 to enable the processing time
3194* @access public
3195* @see $processing_time
3196*/
3197    public function setEnableProcessingTime($enable = 0)
3198    {
3199        if ($enable) {
3200            $this->enable_processing_time = "1";
3201        } else {
3202            $this->enable_processing_time = "0";
3203        }
3204    }
3205
3206    /**
3207    * Sets wheather the processing time should be reset or not
3208    *
3209    * @param integer $reset 1 to reset the processing time, 0 otherwise
3210    * @access public
3211    * @see $processing_time
3212    */
3213    public function setResetProcessingTime($reset = 0)
3214    {
3215        if ($reset) {
3216            $this->reset_processing_time = 1;
3217        } else {
3218            $this->reset_processing_time = 0;
3219        }
3220    }
3221
3222    /**
3223    * Sets the count system for the calculation of points
3224    *
3225    * @param integer $a_count_system The count system for the calculation of points.
3226    * @access public
3227    * @see $count_system
3228    */
3229    public function setCountSystem($a_count_system = COUNT_PARTIAL_SOLUTIONS)
3230    {
3231        $this->count_system = $a_count_system;
3232    }
3233
3234    /**
3235     * @return boolean
3236     */
3237    public function isPasswordEnabled()
3238    {
3239        return $this->passwordEnabled;
3240    }
3241
3242    /**
3243     * @param boolean $passwordEnabled
3244     */
3245    public function setPasswordEnabled($passwordEnabled)
3246    {
3247        $this->passwordEnabled = $passwordEnabled;
3248    }
3249
3250    /**
3251     * Returns the password for test access
3252     *
3253     * @return striong  Password for test access
3254     * @access public
3255     * @see $password
3256     */
3257    public function getPassword()
3258    {
3259        return (strlen($this->password)) ? $this->password : null;
3260    }
3261
3262    /**
3263     * Sets the password for test access
3264     *
3265     * @param string $a_password The password for test access
3266     * @access public
3267     * @see $password
3268     */
3269    public function setPassword($a_password = null)
3270    {
3271        $this->password = $a_password;
3272    }
3273
3274    /**
3275    * Sets the type of score cutting
3276    *
3277    * @param integer $a_score_cutting The type of score cutting. 0 for cut questions, 1 for cut tests
3278    * @access public
3279    * @see $score_cutting
3280    */
3281    public function setScoreCutting($a_score_cutting = SCORE_CUT_QUESTION)
3282    {
3283        $this->score_cutting = $a_score_cutting;
3284    }
3285
3286    /**
3287    * Sets the multiple choice scoring
3288    *
3289    * @param integer $a_mc_scoring The scoring for multiple choice questions
3290    * @access public
3291    * @see $mc_scoring
3292    */
3293    public function setMCScoring($a_mc_scoring = SCORE_ZERO_POINTS_WHEN_UNANSWERED)
3294    {
3295        $this->mc_scoring = $a_mc_scoring;
3296    }
3297
3298    /**
3299    * Sets the pass scoring
3300    *
3301    * @param integer $a_pass_scoring The pass scoring type
3302    * @access public
3303    * @see $pass_scoring
3304    */
3305    public function setPassScoring($a_pass_scoring = SCORE_LAST_PASS)
3306    {
3307        switch ($a_pass_scoring) {
3308            case SCORE_BEST_PASS:
3309                $this->pass_scoring = SCORE_BEST_PASS;
3310                break;
3311            default:
3312                $this->pass_scoring = SCORE_LAST_PASS;
3313                break;
3314        }
3315    }
3316
3317    /**
3318     * @return string
3319     */
3320    public function getPassWaiting()
3321    {
3322        return $this->pass_waiting;
3323    }
3324
3325    /**
3326     * @param string $pass_waiting   mm:ddd:hh:ii:ss
3327     */
3328    public function setPassWaiting($pass_waiting)
3329    {
3330        $this->pass_waiting = $pass_waiting;
3331    }
3332    /**
3333     * @return bool
3334     */
3335    public function isPassWaitingEnabled()
3336    {
3337        if (array_sum(explode(':', $this->getPassWaiting())) > 0) {
3338            return true;
3339        }
3340        return false;
3341    }
3342
3343    /**
3344     * @param int $questionId
3345     * @param array $activeIds
3346     * @param ilTestReindexedSequencePositionMap $reindexedSequencePositionMap
3347     */
3348    public function removeQuestionFromSequences($questionId, $activeIds, ilTestReindexedSequencePositionMap $reindexedSequencePositionMap)
3349    {
3350        global $DIC; /* @var ILIAS\DI\Container $DIC */
3351
3352        $testSequenceFactory = new ilTestSequenceFactory(
3353            $DIC->database(),
3354            $DIC->language(),
3355            $DIC['ilPluginAdmin'],
3356            $this
3357        );
3358
3359        foreach ($activeIds as $activeId) {
3360            $passSelector = new ilTestPassesSelector($DIC->database(), $this);
3361            $passSelector->setActiveId($activeId);
3362
3363            foreach ($passSelector->getExistingPasses() as $pass) {
3364                $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($activeId, $pass);
3365                $testSequence->loadFromDb();
3366
3367                $testSequence->removeQuestion($questionId, $reindexedSequencePositionMap);
3368                $testSequence->saveToDb();
3369            }
3370        }
3371    }
3372
3373    /**
3374     * @param array $removeQuestionIds
3375     */
3376    public function removeQuestions($removeQuestionIds)
3377    {
3378        foreach ($removeQuestionIds as $value) {
3379            $this->removeQuestion($value);
3380        }
3381
3382        $this->reindexFixedQuestionOrdering();
3383    }
3384
3385    /**
3386    * Removes a question from the test object
3387    *
3388    * @param integer $question_id The database id of the question to be removed
3389    * @access public
3390    * @see $test_id
3391    */
3392    public function removeQuestion($question_id)
3393    {
3394        $question = &ilObjTest::_instanciateQuestion($question_id);
3395        include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3396        if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
3397            $this->logAction($this->lng->txtlng("assessment", "log_question_removed", ilObjAssessmentFolder::_getLogLanguage()), $question_id);
3398        }
3399        $question->delete($question_id);
3400    }
3401
3402    /**
3403     * - at the time beeing ilObjTest::removeTestResults needs to call the LP service for deletion
3404     * - ilTestLP calls ilObjTest::removeTestResultsByUserIds
3405     *
3406     * this method should only be used from non refactored soap context i think
3407     *
3408     * @param $userIds
3409     */
3410    public function removeTestResultsFromSoapLpAdministration($userIds)
3411    {
3412        $this->removeTestResultsByUserIds($userIds);
3413
3414        global $DIC;
3415        $ilDB = $DIC['ilDB'];
3416        $lng = $DIC['lng'];
3417
3418        require_once 'Modules/Test/classes/class.ilTestParticipantData.php';
3419        $participantData = new ilTestParticipantData($ilDB, $lng);
3420        $participantData->setUserIdsFilter($userIds);
3421        $participantData->load($this->getTestId());
3422
3423        $this->removeTestActives($participantData->getActiveIds());
3424    }
3425
3426    public function removeTestResults(ilTestParticipantData $participantData)
3427    {
3428        if (count($participantData->getAnonymousActiveIds())) {
3429            $this->removeTestResultsByActiveIds($participantData->getAnonymousActiveIds());
3430        }
3431
3432        if (count($participantData->getUserIds())) {
3433            /* @var ilTestLP $testLP */
3434            require_once 'Services/Object/classes/class.ilObjectLP.php';
3435            $testLP = ilObjectLP::getInstance($this->getId());
3436            $testLP->setTestObject($this);
3437            $testLP->resetLPDataForUserIds($participantData->getUserIds(), false);
3438        }
3439
3440        if (count($participantData->getActiveIds())) {
3441            $this->removeTestActives($participantData->getActiveIds());
3442        }
3443    }
3444
3445    public function removeTestResultsByUserIds($userIds)
3446    {
3447        global $DIC;
3448        $ilDB = $DIC['ilDB'];
3449        $lng = $DIC['lng'];
3450
3451        require_once 'Modules/Test/classes/class.ilTestParticipantData.php';
3452        $participantData = new ilTestParticipantData($ilDB, $lng);
3453        $participantData->setUserIdsFilter($userIds);
3454        $participantData->load($this->getTestId());
3455
3456        $IN_userIds = $ilDB->in('usr_id', $participantData->getUserIds(), false, 'integer');
3457        $ilDB->manipulateF(
3458            "DELETE FROM usr_pref WHERE $IN_userIds AND keyword = %s",
3459            array('text'),
3460            array("tst_password_" . $this->getTestId())
3461        );
3462
3463        if (count($participantData->getActiveIds())) {
3464            $this->removeTestResultsByActiveIds($participantData->getActiveIds());
3465        }
3466    }
3467
3468    public function removeTestResultsByActiveIds($activeIds)
3469    {
3470        global $DIC;
3471        $ilDB = $DIC['ilDB'];
3472
3473        $IN_activeIds = $ilDB->in('active_fi', $activeIds, false, 'integer');
3474
3475        $ilDB->manipulate("DELETE FROM tst_solutions WHERE $IN_activeIds");
3476        $ilDB->manipulate("DELETE FROM tst_qst_solved WHERE $IN_activeIds");
3477        $ilDB->manipulate("DELETE FROM tst_test_result WHERE $IN_activeIds");
3478        $ilDB->manipulate("DELETE FROM tst_pass_result WHERE $IN_activeIds");
3479        $ilDB->manipulate("DELETE FROM tst_result_cache WHERE $IN_activeIds");
3480        $ilDB->manipulate("DELETE FROM tst_sequence WHERE $IN_activeIds");
3481        $ilDB->manipulate("DELETE FROM tst_times WHERE $IN_activeIds");
3482
3483        if ($this->isRandomTest()) {
3484            $ilDB->manipulate("DELETE FROM tst_test_rnd_qst WHERE $IN_activeIds");
3485        } elseif ($this->isDynamicTest()) {
3486            $ilDB->manipulate("DELETE FROM tst_seq_qst_tracking WHERE $IN_activeIds");
3487            $ilDB->manipulate("DELETE FROM tst_seq_qst_answstatus WHERE $IN_activeIds");
3488            $ilDB->manipulate("DELETE FROM tst_seq_qst_postponed WHERE $IN_activeIds");
3489            $ilDB->manipulate("DELETE FROM tst_seq_qst_checked WHERE $IN_activeIds");
3490        }
3491
3492        include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3493
3494        foreach ($activeIds as $active_id) {
3495            // remove file uploads
3496            if (@is_dir(CLIENT_WEB_DIR . "/assessment/tst_" . $this->getTestId() . "/$active_id")) {
3497                ilUtil::delDir(CLIENT_WEB_DIR . "/assessment/tst_" . $this->getTestId() . "/$active_id");
3498            }
3499
3500            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
3501                $this->logAction(sprintf($this->lng->txtlng("assessment", "log_selected_user_data_removed", ilObjAssessmentFolder::_getLogLanguage()), $this->userLookupFullName($this->_getUserIdFromActiveId($active_id))));
3502            }
3503        }
3504
3505        require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintTracking.php';
3506        ilAssQuestionHintTracking::deleteRequestsByActiveIds($activeIds);
3507    }
3508
3509    public function removeTestActives($activeIds)
3510    {
3511        global $DIC;
3512        $ilDB = $DIC['ilDB'];
3513
3514        $IN_activeIds = $ilDB->in('active_id', $activeIds, false, 'integer');
3515        $ilDB->manipulate("DELETE FROM tst_active WHERE $IN_activeIds");
3516    }
3517
3518    /**
3519    * Moves a question up in order
3520    *
3521    * @param integer $question_id The database id of the question to be moved up
3522    * @access public
3523    * @see $test_id
3524    */
3525    public function questionMoveUp($question_id)
3526    {
3527        global $DIC;
3528        $ilDB = $DIC['ilDB'];
3529
3530        // Move a question up in sequence
3531        $result = $ilDB->queryF(
3532            "SELECT * FROM tst_test_question WHERE test_fi=%s AND question_fi=%s",
3533            array('integer', 'integer'),
3534            array($this->getTestId(), $question_id)
3535        );
3536        $data = $ilDB->fetchObject($result);
3537        if ($data->sequence > 1) {
3538            // OK, it's not the top question, so move it up
3539            $result = $ilDB->queryF(
3540                "SELECT * FROM tst_test_question WHERE test_fi=%s AND sequence=%s",
3541                array('integer','integer'),
3542                array($this->getTestId(), $data->sequence - 1)
3543            );
3544            $data_previous = $ilDB->fetchObject($result);
3545            // change previous dataset
3546            $affectedRows = $ilDB->manipulateF(
3547                "UPDATE tst_test_question SET sequence=%s WHERE test_question_id=%s",
3548                array('integer','integer'),
3549                array($data->sequence, $data_previous->test_question_id)
3550            );
3551            // move actual dataset up
3552            $affectedRows = $ilDB->manipulateF(
3553                "UPDATE tst_test_question SET sequence=%s WHERE test_question_id=%s",
3554                array('integer','integer'),
3555                array($data->sequence - 1, $data->test_question_id)
3556            );
3557            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3558            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
3559                $this->logAction($this->lng->txtlng("assessment", "log_question_position_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . ($data->sequence) . " => " . ($data->sequence - 1), $question_id);
3560            }
3561        }
3562        $this->loadQuestions();
3563    }
3564
3565    /**
3566    * Moves a question down in order
3567    *
3568    * @param integer $question_id The database id of the question to be moved down
3569    * @access public
3570    * @see $test_id
3571    */
3572    public function questionMoveDown($question_id)
3573    {
3574        global $DIC;
3575        $ilDB = $DIC['ilDB'];
3576
3577        // Move a question down in sequence
3578        $result = $ilDB->queryF(
3579            "SELECT * FROM tst_test_question WHERE test_fi=%s AND question_fi=%s",
3580            array('integer','integer'),
3581            array($this->getTestId(), $question_id)
3582        );
3583        $data = $ilDB->fetchObject($result);
3584        $result = $ilDB->queryF(
3585            "SELECT * FROM tst_test_question WHERE test_fi=%s AND sequence=%s",
3586            array('integer','integer'),
3587            array($this->getTestId(), $data->sequence + 1)
3588        );
3589        if ($result->numRows() == 1) {
3590            // OK, it's not the last question, so move it down
3591            $data_next = $ilDB->fetchObject($result);
3592            // change next dataset
3593            $affectedRows = $ilDB->manipulateF(
3594                "UPDATE tst_test_question SET sequence=%s WHERE test_question_id=%s",
3595                array('integer','integer'),
3596                array($data->sequence, $data_next->test_question_id)
3597            );
3598            // move actual dataset down
3599            $affectedRows = $ilDB->manipulateF(
3600                "UPDATE tst_test_question SET sequence=%s WHERE test_question_id=%s",
3601                array('integer','integer'),
3602                array($data->sequence + 1, $data->test_question_id)
3603            );
3604            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3605            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
3606                $this->logAction($this->lng->txtlng("assessment", "log_question_position_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . ($data->sequence) . " => " . ($data->sequence + 1), $question_id);
3607            }
3608        }
3609        $this->loadQuestions();
3610    }
3611
3612    /**
3613    * Takes a question and creates a copy of the question for use in the test
3614    *
3615    * @param integer $question_id The database id of the question
3616    * @result integer The database id of the copied question
3617    * @access public
3618    */
3619    public function duplicateQuestionForTest($question_id)
3620    {
3621        global $DIC;
3622        $ilUser = $DIC['ilUser'];
3623        $question = &ilObjTest::_instanciateQuestion($question_id);
3624        $duplicate_id = $question->duplicate(true, null, null, null, $this->getId());
3625
3626        return $duplicate_id;
3627    }
3628
3629    /**
3630     * Insert a question in the list of questions
3631     *
3632     * @param ilTestQuestionSetConfig $testQuestionSetConfig
3633     * @param integer $question_id The database id of the inserted question
3634     * @param boolean $linkOnly
3635     * @return integer $duplicate_id
3636     */
3637    public function insertQuestion(ilTestQuestionSetConfig $testQuestionSetConfig, $question_id, $linkOnly = false)
3638    {
3639        global $DIC;
3640        $ilDB = $DIC['ilDB'];
3641        #var_dump($question_id);
3642        if ($linkOnly) {
3643            $duplicate_id = $question_id;
3644        } else {
3645            $duplicate_id = $this->duplicateQuestionForTest($question_id);
3646        }
3647
3648        // get maximum sequence index in test
3649        $result = $ilDB->queryF(
3650            "SELECT MAX(sequence) seq FROM tst_test_question WHERE test_fi=%s",
3651            array('integer'),
3652            array($this->getTestId())
3653        );
3654        $sequence = 1;
3655
3656        if ($result->numRows() == 1) {
3657            $data = $ilDB->fetchObject($result);
3658            $sequence = $data->seq + 1;
3659        }
3660
3661        $next_id = $ilDB->nextId('tst_test_question');
3662        $affectedRows = $ilDB->manipulateF(
3663            "INSERT INTO tst_test_question (test_question_id, test_fi, question_fi, sequence, tstamp) VALUES (%s, %s, %s, %s, %s)",
3664            array('integer', 'integer','integer','integer','integer'),
3665            array($next_id, $this->getTestId(), $duplicate_id, $sequence, time())
3666        );
3667        if ($affectedRows == 1) {
3668            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
3669            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
3670                $this->logAction($this->lng->txtlng("assessment", "log_question_added", ilObjAssessmentFolder::_getLogLanguage()) . ": " . $sequence, $duplicate_id);
3671            }
3672        }
3673        // remove test_active entries, because test has changed
3674        $affectedRows = $ilDB->manipulateF(
3675            "DELETE FROM tst_active WHERE test_fi = %s",
3676            array('integer'),
3677            array($this->getTestId())
3678        );
3679        $this->loadQuestions();
3680        $this->saveCompleteStatus($testQuestionSetConfig);
3681        return $duplicate_id;
3682    }
3683
3684    /**
3685    * Returns the titles of the test questions in question sequence
3686    *
3687    * @return array The question titles
3688    * @access public
3689    * @see $questions
3690    */
3691    public function &getQuestionTitles()
3692    {
3693        $titles = array();
3694        if ($this->getQuestionSetType() == self::QUESTION_SET_TYPE_FIXED) {
3695            global $DIC;
3696            $ilDB = $DIC['ilDB'];
3697            $result = $ilDB->queryF(
3698                "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",
3699                array('integer'),
3700                array($this->getTestId())
3701            );
3702            while ($row = $ilDB->fetchAssoc($result)) {
3703                array_push($titles, $row["title"]);
3704            }
3705        }
3706        return $titles;
3707    }
3708
3709    /**
3710    * Returns the titles of the test questions in question sequence
3711    *
3712    * @return array The question titles
3713    * @access public
3714    * @see $questions
3715    */
3716    public function &getQuestionTitlesAndIndexes()
3717    {
3718        $titles = array();
3719        if ($this->getQuestionSetType() == self::QUESTION_SET_TYPE_FIXED) {
3720            global $DIC;
3721            $ilDB = $DIC['ilDB'];
3722            $result = $ilDB->queryF(
3723                "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",
3724                array('integer'),
3725                array($this->getTestId())
3726            );
3727            while ($row = $ilDB->fetchAssoc($result)) {
3728                $titles[$row['question_id']] = $row["title"];
3729            }
3730        }
3731        return $titles;
3732    }
3733
3734    // fau: testNav - add number parameter (to show if title should not be shown)
3735    /**
3736     * Returns the title of a test question and checks if the title output is allowed.
3737     * If not, the localized text "question" will be returned.
3738     *
3739     * @param string $title The original title of the question
3740     * @param integer $nr The number of the question in the sequence
3741     * @return string The title for the question title output
3742     * @access public
3743     */
3744    public function getQuestionTitle($title, $nr = null)
3745    {
3746        if ($this->getTitleOutput() == 2) {
3747            if ($this->getQuestionSetType() == self::QUESTION_SET_TYPE_DYNAMIC) {
3748                // avoid legacy setting combination: ctm without question titles
3749                return $title;
3750            } elseif (isset($nr)) {
3751                return $this->lng->txt("ass_question") . ' ' . $nr;
3752            } else {
3753                return $this->lng->txt("ass_question");
3754            }
3755        } else {
3756            return $title;
3757        }
3758    }
3759    // fau.
3760
3761    /**
3762    * Returns the dataset for a given question id
3763    *
3764    * @param integer $question_id The database id of the question
3765    * @return object Question dataset
3766    * @access public
3767    * @see $questions
3768    */
3769    public function getQuestionDataset($question_id)
3770    {
3771        global $DIC;
3772        $ilDB = $DIC['ilDB'];
3773
3774        $result = $ilDB->queryF(
3775            "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",
3776            array('integer'),
3777            array($question_id)
3778        );
3779        $row = $ilDB->fetchObject($result);
3780        return $row;
3781    }
3782
3783    /**
3784    * Get the id's of the questions which are already part of the test
3785    *
3786    * @return array An array containing the already existing questions
3787    * @access	public
3788    */
3789    public function &getExistingQuestions($pass = null)
3790    {
3791        global $DIC;
3792        $ilUser = $DIC['ilUser'];
3793        $ilDB = $DIC['ilDB'];
3794
3795        $existing_questions = array();
3796        $active_id = $this->getActiveIdOfUser($ilUser->getId());
3797        if ($this->isRandomTest()) {
3798            if (is_null($pass)) {
3799                $pass = 0;
3800            }
3801            $result = $ilDB->queryF(
3802                "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",
3803                array('integer','integer'),
3804                array($active_id, $pass)
3805            );
3806        } else {
3807            $result = $ilDB->queryF(
3808                "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",
3809                array('integer'),
3810                array($this->getTestId())
3811            );
3812        }
3813        while ($data = $ilDB->fetchObject($result)) {
3814            if ($data->original_id === null) {
3815                continue;
3816            }
3817
3818            array_push($existing_questions, $data->original_id);
3819        }
3820        return $existing_questions;
3821    }
3822
3823    /**
3824    * Returns the question type of a question with a given id
3825    *
3826    * @param integer $question_id The database id of the question
3827    * @result string The question type string
3828    * @access private
3829    */
3830    public function getQuestionType($question_id)
3831    {
3832        global $DIC;
3833        $ilDB = $DIC['ilDB'];
3834
3835        if ($question_id < 1) {
3836            return -1;
3837        }
3838        $result = $ilDB->queryF(
3839            "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",
3840            array('integer'),
3841            array($question_id)
3842        );
3843        if ($result->numRows() == 1) {
3844            $data = $ilDB->fetchObject($result);
3845            return $data->type_tag;
3846        } else {
3847            return "";
3848        }
3849    }
3850
3851    /**
3852    * Write the initial entry for the tests working time to the database
3853    *
3854    * @param integer $user_id The database id of the user working with the test
3855    * @access	public
3856    */
3857    public function startWorkingTime($active_id, $pass)
3858    {
3859        global $DIC;
3860        $ilDB = $DIC['ilDB'];
3861
3862        $next_id = $ilDB->nextId('tst_times');
3863        $affectedRows = $ilDB->manipulateF(
3864            "INSERT INTO tst_times (times_id, active_fi, started, finished, pass, tstamp) VALUES (%s, %s, %s, %s, %s, %s)",
3865            array('integer', 'integer', 'timestamp', 'timestamp', 'integer', 'integer'),
3866            array($next_id, $active_id, strftime("%Y-%m-%d %H:%M:%S"), strftime("%Y-%m-%d %H:%M:%S"), $pass, time())
3867        );
3868        return $next_id;
3869    }
3870
3871    /**
3872    * Update the working time of a test when a question is answered
3873    *
3874    * @param integer $times_id The database id of a working time entry
3875    * @access	public
3876    */
3877    public function updateWorkingTime($times_id)
3878    {
3879        global $DIC;
3880        $ilDB = $DIC['ilDB'];
3881
3882        $affectedRows = $ilDB->manipulateF(
3883            "UPDATE tst_times SET finished = %s, tstamp = %s WHERE times_id = %s",
3884            array('timestamp', 'integer', 'integer'),
3885            array(strftime("%Y-%m-%d %H:%M:%S"), time(), $times_id)
3886        );
3887    }
3888
3889    /**
3890    * Gets the id's of all questions a user already worked through
3891    *
3892    * @return array The question id's of the questions already worked through
3893    * @access	public
3894    */
3895    public function &getWorkedQuestions($active_id, $pass = null)
3896    {
3897        global $DIC;
3898        $ilUser = $DIC['ilUser'];
3899        $ilDB = $DIC['ilDB'];
3900
3901        if (is_null($pass)) {
3902            $result = $ilDB->queryF(
3903                "SELECT question_fi FROM tst_solutions WHERE active_fi = %s AND pass = %s GROUP BY question_fi",
3904                array('integer','integer'),
3905                array($active_id, 0)
3906            );
3907        } else {
3908            $result = $ilDB->queryF(
3909                "SELECT question_fi FROM tst_solutions WHERE active_fi = %s AND pass = %s GROUP BY question_fi",
3910                array('integer','integer'),
3911                array($active_id, $pass)
3912            );
3913        }
3914        $result_array = array();
3915        while ($row = $ilDB->fetchAssoc($result)) {
3916            array_push($result_array, $row["question_fi"]);
3917        }
3918        return $result_array;
3919    }
3920
3921    /**
3922    * Returns true if an active user completed a test pass and did not start a new pass
3923    *
3924    * @param integer $active_id The active id of the user
3925    * @param integer $currentpass The current test pass of the user
3926    * @return boolean true if an active user completed a test pass and did not start a new pass, false otherwise
3927    * @access public
3928    */
3929    public function isTestFinishedToViewResults($active_id, $currentpass)
3930    {
3931        $num = ilObjTest::lookupPassResultsUpdateTimestamp($active_id, $currentpass);
3932        return ((($currentpass > 0) && ($num == 0)) || $this->isTestFinished($active_id)) ? true : false;
3933    }
3934
3935    /**
3936    * Returns all questions of a test in test order
3937    *
3938    * @return array An array containing the id's as keys and the database row objects as values
3939    * @access public
3940    */
3941    public function &getAllQuestions($pass = null)
3942    {
3943        global $DIC;
3944        $ilUser = $DIC['ilUser'];
3945        $ilDB = $DIC['ilDB'];
3946
3947        $result_array = array();
3948        if ($this->isRandomTest()) {
3949            $active_id = $this->getActiveIdOfUser($ilUser->getId());
3950            $this->loadQuestions($active_id, $pass);
3951            if (count($this->questions) == 0) {
3952                return $result_array;
3953            }
3954            if (is_null($pass)) {
3955                $pass = self::_getPass($active_id);
3956            }
3957            $result = $ilDB->queryF(
3958                "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'),
3959                array('integer','integer'),
3960                array($active_id, $pass)
3961            );
3962        } else {
3963            if (count($this->questions) == 0) {
3964                return $result_array;
3965            }
3966            $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'));
3967        }
3968        while ($row = $ilDB->fetchAssoc($result)) {
3969            $result_array[$row["question_id"]] = $row;
3970        }
3971        return $result_array;
3972    }
3973
3974    /**
3975    * Gets the active id of a given user
3976    *
3977    * @param integer $user_id The database id of the user
3978    * @param string $anonymous_id The anonymous id if the test is an anonymized test
3979    * @return integer The active ID
3980    * @access	public
3981    */
3982    public function getActiveIdOfUser($user_id = "", $anonymous_id = "")
3983    {
3984        global $DIC;
3985        $ilDB = $DIC['ilDB'];
3986        $ilUser = $DIC['ilUser'];
3987
3988        if (!$user_id) {
3989            $user_id = $ilUser->getId();
3990        }
3991        if (($GLOBALS['DIC']['ilUser']->getId() == ANONYMOUS_USER_ID) && (strlen($_SESSION["tst_access_code"][$this->getTestId()]))) {
3992            $result = $ilDB->queryF(
3993                "SELECT active_id FROM tst_active WHERE user_fi = %s AND test_fi = %s AND anonymous_id = %s",
3994                array('integer','integer','text'),
3995                array($user_id, $this->test_id, $_SESSION["tst_access_code"][$this->getTestId()])
3996                );
3997        } elseif (strlen($anonymous_id)) {
3998            $result = $ilDB->queryF(
3999                "SELECT active_id FROM tst_active WHERE user_fi = %s AND test_fi = %s AND anonymous_id = %s",
4000                array('integer','integer','text'),
4001                array($user_id, $this->test_id, $anonymous_id)
4002                );
4003        } else {
4004            if ($GLOBALS['DIC']['ilUser']->getId() == ANONYMOUS_USER_ID) {
4005                return null;
4006            }
4007            $result = $ilDB->queryF(
4008                "SELECT active_id FROM tst_active WHERE user_fi = %s AND test_fi = %s",
4009                array('integer','integer'),
4010                array($user_id, $this->test_id)
4011                );
4012        }
4013        if ($result->numRows()) {
4014            $row = $ilDB->fetchAssoc($result);
4015            return $row["active_id"];
4016        } else {
4017            return 0;
4018        }
4019    }
4020
4021    /**
4022    * Gets the active id of the tst_active table for the active user
4023    *
4024    * @param integer $user_id The database id of the user
4025    * @param integer $test_id The database id of the test
4026    * @return object The database row of the tst_active table
4027    * @access	public
4028    */
4029    public static function _getActiveIdOfUser($user_id = "", $test_id = "")
4030    {
4031        global $DIC;
4032        $ilDB = $DIC['ilDB'];
4033        $ilUser = $DIC['ilUser'];
4034
4035        if (!$user_id) {
4036            $user_id = $ilUser->id;
4037        }
4038        if (!$test_id) {
4039            return "";
4040        }
4041        $result = $ilDB->queryF(
4042            "SELECT tst_active.active_id FROM tst_active WHERE user_fi = %s AND test_fi = %s",
4043            array('integer', 'integer'),
4044            array($user_id, $test_id)
4045        );
4046        if ($result->numRows()) {
4047            $row = $ilDB->fetchAssoc($result);
4048            return $row["active_id"];
4049        } else {
4050            return "";
4051        }
4052    }
4053
4054    /**
4055    * Shuffles the values of a given array
4056    *
4057    * @param array $array An array which should be shuffled
4058    * @access public
4059    */
4060    public function pcArrayShuffle($array)
4061    {
4062        $keys = array_keys($array);
4063        shuffle($keys);
4064        $result = array();
4065        foreach ($keys as $key) {
4066            $result[$key] = $array[$key];
4067        }
4068        return $result;
4069    }
4070
4071    /**
4072    * Calculates the results of a test for a given user
4073    * and returns an array with all test results
4074    *
4075    * @return array An array containing the test results for the given user
4076    * @access public
4077    */
4078    public function &getTestResult($active_id, $pass = null, $ordered_sequence = false, $considerHiddenQuestions = true, $considerOptionalQuestions = true)
4079    {
4080        global $DIC;
4081        $tree = $DIC['tree'];
4082        $ilDB = $DIC['ilDB'];
4083        $lng = $DIC['lng'];
4084        $ilPluginAdmin = $DIC['ilPluginAdmin'];
4085
4086        $results = $this->getResultsForActiveId($active_id);
4087
4088        if (is_null($pass)) {
4089            $pass = $results['pass'];
4090        }
4091
4092        require_once 'Modules/Test/classes/class.ilTestSessionFactory.php';
4093        $testSessionFactory = new ilTestSessionFactory($this);
4094        $testSession = $testSessionFactory->getSession($active_id);
4095
4096        require_once 'Modules/Test/classes/class.ilTestSequenceFactory.php';
4097        $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $ilPluginAdmin, $this);
4098        $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($active_id, $pass);
4099
4100        if ($this->isDynamicTest()) {
4101            require_once 'Modules/Test/classes/class.ilObjTestDynamicQuestionSetConfig.php';
4102            $dynamicQuestionSetConfig = new ilObjTestDynamicQuestionSetConfig($tree, $ilDB, $ilPluginAdmin, $this);
4103            $dynamicQuestionSetConfig->loadFromDb();
4104
4105            $testSequence->loadFromDb($dynamicQuestionSetConfig);
4106            $testSequence->loadQuestions($dynamicQuestionSetConfig, new ilTestDynamicQuestionSetFilterSelection());
4107
4108            $sequence = $testSequence->getUserSequenceQuestions();
4109        } else {
4110            $testSequence->setConsiderHiddenQuestionsEnabled($considerHiddenQuestions);
4111            $testSequence->setConsiderOptionalQuestionsEnabled($considerOptionalQuestions);
4112
4113            $testSequence->loadFromDb();
4114            $testSequence->loadQuestions();
4115
4116            if ($ordered_sequence) {
4117                $sequence = $testSequence->getOrderedSequenceQuestions();
4118            } else {
4119                $sequence = $testSequence->getUserSequenceQuestions();
4120            }
4121        }
4122
4123        $arrResults = array();
4124
4125        $query = "
4126			SELECT		tst_test_result.question_fi,
4127						tst_test_result.points reached,
4128						tst_test_result.hint_count requested_hints,
4129						tst_test_result.hint_points hint_points,
4130						tst_test_result.answered answered
4131
4132			FROM		tst_test_result
4133
4134			LEFT JOIN	tst_solutions
4135			ON			tst_solutions.active_fi = tst_test_result.active_fi
4136			AND			tst_solutions.question_fi = tst_test_result.question_fi
4137
4138			WHERE		tst_test_result.active_fi = %s
4139			AND			tst_test_result.pass = %s
4140		";
4141
4142        $solutionresult = $ilDB->queryF(
4143            $query,
4144            array('integer', 'integer'),
4145            array($active_id, $pass)
4146        );
4147
4148        while ($row = $ilDB->fetchAssoc($solutionresult)) {
4149            $arrResults[ $row['question_fi'] ] = $row;
4150        }
4151
4152        $numWorkedThrough = count($arrResults);
4153
4154        require_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
4155
4156        $IN_question_ids = $ilDB->in('qpl_questions.question_id', $sequence, false, 'integer');
4157
4158        $query = "
4159			SELECT		qpl_questions.*,
4160						qpl_qst_type.type_tag,
4161						qpl_sol_sug.question_fi has_sug_sol
4162
4163			FROM		qpl_qst_type,
4164						qpl_questions
4165
4166			LEFT JOIN	qpl_sol_sug
4167			ON			qpl_sol_sug.question_fi = qpl_questions.question_id
4168
4169			WHERE		qpl_qst_type.question_type_id = qpl_questions.question_type_fi
4170			AND			$IN_question_ids
4171		";
4172
4173        $result = $ilDB->query($query);
4174
4175        $unordered = array();
4176
4177        $key = 1;
4178
4179        $obligationsAnswered = true;
4180
4181        while ($row = $ilDB->fetchAssoc($result)) {
4182            $percentvalue = (
4183                $row['points'] ? $arrResults[ $row['question_id'] ]['reached'] / $row['points'] : 0
4184            );
4185
4186            if ($percentvalue < 0) {
4187                $percentvalue = 0.0;
4188            }
4189
4190            $data = array(
4191                "nr" => "$key",
4192                "title" => ilUtil::prepareFormOutput($row['title']),
4193                "max" => round($row['points'], 2),
4194                "reached" => round($arrResults[$row['question_id']]['reached'], 2),
4195                'requested_hints' => $arrResults[$row['question_id']]['requested_hints'],
4196                'hint_points' => $arrResults[$row['question_id']]['hint_points'],
4197                "percent" => sprintf("%2.2f ", ($percentvalue) * 100) . "%",
4198                "solution" => ($row['has_sug_sol']) ? assQuestion::_getSuggestedSolutionOutput($row['question_id']) : '',
4199                "type" => $row["type_tag"],
4200                "qid" => $row['question_id'],
4201                "original_id" => $row["original_id"],
4202                "workedthrough" => isset($arrResults[$row['question_id']]) ? 1 : 0,
4203                'answered' => $arrResults[$row['question_id']]['answered']
4204            );
4205
4206            if (!$arrResults[ $row['question_id'] ]['answered']) {
4207                $obligationsAnswered = false;
4208            }
4209
4210            $unordered[ $row['question_id'] ] = $data;
4211
4212            $key++;
4213        }
4214
4215        $numQuestionsTotal = count($unordered);
4216
4217        $pass_max = 0;
4218        $pass_reached = 0;
4219        $pass_requested_hints = 0;
4220        $pass_hint_points = 0;
4221        $key = 1;
4222
4223        $found = array();
4224
4225        foreach ($sequence as $qid) {
4226            // building pass point sums based on prepared data
4227            // for question that exists in users qst sequence
4228            $pass_max += round($unordered[$qid]['max'], 2);
4229            $pass_reached += round($unordered[$qid]['reached'], 2);
4230            $pass_requested_hints += $unordered[$qid]['requested_hints'];
4231            $pass_hint_points += $unordered[$qid]['hint_points'];
4232
4233            // pickup prepared data for question
4234            // that exists in users qst sequence
4235            $unordered[$qid]['nr'] = $key;
4236            array_push($found, $unordered[$qid]);
4237
4238            // increment key counter
4239            $key++;
4240        }
4241
4242        $unordered = null;
4243
4244        if ($this->getScoreCutting() == 1) {
4245            if ($results['reached_points'] < 0) {
4246                $results['reached_points'] = 0;
4247            }
4248
4249            if ($pass_reached < 0) {
4250                $pass_reached = 0;
4251            }
4252        }
4253
4254        $found['pass']['total_max_points'] = $pass_max;
4255        $found['pass']['total_reached_points'] = $pass_reached;
4256        $found['pass']['total_requested_hints'] = $pass_requested_hints;
4257        $found['pass']['total_hint_points'] = $pass_hint_points;
4258        $found['pass']['percent'] = ($pass_max > 0) ? $pass_reached / $pass_max : 0;
4259        $found['pass']['obligationsAnswered'] = $obligationsAnswered;
4260        $found['pass']['num_workedthrough'] = $numWorkedThrough;
4261        $found['pass']['num_questions_total'] = $numQuestionsTotal;
4262
4263        $found["test"]["total_max_points"] = $results['max_points'];
4264        $found["test"]["total_reached_points"] = $results['reached_points'];
4265        $found["test"]["total_requested_hints"] = $results['hint_count'];
4266        $found["test"]["total_hint_points"] = $results['hint_points'];
4267        $found["test"]["result_pass"] = $results['pass'];
4268        $found['test']['result_tstamp'] = $results['tstamp'];
4269        $found['test']['obligations_answered'] = $results['obligations_answered'];
4270
4271        if ((!$total_reached_points) or (!$total_max_points)) {
4272            $percentage = 0.0;
4273        } else {
4274            $percentage = ($total_reached_points / $total_max_points) * 100.0;
4275
4276            if ($percentage < 0) {
4277                $percentage = 0.0;
4278            }
4279        }
4280
4281        $found["test"]["passed"] = $results['passed'];
4282
4283        return $found;
4284    }
4285
4286    /**
4287    * Returns the number of persons who started the test
4288    *
4289    * @return integer The number of persons who started the test
4290    * @access public
4291    */
4292    public function evalTotalPersons()
4293    {
4294        global $DIC;
4295        $ilDB = $DIC['ilDB'];
4296
4297        $result = $ilDB->queryF(
4298            "SELECT COUNT(active_id) total FROM tst_active WHERE test_fi = %s",
4299            array('integer'),
4300            array($this->getTestId())
4301        );
4302        $row = $ilDB->fetchAssoc($result);
4303        return $row["total"];
4304    }
4305
4306    /**
4307    * Returns the complete working time in seconds a user worked on the test
4308    *
4309    * @return integer The working time in seconds
4310    * @access public
4311    */
4312    public function getCompleteWorkingTime($user_id)
4313    {
4314        global $DIC;
4315        $ilDB = $DIC['ilDB'];
4316
4317        $result = $ilDB->queryF(
4318            "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",
4319            array('integer','integer'),
4320            array($this->getTestId(), $user_id)
4321        );
4322        $time = 0;
4323        while ($row = $ilDB->fetchAssoc($result)) {
4324            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
4325            $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4326            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
4327            $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4328            $time += ($epoch_2 - $epoch_1);
4329        }
4330        return $time;
4331    }
4332
4333    /**
4334    * Returns the complete working time in seconds for all test participants
4335    *
4336    * @return array An array containing the working time in seconds for all test participants
4337    * @access public
4338    */
4339    public function &getCompleteWorkingTimeOfParticipants()
4340    {
4341        return $this->_getCompleteWorkingTimeOfParticipants($this->getTestId());
4342    }
4343
4344    /**
4345    * Returns the complete working time in seconds for all test participants
4346    *
4347    * @param integer $test_id The database ID of the test
4348    * @return array An array containing the working time in seconds for all test participants
4349    * @access public
4350    */
4351    public function &_getCompleteWorkingTimeOfParticipants($test_id)
4352    {
4353        global $DIC;
4354        $ilDB = $DIC['ilDB'];
4355
4356        $result = $ilDB->queryF(
4357            "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",
4358            array('integer'),
4359            array($test_id)
4360        );
4361        $time = 0;
4362        $times = array();
4363        while ($row = $ilDB->fetchAssoc($result)) {
4364            if (!array_key_exists($row["active_fi"], $times)) {
4365                $times[$row["active_fi"]] = 0;
4366            }
4367            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
4368            $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4369            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
4370            $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4371            $times[$row["active_fi"]] += ($epoch_2 - $epoch_1);
4372        }
4373        return $times;
4374    }
4375
4376    /**
4377    * Returns the complete working time in seconds for a test participant
4378    *
4379    * @return integer The working time in seconds for the test participant
4380    * @access public
4381    */
4382    public function getCompleteWorkingTimeOfParticipant($active_id)
4383    {
4384        global $DIC;
4385        $ilDB = $DIC['ilDB'];
4386
4387        $result = $ilDB->queryF(
4388            "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",
4389            array('integer','integer'),
4390            array($this->getTestId(), $active_id)
4391        );
4392        $time = 0;
4393        while ($row = $ilDB->fetchAssoc($result)) {
4394            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
4395            $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4396            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
4397            $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4398            $time += ($epoch_2 - $epoch_1);
4399        }
4400        return $time;
4401    }
4402
4403    /**
4404    * Returns the complete working time in seconds for a test participant
4405    *
4406    * @return integer The working time in seconds for the test participant
4407    * @access public
4408    */
4409    public static function _getWorkingTimeOfParticipantForPass($active_id, $pass)
4410    {
4411        global $DIC;
4412        $ilDB = $DIC['ilDB'];
4413
4414        $result = $ilDB->queryF(
4415            "SELECT * FROM tst_times WHERE active_fi = %s AND pass = %s ORDER BY started",
4416            array('integer','integer'),
4417            array($active_id, $pass)
4418        );
4419        $time = 0;
4420        while ($row = $ilDB->fetchAssoc($result)) {
4421            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
4422            $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4423            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
4424            $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4425            $time += ($epoch_2 - $epoch_1);
4426        }
4427        return $time;
4428    }
4429
4430    /**
4431    * Returns the first and last visit of a participant
4432    *
4433    * @param integer $active_id The active ID of the participant
4434    * @return array The first and last visit of a participant
4435    * @access public
4436    */
4437    public function getVisitTimeOfParticipant($active_id)
4438    {
4439        return ilObjTest::_getVisitTimeOfParticipant($this->getTestId(), $active_id);
4440    }
4441
4442    /**
4443    * Returns the first and last visit of a participant
4444    *
4445    * @param integer $test_id The database ID of the test
4446    * @param integer $active_id The active ID of the participant
4447    * @return array The first and last visit of a participant
4448    * @access public
4449    */
4450    public function _getVisitTimeOfParticipant($test_id, $active_id)
4451    {
4452        global $DIC;
4453        $ilDB = $DIC['ilDB'];
4454
4455        $result = $ilDB->queryF(
4456            "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",
4457            array('integer','integer'),
4458            array($test_id, $active_id)
4459        );
4460        $firstvisit = 0;
4461        $lastvisit = 0;
4462        while ($row = $ilDB->fetchAssoc($result)) {
4463            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches);
4464            $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4465            if ($firstvisit == 0 || $epoch_1 < $firstvisit) {
4466                $firstvisit = $epoch_1;
4467            }
4468            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["finished"], $matches);
4469            $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4470            if ($epoch_2 > $lastvisit) {
4471                $lastvisit = $epoch_2;
4472            }
4473        }
4474        return array("firstvisit" => $firstvisit, "lastvisit" => $lastvisit);
4475    }
4476
4477    /**
4478    * Returns the statistical evaluation of the test for a specified user
4479    *
4480    * @return arrary The statistical evaluation array of the test
4481    * @access public
4482    */
4483    public function &evalStatistical($active_id)
4484    {
4485        global $DIC;
4486        $ilDB = $DIC['ilDB'];
4487        //		$ilBench = $DIC['ilBench'];
4488        $pass = ilObjTest::_getResultPass($active_id);
4489        $test_result = &$this->getTestResult($active_id, $pass);
4490        $result = $ilDB->queryF(
4491            "SELECT tst_times.* FROM tst_active, tst_times WHERE tst_active.active_id = %s AND tst_active.active_id = tst_times.active_fi",
4492            array('integer'),
4493            array($active_id)
4494        );
4495        $times = array();
4496        $first_visit = 0;
4497        $last_visit = 0;
4498        while ($row = $ilDB->fetchObject($result)) {
4499            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row->started, $matches);
4500            $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4501            if (!$first_visit) {
4502                $first_visit = $epoch_1;
4503            }
4504            if ($epoch_1 < $first_visit) {
4505                $first_visit = $epoch_1;
4506            }
4507            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row->finished, $matches);
4508            $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
4509            if (!$last_visit) {
4510                $last_visit = $epoch_2;
4511            }
4512            if ($epoch_2 > $last_visit) {
4513                $last_visit = $epoch_2;
4514            }
4515            $times[$row->active_fi] += ($epoch_2 - $epoch_1);
4516        }
4517        $max_time = 0;
4518        foreach ($times as $key => $value) {
4519            $max_time += $value;
4520        }
4521        if ((!$test_result["test"]["total_reached_points"]) or (!$test_result["test"]["total_max_points"])) {
4522            $percentage = 0.0;
4523        } else {
4524            $percentage = ($test_result["test"]["total_reached_points"] / $test_result["test"]["total_max_points"]) * 100.0;
4525            if ($percentage < 0) {
4526                $percentage = 0.0;
4527            }
4528        }
4529        $mark_obj = $this->mark_schema->getMatchingMark($percentage);
4530        $first_date = getdate($first_visit);
4531        $last_date = getdate($last_visit);
4532        $qworkedthrough = 0;
4533        foreach ($test_result as $key => $value) {
4534            if (preg_match("/\d+/", $key)) {
4535                $qworkedthrough += $value["workedthrough"];
4536            }
4537        }
4538        if (!$qworkedthrough) {
4539            $atimeofwork = 0;
4540        } else {
4541            $atimeofwork = $max_time / $qworkedthrough;
4542        }
4543
4544        $obligationsAnswered = $test_result["test"]["obligations_answered"];
4545
4546        $result_mark = "";
4547        $passed = "";
4548
4549        if ($mark_obj) {
4550            $result_mark = $mark_obj->getShortName();
4551
4552            if ($mark_obj->getPassed() && $obligationsAnswered) {
4553                $passed = 1;
4554            } else {
4555                $passed = 0;
4556            }
4557        }
4558        $percent_worked_through = 0;
4559        if (count($this->questions)) {
4560            $percent_worked_through = $qworkedthrough / count($this->questions);
4561        }
4562        $result_array = array(
4563            "qworkedthrough" => $qworkedthrough,
4564            "qmax" => count($this->questions),
4565            "pworkedthrough" => $percent_worked_through,
4566            "timeofwork" => $max_time,
4567            "atimeofwork" => $atimeofwork,
4568            "firstvisit" => $first_date,
4569            "lastvisit" => $last_date,
4570            "resultspoints" => $test_result["test"]["total_reached_points"],
4571            "maxpoints" => $test_result["test"]["total_max_points"],
4572            "resultsmarks" => $result_mark,
4573            "passed" => $passed,
4574            "distancemedian" => "0"
4575        );
4576        foreach ($test_result as $key => $value) {
4577            if (preg_match("/\d+/", $key)) {
4578                $result_array[$key] = $value;
4579            }
4580        }
4581        return $result_array;
4582    }
4583
4584    /**
4585    * Returns an array with the total points of all users who passed the test
4586    * This array could be used for statistics
4587    *
4588    * @return array The total point values
4589    * @access public
4590    */
4591    public function &getTotalPointsPassedArray()
4592    {
4593        $totalpoints_array = array();
4594        $all_users = &$this->evalTotalParticipantsArray();
4595        foreach ($all_users as $active_id => $user_name) {
4596            $test_result = &$this->getTestResult($active_id);
4597            $reached = $test_result["test"]["total_reached_points"];
4598            $total = $test_result["test"]["total_max_points"];
4599            $percentage = $total != 0 ? $reached / $total : 0;
4600            $mark = $this->mark_schema->getMatchingMark($percentage * 100.0);
4601
4602            $obligationsAnswered = $test_result["test"]["obligations_answered"];
4603
4604            if ($mark) {
4605                if ($mark->getPassed() && $obligationsAnswered) {
4606                    array_push($totalpoints_array, $test_result["test"]["total_reached_points"]);
4607                }
4608            }
4609        }
4610        return $totalpoints_array;
4611    }
4612
4613    /**
4614     * Returns all persons who started the test
4615     *
4616     * @return array The active ids, names and logins of the persons who started the test
4617    */
4618    public function &getParticipants()
4619    {
4620        global $DIC;
4621        $ilDB = $DIC['ilDB'];
4622        $result = $ilDB->queryF(
4623            "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",
4624            array('integer'),
4625            array($this->getTestId())
4626        );
4627        $persons_array = array();
4628        while ($row = $ilDB->fetchAssoc($result)) {
4629            $name = $this->lng->txt("anonymous");
4630            $fullname = $this->lng->txt("anonymous");
4631            $login = "";
4632            if (!$this->getAnonymity()) {
4633                if (strlen($row["firstname"] . $row["lastname"] . $row["title"]) == 0) {
4634                    $name = $this->lng->txt("deleted_user");
4635                    $fullname = $this->lng->txt("deleted_user");
4636                    $login = $this->lng->txt("unknown");
4637                } else {
4638                    $login = $row["login"];
4639                    if ($row["user_fi"] == ANONYMOUS_USER_ID) {
4640                        $name = $this->lng->txt("anonymous");
4641                        $fullname = $this->lng->txt("anonymous");
4642                    } else {
4643                        $name = trim($row["lastname"] . ", " . $row["firstname"] . " " . $row["title"]);
4644                        $fullname = trim($row["title"] . " " . $row["firstname"] . " " . $row["lastname"]);
4645                    }
4646                }
4647            }
4648            $persons_array[$row["active_id"]] = array(
4649                "name" => $name,
4650                "fullname" => $fullname,
4651                "login" => $login
4652            );
4653        }
4654        return $persons_array;
4655    }
4656
4657    /**
4658    * Returns all persons who started the test
4659    *
4660    * @return arrary The user id's and names of the persons who started the test
4661    * @access public
4662    */
4663    public function &evalTotalPersonsArray($name_sort_order = "asc")
4664    {
4665        global $DIC;
4666        $ilDB = $DIC['ilDB'];
4667        $result = $ilDB->queryF(
4668            "SELECT 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),
4669            array('integer'),
4670            array($this->getTestId())
4671        );
4672        $persons_array = array();
4673        while ($row = $ilDB->fetchAssoc($result)) {
4674            if ($this->getAccessFilteredParticipantList() && !$this->getAccessFilteredParticipantList()->isActiveIdInList($row["active_id"])) {
4675                continue;
4676            }
4677
4678            if ($this->getAnonymity()) {
4679                $persons_array[$row["active_id"]] = $this->lng->txt("anonymous");
4680            } else {
4681                if (strlen($row["firstname"] . $row["lastname"] . $row["title"]) == 0) {
4682                    $persons_array[$row["active_id"]] = $this->lng->txt("deleted_user");
4683                } else {
4684                    if ($row["user_fi"] == ANONYMOUS_USER_ID) {
4685                        $persons_array[$row["active_id"]] = $row["lastname"];
4686                    } else {
4687                        $persons_array[$row["active_id"]] = trim($row["lastname"] . ", " . $row["firstname"] . " " . $row["title"]);
4688                    }
4689                }
4690            }
4691        }
4692        return $persons_array;
4693    }
4694
4695    /**
4696    * Returns all participants who started the test
4697    *
4698    * @return arrary The active user id's and names of the persons who started the test
4699    * @access public
4700    */
4701    public function &evalTotalParticipantsArray($name_sort_order = "asc")
4702    {
4703        global $DIC;
4704        $ilDB = $DIC['ilDB'];
4705        $result = $ilDB->queryF(
4706            "SELECT 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),
4707            array('integer'),
4708            array($this->getTestId())
4709        );
4710        $persons_array = array();
4711        while ($row = $ilDB->fetchAssoc($result)) {
4712            if ($this->getAnonymity()) {
4713                $persons_array[$row["active_id"]] = array("name" => $this->lng->txt("anonymous"));
4714            } else {
4715                if (strlen($row["firstname"] . $row["lastname"] . $row["title"]) == 0) {
4716                    $persons_array[$row["active_id"]] = array("name" => $this->lng->txt("deleted_user"));
4717                } else {
4718                    if ($row["user_fi"] == ANONYMOUS_USER_ID) {
4719                        $persons_array[$row["active_id"]] = array("name" => $row["lastname"]);
4720                    } else {
4721                        $persons_array[$row["active_id"]] = array("name" => trim($row["lastname"] . ", " . $row["firstname"] . " " . $row["title"]), "login" => $row["login"]);
4722                    }
4723                }
4724            }
4725        }
4726        return $persons_array;
4727    }
4728
4729    /**
4730    * Retrieves all the assigned questions for all test passes of a test participant
4731    *
4732    * @return array An associated array containing the questions
4733    * @access public
4734    */
4735    public function &getQuestionsOfTest($active_id)
4736    {
4737        global $DIC;
4738        $ilDB = $DIC['ilDB'];
4739        if ($this->isRandomTest()) {
4740            $ilDB->setLimit($this->getQuestionCount(), 0);
4741            $result = $ilDB->queryF(
4742                "SELECT tst_test_rnd_qst.sequence, tst_test_rnd_qst.question_fi, " .
4743                "tst_test_rnd_qst.pass, qpl_questions.points " .
4744                "FROM tst_test_rnd_qst, qpl_questions " .
4745                "WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id " .
4746                "AND tst_test_rnd_qst.active_fi = %s ORDER BY tst_test_rnd_qst.sequence",
4747                array('integer'),
4748                array($active_id)
4749            );
4750        } else {
4751            $result = $ilDB->queryF(
4752                "SELECT tst_test_question.sequence, tst_test_question.question_fi, " .
4753                "qpl_questions.points " .
4754                "FROM tst_test_question, tst_active, qpl_questions " .
4755                "WHERE tst_test_question.question_fi = qpl_questions.question_id " .
4756                "AND tst_active.active_id = %s AND tst_active.test_fi = tst_test_question.test_fi",
4757                array('integer'),
4758                array($active_id)
4759            );
4760        }
4761        $qtest = array();
4762        if ($result->numRows()) {
4763            while ($row = $ilDB->fetchAssoc($result)) {
4764                array_push($qtest, $row);
4765            }
4766        }
4767        return $qtest;
4768    }
4769
4770    /**
4771    * Retrieves all the assigned questions for a test participant in a given test pass
4772    *
4773    * @return array An associated array containing the questions
4774    * @access public
4775    */
4776    public function &getQuestionsOfPass($active_id, $pass)
4777    {
4778        global $DIC;
4779        $ilDB = $DIC['ilDB'];
4780        if ($this->isRandomTest()) {
4781            $ilDB->setLimit($this->getQuestionCount(), 0);
4782            $result = $ilDB->queryF(
4783                "SELECT tst_test_rnd_qst.sequence, tst_test_rnd_qst.question_fi, " .
4784                "qpl_questions.points " .
4785                "FROM tst_test_rnd_qst, qpl_questions " .
4786                "WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id " .
4787                "AND tst_test_rnd_qst.active_fi = %s AND tst_test_rnd_qst.pass = %s " .
4788                "ORDER BY tst_test_rnd_qst.sequence",
4789                array('integer', 'integer'),
4790                array($active_id, $pass)
4791            );
4792        } else {
4793            $result = $ilDB->queryF(
4794                "SELECT tst_test_question.sequence, tst_test_question.question_fi, " .
4795                "qpl_questions.points " .
4796                "FROM tst_test_question, tst_active, qpl_questions " .
4797                "WHERE tst_test_question.question_fi = qpl_questions.question_id " .
4798                "AND tst_active.active_id = %s AND tst_active.test_fi = tst_test_question.test_fi",
4799                array('integer'),
4800                array($active_id)
4801            );
4802        }
4803        $qpass = array();
4804        if ($result->numRows()) {
4805            while ($row = $ilDB->fetchAssoc($result)) {
4806                array_push($qpass, $row);
4807            }
4808        }
4809        return $qpass;
4810    }
4811
4812    /**
4813     * @var ilTestParticipantList
4814     */
4815    protected $accessFilteredParticipantList;
4816
4817    /**
4818     * @return ilTestParticipantList
4819     */
4820    public function getAccessFilteredParticipantList()
4821    {
4822        return $this->accessFilteredParticipantList;
4823    }
4824
4825    /**
4826     * @param ilTestParticipantList $accessFilteredParticipantList
4827     */
4828    public function setAccessFilteredParticipantList($accessFilteredParticipantList)
4829    {
4830        $this->accessFilteredParticipantList = $accessFilteredParticipantList;
4831    }
4832
4833    /**
4834     * @return ilTestParticipantList
4835     */
4836    public function buildStatisticsAccessFilteredParticipantList()
4837    {
4838        require_once 'Modules/Test/classes/class.ilTestParticipantList.php';
4839        require_once 'Modules/Test/classes/class.ilTestParticipantAccessFilter.php';
4840
4841        $list = new ilTestParticipantList($this);
4842        $list->initializeFromDbRows($this->getTestParticipants());
4843
4844        $list = $list->getAccessFilteredList(
4845            ilTestParticipantAccessFilter::getAccessStatisticsUserFilter($this->getRefId())
4846        );
4847
4848        return $list;
4849    }
4850
4851    public function getUnfilteredEvaluationData()
4852    {
4853        /** @var $DIC ILIAS\DI\Container */
4854        global $DIC;
4855
4856        $ilDB = $DIC->database();
4857
4858        include_once "./Modules/Test/classes/class.ilTestEvaluationPassData.php";
4859        include_once "./Modules/Test/classes/class.ilTestEvaluationUserData.php";
4860        include_once "./Modules/Test/classes/class.ilTestEvaluationData.php";
4861
4862        $data = new ilTestEvaluationData($this);
4863
4864        $query = "
4865			SELECT		tst_test_result.*,
4866						qpl_questions.original_id,
4867						qpl_questions.title questiontitle,
4868						qpl_questions.points maxpoints
4869
4870			FROM		tst_test_result, qpl_questions, tst_active
4871
4872			WHERE		tst_active.active_id = tst_test_result.active_fi
4873			AND			qpl_questions.question_id = tst_test_result.question_fi
4874			AND			tst_active.test_fi = %s
4875
4876			ORDER BY	tst_active.active_id ASC, tst_test_result.pass ASC, tst_test_result.tstamp DESC
4877		";
4878
4879        $result = $ilDB->queryF(
4880            $query,
4881            array('integer'),
4882            array($this->getTestId())
4883        );
4884
4885        $pass = null;
4886        $checked = array();
4887        $datasets = 0;
4888        $questionData = [];
4889
4890        while ($row = $ilDB->fetchAssoc($result)) {
4891            $participantObject = $data->getParticipant($row["active_fi"]);
4892
4893            if (!($participantObject instanceof ilTestEvaluationUserData)) {
4894                continue;
4895            }
4896
4897            $passObject = $participantObject->getPass($row["pass"]);
4898
4899            if (!($passObject instanceof ilTestEvaluationPassData)) {
4900                continue;
4901            }
4902
4903            $passObject->addAnsweredQuestion(
4904                $row["question_fi"],
4905                $row["maxpoints"],
4906                $row["points"],
4907                $row['answered'],
4908                null,
4909                $row['manual']
4910            );
4911        }
4912
4913        foreach (array_keys($data->getParticipants()) as $active_id) {
4914            if ($this->isRandomTest()) {
4915                for ($testpass = 0; $testpass <= $data->getParticipant($active_id)->getLastPass(); $testpass++) {
4916                    $ilDB->setLimit($this->getQuestionCount(), 0);
4917
4918                    $query = "
4919						SELECT tst_test_rnd_qst.sequence, tst_test_rnd_qst.question_fi, qpl_questions.original_id,
4920						tst_test_rnd_qst.pass, qpl_questions.points, qpl_questions.title
4921						FROM tst_test_rnd_qst, qpl_questions
4922						WHERE tst_test_rnd_qst.question_fi = qpl_questions.question_id
4923						AND tst_test_rnd_qst.pass = %s
4924						AND tst_test_rnd_qst.active_fi = %s ORDER BY tst_test_rnd_qst.sequence
4925					";
4926
4927                    $result = $ilDB->queryF(
4928                        $query,
4929                        array('integer','integer'),
4930                        array($testpass, $active_id)
4931                    );
4932
4933                    if ($result->numRows()) {
4934                        while ($row = $ilDB->fetchAssoc($result)) {
4935                            $tpass = array_key_exists("pass", $row) ? $row["pass"] : 0;
4936
4937                            $data->getParticipant($active_id)->addQuestion(
4938                                $row["original_id"],
4939                                $row["question_fi"],
4940                                $row["points"],
4941                                $row["sequence"],
4942                                $tpass
4943                            );
4944
4945                            $data->addQuestionTitle($row["question_fi"], $row["title"]);
4946                        }
4947                    }
4948                }
4949            } elseif ($this->isDynamicTest()) {
4950                $lastPass = $data->getParticipant($active_id)->getLastPass();
4951                for ($testpass = 0; $testpass <= $lastPass; $testpass++) {
4952                    require_once 'Modules/Test/classes/class.ilObjTestDynamicQuestionSetConfig.php';
4953                    $dynamicQuestionSetConfig = new ilObjTestDynamicQuestionSetConfig(
4954                        $DIC->repositoryTree(),
4955                        $DIC->database(),
4956                        $DIC['ilPluginAdmin'],
4957                        $this
4958                    );
4959                    $dynamicQuestionSetConfig->loadFromDb();
4960
4961                    require_once 'Modules/Test/classes/class.ilTestSequenceFactory.php';
4962                    $testSequenceFactory = new ilTestSequenceFactory($DIC->database(), $DIC->language(), $DIC['ilPluginAdmin'], $this);
4963                    $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($active_id, $testpass);
4964
4965                    $testSequence->loadFromDb($dynamicQuestionSetConfig);
4966                    $testSequence->loadQuestions($dynamicQuestionSetConfig, new ilTestDynamicQuestionSetFilterSelection());
4967
4968                    $sequence = (array) $testSequence->getUserSequenceQuestions();
4969
4970                    $questionsIdsToRequest = array_diff(array_values($sequence), array_values($questionData));
4971                    if (count($questionsIdsToRequest) > 0) {
4972                        $questionIdsCondition = ' ' . $DIC->database()->in('question_id', array_values($questionsIdsToRequest), false, 'integer') . ' ';
4973
4974                        $res = $DIC->database()->queryF(
4975                            "
4976							SELECT *
4977							FROM qpl_questions
4978							WHERE {$questionIdsCondition}",
4979                            array('integer'),
4980                            array($active_id)
4981                        );
4982                        while ($row = $DIC->database()->fetchAssoc($res)) {
4983                            $questionData[$row['question_id']] = $row;
4984                            $data->addQuestionTitle($row['question_id'], $row['title']);
4985                        }
4986                    }
4987
4988                    foreach ($sequence as $questionId) {
4989                        if (!isset($questionData[$questionId])) {
4990                            continue;
4991                        }
4992
4993                        $row = $questionData[$questionId];
4994
4995                        $data->getParticipant(
4996                            $active_id
4997                        )->addQuestion(
4998                            $row['original_id'],
4999                            $row['question_id'],
5000                            $row['points'],
5001                            null,
5002                            $testpass
5003                        );
5004                    }
5005                }
5006            } else {
5007                $query = "
5008					SELECT tst_test_question.sequence, tst_test_question.question_fi,
5009					qpl_questions.points, qpl_questions.title, qpl_questions.original_id
5010					FROM tst_test_question, tst_active, qpl_questions
5011					WHERE tst_test_question.question_fi = qpl_questions.question_id
5012					AND tst_active.active_id = %s
5013					AND tst_active.test_fi = tst_test_question.test_fi
5014					ORDER BY tst_test_question.sequence
5015				";
5016
5017                $result = $ilDB->queryF(
5018                    $query,
5019                    array('integer'),
5020                    array($active_id)
5021                );
5022
5023                if ($result->numRows()) {
5024                    $questionsbysequence = array();
5025
5026                    while ($row = $ilDB->fetchAssoc($result)) {
5027                        $questionsbysequence[$row["sequence"]] = $row;
5028                    }
5029
5030                    $seqresult = $ilDB->queryF(
5031                        "SELECT * FROM tst_sequence WHERE active_fi = %s",
5032                        array('integer'),
5033                        array($active_id)
5034                    );
5035
5036                    while ($seqrow = $ilDB->fetchAssoc($seqresult)) {
5037                        $questionsequence = unserialize($seqrow["sequence"]);
5038
5039                        foreach ($questionsequence as $sidx => $seq) {
5040                            $data->getParticipant($active_id)->addQuestion(
5041                                $questionsbysequence[$seq]["original_id"],
5042                                $questionsbysequence[$seq]["question_fi"],
5043                                $questionsbysequence[$seq]["points"],
5044                                $sidx + 1,
5045                                $seqrow["pass"]
5046                            );
5047
5048                            $data->addQuestionTitle(
5049                                $questionsbysequence[$seq]["question_fi"],
5050                                $questionsbysequence[$seq]["title"]
5051                            );
5052                        }
5053                    }
5054                }
5055            }
5056        }
5057
5058        if ($this->getECTSOutput()) {
5059            $passed_array = &$this->getTotalPointsPassedArray();
5060        }
5061
5062        foreach (array_keys($data->getParticipants()) as $active_id) {
5063            $tstUserData = $data->getParticipant($active_id);
5064
5065            $percentage = $tstUserData->getReachedPointsInPercent();
5066
5067            $obligationsAnswered = $tstUserData->areObligationsAnswered();
5068
5069            $mark = $this->mark_schema->getMatchingMark($percentage);
5070
5071            if (is_object($mark)) {
5072                $tstUserData->setMark($mark->getShortName());
5073                $tstUserData->setMarkOfficial($mark->getOfficialName());
5074
5075                $tstUserData->setPassed(
5076                    $mark->getPassed() && $tstUserData->areObligationsAnswered()
5077                );
5078            }
5079
5080            if ($this->getECTSOutput()) {
5081                $ects_mark = $this->getECTSGrade(
5082                    $passed_array,
5083                    $tstUserData->getReached(),
5084                    $tstUserData->getMaxPoints()
5085                );
5086
5087                $tstUserData->setECTSMark($ects_mark);
5088            }
5089
5090            $visitingTime = &$this->getVisitTimeOfParticipant($active_id);
5091
5092            $tstUserData->setFirstVisit($visitingTime["firstvisit"]);
5093            $tstUserData->setLastVisit($visitingTime["lastvisit"]);
5094        }
5095
5096        return $data;
5097    }
5098
5099    public static function _getQuestionCountAndPointsForPassOfParticipant($active_id, $pass)
5100    {
5101        global $DIC;
5102        $ilDB = $DIC['ilDB'];
5103
5104        $questionSetType = ilObjTest::lookupQuestionSetTypeByActiveId($active_id);
5105
5106        switch ($questionSetType) {
5107            case ilObjTest::QUESTION_SET_TYPE_DYNAMIC:
5108
5109                $res = $ilDB->queryF(
5110                    "
5111						SELECT		COUNT(qpl_questions.question_id) qcount,
5112									SUM(qpl_questions.points) qsum
5113						FROM		tst_active
5114						INNER JOIN	tst_tests
5115						ON			tst_tests.test_id = tst_active.test_fi
5116						INNER JOIN	tst_dyn_quest_set_cfg
5117						ON          tst_dyn_quest_set_cfg.test_fi = tst_tests.test_id
5118						INNER JOIN  qpl_questions
5119						ON          qpl_questions.obj_fi = tst_dyn_quest_set_cfg.source_qpl_fi
5120						AND         qpl_questions.original_id IS NULL
5121						AND         qpl_questions.complete = %s
5122						WHERE		tst_active.active_id = %s
5123					",
5124                    array('integer', 'integer'),
5125                    array(1, $active_id)
5126                );
5127
5128                break;
5129
5130            case ilObjTest::QUESTION_SET_TYPE_RANDOM:
5131
5132                $res = $ilDB->queryF(
5133                    "
5134						SELECT		tst_test_rnd_qst.pass,
5135									COUNT(tst_test_rnd_qst.question_fi) qcount,
5136									SUM(qpl_questions.points) qsum
5137
5138						FROM		tst_test_rnd_qst,
5139									qpl_questions
5140
5141						WHERE		tst_test_rnd_qst.question_fi = qpl_questions.question_id
5142						AND			tst_test_rnd_qst.active_fi = %s
5143						AND			pass = %s
5144
5145						GROUP BY	tst_test_rnd_qst.active_fi,
5146									tst_test_rnd_qst.pass
5147					",
5148                    array('integer', 'integer'),
5149                    array($active_id, $pass)
5150                );
5151
5152                break;
5153
5154            case ilObjTest::QUESTION_SET_TYPE_FIXED:
5155
5156                $res = $ilDB->queryF(
5157                    "
5158						SELECT		COUNT(tst_test_question.question_fi) qcount,
5159									SUM(qpl_questions.points) qsum
5160
5161						FROM		tst_test_question,
5162									qpl_questions,
5163									tst_active
5164
5165						WHERE		tst_test_question.question_fi = qpl_questions.question_id
5166						AND			tst_test_question.test_fi = tst_active.test_fi
5167						AND			tst_active.active_id = %s
5168
5169						GROUP BY	tst_test_question.test_fi
5170					",
5171                    array('integer'),
5172                    array($active_id)
5173                );
5174
5175                break;
5176
5177            default:
5178
5179                throw new ilTestException("not supported question set type: $questionSetType");
5180        }
5181
5182        $row = $ilDB->fetchAssoc($res);
5183
5184        if (is_array($row)) {
5185            return array("count" => $row["qcount"], "points" => $row["qsum"]);
5186        }
5187
5188        return array("count" => 0, "points" => 0);
5189    }
5190
5191    public function &getCompleteEvaluationData($withStatistics = true, $filterby = "", $filtertext = "")
5192    {
5193        include_once "./Modules/Test/classes/class.ilTestEvaluationData.php";
5194        include_once "./Modules/Test/classes/class.ilTestEvaluationPassData.php";
5195        include_once "./Modules/Test/classes/class.ilTestEvaluationUserData.php";
5196        $data = $this->getUnfilteredEvaluationData();
5197        if ($withStatistics) {
5198            $data->calculateStatistics();
5199        }
5200        $data->setFilter($filterby, $filtertext);
5201        return $data;
5202    }
5203
5204    /**
5205    * Creates an associated array with the results of all participants of a test
5206    *
5207    * @return array An associated array containing the results
5208    * @access public
5209    */
5210    public function &evalResultsOverview()
5211    {
5212        return $this->_evalResultsOverview($this->getTestId());
5213    }
5214
5215    /**
5216    * Creates an associated array with the results of all participants of a test
5217    *
5218    * @return array An associated array containing the results
5219    * @access public
5220    */
5221    public function &_evalResultsOverview($test_id)
5222    {
5223        global $DIC;
5224        $ilDB = $DIC['ilDB'];
5225
5226        $result = $ilDB->queryF(
5227            "SELECT usr_data.usr_id, usr_data.firstname, usr_data.lastname, usr_data.title, usr_data.login, " .
5228            "tst_test_result.*, qpl_questions.original_id, qpl_questions.title questiontitle, " .
5229            "qpl_questions.points maxpoints " .
5230            "FROM tst_test_result, qpl_questions, tst_active " .
5231            "LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id " .
5232            "WHERE tst_active.active_id = tst_test_result.active_fi " .
5233            "AND qpl_questions.question_id = tst_test_result.question_fi " .
5234            "AND tst_active.test_fi = %s " .
5235            "ORDER BY tst_active.active_id, tst_test_result.pass, tst_test_result.tstamp",
5236            array('integer'),
5237            array($test_id)
5238        );
5239        $overview = array();
5240        while ($row = $ilDB->fetchAssoc($result)) {
5241            if (!array_key_exists($row["active_fi"], $overview)) {
5242                $overview[$row["active_fi"]] = array();
5243                $overview[$row["active_fi"]]["firstname"] = $row["firstname"];
5244                $overview[$row["active_fi"]]["lastname"] = $row["lastname"];
5245                $overview[$row["active_fi"]]["title"] = $row["title"];
5246                $overview[$row["active_fi"]]["login"] = $row["login"];
5247                $overview[$row["active_fi"]]["usr_id"] = $row["usr_id"];
5248                $overview[$row["active_fi"]]["started"] = $row["started"];
5249                $overview[$row["active_fi"]]["finished"] = $row["finished"];
5250            }
5251            if (!array_key_exists($row["pass"], $overview[$row["active_fi"]])) {
5252                $overview[$row["active_fi"]][$row["pass"]] = array();
5253                $overview[$row["active_fi"]][$row["pass"]]["reached"] = 0;
5254                $overview[$row["active_fi"]][$row["pass"]]["maxpoints"] = $row["maxpoints"];
5255            }
5256            array_push($overview[$row["active_fi"]][$row["pass"]], $row);
5257            $overview[$row["active_fi"]][$row["pass"]]["reached"] += $row["points"];
5258        }
5259        return $overview;
5260    }
5261
5262    /**
5263    * Creates an associated array with the results for a given participant of a test
5264    *
5265    * @param integer $active_id The active id of the participant
5266    * @return array An associated array containing the results
5267    * @access public
5268    */
5269    public function &evalResultsOverviewOfParticipant($active_id)
5270    {
5271        global $DIC;
5272        $ilDB = $DIC['ilDB'];
5273
5274        $result = $ilDB->queryF(
5275            "SELECT usr_data.usr_id, usr_data.firstname, usr_data.lastname, usr_data.title, usr_data.login, " .
5276            "tst_test_result.*, qpl_questions.original_id, qpl_questions.title questiontitle, " .
5277            "qpl_questions.points maxpoints " .
5278            "FROM tst_test_result, qpl_questions, tst_active " .
5279            "LEFT JOIN usr_data ON tst_active.user_fi = usr_data.usr_id " .
5280            "WHERE tst_active.active_id = tst_test_result.active_fi " .
5281            "AND qpl_questions.question_id = tst_test_result.question_fi " .
5282            "AND tst_active.test_fi = %s AND tst_active.active_id = %s" .
5283            "ORDER BY tst_active.active_id, tst_test_result.pass, tst_test_result.tstamp",
5284            array('integer', 'integer'),
5285            array($this->getTestId(), $active_id)
5286        );
5287        $overview = array();
5288        while ($row = $ilDB->fetchAssoc($result)) {
5289            if (!array_key_exists($row["active_fi"], $overview)) {
5290                $overview[$row["active_fi"]] = array();
5291                $overview[$row["active_fi"]]["firstname"] = $row["firstname"];
5292                $overview[$row["active_fi"]]["lastname"] = $row["lastname"];
5293                $overview[$row["active_fi"]]["title"] = $row["title"];
5294                $overview[$row["active_fi"]]["login"] = $row["login"];
5295                $overview[$row["active_fi"]]["usr_id"] = $row["usr_id"];
5296                $overview[$row["active_fi"]]["started"] = $row["started"];
5297                $overview[$row["active_fi"]]["finished"] = $row["finished"];
5298            }
5299            if (!array_key_exists($row["pass"], $overview[$row["active_fi"]])) {
5300                $overview[$row["active_fi"]][$row["pass"]] = array();
5301                $overview[$row["active_fi"]][$row["pass"]]["reached"] = 0;
5302                $overview[$row["active_fi"]][$row["pass"]]["maxpoints"] = $row["maxpoints"];
5303            }
5304            array_push($overview[$row["active_fi"]][$row["pass"]], $row);
5305            $overview[$row["active_fi"]][$row["pass"]]["reached"] += $row["points"];
5306        }
5307        return $overview;
5308    }
5309
5310    /**
5311    * Builds a user name for the output depending on test type and existence of
5312    * the user
5313    *
5314    * @param int $user_id The database ID of the user
5315    * @param string $firstname The first name of the user
5316    * @param string $lastname The last name of the user
5317    * @param string $title The title of the user
5318    * @return string The output name of the user
5319    * @access public
5320    */
5321    public function buildName($user_id, $firstname, $lastname, $title)
5322    {
5323        $name = "";
5324        if (strlen($firstname . $lastname . $title) == 0) {
5325            $name = $this->lng->txt("deleted_user");
5326        } else {
5327            if ($user_id == ANONYMOUS_USER_ID) {
5328                $name = $lastname;
5329            } else {
5330                $name = trim($lastname . ", " . $firstname . " " . $title);
5331            }
5332            if ($this->getAnonymity()) {
5333                $name = $this->lng->txt("anonymous");
5334            }
5335        }
5336        return $name;
5337    }
5338
5339    /**
5340    * Builds a user name for the output depending on test type and existence of
5341    * the user
5342    *
5343    * @param boolean $is_anonymous Indicates if it is an anonymized test or not
5344    * @param int $user_id The database ID of the user
5345    * @param string $firstname The first name of the user
5346    * @param string $lastname The last name of the user
5347    * @param string $title The title of the user
5348    * @return string The output name of the user
5349    * @access public
5350    */
5351    public function _buildName($is_anonymous, $user_id, $firstname, $lastname, $title)
5352    {
5353        global $DIC;
5354        $lng = $DIC['lng'];
5355        $name = "";
5356        if (strlen($firstname . $lastname . $title) == 0) {
5357            $name = $lng->txt("deleted_user");
5358        } else {
5359            if ($user_id == ANONYMOUS_USER_ID) {
5360                $name = $lastname;
5361            } else {
5362                $name = trim($lastname . ", " . $firstname . " " . $title);
5363            }
5364            if ($is_anonymous) {
5365                $name = $lng->txt("anonymous");
5366            }
5367        }
5368        return $name;
5369    }
5370
5371    /**
5372    * Returns the average processing time for all started tests
5373    *
5374    * @return integer The average processing time for all started tests
5375    * @access public
5376    */
5377    public function evalTotalStartedAverageTime($activeIdsFilter = null)
5378    {
5379        global $DIC; /* @var ILIAS\DI\Container $DIC */
5380
5381        $query = "SELECT tst_times.* FROM tst_active, tst_times WHERE tst_active.test_fi = %s AND tst_active.active_id = tst_times.active_fi";
5382
5383        if (is_array($activeIdsFilter) && count($activeIdsFilter)) {
5384            $query .= " AND " . $DIC->database()->in('active_id', $activeIdsFilter, false, 'integer');
5385        }
5386
5387        $result = $DIC->database()->queryF($query, array('integer'), array($this->getTestId()));
5388        $times = array();
5389        while ($row = $DIC->database()->fetchObject($result)) {
5390            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row->started, $matches);
5391            $epoch_1 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
5392            preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row->finished, $matches);
5393            $epoch_2 = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
5394            $times[$row->active_fi] += ($epoch_2 - $epoch_1);
5395        }
5396        $max_time = 0;
5397        $counter = 0;
5398        foreach ($times as $key => $value) {
5399            $max_time += $value;
5400            $counter++;
5401        }
5402        if ($counter) {
5403            $average_time = round($max_time / $counter);
5404        } else {
5405            $average_time = 0;
5406        }
5407        return $average_time;
5408    }
5409
5410    /**
5411    * Returns the available question pools for the active user
5412    *
5413    * @return array The available question pools
5414    * @access public
5415    */
5416    public function &getAvailableQuestionpools($use_object_id = false, $equal_points = false, $could_be_offline = false, $show_path = false, $with_questioncount = false, $permission = "read")
5417    {
5418        include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
5419        return ilObjQuestionPool::_getAvailableQuestionpools($use_object_id, $equal_points, $could_be_offline, $show_path, $with_questioncount, $permission);
5420    }
5421
5422    /**
5423    * Returns the estimated working time for the test calculated from the working time of the contained questions
5424    *
5425    * @return array An associative array containing the working time. array["h"] = hours, array["m"] = minutes, array["s"] = seconds
5426    * @access public
5427    */
5428    public function getEstimatedWorkingTime()
5429    {
5430        $time_in_seconds = 0;
5431        foreach ($this->questions as $question_id) {
5432            $question = &ilObjTest::_instanciateQuestion($question_id);
5433            $est_time = $question->getEstimatedWorkingTime();
5434            $time_in_seconds += $est_time["h"] * 3600 + $est_time["m"] * 60 + $est_time["s"];
5435        }
5436        $hours = (int) ($time_in_seconds / 3600)	;
5437        $time_in_seconds = $time_in_seconds - ($hours * 3600);
5438        $minutes = (int) ($time_in_seconds / 60);
5439        $time_in_seconds = $time_in_seconds - ($minutes * 60);
5440        $result = array("hh" => $hours, "mm" => $minutes, "ss" => $time_in_seconds);
5441        return $result;
5442    }
5443
5444    /**
5445    * Returns the image path for web accessable images of a test
5446    * The image path is under the CLIENT_WEB_DIR in assessment/REFERENCE_ID_OF_TEST/images
5447    *
5448    * @access public
5449    */
5450    public function getImagePath()
5451    {
5452        return CLIENT_WEB_DIR . "/assessment/" . $this->getId() . "/images/";
5453    }
5454
5455    /**
5456    * Returns the web image path for web accessable images of a test
5457    * The image path is under the web accessable data dir in assessment/REFERENCE_ID_OF_TEST/images
5458    *
5459    * @access public
5460    */
5461    public function getImagePathWeb()
5462    {
5463        include_once "./Services/Utilities/classes/class.ilUtil.php";
5464        $webdir = ilUtil::removeTrailingPathSeparators(CLIENT_WEB_DIR) . "/assessment/" . $this->getId() . "/images/";
5465        return str_replace(ilUtil::removeTrailingPathSeparators(ILIAS_ABSOLUTE_PATH), ilUtil::removeTrailingPathSeparators(ILIAS_HTTP_PATH), $webdir);
5466    }
5467
5468    /**
5469    * Creates a question GUI instance of a given question type
5470    *
5471    * @param integer $question_type The question type of the question
5472    * @param integer $question_id The question id of the question, if available
5473    * @return assQuestionGUI $questionGUI The question GUI instance
5474    * @access	public
5475    */
5476    public function &createQuestionGUI($question_type, $question_id = -1)
5477    {
5478        if ((!$question_type) and ($question_id > 0)) {
5479            $question_type = $this->getQuestionType($question_id);
5480        }
5481
5482        if (!strlen($question_type)) {
5483            return null;
5484        }
5485
5486        include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
5487        assQuestion::_includeClass($question_type, 1);
5488
5489        $question_type_gui = assQuestion::getGuiClassNameByQuestionType($question_type);
5490        $question = new $question_type_gui();
5491
5492        if ($question_id > 0) {
5493            $question->object->loadFromDb($question_id);
5494
5495            global $DIC;
5496            $ilCtrl = $DIC['ilCtrl'];
5497            $ilDB = $DIC['ilDB'];
5498            $ilUser = $DIC['ilUser'];
5499            $lng = $DIC['lng'];
5500
5501            $feedbackObjectClassname = assQuestion::getFeedbackClassNameByQuestionType($question_type);
5502            $question->object->feedbackOBJ = new $feedbackObjectClassname($question->object, $ilCtrl, $ilDB, $lng);
5503
5504            $assSettings = new ilSetting('assessment');
5505            require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
5506            $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
5507            $processLockerFactory->setQuestionId($question->object->getId());
5508            $processLockerFactory->setUserId($ilUser->getId());
5509            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
5510            $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
5511            $question->object->setProcessLocker($processLockerFactory->getLocker());
5512        }
5513
5514        return $question;
5515    }
5516
5517    /**
5518    * Creates an instance of a question with a given question id
5519    *
5520    * @param integer $question_id The question id
5521    * @return object The question instance
5522    * @access public
5523     *
5524     * @deprecated use assQuestion::_instanciateQuestion($question_id) instead
5525    */
5526    public static function _instanciateQuestion($question_id)
5527    {
5528        if (strcmp($question_id, "") != 0) {
5529            include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
5530            return assQuestion::_instanciateQuestion($question_id);
5531        }
5532    }
5533
5534    /**
5535    * Move questions to another position
5536    *
5537    * @param array $move_questions An array with the question id's of the questions to move
5538    * @param integer $target_index The question id of the target position
5539    * @param integer $insert_mode 0, if insert before the target position, 1 if insert after the target position
5540    * @access public
5541    */
5542    public function moveQuestions($move_questions, $target_index, $insert_mode)
5543    {
5544        $this->questions = array_values($this->questions);
5545        $array_pos = array_search($target_index, $this->questions);
5546        if ($insert_mode == 0) {
5547            $part1 = array_slice($this->questions, 0, $array_pos);
5548            $part2 = array_slice($this->questions, $array_pos);
5549        } elseif ($insert_mode == 1) {
5550            $part1 = array_slice($this->questions, 0, $array_pos + 1);
5551            $part2 = array_slice($this->questions, $array_pos + 1);
5552        }
5553        foreach ($move_questions as $question_id) {
5554            if (!(array_search($question_id, $part1) === false)) {
5555                unset($part1[array_search($question_id, $part1)]);
5556            }
5557            if (!(array_search($question_id, $part2) === false)) {
5558                unset($part2[array_search($question_id, $part2)]);
5559            }
5560        }
5561        $part1 = array_values($part1);
5562        $part2 = array_values($part2);
5563        $new_array = array_values(array_merge($part1, $move_questions, $part2));
5564        $this->questions = array();
5565        $counter = 1;
5566        foreach ($new_array as $question_id) {
5567            $this->questions[$counter] = $question_id;
5568            $counter++;
5569        }
5570        $this->saveQuestionsToDb();
5571    }
5572
5573
5574    /**
5575    * Returns true if the starting time of a test is reached
5576    * A starting time is not available for self assessment tests
5577    *
5578    * @return boolean true if the starting time is reached, otherwise false
5579    * @access public
5580    */
5581    public function startingTimeReached()
5582    {
5583        if ($this->isStartingTimeEnabled() && $this->getStartingTime() != 0) {
5584            $now = time();
5585            if ($now < $this->getStartingTime()) {
5586                return false;
5587            }
5588        }
5589        return true;
5590    }
5591
5592    /**
5593    * Returns true if the ending time of a test is reached
5594    * An ending time is not available for self assessment tests
5595    *
5596    * @return boolean true if the ending time is reached, otherwise false
5597    * @access public
5598    */
5599    public function endingTimeReached()
5600    {
5601        if ($this->isEndingTimeEnabled() && $this->getEndingTime() != 0) {
5602            $now = time();
5603            if ($now > $this->getEndingTime()) {
5604                return true;
5605            }
5606        }
5607        return false;
5608    }
5609
5610    /**
5611    * Calculates the available questions for a test
5612    *
5613    * @access public
5614    */
5615    public function getAvailableQuestions($arrFilter, $completeonly = 0)
5616    {
5617        global $DIC;
5618        $pluginAdmin = $DIC['ilPluginAdmin'];
5619        $lng = $DIC['lng'];
5620        $ilUser = $DIC['ilUser'];
5621        $ilDB = $DIC['ilDB'];
5622
5623        include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
5624        $available_pools = array_keys(ilObjQuestionPool::_getAvailableQuestionpools($use_object_id = true, $equal_points = false, $could_be_offline = false, $showPath = false, $with_questioncount = false));
5625        $available = "";
5626        if (count($available_pools)) {
5627            $available = " AND " . $ilDB->in('qpl_questions.obj_fi', $available_pools, false, 'integer');
5628        } else {
5629            return array();
5630        }
5631        if ($completeonly) {
5632            $available .= " AND qpl_questions.complete = " . $ilDB->quote("1", 'text');
5633        }
5634
5635        $where = "";
5636        if (is_array($arrFilter)) {
5637            if (array_key_exists('title', $arrFilter) && strlen($arrFilter['title'])) {
5638                $where .= " AND " . $ilDB->like('qpl_questions.title', 'text', "%%" . $arrFilter['title'] . "%%");
5639            }
5640            if (array_key_exists('description', $arrFilter) && strlen($arrFilter['description'])) {
5641                $where .= " AND " . $ilDB->like('qpl_questions.description', 'text', "%%" . $arrFilter['description'] . "%%");
5642            }
5643            if (array_key_exists('author', $arrFilter) && strlen($arrFilter['author'])) {
5644                $where .= " AND " . $ilDB->like('qpl_questions.author', 'text', "%%" . $arrFilter['author'] . "%%");
5645            }
5646            if (array_key_exists('type', $arrFilter) && strlen($arrFilter['type'])) {
5647                $where .= " AND qpl_qst_type.type_tag = " . $ilDB->quote($arrFilter['type'], 'text');
5648            }
5649            if (array_key_exists('qpl', $arrFilter) && strlen($arrFilter['qpl'])) {
5650                $where .= " AND " . $ilDB->like('object_data.title', 'text', "%%" . $arrFilter['qpl'] . "%%");
5651            }
5652        }
5653
5654        $original_ids = &$this->getExistingQuestions();
5655        $original_clause = " qpl_questions.original_id IS NULL";
5656        if (count($original_ids)) {
5657            $original_clause = " qpl_questions.original_id IS NULL AND " . $ilDB->in('qpl_questions.question_id', $original_ids, true, 'integer');
5658        }
5659
5660        $query_result = $ilDB->query("
5661			SELECT		qpl_questions.*, qpl_questions.tstamp,
5662						qpl_qst_type.type_tag, qpl_qst_type.plugin, qpl_qst_type.plugin_name,
5663						object_data.title parent_title
5664			FROM		qpl_questions, qpl_qst_type, object_data
5665			WHERE $original_clause $available
5666			AND object_data.obj_id = qpl_questions.obj_fi
5667			AND qpl_questions.tstamp > 0
5668			AND qpl_questions.question_type_fi = qpl_qst_type.question_type_id
5669			$where
5670		");
5671        $rows = array();
5672        $types = $this->getQuestionTypeTranslations();
5673        if ($query_result->numRows()) {
5674            while ($row = $ilDB->fetchAssoc($query_result)) {
5675                $row = ilAssQuestionType::completeMissingPluginName($row);
5676
5677                if (!$row['plugin']) {
5678                    $row[ 'ttype' ] = $lng->txt($row[ "type_tag" ]);
5679
5680                    $rows[] = $row;
5681                    continue;
5682                }
5683
5684                if (!$pluginAdmin->isActive(IL_COMP_MODULE, 'TestQuestionPool', 'qst', $row['plugin_name'])) {
5685                    continue;
5686                }
5687
5688                $pl = ilPlugin::getPluginObject(IL_COMP_MODULE, 'TestQuestionPool', 'qst', $row['plugin_name']);
5689                $row[ 'ttype' ] = $pl->getQuestionTypeTranslation();
5690
5691                $rows[] = $row;
5692            }
5693        }
5694        return $rows;
5695    }
5696
5697    /**
5698     * Receives parameters from a QTI parser and creates a valid ILIAS test object
5699     * @param ilQTIAssessment $assessment
5700     */
5701    public function fromXML(ilQTIAssessment $assessment)
5702    {
5703        unset($_SESSION["import_mob_xhtml"]);
5704
5705        $this->setDescription($assessment->getComment());
5706        $this->setTitle($assessment->getTitle());
5707
5708        $this->setIntroductionEnabled(false);
5709        foreach ($assessment->objectives as $objectives) {
5710            foreach ($objectives->materials as $material) {
5711                $intro = $this->QTIMaterialToString($material);
5712                $this->setIntroduction($intro);
5713                $this->setIntroductionEnabled(strlen($intro) > 0);
5714            }
5715        }
5716
5717        if (
5718            $assessment->getPresentationMaterial() &&
5719            $assessment->getPresentationMaterial()->getFlowMat(0) &&
5720            $assessment->getPresentationMaterial()->getFlowMat(0)->getMaterial(0)
5721        ) {
5722            $this->setFinalStatement($this->QTIMaterialToString($assessment->getPresentationMaterial()->getFlowMat(0)->getMaterial(0)));
5723        }
5724
5725        foreach ($assessment->assessmentcontrol as $assessmentcontrol) {
5726            switch ($assessmentcontrol->getSolutionswitch()) {
5727                case "Yes":
5728                    $this->setInstantFeedbackSolution(1);
5729                    break;
5730                default:
5731                    $this->setInstantFeedbackSolution(0);
5732                    break;
5733            }
5734        }
5735
5736        $this->setStartingTimeEnabled(false);
5737        $this->setEndingTimeEnabled(false);
5738        $this->setPasswordEnabled(false);
5739        $this->setLimitUsersEnabled(false);
5740
5741        foreach ($assessment->qtimetadata as $metadata) {
5742            switch ($metadata["label"]) {
5743                case "test_type":
5744                    // for old tests with a test type
5745                    $type = $metadata["entry"];
5746                    switch ($type) {
5747                        case 1:
5748                            // assessment
5749                            $this->setAnonymity(1);
5750                            break;
5751                        case 2:
5752                            // self assessment
5753                            break;
5754                        case 4:
5755                            // online exam
5756                            $this->setFixedParticipants(1);
5757                            $this->setListOfQuestionsSettings(7);
5758                            $this->setShowSolutionPrintview(1);
5759                            break;
5760                        case 5:
5761                            // varying random test
5762                            break;
5763                    }
5764                    break;
5765                case "sequence_settings":
5766                    $this->setSequenceSettings($metadata["entry"]);
5767                    break;
5768                case "solution_details":
5769                    $this->setShowSolutionDetails((int) $metadata["entry"]);
5770                    break;
5771                case "print_bs_with_res":
5772                    $this->setPrintBestSolutionWithResult((int) $metadata["entry"]);
5773                    break;
5774                case "author":
5775                    $this->setAuthor($metadata["entry"]);
5776                    break;
5777                case "nr_of_tries":
5778                    $this->setNrOfTries($metadata["entry"]);
5779                    break;
5780                case 'block_after_passed':
5781                    $this->setBlockPassesAfterPassedEnabled((bool) $metadata['entry']);
5782                    break;
5783                case "pass_waiting":
5784                    $this->setPassWaiting($metadata["entry"]);
5785                    break;
5786                case "kiosk":
5787                    $this->setKiosk($metadata["entry"]);
5788                    break;
5789                case "showfinalstatement":
5790                    $this->setShowFinalStatement($metadata["entry"]);
5791                    break;
5792                case "showinfo":
5793                    $this->setShowInfo($metadata["entry"]);
5794                    break;
5795                case "forcejs":
5796                    $this->setForceJS($metadata["entry"]);
5797                    break;
5798                case "customstyle":
5799                    $this->setCustomStyle($metadata["entry"]);
5800                    break;
5801
5802                case "highscore_enabled":
5803                    $this->setHighscoreEnabled($metadata["entry"]);
5804                    break;
5805
5806                case "highscore_anon":
5807                    $this->setHighscoreAnon($metadata["entry"]);
5808                    break;
5809
5810                case "highscore_achieved_ts":
5811                    $this->setHighscoreAchievedTS($metadata["entry"]);
5812                    break;
5813
5814                case "highscore_score":
5815                    $this->setHighscoreScore($metadata["entry"]);
5816                    break;
5817
5818                case "highscore_percentage":
5819                    $this->setHighscorePercentage($metadata["entry"]);
5820                    break;
5821
5822                case "highscore_hints":
5823                    $this->setHighscoreHints($metadata["entry"]);
5824                    break;
5825
5826                case "highscore_wtime":
5827                    $this->setHighscoreWTime($metadata["entry"]);
5828                    break;
5829
5830                case "highscore_own_table":
5831                    $this->setHighscoreOwnTable($metadata["entry"]);
5832                    break;
5833
5834                case "highscore_top_table":
5835                    $this->setHighscoreTopTable($metadata["entry"]);
5836                    break;
5837
5838                case "highscore_top_num":
5839                    $this->setHighscoreTopNum($metadata["entry"]);
5840                    break;
5841
5842                case "hide_previous_results":
5843                    if ($metadata["entry"] == 0) {
5844                        $this->setUsePreviousAnswers(1);
5845                    } else {
5846                        $this->setUsePreviousAnswers(0);
5847                    }
5848                    break;
5849                case "use_previous_answers":
5850                    $this->setUsePreviousAnswers($metadata["entry"]);
5851                    break;
5852                case "answer_feedback":
5853                    $this->setAnswerFeedback($metadata["entry"]);
5854                    break;
5855                case "hide_title_points":
5856                    $this->setTitleOutput($metadata["entry"]);
5857                    break;
5858                case "title_output":
5859                    $this->setTitleOutput($metadata["entry"]);
5860                    break;
5861                case "question_set_type":
5862                    $this->setQuestionSetType($metadata["entry"]);
5863                    break;
5864                case "random_test":
5865                    if ($metadata["entry"]) {
5866                        $this->setQuestionSetType(self::QUESTION_SET_TYPE_RANDOM);
5867                    } else {
5868                        $this->setQuestionSetType(self::QUESTION_SET_TYPE_FIXED);
5869                    }
5870                    break;
5871                case "results_presentation":
5872                    $this->setResultsPresentation($metadata["entry"]);
5873                    break;
5874                case "reset_processing_time":
5875                    $this->setResetProcessingTime($metadata["entry"]);
5876                    break;
5877                case "instant_verification":
5878                    $this->setInstantFeedbackSolution($metadata["entry"]);
5879                    break;
5880                case "follow_qst_answer_fixation":
5881                    $this->setFollowupQuestionAnswerFixationEnabled((bool) $metadata["entry"]);
5882                    break;
5883                case "instant_feedback_answer_fixation":
5884                    $this->setInstantFeedbackAnswerFixationEnabled((bool) $metadata["entry"]);
5885                    break;
5886                case "force_instant_feedback":
5887                    $this->setForceInstantFeedbackEnabled((bool) $metadata["entry"]);
5888                    break;
5889                case "answer_feedback_points":
5890                    $this->setAnswerFeedbackPoints($metadata["entry"]);
5891                    break;
5892                case "anonymity":
5893                    $this->setAnonymity($metadata["entry"]);
5894                    break;
5895                case "use_pool":
5896                    $this->setPoolUsage((int) $metadata["entry"]);
5897                    break;
5898                case "show_cancel":
5899                    $this->setShowCancel($metadata["entry"]);
5900                    break;
5901                case "show_marker":
5902                    $this->setShowMarker($metadata["entry"]);
5903                    break;
5904                case "fixed_participants":
5905                    $this->setFixedParticipants($metadata["entry"]);
5906                    break;
5907                case "score_reporting":
5908                    $this->setScoreReporting($metadata["entry"]);
5909                    break;
5910                case "shuffle_questions":
5911                    $this->setShuffleQuestions($metadata["entry"]);
5912                    break;
5913                case "count_system":
5914                    $this->setCountSystem($metadata["entry"]);
5915                    break;
5916                case "mc_scoring":
5917                    $this->setMCScoring($metadata["entry"]);
5918                    break;
5919                case "mailnotification":
5920                    $this->setMailNotification($metadata["entry"]);
5921                    break;
5922                case "mailnottype":
5923                    $this->setMailNotificationType($metadata["entry"]);
5924                    break;
5925                case "exportsettings":
5926                    $this->setExportSettings($metadata['entry']);
5927                    break;
5928                case "score_cutting":
5929                    $this->setScoreCutting($metadata["entry"]);
5930                    break;
5931                case "password":
5932                    $this->setPassword($metadata["entry"]);
5933                    $this->setPasswordEnabled(strlen($metadata["entry"]) > 0);
5934                    break;
5935                case "allowedUsers":
5936                    $this->setAllowedUsers($metadata["entry"]);
5937                    $this->setLimitUsersEnabled((int) $metadata["entry"] > 0);
5938                    break;
5939                case "allowedUsersTimeGap":
5940                    $this->setAllowedUsersTimeGap($metadata["entry"]);
5941                    break;
5942                case "pass_scoring":
5943                    $this->setPassScoring($metadata["entry"]);
5944                    break;
5945                case 'pass_deletion_allowed':
5946                    $this->setPassDeletionAllowed((int) $metadata['entry']);
5947                    break;
5948                case "show_summary":
5949                    $this->setListOfQuestionsSettings($metadata["entry"]);
5950                    break;
5951                case "reporting_date":
5952                    $iso8601period = $metadata["entry"];
5953                    if (preg_match("/P(\d+)Y(\d+)M(\d+)DT(\d+)H(\d+)M(\d+)S/", $iso8601period, $matches)) {
5954                        $this->setReportingDate(sprintf("%02d%02d%02d%02d%02d%02d", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]));
5955                    }
5956                    break;
5957                case 'enable_processing_time':
5958                    $this->setEnableProcessingTime($metadata['entry']);
5959                    break;
5960                case "processing_time":
5961                    $this->setProcessingTime($metadata['entry']);
5962                    break;
5963                case "starting_time":
5964                    $iso8601period = $metadata["entry"];
5965                    if (preg_match("/P(\d+)Y(\d+)M(\d+)DT(\d+)H(\d+)M(\d+)S/", $iso8601period, $matches)) {
5966                        $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);
5967                        $this->setStartingTime($date_time->get(IL_CAL_UNIX));
5968                        $this->setStartingTimeEnabled(true);
5969                    }
5970                    break;
5971                case "ending_time":
5972                    $iso8601period = $metadata["entry"];
5973                    if (preg_match("/P(\d+)Y(\d+)M(\d+)DT(\d+)H(\d+)M(\d+)S/", $iso8601period, $matches)) {
5974                        $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);
5975                        $this->setEndingTime($date_time->get(IL_CAL_UNIX));
5976                        $this->setEndingTimeEnabled(true);
5977                    }
5978                    break;
5979                case "enable_examview":
5980                    $this->setEnableExamview($metadata["entry"]);
5981                    break;
5982                case 'show_examview_html':
5983                    $this->setShowExamviewHtml($metadata['entry']);
5984                    break;
5985                case 'show_examview_pdf':
5986                    $this->setShowExamviewPdf($metadata['entry']);
5987                    break;
5988                case 'redirection_mode':
5989                    $this->setRedirectionMode($metadata['entry']);
5990                    break;
5991                case 'redirection_url':
5992                    $this->setRedirectionUrl($metadata['entry']);
5993                    break;
5994                case 'examid_in_kiosk':
5995                case 'examid_in_test_pass':
5996                    $this->setShowExamIdInTestPassEnabled($metadata['entry']);
5997                    break;
5998                case 'show_exam_id':
5999                case 'examid_in_test_res':
6000                    $this->setShowExamIdInTestResultsEnabled($metadata['entry']);
6001                    break;
6002                case 'enable_archiving':
6003                    $this->setEnableArchiving($metadata['entry']);
6004                    break;
6005                case 'sign_submission':
6006                    $this->setSignSubmission($metadata['entry']);
6007                    break;
6008                case 'char_selector_availability':
6009                    $this->setCharSelectorAvailability($metadata['entry']);
6010                    break;
6011                case 'char_selector_definition':
6012                    $this->setCharSelectorDefinition($metadata['entry']);
6013                    break;
6014                case 'skill_service':
6015                    $this->setSkillServiceEnabled((bool) $metadata['entry']);
6016                    break;
6017                case 'result_tax_filters':
6018                    $this->setResultFilterTaxIds(strlen($metadata['entry']) ? unserialize($metadata['entry']) : array());
6019                    break;
6020                case 'show_grading_status':
6021                    $this->setShowGradingStatusEnabled((bool) $metadata['entry']);
6022                    break;
6023                case 'show_grading_mark':
6024                    $this->setShowGradingMarkEnabled((bool) $metadata['entry']);
6025                    break;
6026                case 'activation_limited':
6027                    $this->setActivationLimited($metadata['entry']);
6028                    break;
6029                case 'activation_start_time':
6030                    $this->setActivationStartingTime($metadata['entry']);
6031                    break;
6032                case 'activation_end_time':
6033                    $this->setActivationEndingTime($metadata['entry']);
6034                    break;
6035                case 'activation_visibility':
6036                    $this->setActivationVisibility($metadata['entry']);
6037                    break;
6038                case 'autosave':
6039                    $this->setAutosave($metadata['entry']);
6040                    break;
6041                case 'autosave_ival':
6042                    $this->setAutosaveIval($metadata['entry']);
6043                    break;
6044                case 'offer_question_hints':
6045                    $this->setOfferingQuestionHintsEnabled($metadata['entry']);
6046                    break;
6047                case 'instant_feedback_specific':
6048                    $this->setSpecificAnswerFeedback($metadata['entry']);
6049                    break;
6050                case 'obligations_enabled':
6051                    $this->setObligationsEnabled($metadata['entry']);
6052                    break;
6053            }
6054            if (preg_match("/mark_step_\d+/", $metadata["label"])) {
6055                $xmlmark = $metadata["entry"];
6056                preg_match("/<short>(.*?)<\/short>/", $xmlmark, $matches);
6057                $mark_short = $matches[1];
6058                preg_match("/<official>(.*?)<\/official>/", $xmlmark, $matches);
6059                $mark_official = $matches[1];
6060                preg_match("/<percentage>(.*?)<\/percentage>/", $xmlmark, $matches);
6061                $mark_percentage = $matches[1];
6062                preg_match("/<passed>(.*?)<\/passed>/", $xmlmark, $matches);
6063                $mark_passed = $matches[1];
6064                $this->mark_schema->addMarkStep($mark_short, $mark_official, $mark_percentage, $mark_passed);
6065            }
6066        }
6067        // handle the import of media objects in XHTML code
6068        if (is_array($_SESSION["import_mob_xhtml"])) {
6069            include_once "./Services/MediaObjects/classes/class.ilObjMediaObject.php";
6070            include_once "./Services/RTE/classes/class.ilRTE.php";
6071            include_once "./Modules/TestQuestionPool/classes/class.ilObjQuestionPool.php";
6072            foreach ($_SESSION["import_mob_xhtml"] as $mob) {
6073                $importfile = ilObjTest::_getImportDirectory() . '/' . $_SESSION["tst_import_subdir"] . '/' . $mob["uri"];
6074                if (file_exists($importfile)) {
6075                    $media_object = &ilObjMediaObject::_saveTempFileAsMediaObject(basename($importfile), $importfile, false);
6076                    ilObjMediaObject::_saveUsage($media_object->getId(), "tst:html", $this->getId());
6077                    $this->setIntroduction(ilRTE::_replaceMediaObjectImageSrc(str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $this->getIntroduction()), 1));
6078                    $this->setFinalStatement(ilRTE::_replaceMediaObjectImageSrc(str_replace("src=\"" . $mob["mob"] . "\"", "src=\"" . "il_" . IL_INST_ID . "_mob_" . $media_object->getId() . "\"", $this->getFinalStatement()), 1));
6079                } else {
6080                    global $DIC;
6081                    $ilLog = $DIC['ilLog'];
6082                    $ilLog->write("Error: Could not open XHTML mob file for test introduction during test import. File $importfile does not exist!");
6083                }
6084            }
6085            $this->saveToDb();
6086        }
6087    }
6088
6089    /**
6090     * Returns a QTI xml representation of the test
6091     *
6092     * @return string The QTI xml representation of the test
6093     */
6094    public function toXML()
6095    {
6096        include_once("./Services/Xml/classes/class.ilXmlWriter.php");
6097        $a_xml_writer = new ilXmlWriter;
6098        // set xml header
6099        $a_xml_writer->xmlHeader();
6100        $a_xml_writer->xmlSetDtdDef("<!DOCTYPE questestinterop SYSTEM \"ims_qtiasiv1p2p1.dtd\">");
6101        $a_xml_writer->xmlStartTag("questestinterop");
6102
6103        $attrs = array(
6104            "ident" => "il_" . IL_INST_ID . "_tst_" . $this->getTestId(),
6105            "title" => $this->getTitle()
6106        );
6107        $a_xml_writer->xmlStartTag("assessment", $attrs);
6108        // add qti comment
6109        $a_xml_writer->xmlElement("qticomment", null, $this->getDescription());
6110
6111        // add qti duration
6112        if ($this->enable_processing_time) {
6113            preg_match("/(\d+):(\d+):(\d+)/", $this->processing_time, $matches);
6114            $a_xml_writer->xmlElement("duration", null, sprintf("P0Y0M0DT%dH%dM%dS", $matches[1], $matches[2], $matches[3]));
6115        }
6116
6117        // add the rest of the preferences in qtimetadata tags, because there is no correspondent definition in QTI
6118        $a_xml_writer->xmlStartTag("qtimetadata");
6119        $a_xml_writer->xmlStartTag("qtimetadatafield");
6120        $a_xml_writer->xmlElement("fieldlabel", null, "ILIAS_VERSION");
6121        $a_xml_writer->xmlElement("fieldentry", null, $this->ilias->getSetting("ilias_version"));
6122        $a_xml_writer->xmlEndTag("qtimetadatafield");
6123
6124        // anonymity
6125        $a_xml_writer->xmlStartTag("qtimetadatafield");
6126        $a_xml_writer->xmlElement("fieldlabel", null, "anonymity");
6127        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getAnonymity()));
6128        $a_xml_writer->xmlEndTag("qtimetadatafield");
6129
6130        $a_xml_writer->xmlStartTag("qtimetadatafield");
6131        $a_xml_writer->xmlElement("fieldlabel", null, "use_pool");
6132        $a_xml_writer->xmlElement("fieldentry", null, $this->getPoolUsage() ? 1 : 0);
6133        $a_xml_writer->xmlEndTag("qtimetadatafield");
6134
6135        // question set type (fixed, random, dynamic, ...)
6136        $a_xml_writer->xmlStartTag("qtimetadatafield");
6137        $a_xml_writer->xmlElement("fieldlabel", null, "question_set_type");
6138        $a_xml_writer->xmlElement("fieldentry", null, $this->getQuestionSetType());
6139        $a_xml_writer->xmlEndTag("qtimetadatafield");
6140
6141        // sequence settings
6142        $a_xml_writer->xmlStartTag("qtimetadatafield");
6143        $a_xml_writer->xmlElement("fieldlabel", null, "sequence_settings");
6144        $a_xml_writer->xmlElement("fieldentry", null, $this->getSequenceSettings());
6145        $a_xml_writer->xmlEndTag("qtimetadatafield");
6146
6147        // author
6148        $a_xml_writer->xmlStartTag("qtimetadatafield");
6149        $a_xml_writer->xmlElement("fieldlabel", null, "author");
6150        $a_xml_writer->xmlElement("fieldentry", null, $this->getAuthor());
6151        $a_xml_writer->xmlEndTag("qtimetadatafield");
6152
6153        // reset processing time
6154        $a_xml_writer->xmlStartTag("qtimetadatafield");
6155        $a_xml_writer->xmlElement("fieldlabel", null, "reset_processing_time");
6156        $a_xml_writer->xmlElement("fieldentry", null, $this->getResetProcessingTime());
6157        $a_xml_writer->xmlEndTag("qtimetadatafield");
6158
6159        // count system
6160        $a_xml_writer->xmlStartTag("qtimetadatafield");
6161        $a_xml_writer->xmlElement("fieldlabel", null, "count_system");
6162        $a_xml_writer->xmlElement("fieldentry", null, $this->getCountSystem());
6163        $a_xml_writer->xmlEndTag("qtimetadatafield");
6164
6165        // multiple choice scoring
6166        $a_xml_writer->xmlStartTag("qtimetadatafield");
6167        $a_xml_writer->xmlElement("fieldlabel", null, "mc_scoring");
6168        $a_xml_writer->xmlElement("fieldentry", null, $this->getMCScoring());
6169        $a_xml_writer->xmlEndTag("qtimetadatafield");
6170
6171        // multiple choice scoring
6172        $a_xml_writer->xmlStartTag("qtimetadatafield");
6173        $a_xml_writer->xmlElement("fieldlabel", null, "score_cutting");
6174        $a_xml_writer->xmlElement("fieldentry", null, $this->getScoreCutting());
6175        $a_xml_writer->xmlEndTag("qtimetadatafield");
6176
6177        // multiple choice scoring
6178        $a_xml_writer->xmlStartTag("qtimetadatafield");
6179        $a_xml_writer->xmlElement("fieldlabel", null, "password");
6180        $a_xml_writer->xmlElement("fieldentry", null, $this->getPassword());
6181        $a_xml_writer->xmlEndTag("qtimetadatafield");
6182
6183        // allowed users
6184        $a_xml_writer->xmlStartTag("qtimetadatafield");
6185        $a_xml_writer->xmlElement("fieldlabel", null, "allowedUsers");
6186        $a_xml_writer->xmlElement("fieldentry", null, $this->getAllowedUsers());
6187        $a_xml_writer->xmlEndTag("qtimetadatafield");
6188
6189        // allowed users time gap
6190        $a_xml_writer->xmlStartTag("qtimetadatafield");
6191        $a_xml_writer->xmlElement("fieldlabel", null, "allowedUsersTimeGap");
6192        $a_xml_writer->xmlElement("fieldentry", null, $this->getAllowedUsersTimeGap());
6193        $a_xml_writer->xmlEndTag("qtimetadatafield");
6194
6195        // pass scoring
6196        $a_xml_writer->xmlStartTag("qtimetadatafield");
6197        $a_xml_writer->xmlElement("fieldlabel", null, "pass_scoring");
6198        $a_xml_writer->xmlElement("fieldentry", null, $this->getPassScoring());
6199        $a_xml_writer->xmlEndTag("qtimetadatafield");
6200
6201        $a_xml_writer->xmlStartTag('qtimetadatafield');
6202        $a_xml_writer->xmlElement('fieldlabel', null, 'pass_deletion_allowed');
6203        $a_xml_writer->xmlElement('fieldentry', null, (int) $this->isPassDeletionAllowed());
6204        $a_xml_writer->xmlEndTag('qtimetadatafield');
6205
6206        // score reporting date
6207        if ($this->getReportingDate()) {
6208            $a_xml_writer->xmlStartTag("qtimetadatafield");
6209            $a_xml_writer->xmlElement("fieldlabel", null, "reporting_date");
6210            preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->reporting_date, $matches);
6211            $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]));
6212            $a_xml_writer->xmlEndTag("qtimetadatafield");
6213        }
6214        // number of tries
6215        $a_xml_writer->xmlStartTag("qtimetadatafield");
6216        $a_xml_writer->xmlElement("fieldlabel", null, "nr_of_tries");
6217        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getNrOfTries()));
6218        $a_xml_writer->xmlEndTag("qtimetadatafield");
6219
6220        // number of tries
6221        $a_xml_writer->xmlStartTag('qtimetadatafield');
6222        $a_xml_writer->xmlElement('fieldlabel', null, 'block_after_passed');
6223        $a_xml_writer->xmlElement('fieldentry', null, (int) $this->isBlockPassesAfterPassedEnabled());
6224        $a_xml_writer->xmlEndTag('qtimetadatafield');
6225
6226        // pass_waiting
6227        $a_xml_writer->xmlStartTag("qtimetadatafield");
6228        $a_xml_writer->xmlElement("fieldlabel", null, "pass_waiting");
6229        $a_xml_writer->xmlElement("fieldentry", null, $this->getPassWaiting());
6230        $a_xml_writer->xmlEndTag("qtimetadatafield");
6231
6232        // kiosk
6233        $a_xml_writer->xmlStartTag("qtimetadatafield");
6234        $a_xml_writer->xmlElement("fieldlabel", null, "kiosk");
6235        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getKiosk()));
6236        $a_xml_writer->xmlEndTag("qtimetadatafield");
6237
6238
6239        //redirection_mode
6240        $a_xml_writer->xmlStartTag('qtimetadatafield');
6241        $a_xml_writer->xmlElement("fieldlabel", null, "redirection_mode");
6242        $a_xml_writer->xmlElement("fieldentry", null, $this->getRedirectionMode());
6243        $a_xml_writer->xmlEndTag("qtimetadatafield");
6244
6245        //redirection_url
6246        $a_xml_writer->xmlStartTag('qtimetadatafield');
6247        $a_xml_writer->xmlElement("fieldlabel", null, "redirection_url");
6248        $a_xml_writer->xmlElement("fieldentry", null, $this->getRedirectionUrl());
6249        $a_xml_writer->xmlEndTag("qtimetadatafield");
6250
6251        // use previous answers
6252        $a_xml_writer->xmlStartTag("qtimetadatafield");
6253        $a_xml_writer->xmlElement("fieldlabel", null, "use_previous_answers");
6254        $a_xml_writer->xmlElement("fieldentry", null, $this->getUsePreviousAnswers());
6255        $a_xml_writer->xmlEndTag("qtimetadatafield");
6256
6257        // hide title points
6258        $a_xml_writer->xmlStartTag("qtimetadatafield");
6259        $a_xml_writer->xmlElement("fieldlabel", null, "title_output");
6260        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getTitleOutput()));
6261        $a_xml_writer->xmlEndTag("qtimetadatafield");
6262
6263        // results presentation
6264        $a_xml_writer->xmlStartTag("qtimetadatafield");
6265        $a_xml_writer->xmlElement("fieldlabel", null, "results_presentation");
6266        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getResultsPresentation()));
6267        $a_xml_writer->xmlEndTag("qtimetadatafield");
6268
6269        // examid in test pass
6270        $a_xml_writer->xmlStartTag("qtimetadatafield");
6271        $a_xml_writer->xmlElement("fieldlabel", null, "examid_in_test_pass");
6272        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->isShowExamIdInTestPassEnabled()));
6273        $a_xml_writer->xmlEndTag("qtimetadatafield");
6274
6275        // examid in kiosk
6276        $a_xml_writer->xmlStartTag("qtimetadatafield");
6277        $a_xml_writer->xmlElement("fieldlabel", null, "examid_in_test_res");
6278        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->isShowExamIdInTestResultsEnabled()));
6279        $a_xml_writer->xmlEndTag("qtimetadatafield");
6280
6281        // solution details
6282        $a_xml_writer->xmlStartTag("qtimetadatafield");
6283        $a_xml_writer->xmlElement("fieldlabel", null, "show_summary");
6284        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getListOfQuestionsSettings()));
6285        $a_xml_writer->xmlEndTag("qtimetadatafield");
6286
6287        // solution details
6288        $a_xml_writer->xmlStartTag("qtimetadatafield");
6289        $a_xml_writer->xmlElement("fieldlabel", null, "score_reporting");
6290        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getScoreReporting()));
6291        $a_xml_writer->xmlEndTag("qtimetadatafield");
6292
6293        $a_xml_writer->xmlStartTag("qtimetadatafield");
6294        $a_xml_writer->xmlElement("fieldlabel", null, "solution_details");
6295        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getShowSolutionDetails());
6296        $a_xml_writer->xmlEndTag("qtimetadatafield");
6297        $a_xml_writer->xmlStartTag("qtimetadatafield");
6298        $a_xml_writer->xmlElement("fieldlabel", null, "print_bs_with_res");
6299        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getShowSolutionDetails() ? (int) $this->isBestSolutionPrintedWithResult() : 0);
6300        $a_xml_writer->xmlEndTag("qtimetadatafield");
6301
6302        // solution details
6303        $a_xml_writer->xmlStartTag("qtimetadatafield");
6304        $a_xml_writer->xmlElement("fieldlabel", null, "instant_verification");
6305        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getInstantFeedbackSolution()));
6306        $a_xml_writer->xmlEndTag("qtimetadatafield");
6307
6308        // answer specific feedback
6309        $a_xml_writer->xmlStartTag("qtimetadatafield");
6310        $a_xml_writer->xmlElement("fieldlabel", null, "answer_feedback");
6311        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getAnswerFeedback()));
6312        $a_xml_writer->xmlEndTag("qtimetadatafield");
6313
6314        // answer specific feedback of reached points
6315        $a_xml_writer->xmlStartTag("qtimetadatafield");
6316        $a_xml_writer->xmlElement("fieldlabel", null, "answer_feedback_points");
6317        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getAnswerFeedbackPoints()));
6318        $a_xml_writer->xmlEndTag("qtimetadatafield");
6319
6320        // followup question previous answer freezing
6321        $a_xml_writer->xmlStartTag("qtimetadatafield");
6322        $a_xml_writer->xmlElement("fieldlabel", null, "follow_qst_answer_fixation");
6323        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isFollowupQuestionAnswerFixationEnabled());
6324        $a_xml_writer->xmlEndTag("qtimetadatafield");
6325
6326        // instant response answer freezing
6327        $a_xml_writer->xmlStartTag("qtimetadatafield");
6328        $a_xml_writer->xmlElement("fieldlabel", null, "instant_feedback_answer_fixation");
6329        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isInstantFeedbackAnswerFixationEnabled());
6330        $a_xml_writer->xmlEndTag("qtimetadatafield");
6331
6332        // instant response forced
6333        $a_xml_writer->xmlStartTag("qtimetadatafield");
6334        $a_xml_writer->xmlElement("fieldlabel", null, "force_instant_feedback");
6335        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isForceInstantFeedbackEnabled());
6336        $a_xml_writer->xmlEndTag("qtimetadatafield");
6337
6338
6339        // highscore
6340        $highscore_metadata = array(
6341            'highscore_enabled' => array('value' => $this->getHighscoreEnabled()),
6342            'highscore_anon' => array('value' => $this->getHighscoreAnon()),
6343            'highscore_achieved_ts' => array('value' => $this->getHighscoreAchievedTS()),
6344            'highscore_score' => array('value' => $this->getHighscoreScore()),
6345            'highscore_percentage' => array('value' => $this->getHighscorePercentage()),
6346            'highscore_hints' => array('value' => $this->getHighscoreHints()),
6347            'highscore_wtime' => array('value' => $this->getHighscoreWTime()),
6348            'highscore_own_table' => array('value' => $this->getHighscoreOwnTable()),
6349            'highscore_top_table' => array('value' => $this->getHighscoreTopTable()),
6350            'highscore_top_num' => array('value' => $this->getHighscoreTopNum()),
6351        );
6352        foreach ($highscore_metadata as $label => $data) {
6353            $a_xml_writer->xmlStartTag("qtimetadatafield");
6354            $a_xml_writer->xmlElement("fieldlabel", null, $label);
6355            $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $data['value']));
6356            $a_xml_writer->xmlEndTag("qtimetadatafield");
6357        }
6358
6359        // show cancel
6360        $a_xml_writer->xmlStartTag("qtimetadatafield");
6361        $a_xml_writer->xmlElement("fieldlabel", null, "show_cancel");
6362        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getShowCancel()));
6363        $a_xml_writer->xmlEndTag("qtimetadatafield");
6364
6365        // show marker
6366        $a_xml_writer->xmlStartTag("qtimetadatafield");
6367        $a_xml_writer->xmlElement("fieldlabel", null, "show_marker");
6368        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getShowMarker()));
6369        $a_xml_writer->xmlEndTag("qtimetadatafield");
6370
6371        // fixed participants
6372        $a_xml_writer->xmlStartTag("qtimetadatafield");
6373        $a_xml_writer->xmlElement("fieldlabel", null, "fixed_participants");
6374        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getFixedParticipants()));
6375        $a_xml_writer->xmlEndTag("qtimetadatafield");
6376
6377        // show final statement
6378        $a_xml_writer->xmlStartTag("qtimetadatafield");
6379        $a_xml_writer->xmlElement("fieldlabel", null, "showfinalstatement");
6380        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (($this->getShowFinalStatement()) ? "1" : "0")));
6381        $a_xml_writer->xmlEndTag("qtimetadatafield");
6382
6383        // show introduction only
6384        $a_xml_writer->xmlStartTag("qtimetadatafield");
6385        $a_xml_writer->xmlElement("fieldlabel", null, "showinfo");
6386        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (($this->getShowInfo()) ? "1" : "0")));
6387        $a_xml_writer->xmlEndTag("qtimetadatafield");
6388
6389        // mail notification
6390        $a_xml_writer->xmlStartTag("qtimetadatafield");
6391        $a_xml_writer->xmlElement("fieldlabel", null, "mailnotification");
6392        $a_xml_writer->xmlElement("fieldentry", null, $this->getMailNotification());
6393        $a_xml_writer->xmlEndTag("qtimetadatafield");
6394
6395        // mail notification type
6396        $a_xml_writer->xmlStartTag("qtimetadatafield");
6397        $a_xml_writer->xmlElement("fieldlabel", null, "mailnottype");
6398        $a_xml_writer->xmlElement("fieldentry", null, $this->getMailNotificationType());
6399        $a_xml_writer->xmlEndTag("qtimetadatafield");
6400
6401        // export settings
6402        $a_xml_writer->xmlStartTag("qtimetadatafield");
6403        $a_xml_writer->xmlElement("fieldlabel", null, "exportsettings");
6404        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getExportSettings());
6405        $a_xml_writer->xmlEndTag("qtimetadatafield");
6406
6407        // force JavaScript
6408        $a_xml_writer->xmlStartTag("qtimetadatafield");
6409        $a_xml_writer->xmlElement("fieldlabel", null, "forcejs");
6410        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", (($this->getForceJS()) ? "1" : "0")));
6411        $a_xml_writer->xmlEndTag("qtimetadatafield");
6412
6413        // custom style
6414        $a_xml_writer->xmlStartTag("qtimetadatafield");
6415        $a_xml_writer->xmlElement("fieldlabel", null, "customstyle");
6416        $a_xml_writer->xmlElement("fieldentry", null, $this->getCustomStyle());
6417        $a_xml_writer->xmlEndTag("qtimetadatafield");
6418
6419        // shuffle questions
6420        $a_xml_writer->xmlStartTag("qtimetadatafield");
6421        $a_xml_writer->xmlElement("fieldlabel", null, "shuffle_questions");
6422        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getShuffleQuestions()));
6423        $a_xml_writer->xmlEndTag("qtimetadatafield");
6424
6425        // processing time
6426        $a_xml_writer->xmlStartTag("qtimetadatafield");
6427        $a_xml_writer->xmlElement("fieldlabel", null, "processing_time");
6428        $a_xml_writer->xmlElement("fieldentry", null, $this->getProcessingTime());
6429        $a_xml_writer->xmlEndTag("qtimetadatafield");
6430
6431        // enable_examview
6432        $a_xml_writer->xmlStartTag("qtimetadatafield");
6433        $a_xml_writer->xmlElement("fieldlabel", null, "enable_examview");
6434        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getEnableExamview());
6435        $a_xml_writer->xmlEndTag("qtimetadatafield");
6436
6437        // show_examview_html
6438        $a_xml_writer->xmlStartTag("qtimetadatafield");
6439        $a_xml_writer->xmlElement("fieldlabel", null, "show_examview_html");
6440        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getShowExamviewHtml());
6441        $a_xml_writer->xmlEndTag("qtimetadatafield");
6442
6443        // show_examview_pdf
6444        $a_xml_writer->xmlStartTag("qtimetadatafield");
6445        $a_xml_writer->xmlElement("fieldlabel", null, "show_examview_pdf");
6446        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getShowExamviewPdf());
6447        $a_xml_writer->xmlEndTag("qtimetadatafield");
6448
6449        // enable_archiving
6450        $a_xml_writer->xmlStartTag("qtimetadatafield");
6451        $a_xml_writer->xmlElement("fieldlabel", null, "enable_archiving");
6452        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getEnableArchiving());
6453        $a_xml_writer->xmlEndTag("qtimetadatafield");
6454
6455        // sign_submission
6456        $a_xml_writer->xmlStartTag("qtimetadatafield");
6457        $a_xml_writer->xmlElement("fieldlabel", null, "sign_submission");
6458        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getSignSubmission());
6459        $a_xml_writer->xmlEndTag("qtimetadatafield");
6460
6461        // char_selector_availability
6462        $a_xml_writer->xmlStartTag("qtimetadatafield");
6463        $a_xml_writer->xmlElement("fieldlabel", null, "char_selector_availability");
6464        $a_xml_writer->xmlElement("fieldentry", null, sprintf("%d", $this->getCharSelectorAvailability()));
6465        $a_xml_writer->xmlEndTag("qtimetadatafield");
6466
6467        // char_selector_definition
6468        $a_xml_writer->xmlStartTag("qtimetadatafield");
6469        $a_xml_writer->xmlElement("fieldlabel", null, "char_selector_definition");
6470        $a_xml_writer->xmlElement("fieldentry", null, $this->getCharSelectorDefinition());
6471        $a_xml_writer->xmlEndTag("qtimetadatafield");
6472
6473        // skill_service
6474        $a_xml_writer->xmlStartTag("qtimetadatafield");
6475        $a_xml_writer->xmlElement("fieldlabel", null, "skill_service");
6476        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isSkillServiceEnabled());
6477        $a_xml_writer->xmlEndTag("qtimetadatafield");
6478
6479        // result_tax_filters
6480        $a_xml_writer->xmlStartTag("qtimetadatafield");
6481        $a_xml_writer->xmlElement("fieldlabel", null, "result_tax_filters");
6482        $a_xml_writer->xmlElement("fieldentry", null, serialize((array) $this->getResultFilterTaxIds()));
6483        $a_xml_writer->xmlEndTag("qtimetadatafield");
6484
6485        // show_grading_status
6486        $a_xml_writer->xmlStartTag("qtimetadatafield");
6487        $a_xml_writer->xmlElement("fieldlabel", null, "show_grading_status");
6488        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isShowGradingStatusEnabled());
6489        $a_xml_writer->xmlEndTag("qtimetadatafield");
6490
6491        // show_grading_mark
6492        $a_xml_writer->xmlStartTag("qtimetadatafield");
6493        $a_xml_writer->xmlElement("fieldlabel", null, "show_grading_mark");
6494        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isShowGradingMarkEnabled());
6495        $a_xml_writer->xmlEndTag("qtimetadatafield");
6496
6497
6498        // starting time
6499        if ($this->getStartingTime()) {
6500            $a_xml_writer->xmlStartTag("qtimetadatafield");
6501            $a_xml_writer->xmlElement("fieldlabel", null, "starting_time");
6502            $backward_compatibility_format = $this->buildIso8601PeriodFromUnixtimeForExportCompatibility($this->starting_time);
6503            $a_xml_writer->xmlElement("fieldentry", null, $backward_compatibility_format);
6504            $a_xml_writer->xmlEndTag("qtimetadatafield");
6505        }
6506        // ending time
6507        if ($this->getEndingTime()) {
6508            $a_xml_writer->xmlStartTag("qtimetadatafield");
6509            $a_xml_writer->xmlElement("fieldlabel", null, "ending_time");
6510            $backward_compatibility_format = $this->buildIso8601PeriodFromUnixtimeForExportCompatibility($this->ending_time);
6511            $a_xml_writer->xmlElement("fieldentry", null, $backward_compatibility_format);
6512            $a_xml_writer->xmlEndTag("qtimetadatafield");
6513        }
6514
6515
6516        //activation_limited
6517        $a_xml_writer->xmlStartTag("qtimetadatafield");
6518        $a_xml_writer->xmlElement("fieldlabel", null, "activation_limited");
6519        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isActivationLimited());
6520        $a_xml_writer->xmlEndTag("qtimetadatafield");
6521
6522        //activation_start_time
6523        $a_xml_writer->xmlStartTag("qtimetadatafield");
6524        $a_xml_writer->xmlElement("fieldlabel", null, "activation_start_time");
6525        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getActivationStartingTime());
6526        $a_xml_writer->xmlEndTag("qtimetadatafield");
6527
6528        //activation_end_time
6529        $a_xml_writer->xmlStartTag("qtimetadatafield");
6530        $a_xml_writer->xmlElement("fieldlabel", null, "activation_end_time");
6531        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getActivationEndingTime());
6532        $a_xml_writer->xmlEndTag("qtimetadatafield");
6533
6534        //activation_visibility
6535        $a_xml_writer->xmlStartTag("qtimetadatafield");
6536        $a_xml_writer->xmlElement("fieldlabel", null, "activation_visibility");
6537        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getActivationVisibility());
6538        $a_xml_writer->xmlEndTag("qtimetadatafield");
6539
6540        // autosave
6541        $a_xml_writer->xmlStartTag("qtimetadatafield");
6542        $a_xml_writer->xmlElement("fieldlabel", null, "autosave");
6543        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getAutosave());
6544        $a_xml_writer->xmlEndTag("qtimetadatafield");
6545
6546        // autosave_ival
6547        $a_xml_writer->xmlStartTag("qtimetadatafield");
6548        $a_xml_writer->xmlElement("fieldlabel", null, "autosave_ival");
6549        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getAutosaveIval());
6550        $a_xml_writer->xmlEndTag("qtimetadatafield");
6551
6552        //offer_question_hints
6553        $a_xml_writer->xmlStartTag("qtimetadatafield");
6554        $a_xml_writer->xmlElement("fieldlabel", null, "offer_question_hints");
6555        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isOfferingQuestionHintsEnabled());
6556        $a_xml_writer->xmlEndTag("qtimetadatafield");
6557
6558        //instant_feedback_specific
6559        $a_xml_writer->xmlStartTag("qtimetadatafield");
6560        $a_xml_writer->xmlElement("fieldlabel", null, "instant_feedback_specific");
6561        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getSpecificAnswerFeedback());
6562        $a_xml_writer->xmlEndTag("qtimetadatafield");
6563
6564        //instant_feedback_answer_fixation
6565        $a_xml_writer->xmlStartTag("qtimetadatafield");
6566        $a_xml_writer->xmlElement("fieldlabel", null, "instant_feedback_answer_fixation");
6567        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->isInstantFeedbackAnswerFixationEnabled());
6568        $a_xml_writer->xmlEndTag("qtimetadatafield");
6569
6570        //obligations_enabled
6571        $a_xml_writer->xmlStartTag("qtimetadatafield");
6572        $a_xml_writer->xmlElement("fieldlabel", null, "obligations_enabled");
6573        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->areObligationsEnabled());
6574        $a_xml_writer->xmlEndTag("qtimetadatafield");
6575
6576        //enable_processing_time
6577        $a_xml_writer->xmlStartTag("qtimetadatafield");
6578        $a_xml_writer->xmlElement("fieldlabel", null, "enable_processing_time");
6579        $a_xml_writer->xmlElement("fieldentry", null, (int) $this->getEnableProcessingTime());
6580        $a_xml_writer->xmlEndTag("qtimetadatafield");
6581
6582        foreach ($this->mark_schema->mark_steps as $index => $mark) {
6583            // mark steps
6584            $a_xml_writer->xmlStartTag("qtimetadatafield");
6585            $a_xml_writer->xmlElement("fieldlabel", null, "mark_step_$index");
6586            $a_xml_writer->xmlElement("fieldentry", null, sprintf(
6587                "<short>%s</short><official>%s</official><percentage>%.2f</percentage><passed>%d</passed>",
6588                $mark->getShortName(),
6589                $mark->getOfficialName(),
6590                $mark->getMinimumLevel(),
6591                $mark->getPassed()
6592            ));
6593            $a_xml_writer->xmlEndTag("qtimetadatafield");
6594        }
6595        $a_xml_writer->xmlEndTag("qtimetadata");
6596
6597        // add qti objectives
6598        $a_xml_writer->xmlStartTag("objectives");
6599        $this->addQTIMaterial($a_xml_writer, $this->getIntroduction());
6600        $a_xml_writer->xmlEndTag("objectives");
6601
6602        // add qti assessmentcontrol
6603        if ($this->getInstantFeedbackSolution() == 1) {
6604            $attrs = array(
6605                "solutionswitch" => "Yes"
6606            );
6607        } else {
6608            $attrs = null;
6609        }
6610        $a_xml_writer->xmlElement("assessmentcontrol", $attrs, null);
6611
6612        if (strlen($this->getFinalStatement())) {
6613            // add qti presentation_material
6614            $a_xml_writer->xmlStartTag("presentation_material");
6615            $a_xml_writer->xmlStartTag("flow_mat");
6616            $this->addQTIMaterial($a_xml_writer, $this->getFinalStatement());
6617            $a_xml_writer->xmlEndTag("flow_mat");
6618            $a_xml_writer->xmlEndTag("presentation_material");
6619        }
6620
6621        $attrs = array(
6622            "ident" => "1"
6623        );
6624        $a_xml_writer->xmlElement("section", $attrs, null);
6625        $a_xml_writer->xmlEndTag("assessment");
6626        $a_xml_writer->xmlEndTag("questestinterop");
6627
6628        $xml = $a_xml_writer->xmlDumpMem(false);
6629        return $xml;
6630    }
6631
6632    /**
6633     * @param $unix_timestamp
6634     * @return string
6635     */
6636    protected function buildIso8601PeriodFromUnixtimeForExportCompatibility($unix_timestamp)
6637    {
6638        $date_time_unix = new ilDateTime($unix_timestamp, IL_CAL_UNIX);
6639        $date_time = $date_time_unix->get(IL_CAL_DATETIME);
6640        preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $date_time, $matches);
6641        $iso8601_period = sprintf("P%dY%dM%dDT%dH%dM%dS", $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]);
6642        return $iso8601_period;
6643    }
6644
6645    /**
6646    * export pages of test to xml (see ilias_co.dtd)
6647    *
6648    * @param	object		$a_xml_writer	ilXmlWriter object that receives the
6649    *										xml data
6650    */
6651    public function exportPagesXML(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog)
6652    {
6653        global $DIC;
6654        $ilBench = $DIC['ilBench'];
6655
6656        $this->mob_ids = array();
6657        $this->file_ids = array();
6658
6659        // MetaData
6660        $this->exportXMLMetaData($a_xml_writer);
6661
6662        // PageObjects
6663        $expLog->write(date("[y-m-d H:i:s] ") . "Start Export Page Objects");
6664        $ilBench->start("ContentObjectExport", "exportPageObjects");
6665        $this->exportXMLPageObjects($a_xml_writer, $a_inst, $expLog);
6666        $ilBench->stop("ContentObjectExport", "exportPageObjects");
6667        $expLog->write(date("[y-m-d H:i:s] ") . "Finished Export Page Objects");
6668
6669        // MediaObjects
6670        $expLog->write(date("[y-m-d H:i:s] ") . "Start Export Media Objects");
6671        $ilBench->start("ContentObjectExport", "exportMediaObjects");
6672        $this->exportXMLMediaObjects($a_xml_writer, $a_inst, $a_target_dir, $expLog);
6673        $ilBench->stop("ContentObjectExport", "exportMediaObjects");
6674        $expLog->write(date("[y-m-d H:i:s] ") . "Finished Export Media Objects");
6675
6676        // FileItems
6677        $expLog->write(date("[y-m-d H:i:s] ") . "Start Export File Items");
6678        $ilBench->start("ContentObjectExport", "exportFileItems");
6679        $this->exportFileItems($a_target_dir, $expLog);
6680        $ilBench->stop("ContentObjectExport", "exportFileItems");
6681        $expLog->write(date("[y-m-d H:i:s] ") . "Finished Export File Items");
6682    }
6683
6684    /**
6685    * export content objects meta data to xml (see ilias_co.dtd)
6686    *
6687    * @param	object		$a_xml_writer	ilXmlWriter object that receives the
6688    *										xml data
6689    */
6690    public function exportXMLMetaData(&$a_xml_writer)
6691    {
6692        include_once "./Services/MetaData/classes/class.ilMD2XML.php";
6693        $md2xml = new ilMD2XML($this->getId(), 0, $this->getType());
6694        $md2xml->setExportMode(true);
6695        $md2xml->startExport();
6696        $a_xml_writer->appendXML($md2xml->getXML());
6697    }
6698
6699    /**
6700    * Returns the installation id for a given identifier
6701    *
6702    * @access	private
6703    */
6704    public function modifyExportIdentifier($a_tag, $a_param, $a_value)
6705    {
6706        if ($a_tag == "Identifier" && $a_param == "Entry") {
6707            include_once "./Services/Utilities/classes/class.ilUtil.php";
6708            $a_value = ilUtil::insertInstIntoID($a_value);
6709        }
6710
6711        return $a_value;
6712    }
6713
6714
6715    /**
6716    * export page objects to xml (see ilias_co.dtd)
6717    *
6718    * @param	object		$a_xml_writer	ilXmlWriter object that receives the
6719    *										xml data
6720    */
6721    public function exportXMLPageObjects(&$a_xml_writer, $a_inst, &$expLog)
6722    {
6723        global $DIC;
6724        $ilBench = $DIC['ilBench'];
6725
6726        include_once "./Modules/LearningModule/classes/class.ilLMPageObject.php";
6727
6728        foreach ($this->questions as $question_id) {
6729            $ilBench->start("ContentObjectExport", "exportPageObject");
6730            $expLog->write(date("[y-m-d H:i:s] ") . "Page Object " . $question_id);
6731
6732            $attrs = array();
6733            $a_xml_writer->xmlStartTag("PageObject", $attrs);
6734
6735
6736            // export xml to writer object
6737            $ilBench->start("ContentObjectExport", "exportPageObject_XML");
6738            include_once "./Modules/TestQuestionPool/classes/class.ilAssQuestionPage.php";
6739            $page_object = new ilAssQuestionPage($question_id);
6740            $page_object->buildDom();
6741            $page_object->insertInstIntoIDs($a_inst);
6742            $mob_ids = $page_object->collectMediaObjects(false);
6743            require_once 'Services/COPage/classes/class.ilPCFileList.php';
6744            $file_ids = ilPCFileList::collectFileItems($page_object, $page_object->getDomDoc());
6745            $xml = $page_object->getXMLFromDom(false, false, false, "", true);
6746            $xml = str_replace("&", "&amp;", $xml);
6747            $a_xml_writer->appendXML($xml);
6748            $page_object->freeDom();
6749            unset($page_object);
6750
6751            $ilBench->stop("ContentObjectExport", "exportPageObject_XML");
6752
6753            // collect media objects
6754            $ilBench->start("ContentObjectExport", "exportPageObject_CollectMedia");
6755            //$mob_ids = $page_obj->getMediaObjectIDs();
6756            foreach ($mob_ids as $mob_id) {
6757                $this->mob_ids[$mob_id] = $mob_id;
6758            }
6759            $ilBench->stop("ContentObjectExport", "exportPageObject_CollectMedia");
6760
6761            // collect all file items
6762            $ilBench->start("ContentObjectExport", "exportPageObject_CollectFileItems");
6763            //$file_ids = $page_obj->getFileItemIds();
6764            foreach ($file_ids as $file_id) {
6765                $this->file_ids[$file_id] = $file_id;
6766            }
6767            $ilBench->stop("ContentObjectExport", "exportPageObject_CollectFileItems");
6768
6769            $a_xml_writer->xmlEndTag("PageObject");
6770            //unset($page_obj);
6771
6772            $ilBench->stop("ContentObjectExport", "exportPageObject");
6773        }
6774    }
6775
6776    /**
6777    * export media objects to xml (see ilias_co.dtd)
6778    *
6779    * @param	object		$a_xml_writer	ilXmlWriter object that receives the
6780    *										xml data
6781    */
6782    public function exportXMLMediaObjects(&$a_xml_writer, $a_inst, $a_target_dir, &$expLog)
6783    {
6784        include_once "./Services/MediaObjects/classes/class.ilObjMediaObject.php";
6785
6786        foreach ($this->mob_ids as $mob_id) {
6787            $expLog->write(date("[y-m-d H:i:s] ") . "Media Object " . $mob_id);
6788            if (ilObjMediaObject::_exists($mob_id)) {
6789                $media_obj = new ilObjMediaObject($mob_id);
6790                $media_obj->exportXML($a_xml_writer, $a_inst);
6791                $media_obj->exportFiles($a_target_dir);
6792                unset($media_obj);
6793            }
6794        }
6795    }
6796
6797    /**
6798    * export files of file itmes
6799    *
6800    */
6801    public function exportFileItems($a_target_dir, &$expLog)
6802    {
6803        include_once "./Modules/File/classes/class.ilObjFile.php";
6804
6805        foreach ($this->file_ids as $file_id) {
6806            $expLog->write(date("[y-m-d H:i:s] ") . "File Item " . $file_id);
6807            $file_obj = new ilObjFile($file_id, false);
6808            $file_obj->export($a_target_dir);
6809            unset($file_obj);
6810        }
6811    }
6812
6813    /**
6814    * get array of (two) new created questions for
6815    * import id
6816    */
6817    public function getImportMapping()
6818    {
6819        if (!is_array($this->import_mapping)) {
6820            return array();
6821        } else {
6822            return $this->import_mapping;
6823        }
6824    }
6825
6826    /**
6827     * {@inheritdoc}
6828     */
6829    public function canEditEctsGrades()
6830    {
6831        return $this->canShowEctsGrades() && $this->canEditMarks();
6832    }
6833
6834    /**
6835     * {@inheritdoc}
6836     */
6837    public function canShowEctsGrades()
6838    {
6839        return $this->getReportingDate();
6840    }
6841
6842    /**
6843     * {@inheritdoc}
6844     */
6845    public function getECTSGrade($passed_array, $reached_points, $max_points)
6846    {
6847        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);
6848    }
6849
6850    /**
6851     * {@inheritdoc}
6852     */
6853    public static function _getECTSGrade($points_passed, $reached_points, $max_points, $a, $b, $c, $d, $e, $fx)
6854    {
6855        include_once "./Modules/Test/classes/class.ilStatistics.php";
6856        // calculate the median
6857        $passed_statistics = new ilStatistics();
6858        $passed_statistics->setData($points_passed);
6859        $ects_percentiles = array(
6860            "A" => $passed_statistics->quantile($a),
6861            "B" => $passed_statistics->quantile($b),
6862            "C" => $passed_statistics->quantile($c),
6863            "D" => $passed_statistics->quantile($d),
6864            "E" => $passed_statistics->quantile($e)
6865        );
6866        if (count($points_passed) && ($reached_points >= $ects_percentiles["A"])) {
6867            return "A";
6868        } elseif (count($points_passed) && ($reached_points >= $ects_percentiles["B"])) {
6869            return "B";
6870        } elseif (count($points_passed) && ($reached_points >= $ects_percentiles["C"])) {
6871            return "C";
6872        } elseif (count($points_passed) && ($reached_points >= $ects_percentiles["D"])) {
6873            return "D";
6874        } elseif (count($points_passed) && ($reached_points >= $ects_percentiles["E"])) {
6875            return "E";
6876        } elseif (strcmp($fx, "") != 0) {
6877            if ($max_points > 0) {
6878                $percentage = ($reached_points / $max_points) * 100.0;
6879                if ($percentage < 0) {
6880                    $percentage = 0.0;
6881                }
6882            } else {
6883                $percentage = 0.0;
6884            }
6885            if ($percentage >= $fx) {
6886                return "FX";
6887            } else {
6888                return "F";
6889            }
6890        } else {
6891            return "F";
6892        }
6893    }
6894
6895    /**
6896     * {@inheritdoc}
6897     */
6898    public function checkMarks()
6899    {
6900        return $this->mark_schema->checkMarks();
6901    }
6902
6903    /**
6904     * {@inheritdoc}
6905     */
6906    public function getMarkSchema()
6907    {
6908        return $this->mark_schema;
6909    }
6910
6911    /**
6912     * {@inheritdoc}
6913     */
6914    public function getMarkSchemaForeignId()
6915    {
6916        return $this->getTestId();
6917    }
6918
6919    /**
6920     */
6921    public function onMarkSchemaSaved()
6922    {
6923        /**
6924         * @var $tree          ilTree
6925         * @var $ilDB          ilDBInterface
6926         * @var $ilPluginAdmin ilPluginAdmin
6927         */
6928        global $DIC;
6929        $ilDB = $DIC['ilDB'];
6930        $ilPluginAdmin = $DIC['ilPluginAdmin'];
6931        $tree = $DIC['tree'];
6932
6933        require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
6934        $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $this);
6935        $this->saveCompleteStatus($testQuestionSetConfigFactory->getQuestionSetConfig());
6936
6937        if ($this->participantDataExist()) {
6938            $this->recalculateScores(true);
6939        }
6940    }
6941
6942    /**
6943     * @return {@inheritdoc}
6944     */
6945    public function canEditMarks()
6946    {
6947        $total = $this->evalTotalPersons();
6948        if ($total > 0) {
6949            if ($this->getReportingDate()) {
6950                if (preg_match("/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/", $this->getReportingDate(), $matches)) {
6951                    $epoch_time = mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
6952                    $now = time();
6953                    if ($now < $epoch_time) {
6954                        return true;
6955                    }
6956                }
6957            }
6958            return false;
6959        } else {
6960            return true;
6961        }
6962    }
6963
6964    /**
6965    * Sets the authors name of the ilObjTest object
6966    *
6967    * @param string $author A string containing the name of the test author
6968    * @access public
6969    * @see $author
6970    */
6971    public function setAuthor($author = "")
6972    {
6973        $this->author = $author;
6974    }
6975
6976    /**
6977    * Saves an authors name into the lifecycle metadata if no lifecycle metadata exists
6978    * This will only be called for conversion of "old" tests where the author hasn't been
6979    * stored in the lifecycle metadata
6980    *
6981    * @param string $a_author A string containing the name of the test author
6982    * @access private
6983    * @see $author
6984    */
6985    public function saveAuthorToMetadata($a_author = "")
6986    {
6987        $md = new ilMD($this->getId(), 0, $this->getType());
6988        $md_life = &$md->getLifecycle();
6989        if (!$md_life) {
6990            if (strlen($a_author) == 0) {
6991                global $DIC;
6992                $ilUser = $DIC['ilUser'];
6993                $a_author = $ilUser->getFullname();
6994            }
6995
6996            $md_life = &$md->addLifecycle();
6997            $md_life->save();
6998            $con = &$md_life->addContribute();
6999            $con->setRole("Author");
7000            $con->save();
7001            $ent = &$con->addEntity();
7002            $ent->setEntity($a_author);
7003            $ent->save();
7004        }
7005    }
7006
7007    /**
7008    * Create meta data entry
7009    *
7010    * @access public
7011    */
7012    public function createMetaData()
7013    {
7014        parent::createMetaData();
7015        $this->saveAuthorToMetadata();
7016    }
7017
7018    /**
7019    * Gets the authors name of the ilObjTest object
7020    *
7021    * @return string The string containing the name of the test author
7022    * @access public
7023    * @see $author
7024    */
7025    public function getAuthor()
7026    {
7027        $author = array();
7028        include_once "./Services/MetaData/classes/class.ilMD.php";
7029        $md = new ilMD($this->getId(), 0, $this->getType());
7030        $md_life = &$md->getLifecycle();
7031        if ($md_life) {
7032            $ids = &$md_life->getContributeIds();
7033            foreach ($ids as $id) {
7034                $md_cont = &$md_life->getContribute($id);
7035                if (strcmp($md_cont->getRole(), "Author") == 0) {
7036                    $entids = &$md_cont->getEntityIds();
7037                    foreach ($entids as $entid) {
7038                        $md_ent = &$md_cont->getEntity($entid);
7039                        array_push($author, $md_ent->getEntity());
7040                    }
7041                }
7042            }
7043        }
7044        return join(",", $author);
7045    }
7046
7047    /**
7048    * Gets the authors name of the ilObjTest object
7049    *
7050    * @return string The string containing the name of the test author
7051    * @access public
7052    * @see $author
7053    */
7054    public static function _lookupAuthor($obj_id)
7055    {
7056        $author = array();
7057        include_once "./Services/MetaData/classes/class.ilMD.php";
7058        $md = new ilMD($obj_id, 0, "tst");
7059        $md_life = &$md->getLifecycle();
7060        if ($md_life) {
7061            $ids = &$md_life->getContributeIds();
7062            foreach ($ids as $id) {
7063                $md_cont = &$md_life->getContribute($id);
7064                if (strcmp($md_cont->getRole(), "Author") == 0) {
7065                    $entids = &$md_cont->getEntityIds();
7066                    foreach ($entids as $entid) {
7067                        $md_ent = &$md_cont->getEntity($entid);
7068                        array_push($author, $md_ent->getEntity());
7069                    }
7070                }
7071            }
7072        }
7073        return join(",", $author);
7074    }
7075
7076    /**
7077    * Returns the available tests for the active user
7078    *
7079    * @return array The available tests
7080    * @access public
7081    */
7082    public static function _getAvailableTests($use_object_id = false)
7083    {
7084        global $DIC;
7085        $ilUser = $DIC['ilUser'];
7086        $ilDB = $DIC['ilDB'];
7087
7088        $result_array = array();
7089        $tests = array_slice(
7090            array_reverse(
7091                ilUtil::_getObjectsByOperations("tst", "write", $ilUser->getId(), PHP_INT_MAX)
7092            ),
7093            0,
7094            10000
7095        );
7096
7097        if (count($tests)) {
7098            $titles = ilObject::_prepareCloneSelection($tests, "tst");
7099            foreach ($tests as $ref_id) {
7100                if ($use_object_id) {
7101                    $obj_id = ilObject::_lookupObjId($ref_id);
7102                    $result_array[$obj_id] = $titles[$ref_id];
7103                } else {
7104                    $result_array[$ref_id] = $titles[$ref_id];
7105                }
7106            }
7107        }
7108        return $result_array;
7109    }
7110
7111    /**
7112    * Clone object
7113    *
7114    * @access public
7115    * @param int ref id of parent container
7116    * @param int copy id
7117    * @return object new test object
7118    */
7119    public function cloneObject($a_target_id, $a_copy_id = 0, $a_omit_tree = false)
7120    {
7121        global $DIC;
7122
7123        $certificateLogger = $DIC->logger()->cert();
7124        $tree = $DIC['tree'];
7125        $ilDB = $DIC->database();
7126        $ilPluginAdmin = $DIC['ilPluginAdmin'];
7127
7128        $this->loadFromDb();
7129
7130        // Copy settings
7131        /** @var $newObj ilObjTest */
7132        $newObj = parent::cloneObject($a_target_id, $a_copy_id, $a_omit_tree);
7133        $newObj->setTmpCopyWizardCopyId($a_copy_id);
7134        $this->cloneMetaData($newObj);
7135
7136        // #27082
7137        $newObj->setOfflineStatus(true);
7138        $newObj->update();
7139
7140        $newObj->setAnonymity($this->getAnonymity());
7141        $newObj->setAnswerFeedback($this->getAnswerFeedback());
7142        $newObj->setAnswerFeedbackPoints($this->getAnswerFeedbackPoints());
7143        $newObj->setAuthor($this->getAuthor());
7144        $newObj->setLimitUsersEnabled($this->isLimitUsersEnabled());
7145        $newObj->setAllowedUsers($this->getAllowedUsers());
7146        $newObj->setAllowedUsersTimeGap($this->getAllowedUsersTimeGap());
7147        $newObj->setCountSystem($this->getCountSystem());
7148        $newObj->setECTSFX($this->getECTSFX());
7149        $newObj->setECTSGrades($this->getECTSGrades());
7150        $newObj->setECTSOutput($this->getECTSOutput());
7151        $newObj->setEnableProcessingTime($this->getEnableProcessingTime());
7152        $newObj->setEndingTimeEnabled($this->isEndingTimeEnabled());
7153        $newObj->setEndingTime($this->getEndingTime());
7154        $newObj->setFixedParticipants($this->getFixedParticipants());
7155        $newObj->setInstantFeedbackSolution($this->getInstantFeedbackSolution());
7156        $newObj->setIntroductionEnabled($this->isIntroductionEnabled());
7157        $newObj->setIntroduction($this->getIntroduction());
7158        $newObj->setFinalStatement($this->getFinalStatement());
7159        $newObj->setShowInfo($this->getShowInfo());
7160        $newObj->setForceJS($this->getForceJS());
7161        $newObj->setCustomStyle($this->getCustomStyle());
7162        $newObj->setKiosk($this->getKiosk());
7163        $newObj->setShowFinalStatement($this->getShowFinalStatement());
7164        $newObj->setListOfQuestionsSettings($this->getListOfQuestionsSettings());
7165        $newObj->setMCScoring($this->getMCScoring());
7166        $newObj->setMailNotification($this->getMailNotification());
7167        $newObj->setMailNotificationType($this->getMailNotificationType());
7168        $newObj->setNrOfTries($this->getNrOfTries());
7169        $newObj->setBlockPassesAfterPassedEnabled($this->isBlockPassesAfterPassedEnabled());
7170        $newObj->setPassScoring($this->getPassScoring());
7171        $newObj->setPasswordEnabled($this->isPasswordEnabled());
7172        $newObj->setPassword($this->getPassword());
7173        $newObj->setProcessingTime($this->getProcessingTime());
7174        $newObj->setQuestionSetType($this->getQuestionSetType());
7175        $newObj->setReportingDate($this->getReportingDate());
7176        $newObj->setResetProcessingTime($this->getResetProcessingTime());
7177        $newObj->setResultsPresentation($this->getResultsPresentation());
7178        $newObj->setScoreCutting($this->getScoreCutting());
7179        $newObj->setScoreReporting($this->getScoreReporting());
7180        $newObj->setSequenceSettings($this->getSequenceSettings());
7181        $newObj->setShowCancel($this->getShowCancel());
7182        $newObj->setShowMarker($this->getShowMarker());
7183        $newObj->setShuffleQuestions($this->getShuffleQuestions());
7184        $newObj->setStartingTimeEnabled($this->isStartingTimeEnabled());
7185        $newObj->setStartingTime($this->getStartingTime());
7186        $newObj->setTitleOutput($this->getTitleOutput());
7187        $newObj->setUsePreviousAnswers($this->getUsePreviousAnswers());
7188        $newObj->setRedirectionMode($this->getRedirectionMode());
7189        $newObj->setRedirectionUrl($this->getRedirectionUrl());
7190        $newObj->setCertificateVisibility($this->getCertificateVisibility());
7191        $newObj->mark_schema = clone $this->mark_schema;
7192        $newObj->setEnabledViewMode($this->getEnabledViewMode());
7193        $newObj->setTemplate($this->getTemplate());
7194        $newObj->setPoolUsage($this->getPoolUsage());
7195        $newObj->setPrintBestSolutionWithResult($this->isBestSolutionPrintedWithResult());
7196        $newObj->setShowExamIdInTestPassEnabled($this->isShowExamIdInTestPassEnabled());
7197        $newObj->setShowExamIdInTestResultsEnabled($this->isShowExamIdInTestResultsEnabled());
7198        $newObj->setEnableExamView($this->getEnableExamview());
7199        $newObj->setShowExamViewHtml($this->getShowExamviewHtml());
7200        $newObj->setShowExamViewPdf($this->getShowExamviewPdf());
7201        $newObj->setEnableArchiving($this->getEnableArchiving());
7202        $newObj->setSignSubmission($this->getSignSubmission());
7203        $newObj->setCharSelectorAvailability((int) $this->getCharSelectorAvailability());
7204        $newObj->setCharSelectorDefinition($this->getCharSelectorDefinition());
7205        $newObj->setSkillServiceEnabled($this->isSkillServiceEnabled());
7206        $newObj->setResultFilterTaxIds($this->getResultFilterTaxIds());
7207        $newObj->setFollowupQuestionAnswerFixationEnabled($this->isFollowupQuestionAnswerFixationEnabled());
7208        $newObj->setInstantFeedbackAnswerFixationEnabled($this->isInstantFeedbackAnswerFixationEnabled());
7209        $newObj->setForceInstantFeedbackEnabled($this->isForceInstantFeedbackEnabled());
7210        $newObj->setAutosave($this->getAutosave());
7211        $newObj->setAutosaveIval($this->getAutosaveIval());
7212        $newObj->setOfferingQuestionHintsEnabled($this->isOfferingQuestionHintsEnabled());
7213        $newObj->setSpecificAnswerFeedback($this->getSpecificAnswerFeedback());
7214        if ($this->isPassWaitingEnabled()) {
7215            $newObj->setPassWaiting($this->getPassWaiting());
7216        }
7217        $newObj->setObligationsEnabled($this->areObligationsEnabled());
7218        $newObj->saveToDb();
7219
7220        // clone certificate
7221        $pathFactory = new ilCertificatePathFactory();
7222        $templateRepository = new ilCertificateTemplateRepository($ilDB);
7223
7224        $cloneAction = new ilCertificateCloneAction(
7225            $ilDB,
7226            $pathFactory,
7227            $templateRepository,
7228            $DIC->filesystem()->web(),
7229            $certificateLogger,
7230            new ilCertificateObjectHelper()
7231        );
7232
7233        $cloneAction->cloneCertificate($this, $newObj);
7234
7235        $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $this);
7236        $testQuestionSetConfigFactory->getQuestionSetConfig()->cloneQuestionSetRelatedData($newObj);
7237
7238        require_once 'Modules/Test/classes/class.ilTestSkillLevelThresholdList.php';
7239        $skillLevelThresholdList = new ilTestSkillLevelThresholdList($ilDB);
7240        $skillLevelThresholdList->setTestId($this->getTestId());
7241        $skillLevelThresholdList->loadFromDb();
7242        $skillLevelThresholdList->cloneListForTest($newObj->getTestId());
7243
7244        $newObj->saveToDb();
7245        $newObj->updateMetaData();// #14467
7246
7247        include_once('./Services/Tracking/classes/class.ilLPObjSettings.php');
7248        $obj_settings = new ilLPObjSettings($this->getId());
7249        $obj_settings->cloneSettings($newObj->getId());
7250
7251        return $newObj;
7252    }
7253
7254    /**
7255    * Returns the number of questions in the test
7256    *
7257    * @return integer The number of questions
7258    * @access	public
7259    */
7260    public function getQuestionCount()
7261    {
7262        $num = 0;
7263
7264        if ($this->isRandomTest()) {
7265            global $DIC;
7266            $tree = $DIC['tree'];
7267            $ilDB = $DIC['ilDB'];
7268            $ilPluginAdmin = $DIC['ilPluginAdmin'];
7269
7270            $questionSetConfig = new ilTestRandomQuestionSetConfig(
7271                $tree,
7272                $ilDB,
7273                $ilPluginAdmin,
7274                $this
7275            );
7276
7277            $questionSetConfig->loadFromDb();
7278
7279            if ($questionSetConfig->isQuestionAmountConfigurationModePerPool()) {
7280                require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionList.php';
7281                require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetBuilderWithAmountPerPool.php';
7282                require_once 'Modules/Test/classes/class.ilTestRandomQuestionSetSourcePoolDefinitionFactory.php';
7283
7284                $sourcePoolDefinitionList = new ilTestRandomQuestionSetSourcePoolDefinitionList(
7285                    $ilDB,
7286                    $this,
7287                    new ilTestRandomQuestionSetSourcePoolDefinitionFactory($ilDB, $this)
7288                );
7289
7290                $sourcePoolDefinitionList->loadDefinitions();
7291
7292                $num = $sourcePoolDefinitionList->getQuestionAmount();
7293            } else {
7294                $num = $questionSetConfig->getQuestionAmountPerTest();
7295            }
7296        } else {
7297            $num = count($this->questions);
7298        }
7299
7300        return $num;
7301    }
7302
7303    /**
7304    * Logs an action into the Test&Assessment log
7305    *
7306    * @param string $logtext The log text
7307    * @param integer $question_id If given, saves the question id to the database
7308    * @access public
7309    */
7310    public function logAction($logtext = "", $question_id = "")
7311    {
7312        global $DIC;
7313        $ilUser = $DIC['ilUser'];
7314
7315        $original_id = "";
7316        if (strcmp($question_id, "") != 0) {
7317            include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
7318            $original_id = assQuestion::_getOriginalId($question_id);
7319        }
7320        include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
7321        ilObjAssessmentFolder::_addLog($ilUser->getId(), $this->getId(), $logtext, $question_id, $original_id, true, $this->getRefId());
7322    }
7323
7324    /**
7325    * Returns the ILIAS test object id for a given test id
7326    *
7327    * @param integer $test_id The test id
7328    * @return mixed The ILIAS test object id or FALSE if the query was not successful
7329    * @access public
7330    */
7331    public static function _getObjectIDFromTestID($test_id)
7332    {
7333        global $DIC;
7334        $ilDB = $DIC['ilDB'];
7335        $object_id = false;
7336        $result = $ilDB->queryF(
7337            "SELECT obj_fi FROM tst_tests WHERE test_id = %s",
7338            array('integer'),
7339            array($test_id)
7340        );
7341        if ($result->numRows()) {
7342            $row = $ilDB->fetchAssoc($result);
7343            $object_id = $row["obj_fi"];
7344        }
7345        return $object_id;
7346    }
7347
7348    /**
7349    * Returns the ILIAS test object id for a given active id
7350    *
7351    * @param integer $active_id The active id
7352    * @return mixed The ILIAS test object id or FALSE if the query was not successful
7353    * @access public
7354    */
7355    public static function _getObjectIDFromActiveID($active_id)
7356    {
7357        global $DIC;
7358        $ilDB = $DIC['ilDB'];
7359        $object_id = false;
7360        $result = $ilDB->queryF(
7361            "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",
7362            array('integer'),
7363            array($active_id)
7364        );
7365        if ($result->numRows()) {
7366            $row = $ilDB->fetchAssoc($result);
7367            $object_id = $row["obj_fi"];
7368        }
7369        return $object_id;
7370    }
7371
7372    /**
7373    * Returns the ILIAS test id for a given object id
7374    *
7375    * @param integer $object_id The object id
7376    * @return mixed The ILIAS test id or FALSE if the query was not successful
7377    * @access public
7378    */
7379    public static function _getTestIDFromObjectID($object_id)
7380    {
7381        global $DIC;
7382        $ilDB = $DIC['ilDB'];
7383        $test_id = false;
7384        $result = $ilDB->queryF(
7385            "SELECT test_id FROM tst_tests WHERE obj_fi = %s",
7386            array('integer'),
7387            array($object_id)
7388        );
7389        if ($result->numRows()) {
7390            $row = $ilDB->fetchAssoc($result);
7391            $test_id = $row["test_id"];
7392        }
7393        return $test_id;
7394    }
7395
7396    /**
7397    * Returns the text answer of a given user for a given question
7398    *
7399    * @param integer $user_id The user id
7400    * @param integer $question_id The question id
7401    * @return string The answer text
7402    * @access public
7403    */
7404    public function getTextAnswer($active_id, $question_id, $pass = null)
7405    {
7406        global $DIC;
7407        $ilDB = $DIC['ilDB'];
7408
7409        $res = "";
7410        if (($active_id) && ($question_id)) {
7411            if (is_null($pass)) {
7412                include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
7413                $pass = assQuestion::_getSolutionMaxPass($question_id, $active_id);
7414            }
7415            $result = $ilDB->queryF(
7416                "SELECT value1 FROM tst_solutions WHERE active_fi = %s AND question_fi = %s AND pass = %s",
7417                array('integer', 'integer', 'integer'),
7418                array($active_id, $question_id, $pass)
7419            );
7420            if ($result->numRows() == 1) {
7421                $row = $ilDB->fetchAssoc($result);
7422                $res = $row["value1"];
7423            }
7424        }
7425        return $res;
7426    }
7427
7428    /**
7429    * Returns the question text for a given question
7430    *
7431    * @param integer $question_id The question id
7432    * @return string The question text
7433    * @access public
7434    */
7435    public function getQuestiontext($question_id)
7436    {
7437        global $DIC;
7438        $ilDB = $DIC['ilDB'];
7439
7440        $res = "";
7441        if ($question_id) {
7442            $result = $ilDB->queryF(
7443                "SELECT question_text FROM qpl_questions WHERE question_id = %s",
7444                array('integer'),
7445                array($question_id)
7446            );
7447            if ($result->numRows() == 1) {
7448                $row = $ilDB->fetchAssoc($result);
7449                $res = $row["question_text"];
7450            }
7451        }
7452        return $res;
7453    }
7454
7455    /**
7456     * @return ilTestParticipantList
7457     */
7458    public function getInvitedParticipantList()
7459    {
7460        require_once 'Modules/Test/classes/class.ilTestParticipantList.php';
7461        $participantList = new ilTestParticipantList($this);
7462        $participantList->initializeFromDbRows($this->getInvitedUsers());
7463
7464        return $participantList;
7465    }
7466
7467    /**
7468     * @return ilTestParticipantList
7469     */
7470    public function getActiveParticipantList()
7471    {
7472        require_once 'Modules/Test/classes/class.ilTestParticipantList.php';
7473        $participantList = new ilTestParticipantList($this);
7474        $participantList->initializeFromDbRows($this->getTestParticipants());
7475
7476        return $participantList;
7477    }
7478
7479    /**
7480    * Returns a list of all invited users in a test
7481    *
7482    * @return array array of invited users
7483    * @access public
7484    */
7485    public function &getInvitedUsers($user_id = "", $order = "login, lastname, firstname")
7486    {
7487        global $DIC;
7488        $ilDB = $DIC['ilDB'];
7489
7490        $result_array = array();
7491
7492        if ($this->getAnonymity()) {
7493            if (is_numeric($user_id)) {
7494                $result = $ilDB->queryF(
7495                    "SELECT tst_active.active_id, tst_active.tries, usr_id, %s login, %s lastname, %s firstname, tst_invited_user.clientip, " .
7496                    "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 " .
7497                    "LEFT JOIN tst_active ON tst_active.user_fi = tst_invited_user.user_fi AND tst_active.test_fi = tst_invited_user.test_fi " .
7498                    "WHERE tst_invited_user.test_fi = %s and tst_invited_user.user_fi=usr_data.usr_id AND usr_data.usr_id=%s " .
7499                    "ORDER BY $order",
7500                    array('text', 'text', 'text', 'integer', 'integer'),
7501                    array("", $this->lng->txt("anonymous"), "", $this->getTestId(), $user_id)
7502                );
7503            } else {
7504                $result = $ilDB->queryF(
7505                    "SELECT tst_active.active_id, usr_id, %s login, %s lastname, %s firstname, tst_invited_user.clientip, " .
7506                    "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 " .
7507                    "LEFT JOIN tst_active ON tst_active.user_fi = tst_invited_user.user_fi AND tst_active.test_fi = tst_invited_user.test_fi " .
7508                    "WHERE tst_invited_user.test_fi = %s and tst_invited_user.user_fi=usr_data.usr_id " .
7509                    "ORDER BY $order",
7510                    array('text', 'text', 'text', 'integer'),
7511                    array("", $this->lng->txt("anonymous"), "", $this->getTestId())
7512                );
7513            }
7514        } else {
7515            if (is_numeric($user_id)) {
7516                $result = $ilDB->queryF(
7517                    "SELECT tst_active.active_id, tst_active.tries, usr_id, login, lastname, firstname, tst_invited_user.clientip, " .
7518                    "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 " .
7519                    "LEFT JOIN tst_active ON tst_active.user_fi = tst_invited_user.user_fi AND tst_active.test_fi = tst_invited_user.test_fi " .
7520                    "WHERE tst_invited_user.test_fi = %s and tst_invited_user.user_fi=usr_data.usr_id AND usr_data.usr_id=%s " .
7521                    "ORDER BY $order",
7522                    array('integer', 'integer'),
7523                    array($this->getTestId(), $user_id)
7524                );
7525            } else {
7526                $result = $ilDB->queryF(
7527                    "SELECT tst_active.active_id, tst_active.tries, usr_id, login, lastname, firstname, tst_invited_user.clientip, " .
7528                    "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 " .
7529                    "LEFT JOIN tst_active ON tst_active.user_fi = tst_invited_user.user_fi AND tst_active.test_fi = tst_invited_user.test_fi " .
7530                    "WHERE tst_invited_user.test_fi = %s and tst_invited_user.user_fi=usr_data.usr_id " .
7531                    "ORDER BY $order",
7532                    array('integer'),
7533                    array($this->getTestId())
7534                );
7535            }
7536        }
7537        $result_array = array();
7538        while ($row = $ilDB->fetchAssoc($result)) {
7539            $result_array[$row['usr_id']] = $row;
7540        }
7541        return $result_array;
7542    }
7543
7544    /**
7545    * Returns a list of all participants in a test
7546    *
7547    * @return array The user id's of the participants
7548    * @access public
7549    */
7550    public function &getTestParticipants()
7551    {
7552        global $DIC;
7553        $ilDB = $DIC['ilDB'];
7554
7555        if ($this->getAnonymity()) {
7556            $query = "
7557				SELECT	tst_active.active_id,
7558						tst_active.tries,
7559						tst_active.user_fi usr_id,
7560						%s login,
7561						%s lastname,
7562						%s firstname,
7563						tst_active.submitted test_finished,
7564						usr_data.matriculation,
7565						usr_data.active,
7566						tst_active.lastindex,
7567						COALESCE(tst_active.last_finished_pass, -1) <> tst_active.last_started_pass unfinished_passes
7568				FROM tst_active
7569				LEFT JOIN usr_data
7570				ON tst_active.user_fi = usr_data.usr_id
7571				WHERE tst_active.test_fi = %s
7572				ORDER BY usr_data.lastname
7573			";
7574            $result = $ilDB->queryF(
7575                $query,
7576                array('text', 'text', 'text', 'integer'),
7577                array("", $this->lng->txt("anonymous"), "", $this->getTestId())
7578            );
7579        } else {
7580            $query = "
7581				SELECT	tst_active.active_id,
7582						tst_active.tries,
7583						tst_active.user_fi usr_id,
7584						usr_data.login,
7585						usr_data.lastname,
7586						usr_data.firstname,
7587						tst_active.submitted test_finished,
7588						usr_data.matriculation,
7589						usr_data.active,
7590						tst_active.lastindex,
7591						COALESCE(tst_active.last_finished_pass, -1) <> tst_active.last_started_pass unfinished_passes
7592				FROM tst_active
7593				LEFT JOIN usr_data
7594				ON tst_active.user_fi = usr_data.usr_id
7595				WHERE tst_active.test_fi = %s
7596				ORDER BY usr_data.lastname
7597			";
7598            $result = $ilDB->queryF(
7599                $query,
7600                array('integer'),
7601                array($this->getTestId())
7602            );
7603        }
7604        $data = array();
7605        while ($row = $ilDB->fetchAssoc($result)) {
7606            $data[$row['active_id']] = $row;
7607        }
7608        foreach ($data as $index => $participant) {
7609            if (strlen(trim($participant["firstname"] . $participant["lastname"])) == 0) {
7610                $data[$index]["lastname"] = $this->lng->txt("deleted_user");
7611            }
7612        }
7613        return $data;
7614    }
7615
7616    public function getTestParticipantsForManualScoring($filter = null)
7617    {
7618        global $DIC;
7619        $ilDB = $DIC['ilDB'];
7620
7621        include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
7622        $scoring = ilObjAssessmentFolder::_getManualScoring();
7623        if (count($scoring) == 0) {
7624            return array();
7625        }
7626
7627        $participants = &$this->getTestParticipants();
7628        $filtered_participants = array();
7629        foreach ($participants as $active_id => $participant) {
7630            $qstType_IN_manScoreableQstTypes = $ilDB->in('qpl_questions.question_type_fi', $scoring, false, 'integer');
7631
7632            $queryString = "
7633				SELECT		tst_test_result.manual
7634
7635				FROM		tst_test_result
7636
7637				INNER JOIN	qpl_questions
7638				ON			tst_test_result.question_fi = qpl_questions.question_id
7639
7640				WHERE		tst_test_result.active_fi = %s
7641				AND			$qstType_IN_manScoreableQstTypes
7642			";
7643
7644            $result = $ilDB->queryF(
7645                $queryString,
7646                array("integer"),
7647                array($active_id)
7648            );
7649
7650            $count = $result->numRows();
7651
7652            if ($count > 0) {
7653                switch ($filter) {
7654                    case 1: // only active users
7655                        if ($participant->active) {
7656                            $filtered_participants[$active_id] = $participant;
7657                        }
7658                        break;
7659                    case 2: // only inactive users
7660                        if (!$participant->active) {
7661                            $filtered_participants[$active_id] = $participant;
7662                        }
7663                        break;
7664                    case 3: // all users
7665                        $filtered_participants[$active_id] = $participant;
7666                        break;
7667                    case 4:
7668                        // already scored participants
7669                        //$found = 0;
7670                        //while ($row = $ilDB->fetchAssoc($result))
7671                        //{
7672                        //	if ($row["manual"]) $found++;
7673                        //}
7674                        //if ($found == $count)
7675                        //{
7676                            //$filtered_participants[$active_id] = $participant;
7677                        //}
7678                        //else
7679                        //{
7680                            $assessmentSetting = new ilSetting("assessment");
7681                            $manscoring_done = $assessmentSetting->get("manscoring_done_" . $active_id);
7682                            if ($manscoring_done) {
7683                                $filtered_participants[$active_id] = $participant;
7684                            }
7685                        //}
7686                        break;
7687                    case 5:
7688                        // unscored participants
7689                        //$found = 0;
7690                        //while ($row = $ilDB->fetchAssoc($result))
7691                        //{
7692                        //	if ($row["manual"]) $found++;
7693                        //}
7694                        //if ($found == 0)
7695                        //{
7696                            $assessmentSetting = new ilSetting("assessment");
7697                            $manscoring_done = $assessmentSetting->get("manscoring_done_" . $active_id);
7698                            if (!$manscoring_done) {
7699                                $filtered_participants[$active_id] = $participant;
7700                            }
7701                        //}
7702                        break;
7703                    case 6:
7704                        // partially scored participants
7705                        $found = 0;
7706                        while ($row = $ilDB->fetchAssoc($result)) {
7707                            if ($row["manual"]) {
7708                                $found++;
7709                            }
7710                        }
7711                        if (($found > 0) && ($found < $count)) {
7712                            $filtered_participants[$active_id] = $participant;
7713                        }
7714                        break;
7715                    default:
7716                        $filtered_participants[$active_id] = $participant;
7717                        break;
7718                }
7719            }
7720        }
7721        return $filtered_participants;
7722    }
7723
7724    /**
7725    * Returns a data of all users specified by id list
7726    *
7727    * @param $usr_ids kommaseparated list of ids
7728    * @return array The user data "usr_id, login, lastname, firstname, clientip" of the users with id as key
7729    * @access public
7730    */
7731    public function &getUserData($ids)
7732    {
7733        global $DIC;
7734        $ilDB = $DIC['ilDB'];
7735
7736        if (!is_array($ids) || count($ids) == 0) {
7737            return array();
7738        }
7739
7740        if ($this->getAnonymity()) {
7741            $result = $ilDB->queryF(
7742                "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",
7743                array('text', 'text', 'text'),
7744                array("", $this->lng->txt("anonymous"), "")
7745            );
7746        } else {
7747            $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");
7748        }
7749
7750        $result_array = array();
7751        while ($row = $ilDB->fetchAssoc($result)) {
7752            $result_array[$row["usr_id"]] = $row;
7753        }
7754        return $result_array;
7755    }
7756
7757    public function &getGroupData($ids)
7758    {
7759        if (!is_array($ids) || count($ids) == 0) {
7760            return array();
7761        }
7762        $result = array();
7763        foreach ($ids as $ref_id) {
7764            $obj_id = ilObject::_lookupObjId($ref_id);
7765            $result[$ref_id] = array("ref_id" => $ref_id, "title" => ilObject::_lookupTitle($obj_id), "description" => ilObject::_lookupDescription($obj_id));
7766        }
7767        return $result;
7768    }
7769
7770    public function &getRoleData($ids)
7771    {
7772        if (!is_array($ids) || count($ids) == 0) {
7773            return array();
7774        }
7775        $result = array();
7776        foreach ($ids as $obj_id) {
7777            $result[$obj_id] = array("obj_id" => $obj_id, "title" => ilObject::_lookupTitle($obj_id), "description" => ilObject::_lookupDescription($obj_id));
7778        }
7779        return $result;
7780    }
7781
7782
7783    /**
7784    * Invites all users of a group to a test
7785    *
7786    * @param integer $group_id The database id of the invited group
7787    * @access public
7788    */
7789    public function inviteGroup($group_id)
7790    {
7791        include_once "./Modules/Group/classes/class.ilObjGroup.php";
7792        $group = new ilObjGroup($group_id);
7793        $members = $group->getGroupMemberIds();
7794        include_once './Services/User/classes/class.ilObjUser.php';
7795        foreach ($members as $user_id) {
7796            $this->inviteUser($user_id, ilObjUser::_lookupClientIP($user_id));
7797        }
7798    }
7799
7800    /**
7801    * Invites all users of a role to a test
7802    *
7803    * @param integer $group_id The database id of the invited group
7804    * @access public
7805    */
7806    public function inviteRole($role_id)
7807    {
7808        global $DIC;
7809        $rbacreview = $DIC['rbacreview'];
7810        $members = $rbacreview->assignedUsers($role_id);
7811        include_once './Services/User/classes/class.ilObjUser.php';
7812        foreach ($members as $user_id) {
7813            $this->inviteUser($user_id, ilObjUser::_lookupClientIP($user_id));
7814        }
7815    }
7816
7817
7818
7819    /**
7820    * Disinvites a user from a test
7821    *
7822    * @param integer $user_id The database id of the disinvited user
7823    * @access public
7824    */
7825    public function disinviteUser($user_id)
7826    {
7827        global $DIC;
7828        $ilDB = $DIC['ilDB'];
7829
7830        $affectedRows = $ilDB->manipulateF(
7831            "DELETE FROM tst_invited_user WHERE test_fi = %s AND user_fi = %s",
7832            array('integer', 'integer'),
7833            array($this->getTestId(), $user_id)
7834        );
7835    }
7836
7837    /**
7838    * Invites a user to a test
7839    *
7840    * @param integer $user_id The database id of the invited user
7841    * @access public
7842    */
7843    public function inviteUser($user_id, $client_ip = "")
7844    {
7845        global $DIC;
7846        $ilDB = $DIC['ilDB'];
7847
7848        $affectedRows = $ilDB->manipulateF(
7849            "DELETE FROM tst_invited_user WHERE test_fi = %s AND user_fi = %s",
7850            array('integer', 'integer'),
7851            array($this->getTestId(), $user_id)
7852        );
7853        $affectedRows = $ilDB->manipulateF(
7854            "INSERT INTO tst_invited_user (test_fi, user_fi, clientip, tstamp) VALUES (%s, %s, %s, %s)",
7855            array('integer', 'integer', 'text', 'integer'),
7856            array($this->getTestId(), $user_id, (strlen($client_ip)) ? $client_ip : null, time())
7857        );
7858    }
7859
7860
7861    public function setClientIP($user_id, $client_ip)
7862    {
7863        global $DIC;
7864        $ilDB = $DIC['ilDB'];
7865
7866        $affectedRows = $ilDB->manipulateF(
7867            "UPDATE tst_invited_user SET clientip = %s, tstamp = %s WHERE test_fi=%s and user_fi=%s",
7868            array('text', 'integer', 'integer', 'integer'),
7869            array((strlen($client_ip)) ? $client_ip : null, time(), $this->getTestId(), $user_id)
7870        );
7871    }
7872
7873    /**
7874     * get solved questions
7875     *
7876     * @return array of int containing all question ids which have been set solved for the given user and test
7877     */
7878    public static function _getSolvedQuestions($active_id, $question_fi = null)
7879    {
7880        global $DIC;
7881        $ilDB = $DIC['ilDB'];
7882        if (is_numeric($question_fi)) {
7883            $result = $ilDB->queryF(
7884                "SELECT question_fi, solved FROM tst_qst_solved WHERE active_fi = %s AND question_fi=%s",
7885                array('integer', 'integer'),
7886                array($active_id, $question_fi)
7887            );
7888        } else {
7889            $result = $ilDB->queryF(
7890                "SELECT question_fi, solved FROM tst_qst_solved WHERE active_fi = %s",
7891                array('integer'),
7892                array($active_id)
7893            );
7894        }
7895        $result_array = array();
7896        while ($row = $ilDB->fetchAssoc($result)) {
7897            $result_array[$row["question_fi"]] = $row;
7898        }
7899        return $result_array;
7900    }
7901
7902
7903    /**
7904     * sets question solved state to value for given user_id
7905     */
7906    public function setQuestionSetSolved($value, $question_id, $user_id)
7907    {
7908        global $DIC;
7909        $ilDB = $DIC['ilDB'];
7910
7911        $active_id = $this->getActiveIdOfUser($user_id);
7912        $affectedRows = $ilDB->manipulateF(
7913            "DELETE FROM tst_qst_solved WHERE active_fi = %s AND question_fi = %s",
7914            array('integer', 'integer'),
7915            array($active_id, $question_id)
7916        );
7917        $affectedRows = $ilDB->manipulateF(
7918            "INSERT INTO tst_qst_solved (solved, question_fi, active_fi) VALUES (%s, %s, %s)",
7919            array('integer', 'integer', 'integer'),
7920            array($value, $question_id, $active_id)
7921        );
7922    }
7923
7924    /**
7925     * returns if the active for user_id has been submitted
7926     */
7927    public function isTestFinished($active_id)
7928    {
7929        global $DIC;
7930        $ilDB = $DIC['ilDB'];
7931
7932        $result = $ilDB->queryF(
7933            "SELECT submitted FROM tst_active WHERE active_id=%s AND submitted=%s",
7934            array('integer', 'integer'),
7935            array($active_id, 1)
7936        );
7937        return $result->numRows() == 1;
7938    }
7939
7940    /**
7941     * returns if the active for user_id has been submitted
7942     */
7943    public function isActiveTestSubmitted($user_id = null)
7944    {
7945        global $DIC;
7946        $ilUser = $DIC['ilUser'];
7947        $ilDB = $DIC['ilDB'];
7948
7949        if (!is_numeric($user_id)) {
7950            $user_id = $ilUser->getId();
7951        }
7952
7953        $result = $ilDB->queryF(
7954            "SELECT submitted FROM tst_active WHERE test_fi=%s AND user_fi=%s AND submitted=%s",
7955            array('integer', 'integer', 'integer'),
7956            array($this->getTestId(), $user_id, 1)
7957        );
7958        return $result->numRows() == 1;
7959    }
7960
7961    /**
7962     * returns if the numbers of tries have to be checked
7963     */
7964    public function hasNrOfTriesRestriction()
7965    {
7966        return $this->getNrOfTries() != 0;
7967    }
7968
7969
7970    /**
7971     * returns if number of tries are reached
7972     * @deprecated: tries field differs per situation, outside a pass it's the number of tries, inside a pass it's the current pass number.
7973     */
7974
7975    public function isNrOfTriesReached($tries)
7976    {
7977        return $tries >= (int) $this->getNrOfTries();
7978    }
7979
7980
7981    /**
7982     * returns all test results for all participants
7983     *
7984     * @param array $partipants array of user ids
7985     * @param boolean if true, the result will be prepared for csv output (see processCSVRow)
7986     *
7987     * @return array of fields, see code for column titles
7988     */
7989    public function getAllTestResults($participants, $prepareForCSV = true)
7990    {
7991        $results = array();
7992        $row = array(
7993            "user_id" => $this->lng->txt("user_id"),
7994            "matriculation" => $this->lng->txt("matriculation"),
7995            "lastname" => $this->lng->txt("lastname"),
7996            "firstname" => $this->lng->txt("firstname"),
7997            "login" => $this->lng->txt("login"),
7998            "reached_points" => $this->lng->txt("tst_reached_points"),
7999            "max_points" => $this->lng->txt("tst_maximum_points"),
8000            "percent_value" => $this->lng->txt("tst_percent_solved"),
8001            "mark" => $this->lng->txt("tst_mark"),
8002            "ects" => $this->lng->txt("ects_grade")
8003        );
8004        $results[] = $row;
8005        if (count($participants)) {
8006            if ($this->getECTSOutput()) {
8007                $passed_array = &$this->getTotalPointsPassedArray();
8008            }
8009            foreach ($participants as $active_id => $user_rec) {
8010                $mark = $ects_mark = '';
8011                $row = array();
8012                $reached_points = 0;
8013                $max_points = 0;
8014                foreach ($this->questions as $value) {
8015                    $question = &ilObjTest::_instanciateQuestion($value);
8016                    if (is_object($question)) {
8017                        $max_points += $question->getMaximumPoints();
8018                        $reached_points += $question->getReachedPoints($active_id);
8019                    }
8020                }
8021                if ($max_points > 0) {
8022                    $percentvalue = $reached_points / $max_points;
8023                    if ($percentvalue < 0) {
8024                        $percentvalue = 0.0;
8025                    }
8026                } else {
8027                    $percentvalue = 0;
8028                }
8029                $mark_obj = $this->mark_schema->getMatchingMark($percentvalue * 100);
8030                $passed = "";
8031                if ($mark_obj) {
8032                    $mark = $mark_obj->getOfficialName();
8033                    if ($this->getECTSOutput()) {
8034                        $ects_mark = $this->getECTSGrade($passed_array, $reached_points, $max_points);
8035                    }
8036                }
8037                if ($this->getAnonymity()) {
8038                    $user_rec['firstname'] = "";
8039                    $user_rec['lastname'] = $this->lng->txt("anonymous");
8040                }
8041                $row = array(
8042                    "user_id" => $user_rec['usr_id'],
8043                    "matriculation" => $user_rec['matriculation'],
8044                    "lastname" => $user_rec['lastname'],
8045                    "firstname" => $user_rec['firstname'],
8046                    "login" => $user_rec['login'],
8047                    "reached_points" => $reached_points,
8048                    "max_points" => $max_points,
8049                    "percent_value" => $percentvalue,
8050                    "mark" => $mark,
8051                    "ects" => $ects_mark
8052                );
8053                $results[] = $prepareForCSV ? $this->processCSVRow($row, true) : $row;
8054            }
8055        }
8056        return $results;
8057    }
8058
8059    /**
8060    * Processes an array as a CSV row and converts the array values to correct CSV
8061    * values. The "converted" array is returned
8062    *
8063    * @param array $row The array containing the values for a CSV row
8064    * @param string $quoteAll Indicates to quote every value (=TRUE) or only values containing quotes and separators (=FALSE, default)
8065    * @param string $separator The value separator in the CSV row (used for quoting) (; = default)
8066    * @return array The converted array ready for CSV use
8067    * @access public
8068    */
8069    public function &processCSVRow($row, $quoteAll = false, $separator = ";")
8070    {
8071        $resultarray = array();
8072        foreach ($row as $rowindex => $entry) {
8073            $surround = false;
8074            if ($quoteAll) {
8075                $surround = true;
8076            }
8077            if (strpos($entry, "\"") !== false) {
8078                $entry = str_replace("\"", "\"\"", $entry);
8079                $surround = true;
8080            }
8081            if (strpos($entry, $separator) !== false) {
8082                $surround = true;
8083            }
8084            // replace all CR LF with LF (for Excel for Windows compatibility
8085            $entry = str_replace(chr(13) . chr(10), chr(10), $entry);
8086
8087            if ($surround) {
8088                $entry = "\"" . $entry . "\"";
8089            }
8090
8091            $resultarray[$rowindex] = $entry;
8092        }
8093        return $resultarray;
8094    }
8095
8096    /**
8097    * Retrieves the actual pass of a given user for a given test
8098    *
8099    * @param integer $user_id The user id
8100    * @param integer $test_id The test id
8101    * @return integer The pass of the user for the given test
8102    * @access public
8103    */
8104    public static function _getPass($active_id)
8105    {
8106        global $DIC;
8107        $ilDB = $DIC['ilDB'];
8108        $result = $ilDB->queryF(
8109            "SELECT tries FROM tst_active WHERE active_id = %s",
8110            array('integer'),
8111            array($active_id)
8112        );
8113        if ($result->numRows()) {
8114            $row = $ilDB->fetchAssoc($result);
8115            return $row["tries"];
8116        } else {
8117            return 0;
8118        }
8119    }
8120
8121    /**
8122    * Retrieves the maximum pass of a given user for a given test
8123    * in which the user answered at least one question
8124    *
8125    * @param integer $user_id The user id
8126    * @param integer $test_id The test id
8127    * @return integer The pass of the user for the given test
8128    * @access public
8129    */
8130    public static function _getMaxPass($active_id)
8131    {
8132        global $DIC;
8133        $ilDB = $DIC['ilDB'];
8134        $result = $ilDB->queryF(
8135            "SELECT MAX(pass) maxpass FROM tst_pass_result WHERE active_fi = %s",
8136            array('integer'),
8137            array($active_id)
8138            );
8139        if ($result->numRows()) {
8140            $row = $ilDB->fetchAssoc($result);
8141            $max = $row["maxpass"];
8142        } else {
8143            $max = null;
8144        }
8145        return $max;
8146    }
8147
8148    /**
8149     * Retrieves the best pass of a given user for a given test
8150     * @param int $active_id
8151     * @return int|mixed
8152     */
8153    public static function _getBestPass($active_id)
8154    {
8155        global $DIC;
8156        $ilDB = $DIC['ilDB'];
8157
8158        $result = $ilDB->queryF(
8159            "SELECT * FROM tst_pass_result WHERE active_fi = %s",
8160            array('integer'),
8161            array($active_id)
8162        );
8163        if ($result->numRows()) {
8164            $bestrow = null;
8165            $bestfactor = 0;
8166            while ($row = $ilDB->fetchAssoc($result)) {
8167                if ($row["maxpoints"] > 0) {
8168                    $factor = $row["points"] / $row["maxpoints"];
8169                } else {
8170                    $factor = 0;
8171                }
8172
8173                if ($factor > $bestfactor) {
8174                    $bestrow = $row;
8175                    $bestfactor = $factor;
8176                }
8177            }
8178            if (is_array($bestrow)) {
8179                return $bestrow["pass"];
8180            } else {
8181                return 0;
8182            }
8183        } else {
8184            return 0;
8185        }
8186    }
8187
8188    /**
8189    * Retrieves the pass number that should be counted for a given user
8190    *
8191    * @param integer $user_id The user id
8192    * @param integer $test_id The test id
8193    * @return integer The result pass of the user for the given test
8194    * @access public
8195    */
8196    public static function _getResultPass($active_id)
8197    {
8198        $counted_pass = null;
8199        if (ilObjTest::_getPassScoring($active_id) == SCORE_BEST_PASS) {
8200            $counted_pass = ilObjTest::_getBestPass($active_id);
8201        } else {
8202            $counted_pass = ilObjTest::_getMaxPass($active_id);
8203        }
8204        return $counted_pass;
8205    }
8206
8207    /**
8208    * Retrieves the number of answered questions for a given user in a given test
8209    *
8210    * @param integer $user_id The user id
8211    * @param integer $test_id The test id
8212    * @param integer $pass The pass of the test (optional)
8213    * @return integer The number of answered questions
8214    * @access public
8215    */
8216    public function getAnsweredQuestionCount($active_id, $pass = null)
8217    {
8218        if ($this->isDynamicTest()) {
8219            global $DIC;
8220            $tree = $DIC['tree'];
8221            $ilDB = $DIC['ilDB'];
8222            $lng = $DIC['lng'];
8223            $ilPluginAdmin = $DIC['ilPluginAdmin'];
8224
8225            require_once 'Modules/Test/classes/class.ilTestSessionFactory.php';
8226            $testSessionFactory = new ilTestSessionFactory($this);
8227            $testSession = $testSessionFactory->getSession($active_id);
8228
8229            require_once 'Modules/Test/classes/class.ilTestSequenceFactory.php';
8230            $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $ilPluginAdmin, $this);
8231            $testSequence = $testSequenceFactory->getSequenceByTestSession($testSession);
8232
8233            require_once 'Modules/Test/classes/class.ilObjTestDynamicQuestionSetConfig.php';
8234            $dynamicQuestionSetConfig = new ilObjTestDynamicQuestionSetConfig($tree, $ilDB, $ilPluginAdmin, $this);
8235            $dynamicQuestionSetConfig->loadFromDb();
8236
8237            $testSequence->loadFromDb($dynamicQuestionSetConfig);
8238            $testSequence->loadQuestions($dynamicQuestionSetConfig, new ilTestDynamicQuestionSetFilterSelection());
8239
8240            return $testSequence->getTrackedQuestionCount();
8241        }
8242
8243        if ($this->isRandomTest()) {
8244            $this->loadQuestions($active_id, $pass);
8245        }
8246        include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
8247        $workedthrough = 0;
8248        foreach ($this->questions as $value) {
8249            if (assQuestion::_isWorkedThrough($active_id, $value, $pass)) {
8250                $workedthrough += 1;
8251            }
8252        }
8253        return $workedthrough;
8254    }
8255
8256    /**
8257     * @param int $active_id
8258     * @param int $pass
8259     *
8260     * @return int
8261     */
8262    public static function lookupPassResultsUpdateTimestamp($active_id, $pass)
8263    {
8264        global $DIC;
8265        $ilDB = $DIC['ilDB'];
8266
8267        if (is_null($pass)) {
8268            $pass = 0;
8269        }
8270
8271        $query = "
8272			SELECT	tst_pass_result.tstamp pass_res_tstamp,
8273					tst_test_result.tstamp quest_res_tstamp
8274
8275			FROM tst_pass_result
8276
8277			LEFT JOIN tst_test_result
8278			ON tst_test_result.active_fi = tst_pass_result.active_fi
8279			AND tst_test_result.pass = tst_pass_result.pass
8280
8281			WHERE tst_pass_result.active_fi = %s
8282			AND tst_pass_result.pass = %s
8283
8284			ORDER BY tst_test_result.tstamp DESC
8285		";
8286
8287        $result = $ilDB->queryF(
8288            $query,
8289            array('integer', 'integer'),
8290            array($active_id, $pass)
8291        );
8292
8293        while ($row = $ilDB->fetchAssoc($result)) {
8294            if ($row['qres_tstamp']) {
8295                return $row['quest_res_tstamp'];
8296            }
8297
8298            return $row['pass_res_tstamp'];
8299        }
8300
8301        return 0;
8302    }
8303
8304    /**
8305     * Checks if the test is executable by the given user
8306     *
8307     * @param ilTestSession|ilTestSessionDynamicQuestionSet
8308     * @param integer $user_id The user id
8309     * @return array Result array
8310     * @access public
8311     */
8312    public function isExecutable($testSession, $user_id, $allowPassIncrease = false)
8313    {
8314        $result = array(
8315            "executable" => true,
8316            "errormessage" => ""
8317        );
8318        if (!$this->startingTimeReached()) {
8319            $result["executable"] = false;
8320            $result["errormessage"] = sprintf($this->lng->txt("detail_starting_time_not_reached"), ilDatePresentation::formatDate(new ilDateTime($this->getStartingTime(), IL_CAL_UNIX)));
8321            return $result;
8322        }
8323        if ($this->endingTimeReached()) {
8324            $result["executable"] = false;
8325            $result["errormessage"] = sprintf($this->lng->txt("detail_ending_time_reached"), ilDatePresentation::formatDate(new ilDateTime($this->getEndingTime(), IL_CAL_UNIX)));
8326            return $result;
8327        }
8328
8329        $active_id = $this->getActiveIdOfUser($user_id);
8330
8331        if ($this->getEnableProcessingTime()) {
8332            if ($active_id > 0) {
8333                $starting_time = $this->getStartingTimeOfUser($active_id);
8334                if ($starting_time !== false) {
8335                    if ($this->isMaxProcessingTimeReached($starting_time, $active_id)) {
8336                        if ($allowPassIncrease && $this->getResetProcessingTime() && (($this->getNrOfTries() == 0) || ($this->getNrOfTries() > (self::_getPass($active_id) + 1)))) {
8337                            // a test pass was quitted because the maximum processing time was reached, but the time
8338                            // will be resetted for future passes, so if there are more passes allowed, the participant may
8339                            // start the test again.
8340                            // This code block is only called when $allowPassIncrease is TRUE which only happens when
8341                            // the test info page is opened. Otherwise this will lead to unexpected results!
8342                            $testSession->increasePass();
8343                            $testSession->setLastSequence(0);
8344                            $testSession->saveToDb();
8345                        } else {
8346                            $result["executable"] = false;
8347                            $result["errormessage"] = $this->lng->txt("detail_max_processing_time_reached");
8348                        }
8349                        return $result;
8350                    }
8351                }
8352            }
8353        }
8354        global $DIC;
8355        require_once 'Modules/Test/classes/class.ilTestPassesSelector.php';
8356        $testPassesSelector = new ilTestPassesSelector($DIC['ilDB'], $this);
8357        $testPassesSelector->setActiveId($active_id);
8358        $testPassesSelector->setLastFinishedPass($testSession->getLastFinishedPass());
8359
8360        if ($this->hasNrOfTriesRestriction() && ($active_id > 0)) {
8361            $closedPasses = $testPassesSelector->getClosedPasses();
8362
8363            if (count($closedPasses) >= $this->getNrOfTries()) {
8364                $result["executable"] = false;
8365                $result["errormessage"] = $this->lng->txt("maximum_nr_of_tries_reached");
8366                return $result;
8367            }
8368
8369            if ($this->isBlockPassesAfterPassedEnabled() && !$testPassesSelector->openPassExists()) {
8370                if (ilObjTestAccess::_isPassed($user_id, $this->getId())) {
8371                    $result['executable'] = false;
8372                    $result['errormessage'] = $this->lng->txt("tst_addit_passes_blocked_after_passed_msg");
8373                    return $result;
8374                }
8375            }
8376        }
8377        if ($this->isPassWaitingEnabled() && $testPassesSelector->getLastFinishedPass() !== null) {
8378            $lastPass = $testPassesSelector->getLastFinishedPassTimestamp();
8379            if ($lastPass && strlen($this->getPassWaiting())) {
8380                $pass_waiting_string = $this->getPassWaiting();
8381                $time_values = explode(":", $pass_waiting_string);
8382                $next_pass_allowed = strtotime('+ ' . $time_values[0] . ' Months + ' . $time_values[1] . ' Days + ' . $time_values[2] . ' Hours' . $time_values[3] . ' Minutes', $lastPass);
8383
8384                if (time() < $next_pass_allowed) {
8385                    $date = ilDatePresentation::formatDate(new ilDateTime($next_pass_allowed, IL_CAL_UNIX));
8386
8387                    $result["executable"] = false;
8388                    $result["errormessage"] = sprintf($this->lng->txt('wait_for_next_pass_hint_msg'), $date);
8389                    return $result;
8390                }
8391            }
8392        }
8393        return $result;
8394    }
8395
8396
8397    public function canShowTestResults(ilTestSession $testSession)
8398    {
8399        global $DIC; /* @var ILIAS\DI\Container $DIC */
8400
8401        require_once 'Modules/Test/classes/class.ilTestPassesSelector.php';
8402        $passSelector = new ilTestPassesSelector($DIC->database(), $this);
8403
8404        $passSelector->setActiveId($testSession->getActiveId());
8405        $passSelector->setLastFinishedPass($testSession->getLastFinishedPass());
8406
8407        return $passSelector->hasReportablePasses();
8408    }
8409
8410    public function hasAnyTestResult(ilTestSession $testSession)
8411    {
8412        global $DIC; /* @var ILIAS\DI\Container $DIC */
8413
8414        require_once 'Modules/Test/classes/class.ilTestPassesSelector.php';
8415        $passSelector = new ilTestPassesSelector($DIC->database(), $this);
8416
8417        $passSelector->setActiveId($testSession->getActiveId());
8418        $passSelector->setLastFinishedPass($testSession->getLastFinishedPass());
8419
8420        return $passSelector->hasExistingPasses();
8421    }
8422
8423    /**
8424    * Returns the unix timestamp of the time a user started a test
8425    *
8426    * @param integer $active_id The active id of the user
8427    * @return mixed The unix timestamp if the user started the test, FALSE otherwise
8428    * @access public
8429    */
8430    public function getStartingTimeOfUser($active_id, $pass = null)
8431    {
8432        global $DIC;
8433        $ilDB = $DIC['ilDB'];
8434
8435        if ($active_id < 1) {
8436            return false;
8437        }
8438        if ($pass === null) {
8439            $pass = ($this->getResetProcessingTime()) ? self::_getPass($active_id) : 0;
8440        }
8441        $result = $ilDB->queryF(
8442            "SELECT tst_times.started FROM tst_times WHERE tst_times.active_fi = %s AND tst_times.pass = %s ORDER BY tst_times.started",
8443            array('integer', 'integer'),
8444            array($active_id, $pass)
8445        );
8446        if ($result->numRows()) {
8447            $row = $ilDB->fetchAssoc($result);
8448            if (preg_match("/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/", $row["started"], $matches)) {
8449                return mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1]);
8450            } else {
8451                return time();
8452            }
8453        } else {
8454            return time();
8455        }
8456    }
8457
8458    /**
8459    * Returns whether the maximum processing time for a test is reached or not
8460    *
8461    * @param long $starting_time The unix timestamp of the starting time of the test
8462    * @return boolean TRUE if the maxium processing time is reached, FALSE if the
8463    *					maximum processing time is not reached or no maximum processing time is given
8464    * @access public
8465    */
8466    public function isMaxProcessingTimeReached($starting_time, $active_id)
8467    {
8468        if ($this->getEnableProcessingTime()) {
8469            $processing_time = $this->getProcessingTimeInSeconds($active_id);
8470            $now = time();
8471            if ($now > ($starting_time + $processing_time)) {
8472                return true;
8473            } else {
8474                return false;
8475            }
8476        } else {
8477            return false;
8478        }
8479    }
8480
8481    public function &getTestQuestions()
8482    {
8483        global $DIC;
8484        $ilDB = $DIC['ilDB'];
8485
8486        $query = "
8487			SELECT		questions.*,
8488						questtypes.type_tag,
8489						tstquest.sequence,
8490						tstquest.obligatory,
8491						origquest.obj_fi orig_obj_fi
8492
8493			FROM		qpl_questions questions
8494
8495			INNER JOIN	qpl_qst_type questtypes
8496			ON			questtypes.question_type_id = questions.question_type_fi
8497
8498			INNER JOIN	tst_test_question tstquest
8499			ON			tstquest.question_fi = questions.question_id
8500
8501			LEFT JOIN	qpl_questions origquest
8502			ON			origquest.question_id = questions.original_id
8503
8504			WHERE		tstquest.test_fi = %s
8505
8506			ORDER BY	tstquest.sequence
8507		";
8508
8509        $query_result = $ilDB->queryF(
8510            $query,
8511            array('integer'),
8512            array($this->getTestId())
8513        );
8514
8515        $questions = array();
8516
8517        while ($row = $ilDB->fetchAssoc($query_result)) {
8518            $question = $row;
8519
8520            $question['obligationPossible'] = self::isQuestionObligationPossible($row['question_id']);
8521
8522            $questions[] = $question;
8523        }
8524
8525        return $questions;
8526    }
8527
8528    /**
8529     * @param int $questionId
8530     * @return bool
8531     */
8532    public function isTestQuestion($questionId)
8533    {
8534        foreach ($this->getTestQuestions() as $questionData) {
8535            if ($questionData['question_id'] != $questionId) {
8536                continue;
8537            }
8538
8539            return true;
8540        }
8541
8542        return false;
8543    }
8544
8545    public function checkQuestionParent($questionId)
8546    {
8547        global $DIC; /* @var ILIAS\DI\Container $DIC */
8548
8549        $row = $DIC->database()->fetchAssoc($DIC->database()->queryF(
8550            "SELECT COUNT(question_id) cnt FROM qpl_questions WHERE question_id = %s AND obj_fi = %s",
8551            array('integer', 'integer'),
8552            array($questionId, $this->getId())
8553        ));
8554
8555        return (bool) $row['cnt'];
8556    }
8557
8558    /**
8559     * @return float
8560     */
8561    public function getFixedQuestionSetTotalPoints()
8562    {
8563        $points = 0;
8564
8565        foreach ($this->getTestQuestions() as $questionData) {
8566            $points += $questionData['points'];
8567        }
8568
8569        return $points;
8570    }
8571
8572    /**
8573     * @return string
8574     */
8575    public function getFixedQuestionSetTotalWorkingTime()
8576    {
8577        $totalWorkingTime = '00:00:00';
8578
8579        foreach ($this->getTestQuestions() as $questionData) {
8580            $totalWorkingTime = assQuestion::sumTimesInISO8601FormatH_i_s_Extended(
8581                $totalWorkingTime,
8582                $questionData['working_time']
8583            );
8584        }
8585
8586        return $totalWorkingTime;
8587    }
8588
8589    /**
8590     * @return array
8591     */
8592    public function getPotentialRandomTestQuestions()
8593    {
8594        /**
8595         * @var $ilDB ilDBInterface
8596         */
8597        global $DIC;
8598        $ilDB = $DIC['ilDB'];
8599
8600        $query = "
8601			SELECT		questions.*,
8602						questtypes.type_tag,
8603						origquest.obj_fi orig_obj_fi
8604
8605			FROM		qpl_questions questions
8606
8607			INNER JOIN	qpl_qst_type questtypes
8608			ON			questtypes.question_type_id = questions.question_type_fi
8609
8610			INNER JOIN	tst_rnd_cpy tstquest
8611			ON			tstquest.qst_fi = questions.question_id
8612
8613			LEFT JOIN	qpl_questions origquest
8614			ON			origquest.question_id = questions.original_id
8615
8616			WHERE		tstquest.tst_fi = %s
8617		";
8618
8619        $query_result = $ilDB->queryF(
8620            $query,
8621            array('integer'),
8622            array($this->getTestId())
8623        );
8624
8625        $questions = array();
8626
8627        while ($row = $ilDB->fetchAssoc($query_result)) {
8628            $question = $row;
8629
8630            $question['obligationPossible'] = self::isQuestionObligationPossible($row['question_id']);
8631
8632            $questions[] = $question;
8633        }
8634
8635        return $questions;
8636    }
8637
8638    /**
8639    * Returns the status of the shuffle_questions variable
8640    *
8641    * @return integer 0 if the test questions are not shuffled, 1 if the test questions are shuffled
8642    * @access public
8643    */
8644    public function getShuffleQuestions()
8645    {
8646        return ($this->shuffle_questions) ? 1 : 0;
8647    }
8648
8649    /**
8650    * Sets the status of the shuffle_questions variable
8651    *
8652    * @param boolean $a_shuffle 0 if the test questions are not shuffled, 1 if the test questions are shuffled
8653    * @access public
8654    */
8655    public function setShuffleQuestions($a_shuffle)
8656    {
8657        $this->shuffle_questions = ($a_shuffle) ? 1 : 0;
8658    }
8659
8660    /**
8661    * Returns the settings for the list of questions options in the test properties
8662    * This could contain one of the following values:
8663    *   0 = No list of questions offered
8664    *   1 = A list of questions is offered
8665    *   3 = A list of questions is offered and the list of questions is shown as first page of the test
8666    *   5 = A list of questions is offered and the list of questions is shown as last page of the test
8667    *   7 = A list of questions is offered and the list of questions is shown as first and last page of the test
8668    *
8669    * @return integer TRUE if the list of questions should be presented, FALSE otherwise
8670    * @access public
8671    */
8672    public function getListOfQuestionsSettings()
8673    {
8674        return ($this->show_summary) ? $this->show_summary : 0;
8675    }
8676
8677    /**
8678    * Sets the settings for the list of questions options in the test properties
8679    * This could contain one of the following values:
8680    *   0 = No list of questions offered
8681    *   1 = A list of questions is offered
8682    *   3 = A list of questions is offered and the list of questions is shown as first page of the test
8683    *   5 = A list of questions is offered and the list of questions is shown as last page of the test
8684    *   7 = A list of questions is offered and the list of questions is shown as first and last page of the test
8685    *
8686    * @param integer $a_value 0, 1, 3, 5 or 7
8687    * @access public
8688    */
8689    public function setListOfQuestionsSettings($a_value = 0)
8690    {
8691        $this->show_summary = $a_value;
8692    }
8693
8694    /**
8695    * Returns if the list of questions should be presented to the user or not
8696    *
8697    * @return boolean TRUE if the list of questions should be presented, FALSE otherwise
8698    * @access public
8699    */
8700    public function getListOfQuestions()
8701    {
8702        if (($this->show_summary & 1) > 0) {
8703            return true;
8704        } else {
8705            return false;
8706        }
8707    }
8708
8709    /**
8710    * Sets if the the list of questions should be presented to the user or not
8711    *
8712    * @param boolean $a_value TRUE if the list of questions should be presented, FALSE otherwise
8713    * @access public
8714    */
8715    public function setListOfQuestions($a_value = true)
8716    {
8717        if ($a_value) {
8718            $this->show_summary = 1;
8719        } else {
8720            $this->show_summary = 0;
8721        }
8722    }
8723
8724    /**
8725    * Returns if the list of questions should be presented as the first page of the test
8726    *
8727    * @return boolean TRUE if the list of questions is shown as first page of the test, FALSE otherwise
8728    * @access public
8729    */
8730    public function getListOfQuestionsStart()
8731    {
8732        if (($this->show_summary & 2) > 0) {
8733            return true;
8734        } else {
8735            return false;
8736        }
8737    }
8738
8739    /**
8740    * Sets if the the list of questions as the start page of the test
8741    *
8742    * @param boolean $a_value TRUE if the list of questions should be the start page, FALSE otherwise
8743    * @access public
8744    */
8745    public function setListOfQuestionsStart($a_value = true)
8746    {
8747        if ($a_value && $this->getListOfQuestions()) {
8748            $this->show_summary = $this->show_summary | 2;
8749        }
8750        if (!$a_value && $this->getListOfQuestions()) {
8751            if ($this->getListOfQuestionsStart()) {
8752                $this->show_summary = $this->show_summary ^ 2;
8753            }
8754        }
8755    }
8756
8757    /**
8758    * Returns if the list of questions should be presented as the last page of the test
8759    *
8760    * @return boolean TRUE if the list of questions is shown as last page of the test, FALSE otherwise
8761    * @access public
8762    */
8763    public function getListOfQuestionsEnd()
8764    {
8765        if (($this->show_summary & 4) > 0) {
8766            return true;
8767        } else {
8768            return false;
8769        }
8770    }
8771
8772    /**
8773    * Sets if the the list of questions as the end page of the test
8774    *
8775    * @param boolean $a_value TRUE if the list of questions should be the end page, FALSE otherwise
8776    * @access public
8777    */
8778    public function setListOfQuestionsEnd($a_value = true)
8779    {
8780        if ($a_value && $this->getListOfQuestions()) {
8781            $this->show_summary = $this->show_summary | 4;
8782        }
8783        if (!$a_value && $this->getListOfQuestions()) {
8784            if ($this->getListOfQuestionsEnd()) {
8785                $this->show_summary = $this->show_summary ^ 4;
8786            }
8787        }
8788    }
8789
8790    /**
8791    * Returns TRUE if the list of questions should be presented with the question descriptions
8792    *
8793    * @return boolean TRUE if the list of questions is shown with the question descriptions, FALSE otherwise
8794    * @access public
8795    */
8796    public function getListOfQuestionsDescription()
8797    {
8798        if (($this->show_summary & 8) > 0) {
8799            return true;
8800        } else {
8801            return false;
8802        }
8803    }
8804
8805    /**
8806    * Sets the show_summary attribute to TRUE if the list of questions should be presented with the question descriptions
8807    *
8808    * @param boolean $a_value TRUE if the list of questions should be shown with question descriptions, FALSE otherwise
8809    * @access public
8810    */
8811    public function setListOfQuestionsDescription($a_value = true)
8812    {
8813        if ($a_value && $this->getListOfQuestions()) {
8814            $this->show_summary = $this->show_summary | 8;
8815        }
8816        if (!$a_value && $this->getListOfQuestions()) {
8817            if ($this->getListOfQuestionsDescription()) {
8818                $this->show_summary = $this->show_summary ^ 8;
8819            }
8820        }
8821    }
8822
8823    /**
8824    * Returns the combined results presentation value
8825    *
8826    * @return integer The combined results presentation value
8827    * @access public
8828    */
8829    public function getResultsPresentation()
8830    {
8831        return ($this->results_presentation) ? $this->results_presentation : 0;
8832    }
8833
8834    /**
8835    * Returns if the pass details should be shown when a test is not finished
8836    *
8837    * @return boolean TRUE if the pass details should be shown, FALSE otherwise
8838    * @access public
8839    */
8840    public function getShowPassDetails()
8841    {
8842        if (($this->results_presentation & 1) > 0) {
8843            return true;
8844        } else {
8845            return false;
8846        }
8847    }
8848
8849    /**
8850    * Returns if the solution details should be presented to the user or not
8851    *
8852    * @return boolean TRUE if the solution details should be presented, FALSE otherwise
8853    * @access public
8854    */
8855    public function getShowSolutionDetails()
8856    {
8857        if (($this->results_presentation & 2) > 0) {
8858            return true;
8859        } else {
8860            return false;
8861        }
8862    }
8863
8864    /**
8865    * Returns if the solution printview should be presented to the user or not
8866    *
8867    * @return boolean TRUE if the solution printview should be presented, FALSE otherwise
8868    * @access public
8869    */
8870    public function getShowSolutionPrintview()
8871    {
8872        if (($this->results_presentation & 4) > 0) {
8873            return true;
8874        } else {
8875            return false;
8876        }
8877    }
8878
8879    /**
8880    * Returns if the feedback should be presented to the solution or not
8881    *
8882    * @return boolean TRUE if the feedback should be presented in the solution, FALSE otherwise
8883    * @access public
8884    */
8885    public function getShowSolutionFeedback()
8886    {
8887        if (($this->results_presentation & 8) > 0) {
8888            return true;
8889        } else {
8890            return false;
8891        }
8892    }
8893
8894    /**
8895    * Returns if the full solution (including ILIAS content) should be presented to the solution or not
8896    *
8897    * @return boolean TRUE if the full solution should be presented in the solution output, FALSE otherwise
8898    * @access public
8899    */
8900    public function getShowSolutionAnswersOnly()
8901    {
8902        if (($this->results_presentation & 16) > 0) {
8903            return true;
8904        } else {
8905            return false;
8906        }
8907    }
8908
8909    /**
8910    * Returns if the signature field should be shown in the test results
8911    *
8912    * @return boolean TRUE if the signature field should be shown, FALSE otherwise
8913    * @access public
8914    */
8915    public function getShowSolutionSignature()
8916    {
8917        if (($this->results_presentation & 32) > 0) {
8918            return true;
8919        } else {
8920            return false;
8921        }
8922    }
8923
8924    /**
8925    * @return boolean TRUE if the suggested solutions should be shown, FALSE otherwise
8926    * @access public
8927    */
8928    public function getShowSolutionSuggested()
8929    {
8930        if (($this->results_presentation & 64) > 0) {
8931            return true;
8932        } else {
8933            return false;
8934        }
8935    }
8936
8937    /**
8938     * @return boolean TRUE if the results should be compared with the correct results in the list of answers, FALSE otherwise
8939     * @access public
8940     */
8941    public function getShowSolutionListComparison()
8942    {
8943        if (($this->results_presentation & 128) > 0) {
8944            return true;
8945        } else {
8946            return false;
8947        }
8948    }
8949
8950    /**
8951    * Sets the combined results presentation value
8952    *
8953    * @param integer $a_results_presentation The combined results presentation value
8954    * @access public
8955    */
8956    public function setResultsPresentation($a_results_presentation = 3)
8957    {
8958        $this->results_presentation = $a_results_presentation;
8959    }
8960
8961    /**
8962    * Sets if the pass details should be shown when a test is not finished
8963    *
8964    * Sets if the pass details should be shown when a test is not finished
8965    *
8966    * @param boolean $a_details TRUE if the pass details should be shown, FALSE otherwise
8967    * @access public
8968    */
8969    public function setShowPassDetails($a_details = 1)
8970    {
8971        if ($a_details) {
8972            $this->results_presentation = $this->results_presentation | 1;
8973        } else {
8974            if ($this->getShowPassDetails()) {
8975                $this->results_presentation = $this->results_presentation ^ 1;
8976            }
8977        }
8978    }
8979
8980    /**
8981    * Sets if the the solution details should be presented to the user or not
8982    *
8983    * @param integer $a_details 1 if the solution details should be presented, 0 otherwise
8984    * @access public
8985    */
8986    public function setShowSolutionDetails($a_details = 1)
8987    {
8988        if ($a_details) {
8989            $this->results_presentation = $this->results_presentation | 2;
8990        } else {
8991            if ($this->getShowSolutionDetails()) {
8992                $this->results_presentation = $this->results_presentation ^ 2;
8993            }
8994        }
8995    }
8996
8997    /**
8998    * Calculates if a user may see the solution printview of his/her test results
8999    *
9000    * @return boolean TRUE if the user may see the printview, FALSE otherwise
9001    * @access public
9002    */
9003    public function canShowSolutionPrintview($user_id = null)
9004    {
9005        return $this->getShowSolutionPrintview();
9006    }
9007
9008    /**
9009    * Sets if the the solution printview should be presented to the user or not
9010    *
9011    * @param boolean $a_details TRUE if the solution printview should be presented, FALSE otherwise
9012    * @access public
9013    */
9014    public function setShowSolutionPrintview($a_printview = 1)
9015    {
9016        if ($a_printview) {
9017            $this->results_presentation = $this->results_presentation | 4;
9018        } else {
9019            if ($this->getShowSolutionPrintview()) {
9020                $this->results_presentation = $this->results_presentation ^ 4;
9021            }
9022        }
9023    }
9024
9025    /**
9026    * Sets if the the feedback should be presented to the user in the solution or not
9027    *
9028    * @param boolean $a_feedback TRUE if the feedback should be presented in the solution, FALSE otherwise
9029    * @access public
9030    */
9031    public function setShowSolutionFeedback($a_feedback = true)
9032    {
9033        if ($a_feedback) {
9034            $this->results_presentation = $this->results_presentation | 8;
9035        } else {
9036            if ($this->getShowSolutionFeedback()) {
9037                $this->results_presentation = $this->results_presentation ^ 8;
9038            }
9039        }
9040    }
9041
9042    /**
9043    * Set to true, if the full solution (including the ILIAS content pages) should be shown in the solution output
9044    *
9045    * @param boolean $a_full TRUE if the full solution should be shown in the solution output, FALSE otherwise
9046    * @access public
9047    */
9048    public function setShowSolutionAnswersOnly($a_full = true)
9049    {
9050        if ($a_full) {
9051            $this->results_presentation = $this->results_presentation | 16;
9052        } else {
9053            if ($this->getShowSolutionAnswersOnly()) {
9054                $this->results_presentation = $this->results_presentation ^ 16;
9055            }
9056        }
9057    }
9058
9059    /**
9060    * Set to TRUE, if the signature field should be shown in the solution
9061    *
9062    * @param boolean $a_signature TRUE if the signature field should be shown, FALSE otherwise
9063    * @access public
9064    */
9065    public function setShowSolutionSignature($a_signature = false)
9066    {
9067        if ($a_signature) {
9068            $this->results_presentation = $this->results_presentation | 32;
9069        } else {
9070            if ($this->getShowSolutionSignature()) {
9071                $this->results_presentation = $this->results_presentation ^ 32;
9072            }
9073        }
9074    }
9075
9076    /**
9077    * Set to TRUE, if the suggested solution should be shown in the solution
9078    *
9079    * @param boolean $a_solution TRUE if the suggested solution should be shown, FALSE otherwise
9080    * @access public
9081    */
9082    public function setShowSolutionSuggested($a_solution = false)
9083    {
9084        if ($a_solution) {
9085            $this->results_presentation = $this->results_presentation | 64;
9086        } else {
9087            if ($this->getShowSolutionSuggested()) {
9088                $this->results_presentation = $this->results_presentation ^ 64;
9089            }
9090        }
9091    }
9092
9093    /**
9094     * Set to TRUE, if the list of answers should be shown prior to finish the test
9095     *
9096     * @param boolean $a_comparison TRUE if the list of answers should be shown prior to finish the test, FALSE otherwise
9097     */
9098    public function setShowSolutionListComparison($a_comparison = false)
9099    {
9100        if ($a_comparison) {
9101            $this->results_presentation = $this->results_presentation | 128;
9102        } else {
9103            if ($this->getShowSolutionListComparison()) {
9104                $this->results_presentation = $this->results_presentation ^ 128;
9105            }
9106        }
9107    }
9108
9109    /**
9110     * @deprecated: use ilTestParticipantData instead
9111     */
9112    public static function _getUserIdFromActiveId($active_id)
9113    {
9114        global $DIC;
9115        $ilDB = $DIC['ilDB'];
9116        $result = $ilDB->queryF(
9117            "SELECT user_fi FROM tst_active WHERE active_id = %s",
9118            array('integer'),
9119            array($active_id)
9120        );
9121        if ($result->numRows()) {
9122            $row = $ilDB->fetchAssoc($result);
9123            return $row["user_fi"];
9124        } else {
9125            return -1;
9126        }
9127    }
9128
9129    /**
9130     * @return boolean
9131     */
9132    public function isLimitUsersEnabled()
9133    {
9134        return $this->limitUsersEnabled;
9135    }
9136
9137    /**
9138     * @param boolean $limitUsersEnabled
9139     */
9140    public function setLimitUsersEnabled($limitUsersEnabled)
9141    {
9142        $this->limitUsersEnabled = $limitUsersEnabled;
9143    }
9144
9145    public function getAllowedUsers()
9146    {
9147        return ($this->allowedUsers) ? $this->allowedUsers : 0;
9148    }
9149
9150    public function setAllowedUsers($a_allowed_users)
9151    {
9152        $this->allowedUsers = $a_allowed_users;
9153    }
9154
9155    public function getAllowedUsersTimeGap()
9156    {
9157        return ($this->allowedUsersTimeGap) ? $this->allowedUsersTimeGap : 0;
9158    }
9159
9160    public function setAllowedUsersTimeGap($a_allowed_users_time_gap)
9161    {
9162        $this->allowedUsersTimeGap = $a_allowed_users_time_gap;
9163    }
9164
9165    public function checkMaximumAllowedUsers()
9166    {
9167        global $DIC;
9168        $ilDB = $DIC['ilDB'];
9169
9170        $nr_of_users = $this->getAllowedUsers();
9171        $time_gap = ($this->getAllowedUsersTimeGap()) ? $this->getAllowedUsersTimeGap() : 60;
9172        if (($nr_of_users > 0) && ($time_gap > 0)) {
9173            $now = time();
9174            $time_border = $now - $time_gap;
9175            $str_time_border = strftime("%Y%m%d%H%M%S", $time_border);
9176            $query = "
9177				SELECT DISTINCT tst_times.active_fi
9178				FROM tst_times
9179				INNER JOIN tst_active
9180				ON tst_times.active_fi = tst_active.active_id
9181				AND (
9182					tst_times.pass > tst_active.last_finished_pass OR tst_active.last_finished_pass IS NULL
9183				)
9184				WHERE tst_times.tstamp > %s
9185				AND tst_active.test_fi = %s
9186			";
9187            $result = $ilDB->queryF($query, array('integer', 'integer'), array($time_border, $this->getTestId()));
9188            if ($result->numRows() >= $nr_of_users) {
9189                include_once "./Modules/Test/classes/class.ilObjAssessmentFolder.php";
9190                if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
9191                    $this->logAction($this->lng->txtlng("assessment", "log_could_not_enter_test_due_to_simultaneous_users", ilObjAssessmentFolder::_getLogLanguage()));
9192                }
9193                return false;
9194            } else {
9195                return true;
9196            }
9197        }
9198        return true;
9199    }
9200
9201    public function _getLastAccess($active_id)
9202    {
9203        global $DIC;
9204        $ilDB = $DIC['ilDB'];
9205
9206        $result = $ilDB->queryF(
9207            "SELECT finished FROM tst_times WHERE active_fi = %s ORDER BY finished DESC",
9208            array('integer'),
9209            array($active_id)
9210        );
9211        if ($result->numRows()) {
9212            $row = $ilDB->fetchAssoc($result);
9213            return $row["finished"];
9214        }
9215        return "";
9216    }
9217
9218    public static function lookupLastTestPassAccess($activeId, $passIndex)
9219    {
9220        global $DIC; /* @var \ILIAS\DI\Container $DIC */
9221
9222        $query = "
9223            SELECT MAX(tst_times.tstamp) as last_pass_access
9224            FROM tst_times
9225            WHERE active_fi = %s
9226            AND pass = %s
9227        ";
9228
9229        $res = $DIC->database()->queryF(
9230            $query,
9231            array('integer', 'integer'),
9232            array($activeId, $passIndex)
9233        );
9234
9235        while ($row = $DIC->database()->fetchAssoc($res)) {
9236            return $row['last_pass_access'];
9237        }
9238
9239        return null;
9240    }
9241
9242    /**
9243    * Checks if a given string contains HTML or not
9244    *
9245    * @param string $a_text Text which should be checked
9246    * @return boolean
9247    * @access public
9248    */
9249    public function isHTML($a_text)
9250    {
9251        if (preg_match("/<[^>]*?>/", $a_text)) {
9252            return true;
9253        } else {
9254            return false;
9255        }
9256    }
9257
9258    /**
9259    * Reads an QTI material tag an creates a text string
9260    *
9261    * @param string $a_material QTI material tag
9262    * @return string text or xhtml string
9263    * @access public
9264    */
9265    public function QTIMaterialToString($a_material)
9266    {
9267        $result = "";
9268        for ($i = 0; $i < $a_material->getMaterialCount(); $i++) {
9269            $material = $a_material->getMaterial($i);
9270            if (strcmp($material["type"], "mattext") == 0) {
9271                $result .= $material["material"]->getContent();
9272            }
9273            if (strcmp($material["type"], "matimage") == 0) {
9274                $matimage = $material["material"];
9275                if (preg_match("/(il_([0-9]+)_mob_([0-9]+))/", $matimage->getLabel(), $matches)) {
9276                    // import an mediaobject which was inserted using tiny mce
9277                    if (!is_array($_SESSION["import_mob_xhtml"])) {
9278                        $_SESSION["import_mob_xhtml"] = array();
9279                    }
9280                    array_push($_SESSION["import_mob_xhtml"], array("mob" => $matimage->getLabel(), "uri" => $matimage->getUri()));
9281                }
9282            }
9283        }
9284        global $DIC;
9285        $ilLog = $DIC['ilLog'];
9286        $ilLog->write(print_r($_SESSION["import_mob_xhtml"], true));
9287        return $result;
9288    }
9289
9290    /**
9291    * Creates a QTI material tag from a plain text or xhtml text
9292    *
9293    * @param object $a_xml_writer Reference to the ILIAS XML writer
9294    * @param string $a_material plain text or html text containing the material
9295    * @return string QTI material tag
9296    * @access public
9297    */
9298    public function addQTIMaterial(&$a_xml_writer, $a_material)
9299    {
9300        include_once "./Services/RTE/classes/class.ilRTE.php";
9301        include_once("./Services/MediaObjects/classes/class.ilObjMediaObject.php");
9302
9303        $a_xml_writer->xmlStartTag("material");
9304        $attrs = array(
9305            "texttype" => "text/plain"
9306        );
9307        if ($this->isHTML($a_material)) {
9308            $attrs["texttype"] = "text/xhtml";
9309        }
9310        $a_xml_writer->xmlElement("mattext", $attrs, ilRTE::_replaceMediaObjectImageSrc($a_material, 0));
9311
9312        $mobs = ilObjMediaObject::_getMobsOfObject("tst:html", $this->getId());
9313        foreach ($mobs as $mob) {
9314            $moblabel = "il_" . IL_INST_ID . "_mob_" . $mob;
9315            if (strpos($a_material, "mm_$mob") !== false) {
9316                if (ilObjMediaObject::_exists($mob)) {
9317                    $mob_obj = new ilObjMediaObject($mob);
9318                    $imgattrs = array(
9319                        "label" => $moblabel,
9320                        "uri" => "objects/" . "il_" . IL_INST_ID . "_mob_" . $mob . "/" . $mob_obj->getTitle()
9321                    );
9322                }
9323                $a_xml_writer->xmlElement("matimage", $imgattrs, null);
9324            }
9325        }
9326        $a_xml_writer->xmlEndTag("material");
9327    }
9328
9329    /**
9330    * Prepares a string for a text area output in tests
9331    *
9332    * @param string $txt_output String which should be prepared for output
9333    * @access public
9334    */
9335    public function prepareTextareaOutput($txt_output, $prepare_for_latex_output = false, $omitNl2BrWhenTextArea = false)
9336    {
9337        include_once "./Services/Utilities/classes/class.ilUtil.php";
9338        return ilUtil::prepareTextareaOutput($txt_output, $prepare_for_latex_output, $omitNl2BrWhenTextArea);
9339    }
9340
9341    /**
9342    * Saves the visibility settings of the certificate
9343    *
9344    * @param integer $a_value The value for the visibility settings (0 = always, 1 = only passed,  2 = never)
9345    * @access private
9346    */
9347    public function saveCertificateVisibility($a_value)
9348    {
9349        global $DIC;
9350        $ilDB = $DIC['ilDB'];
9351
9352        $affectedRows = $ilDB->manipulateF(
9353            "UPDATE tst_tests SET certificate_visibility = %s, tstamp = %s WHERE test_id = %s",
9354            array('text', 'integer', 'integer'),
9355            array($a_value, time(), $this->getTestId())
9356        );
9357    }
9358
9359    /**
9360    * Returns the visibility settings of the certificate
9361    *
9362    * @return integer The value for the visibility settings (0 = always, 1 = only passed,  2 = never)
9363    * @access public
9364    */
9365    public function getCertificateVisibility()
9366    {
9367        return (strlen($this->certificate_visibility)) ? $this->certificate_visibility : 0;
9368    }
9369
9370    /**
9371    * Sets the visibility settings of the certificate
9372    *
9373    * @param integer $a_value The value for the visibility settings (0 = always, 1 = only passed,  2 = never)
9374    * @access public
9375    */
9376    public function setCertificateVisibility($a_value)
9377    {
9378        $this->certificate_visibility = $a_value;
9379    }
9380
9381    /**
9382    * Returns the anonymity status of the test
9383    *
9384    * @return integer The value for the anonymity status (0 = personalized, 1 = anonymized)
9385    * @access public
9386    */
9387    public function getAnonymity()
9388    {
9389        return ($this->anonymity) ? 1 : 0;
9390    }
9391
9392    /**
9393    * Sets the anonymity status of the test
9394    *
9395    * @param integer $a_value The value for the anonymity status (0 = personalized, 1 = anonymized)
9396    * @access public
9397    */
9398    public function setAnonymity($a_value = 0)
9399    {
9400        switch ($a_value) {
9401            case 1:
9402                $this->anonymity = 1;
9403                break;
9404            default:
9405                $this->anonymity = 0;
9406                break;
9407        }
9408    }
9409
9410    /**
9411    * Returns wheather the cancel test button is shown or not
9412    *
9413    * @return integer The value for the show cancel status (0 = don't show, 1 = show)
9414    * @access public
9415    */
9416    public function getShowCancel()
9417    {
9418        return ($this->show_cancel) ? 1 : 0;
9419    }
9420
9421    /**
9422    * Sets the cancel test button status
9423    *
9424    * @param integer $a_value The value for the cancel test status (0 = don't show, 1 = show)
9425    * @access public
9426    */
9427    public function setShowCancel($a_value = 1)
9428    {
9429        switch ($a_value) {
9430            case 1:
9431                $this->show_cancel = 1;
9432                break;
9433            default:
9434                $this->show_cancel = 0;
9435                break;
9436        }
9437    }
9438
9439    /**
9440    * Returns wheather the marker button is shown or not
9441    *
9442    * @return integer The value for the marker status (0 = don't show, 1 = show)
9443    * @access public
9444    */
9445    public function getShowMarker()
9446    {
9447        return ($this->show_marker) ? 1 : 0;
9448    }
9449
9450    /**
9451    * Sets the marker button status
9452    *
9453    * @param integer $a_value The value for the marker status (0 = don't show, 1 = show)
9454    * @access public
9455    */
9456    public function setShowMarker($a_value = 1)
9457    {
9458        switch ($a_value) {
9459            case 1:
9460                $this->show_marker = 1;
9461                break;
9462            default:
9463                $this->show_marker = 0;
9464                break;
9465        }
9466    }
9467
9468    /**
9469    * Returns the fixed participants status
9470    *
9471    * @return integer The value for the fixed participants status (0 = don't allow, 1 = allow)
9472    * @access public
9473    */
9474    public function getFixedParticipants()
9475    {
9476        return ($this->fixed_participants) ? 1 : 0;
9477    }
9478
9479    /**
9480    * Sets the fixed participants status
9481    *
9482    * @param integer $a_value The value for the fixed participants status (0 = don't allow, 1 = allow)
9483    * @access public
9484    */
9485    public function setFixedParticipants($a_value = 1)
9486    {
9487        switch ($a_value) {
9488            case 1:
9489                $this->fixed_participants = 1;
9490                break;
9491            default:
9492                $this->fixed_participants = 0;
9493                break;
9494        }
9495    }
9496
9497    /**
9498    * Returns the anonymity status of a test with a given object id
9499    *
9500    * @param int $a_obj_id The object id of the test object
9501    * @return integer The value for the anonymity status (0 = personalized, 1 = anonymized)
9502    * @access public
9503    */
9504    public static function _lookupAnonymity($a_obj_id)
9505    {
9506        global $DIC;
9507        $ilDB = $DIC['ilDB'];
9508
9509        $result = $ilDB->queryF(
9510            "SELECT anonymity FROM tst_tests WHERE obj_fi = %s",
9511            array('integer'),
9512            array($a_obj_id)
9513        );
9514        while ($row = $ilDB->fetchAssoc($result)) {
9515            return $row['anonymity'];
9516        }
9517        return 0;
9518    }
9519
9520    /**
9521     * returns the question set type of test relating to passed active id
9522     *
9523     * @param integer $activeId
9524     * @return string $questionSetType
9525     */
9526    public static function lookupQuestionSetTypeByActiveId($active_id)
9527    {
9528        global $DIC;
9529        $ilDB = $DIC['ilDB'];
9530
9531        $query = "
9532			SELECT		tst_tests.question_set_type
9533			FROM		tst_active
9534			INNER JOIN	tst_tests
9535			ON			tst_active.test_fi = tst_tests.test_id
9536			WHERE		tst_active.active_id = %s
9537		";
9538
9539        $res = $ilDB->queryF($query, array('integer'), array($active_id));
9540
9541        while ($row = $ilDB->fetchAssoc($res)) {
9542            return $row['question_set_type'];
9543        }
9544
9545        return null;
9546    }
9547
9548    /**
9549    * Returns the random status of a test with a given object id
9550    *
9551    * @param int $a_obj_id The object id of the test object
9552    * @return integer The value for the anonymity status (0 = no random, 1 = random)
9553    * @access public
9554     * @deprecated
9555    */
9556    public function _lookupRandomTestFromActiveId($active_id)
9557    {
9558        throw new Exception(__METHOD__ . ' is deprecated ... use ilObjTest::lookupQuestionSetTypeByActiveId() instead!');
9559
9560        global $DIC;
9561        $ilDB = $DIC['ilDB'];
9562
9563        $result = $ilDB->queryF(
9564            "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",
9565            array('integer'),
9566            array($active_id)
9567        );
9568        while ($row = $ilDB->fetchAssoc($result)) {
9569            return $row['random_test'];
9570        }
9571        return 0;
9572    }
9573
9574    /**
9575     * Returns the full name of a test user according to the anonymity status
9576     *
9577     * @param int $user_id The database ID of the user
9578     * @param boolean $overwrite_anonymity Indicates if the anonymity status should be ignored
9579     * @return string The full name of the user or UNKNOWN if the anonymity status is affected
9580     * @access public
9581     *
9582     * @deprecated: use ilTestParticipantData instead
9583     */
9584    public function userLookupFullName($user_id, $overwrite_anonymity = false, $sorted_order = false, $suffix = "")
9585    {
9586        if ($this->getAnonymity() && !$overwrite_anonymity) {
9587            return $this->lng->txt("anonymous") . $suffix;
9588        } else {
9589            include_once './Services/User/classes/class.ilObjUser.php';
9590            $uname = ilObjUser::_lookupName($user_id);
9591            if (strlen($uname["firstname"] . $uname["lastname"]) == 0) {
9592                $uname["firstname"] = $this->lng->txt("deleted_user");
9593            }
9594            if ($sorted_order) {
9595                return trim($uname["lastname"] . ", " . $uname["firstname"]) . $suffix;
9596            } else {
9597                return trim($uname["firstname"] . " " . $uname["lastname"]) . $suffix;
9598            }
9599        }
9600    }
9601
9602    /**
9603    * Returns the "Start the Test" label for the Info page
9604    *
9605    * @param int $active_id The active id of the current user
9606    * @return string The "Start the Test" label
9607    * @access public
9608    */
9609    public function getStartTestLabel($active_id)
9610    {
9611        if ($this->getNrOfTries() == 1) {
9612            return $this->lng->txt("tst_start_test");
9613        }
9614        $active_pass = self::_getPass($active_id);
9615        $res = $this->getNrOfResultsForPass($active_id, $active_pass);
9616        if ($res == 0) {
9617            if ($active_pass == 0) {
9618                return $this->lng->txt("tst_start_test");
9619            } else {
9620                return $this->lng->txt("tst_start_new_test_pass");
9621            }
9622        } else {
9623            return $this->lng->txt("tst_resume_test");
9624        }
9625    }
9626
9627    /**
9628     * Returns the available test defaults for the active user
9629     * @return array An array containing the defaults
9630     * @access public
9631     */
9632    public function getAvailableDefaults()
9633    {
9634        /**
9635         * @var $ilDB   ilDBInterface
9636         * @var $ilUser ilObjUser
9637         */
9638        global $DIC;
9639        $ilDB = $DIC['ilDB'];
9640        $ilUser = $DIC['ilUser'];
9641
9642        $result = $ilDB->queryF(
9643            "SELECT * FROM tst_test_defaults WHERE user_fi = %s ORDER BY name ASC",
9644            array('integer'),
9645            array($ilUser->getId())
9646        );
9647        $defaults = array();
9648        while ($row = $ilDB->fetchAssoc($result)) {
9649            $defaults[$row["test_defaults_id"]] = $row;
9650        }
9651        return $defaults;
9652    }
9653
9654    /**
9655    * Returns the test defaults for a given id
9656    *
9657    * @param integer $test_defaults_id The database id of a test defaults dataset
9658    * @return array An array containing the test defaults
9659    * @access public
9660    */
9661    public function &getTestDefaults($test_defaults_id)
9662    {
9663        return self::_getTestDefaults($test_defaults_id);
9664    }
9665
9666    public static function _getTestDefaults($test_defaults_id)
9667    {
9668        global $DIC;
9669        $ilDB = $DIC['ilDB'];
9670
9671        $result = $ilDB->queryF(
9672            "SELECT * FROM tst_test_defaults WHERE test_defaults_id = %s",
9673            array('integer'),
9674            array($test_defaults_id)
9675        );
9676        if ($result->numRows() == 1) {
9677            $row = $ilDB->fetchAssoc($result);
9678            return $row;
9679        } else {
9680            return null;
9681        }
9682    }
9683
9684    /**
9685    * Deletes the defaults for a test
9686    *
9687    * @param integer $test_default_id The database ID of the test defaults
9688    * @access public
9689    */
9690    public function deleteDefaults($test_default_id)
9691    {
9692        global $DIC;
9693        $ilDB = $DIC['ilDB'];
9694        $affectedRows = $ilDB->manipulateF(
9695            "DELETE FROM tst_test_defaults WHERE test_defaults_id = %s",
9696            array('integer'),
9697            array($test_default_id)
9698        );
9699    }
9700
9701    /**
9702    * Adds the defaults of this test to the test defaults
9703    *
9704    * @param string $a_name The name of the test defaults
9705    * @access public
9706    */
9707    public function addDefaults($a_name)
9708    {
9709        global $DIC;
9710        $ilDB = $DIC['ilDB'];
9711        $ilUser = $DIC['ilUser'];
9712        $testsettings = array(
9713            "TitleOutput" => $this->getTitleOutput(),
9714            "PassScoring" => $this->getPassScoring(),
9715            "IntroEnabled" => $this->isIntroductionEnabled(),
9716            "Introduction" => $this->getIntroduction(),
9717            "FinalStatement" => $this->getFinalStatement(),
9718            "ShowInfo" => $this->getShowInfo(),
9719            "ForceJS" => $this->getForceJS(),
9720            "CustomStyle" => $this->getCustomStyle(),
9721            "ShowFinalStatement" => $this->getShowFinalStatement(),
9722            "SequenceSettings" => $this->getSequenceSettings(),
9723            "ScoreReporting" => $this->getScoreReporting(),
9724            "ScoreCutting" => $this->getScoreCutting(),
9725            'SpecificAnswerFeedback' => $this->getSpecificAnswerFeedback(),
9726            'PrintBsWithRes' => (int) $this->isBestSolutionPrintedWithResult(),
9727            "InstantFeedbackSolution" => $this->getInstantFeedbackSolution(),
9728            "AnswerFeedback" => $this->getAnswerFeedback(),
9729            "AnswerFeedbackPoints" => $this->getAnswerFeedbackPoints(),
9730            "ResultsPresentation" => $this->getResultsPresentation(),
9731            "Anonymity" => $this->getAnonymity(),
9732            "ShowCancel" => $this->getShowCancel(),
9733            "ShowMarker" => $this->getShowMarker(),
9734            "ReportingDate" => $this->getReportingDate(),
9735            "NrOfTries" => $this->getNrOfTries(),
9736            'BlockAfterPassed' => (int) $this->isBlockPassesAfterPassedEnabled(),
9737            "Shuffle" => $this->getShuffleQuestions(),
9738            "Kiosk" => $this->getKiosk(),
9739            "UsePreviousAnswers" => $this->getUsePreviousAnswers(),
9740            "ProcessingTime" => $this->getProcessingTime(),
9741            "EnableProcessingTime" => $this->getEnableProcessingTime(),
9742            "ResetProcessingTime" => $this->getResetProcessingTime(),
9743            "StartingTimeEnabled" => $this->isStartingTimeEnabled(),
9744            "StartingTime" => $this->getStartingTime(),
9745            "EndingTimeEnabled" => $this->isEndingTimeEnabled(),
9746            "EndingTime" => $this->getEndingTime(),
9747            "ECTSOutput" => $this->getECTSOutput(),
9748            "ECTSFX" => $this->getECTSFX(),
9749            "ECTSGrades" => $this->getECTSGrades(),
9750            "questionSetType" => $this->getQuestionSetType(),
9751            "CountSystem" => $this->getCountSystem(),
9752            "MCScoring" => $this->getMCScoring(),
9753            "mailnotification" => $this->getMailNotification(),
9754            "mailnottype" => $this->getMailNotificationType(),
9755            "exportsettings" => $this->getExportSettings(),
9756            "ListOfQuestionsSettings" => $this->getListOfQuestionsSettings(),
9757            'obligations_enabled' => (int) $this->areObligationsEnabled(),
9758            'offer_question_hints' => (int) $this->isOfferingQuestionHintsEnabled(),
9759            'pass_deletion_allowed' => (int) $this->isPassDeletionAllowed(),
9760            'enable_examview' => $this->getEnableExamview(),
9761            'show_examview_html' => $this->getShowExamviewHtml(),
9762            'show_examview_pdf' => $this->getShowExamviewPdf(),
9763            'char_selector_availability' => $this->getCharSelectorAvailability(),
9764            'char_selector_definition' => $this->getCharSelectorDefinition(),
9765            'skill_service' => (int) $this->isSkillServiceEnabled(),
9766            'result_tax_filters' => (array) $this->getResultFilterTaxIds(),
9767            'show_grading_status' => (int) $this->isShowGradingStatusEnabled(),
9768            'show_grading_mark' => (int) $this->isShowGradingMarkEnabled(),
9769
9770            'follow_qst_answer_fixation' => $this->isFollowupQuestionAnswerFixationEnabled(),
9771            'inst_fb_answer_fixation' => $this->isInstantFeedbackAnswerFixationEnabled(),
9772            'force_inst_fb' => $this->isForceInstantFeedbackEnabled(),
9773            'redirection_mode' => $this->getRedirectionMode(),
9774            'redirection_url' => $this->getRedirectionUrl(),
9775            'sign_submission' => $this->getSignSubmission(),
9776            'autosave' => (int) $this->getAutosave(),
9777            'autosave_ival' => (int) $this->getAutosaveIval(),
9778            'examid_in_test_pass' => (int) $this->isShowExamIdInTestPassEnabled(),
9779            'examid_in_test_res' => (int) $this->isShowExamIdInTestResultsEnabled(),
9780
9781            'enable_archiving' => (int) $this->getEnableArchiving(),
9782            'password_enabled' => (int) $this->isPasswordEnabled(),
9783            'password' => (string) $this->getPassword(),
9784            'fixed_participants' => $this->getFixedParticipants(),
9785            'limit_users_enabled' => $this->isLimitUsersEnabled(),
9786            'allowedusers' => $this->getAllowedUsers(),
9787            'alloweduserstimegap' => $this->getAllowedUsersTimeGap(),
9788            'pool_usage' => $this->getPoolUsage(),
9789            'activation_limited' => $this->isActivationLimited(),
9790            'activation_start_time' => $this->getActivationStartingTime(),
9791            'activation_end_time' => $this->getActivationEndingTime(),
9792            'activation_visibility' => $this->getActivationVisibility(),
9793            'highscore_enabled' => $this->getHighscoreEnabled(),
9794            'highscore_anon' => $this->getHighscoreAnon(),
9795            'highscore_achieved_ts' => $this->getHighscoreAchievedTS(),
9796            'highscore_score' => $this->getHighscoreScore(),
9797            'highscore_percentage' => $this->getHighscorePercentage(),
9798            'highscore_hints' => $this->getHighscoreHints(),
9799            'highscore_wtime' => $this->getHighscoreWTime(),
9800            'highscore_own_table' => $this->getHighscoreOwnTable(),
9801            'highscore_top_table' => $this->getHighscoreTopTable(),
9802            'highscore_top_num' => $this->getHighscoreTopNum(),
9803            'use_previous_answers' => (string) $this->getUsePreviousAnswers(),
9804            'pass_waiting' => $this->getPassWaiting()
9805        );
9806
9807        $next_id = $ilDB->nextId('tst_test_defaults');
9808        $ilDB->insert(
9809            'tst_test_defaults',
9810            array(
9811                'test_defaults_id' => array('integer', $next_id),
9812                'name' => array('text', $a_name),
9813                'user_fi' => array('integer', $ilUser->getId()),
9814                'defaults' => array('clob', serialize($testsettings)),
9815                'marks' => array('clob', serialize($this->mark_schema)),
9816                'tstamp' => array('integer', time())
9817            )
9818        );
9819    }
9820
9821    /**
9822     * Applies given test defaults to this test
9823     *
9824     * @param array $test_default The test defaults database id.
9825     *
9826     * @return boolean TRUE if the application succeeds, FALSE otherwise
9827     */
9828    public function applyDefaults($test_defaults)
9829    {
9830        $testsettings = unserialize($test_defaults["defaults"]);
9831        include_once "./Modules/Test/classes/class.assMarkSchema.php";
9832        $this->mark_schema = unserialize($test_defaults["marks"]);
9833
9834        $this->setTitleOutput($testsettings["TitleOutput"]);
9835        $this->setPassScoring($testsettings["PassScoring"]);
9836        $this->setIntroductionEnabled($testsettings["IntroEnabled"]);
9837        $this->setIntroduction($testsettings["Introduction"]);
9838        $this->setFinalStatement($testsettings["FinalStatement"]);
9839        $this->setShowInfo($testsettings["ShowInfo"]);
9840        $this->setForceJS($testsettings["ForceJS"]);
9841        $this->setCustomStyle($testsettings["CustomStyle"]);
9842        $this->setShowFinalStatement($testsettings["ShowFinalStatement"]);
9843        $this->setSequenceSettings($testsettings["SequenceSettings"]);
9844        $this->setScoreReporting($testsettings["ScoreReporting"]);
9845        $this->setScoreCutting($testsettings['ScoreCutting']);
9846        $this->setSpecificAnswerFeedback($testsettings['SpecificAnswerFeedback']);
9847        $this->setPrintBestSolutionWithResult((bool) $testsettings['PrintBsWithRes']);
9848        $this->setInstantFeedbackSolution($testsettings["InstantFeedbackSolution"]);
9849        $this->setAnswerFeedback($testsettings["AnswerFeedback"]);
9850        $this->setAnswerFeedbackPoints($testsettings["AnswerFeedbackPoints"]);
9851        $this->setResultsPresentation($testsettings["ResultsPresentation"]);
9852        $this->setAnonymity($testsettings["Anonymity"]);
9853        $this->setShowCancel($testsettings["ShowCancel"]);
9854        $this->setShuffleQuestions($testsettings["Shuffle"]);
9855        $this->setShowMarker($testsettings["ShowMarker"]);
9856        $this->setReportingDate($testsettings["ReportingDate"]);
9857        $this->setNrOfTries($testsettings["NrOfTries"]);
9858        $this->setBlockPassesAfterPassedEnabled((bool) $testsettings['BlockAfterPassed']);
9859        $this->setUsePreviousAnswers($testsettings["UsePreviousAnswers"]);
9860        $this->setRedirectionMode($testsettings['redirection_mode']);
9861        $this->setRedirectionUrl($testsettings['redirection_url']);
9862        $this->setProcessingTime($testsettings["ProcessingTime"]);
9863        $this->setResetProcessingTime($testsettings["ResetProcessingTime"]);
9864        $this->setEnableProcessingTime($testsettings["EnableProcessingTime"]);
9865        $this->setStartingTimeEnabled($testsettings["StartingTimeEnabled"]);
9866        $this->setStartingTime($testsettings["StartingTime"]);
9867        $this->setKiosk($testsettings["Kiosk"]);
9868        $this->setEndingTimeEnabled($testsettings["EndingTimeEnabled"]);
9869        $this->setEndingTime($testsettings["EndingTime"]);
9870        $this->setECTSOutput($testsettings["ECTSOutput"]);
9871        $this->setECTSFX($testsettings["ECTSFX"]);
9872        $this->setECTSGrades($testsettings["ECTSGrades"]);
9873        if (isset($testsettings["isRandomTest"])) {
9874            if ($testsettings["isRandomTest"]) {
9875                $this->setQuestionSetType(self::QUESTION_SET_TYPE_RANDOM);
9876            } else {
9877                $this->setQuestionSetType(self::QUESTION_SET_TYPE_FIXED);
9878            }
9879        } elseif (isset($testsettings["questionSetType"])) {
9880            $this->setQuestionSetType($testsettings["questionSetType"]);
9881        }
9882        $this->setCountSystem($testsettings["CountSystem"]);
9883        $this->setMCScoring($testsettings["MCScoring"]);
9884        $this->setMailNotification($testsettings["mailnotification"]);
9885        $this->setMailNotificationType($testsettings["mailnottype"]);
9886        $this->setExportSettings($testsettings['exportsettings']);
9887        $this->setListOfQuestionsSettings($testsettings["ListOfQuestionsSettings"]);
9888        $this->setObligationsEnabled($testsettings["obligations_enabled"]);
9889        $this->setOfferingQuestionHintsEnabled($testsettings["offer_question_hints"]);
9890        $this->setHighscoreEnabled($testsettings['highscore_enabled']);
9891        $this->setHighscoreAnon($testsettings['highscore_anon']);
9892        $this->setHighscoreAchievedTS($testsettings['highscore_achieved_ts']);
9893        $this->setHighscoreScore($testsettings['highscore_score']);
9894        $this->setHighscorePercentage($testsettings['highscore_percentage']);
9895        $this->setHighscoreHints($testsettings['highscore_hints']);
9896        $this->setHighscoreWTime($testsettings['highscore_wtime']);
9897        $this->setHighscoreOwnTable($testsettings['highscore_own_table']);
9898        $this->setHighscoreTopTable($testsettings['highscore_top_table']);
9899        $this->setHighscoreTopNum($testsettings['highscore_top_num']);
9900        $this->setPassDeletionAllowed($testsettings['pass_deletion_allowed']);
9901        if (isset($testsettings['examid_in_kiosk'])) {
9902            $this->setShowExamIdInTestPassEnabled($testsettings['examid_in_kiosk']);
9903        } else {
9904            $this->setShowExamIdInTestPassEnabled($testsettings['examid_in_test_pass']);
9905        }
9906        if (isset($testsettings['show_exam_id'])) {
9907            $this->setShowExamIdInTestResultsEnabled($testsettings['show_exam_id']);
9908        } else {
9909            $this->setShowExamIdInTestResultsEnabled($testsettings['examid_in_test_res']);
9910        }
9911        $this->setEnableExamview($testsettings['enable_examview']);
9912        $this->setShowExamviewHtml($testsettings['show_examview_html']);
9913        $this->setShowExamviewPdf($testsettings['show_examview_pdf']);
9914        $this->setEnableArchiving($testsettings['enable_archiving']);
9915        $this->setSignSubmission($testsettings['sign_submission']);
9916        $this->setCharSelectorAvailability($testsettings['char_selector_availability']);
9917        $this->setCharSelectorDefinition($testsettings['char_selector_definition']);
9918        $this->setSkillServiceEnabled((bool) $testsettings['skill_service']);
9919        $this->setResultFilterTaxIds((array) $testsettings['result_tax_filters']);
9920        $this->setShowGradingStatusEnabled((bool) $testsettings['show_grading_status']);
9921        $this->setShowGradingMarkEnabled((bool) $testsettings['show_grading_mark']);
9922
9923        $this->setFollowupQuestionAnswerFixationEnabled($testsettings['follow_qst_answer_fixation']);
9924        $this->setInstantFeedbackAnswerFixationEnabled($testsettings['inst_fb_answer_fixation']);
9925        $this->setForceInstantFeedbackEnabled($testsettings['force_inst_fb']);
9926        $this->setRedirectionMode($testsettings['redirection_mode']);
9927        $this->setRedirectionUrl($testsettings['redirection_url']);
9928
9929        $this->setAutosave($testsettings['autosave']);
9930        $this->setAutosaveIval($testsettings['autosave_ival']);
9931        $this->setShowExamIdInTestResultsEnabled((int) $testsettings['examid_in_test_res']);
9932        $this->setPasswordEnabled($testsettings['password_enabled']);
9933        $this->setPassword($testsettings['password']);
9934        $this->setFixedParticipants($testsettings['fixed_participants']);
9935        $this->setLimitUsersEnabled($testsettings['limit_users_enabled']);
9936        $this->setAllowedUsers($testsettings['allowedusers']);
9937        $this->setAllowedUsersTimeGap($testsettings['alloweduserstimegap']);
9938        $this->setUsePreviousAnswers($testsettings['use_previous_answers']);
9939        $this->setPoolUsage($testsettings['pool_usage']);
9940        $this->setActivationLimited($testsettings['activation_limited']);
9941        $this->setActivationStartingTime($testsettings['activation_start_time']);
9942        $this->setActivationEndingTime($testsettings['activation_end_time']);
9943        $this->setActivationVisibility($testsettings['activation_visibility']);
9944        $this->setPassWaiting($testsettings['pass_waiting']);
9945
9946        $this->saveToDb();
9947
9948        return true;
9949    }
9950
9951    /**
9952    * Convert a print output to XSL-FO
9953    *
9954    * @param string $print_output The print output
9955    * @return string XSL-FO code
9956    * @access public
9957    */
9958    public function processPrintoutput2FO($print_output)
9959    {
9960        if (extension_loaded("tidy")) {
9961            $config = array(
9962                "indent" => false,
9963                "output-xml" => true,
9964                "numeric-entities" => true
9965            );
9966            $tidy = new tidy();
9967            $tidy->parseString($print_output, $config, 'utf8');
9968            $tidy->cleanRepair();
9969            $print_output = tidy_get_output($tidy);
9970            $print_output = preg_replace("/^.*?(<html)/", "\\1", $print_output);
9971        } else {
9972            $print_output = str_replace("&nbsp;", "&#160;", $print_output);
9973            $print_output = str_replace("&otimes;", "X", $print_output);
9974        }
9975        $xsl = file_get_contents("./Modules/Test/xml/question2fo.xsl");
9976
9977        // additional font support
9978        global $DIC;
9979        $xsl = str_replace(
9980            'font-family="Helvetica, unifont"',
9981            'font-family="' . $DIC['ilSetting']->get('rpc_pdf_font', 'Helvetica, unifont') . '"',
9982            $xsl
9983        );
9984
9985        $args = array( '/_xml' => $print_output, '/_xsl' => $xsl );
9986        $xh = xslt_create();
9987        $params = array();
9988        $output = xslt_process($xh, "arg:/_xml", "arg:/_xsl", null, $args, $params);
9989        xslt_error($xh);
9990        xslt_free($xh);
9991        return $output;
9992    }
9993
9994    /**
9995    * Delivers a PDF file from XHTML
9996    *
9997    * @param string $html The XHTML string
9998    * @access public
9999    */
10000    public function deliverPDFfromHTML($content, $title = null)
10001    {
10002        $content = preg_replace("/href=\".*?\"/", "", $content);
10003        $printbody = new ilTemplate("tpl.il_as_tst_print_body.html", true, true, "Modules/Test");
10004        $printbody->setVariable("TITLE", ilUtil::prepareFormOutput($this->getTitle()));
10005        $printbody->setVariable("ADM_CONTENT", $content);
10006        $printbody->setCurrentBlock("css_file");
10007        $printbody->setVariable("CSS_FILE", $this->getTestStyleLocation("filesystem"));
10008        $printbody->parseCurrentBlock();
10009        $printbody->setCurrentBlock("css_file");
10010        $printbody->setVariable("CSS_FILE", ilUtil::getStyleSheetLocation("filesystem", "delos.css"));
10011        $printbody->parseCurrentBlock();
10012        $printoutput = $printbody->get();
10013        $html = str_replace("href=\"./", "href=\"" . ILIAS_HTTP_PATH . "/", $printoutput);
10014        $html = preg_replace("/<div id=\"dontprint\">.*?<\\/div>/ims", "", $html);
10015        if (extension_loaded("tidy")) {
10016            $config = array(
10017                "indent" => false,
10018                "output-xml" => true,
10019                "numeric-entities" => true
10020            );
10021            $tidy = new tidy();
10022            $tidy->parseString($html, $config, 'utf8');
10023            $tidy->cleanRepair();
10024            $html = tidy_get_output($tidy);
10025            $html = preg_replace("/^.*?(<html)/", "\\1", $html);
10026        } else {
10027            $html = str_replace("&nbsp;", "&#160;", $html);
10028            $html = str_replace("&otimes;", "X", $html);
10029        }
10030        $html = preg_replace("/src=\".\\//ims", "src=\"" . ILIAS_HTTP_PATH . "/", $html);
10031        $this->deliverPDFfromFO($this->processPrintoutput2FO($html), $title);
10032    }
10033
10034    /**
10035    * Delivers a PDF file from a XSL-FO string
10036    *
10037    * @param string $fo The XSL-FO string
10038    * @access public
10039    */
10040    public function deliverPDFfromFO($fo, $title = null)
10041    {
10042        global $DIC;
10043        $ilLog = $DIC['ilLog'];
10044
10045        include_once "./Services/Utilities/classes/class.ilUtil.php";
10046        $fo_file = ilUtil::ilTempnam() . ".fo";
10047        $fp = fopen($fo_file, "w");
10048        fwrite($fp, $fo);
10049        fclose($fp);
10050
10051        include_once './Services/WebServices/RPC/classes/class.ilRpcClientFactory.php';
10052        try {
10053            $pdf_base64 = ilRpcClientFactory::factory('RPCTransformationHandler')->ilFO2PDF($fo);
10054            $filename = (strlen($title)) ? $title : $this->getTitle();
10055            ilUtil::deliverData($pdf_base64->scalar, ilUtil::getASCIIFilename($filename) . ".pdf", "application/pdf", false, true);
10056            return true;
10057        } catch (Exception $e) {
10058            $ilLog->write(__METHOD__ . ': ' . $e->getMessage());
10059            return false;
10060        }
10061    }
10062
10063    /**
10064    * Retrieves the feedback comment for a question in a test if it is finalized
10065    *
10066    * @param integer $active_id Active ID of the user
10067    * @param integer $question_id Question ID
10068    * @param integer $pass Pass number
10069    * @return string The feedback text
10070    * @access public
10071    */
10072    public static function getManualFeedback($active_id, $question_id, $pass)
10073    {
10074        $feedback = "";
10075        $row = self::getSingleManualFeedback($active_id, $question_id, $pass);
10076
10077        if (count($row) > 0 && ($row['finalized_evaluation'] || \ilTestService::isManScoringDone($active_id))) {
10078            $feedback = $row['feedback'];
10079        }
10080
10081        return $feedback;
10082    }
10083
10084    /**
10085     * Retrieves the manual feedback for a question in a test
10086     *
10087     * @param integer $active_id Active ID of the user
10088     * @param integer $question_id Question ID
10089     * @param integer $pass Pass number
10090     * @return array The feedback text
10091     * @access public
10092     */
10093    public static function getSingleManualFeedback($active_id, $question_id, $pass)
10094    {
10095        global $DIC;
10096
10097        $ilDB = $DIC->database();
10098        $row = array();
10099        $result = $ilDB->queryF(
10100            "SELECT * FROM tst_manual_fb WHERE active_fi = %s AND question_fi = %s AND pass = %s",
10101            array('integer', 'integer', 'integer'),
10102            array($active_id, $question_id, $pass)
10103        );
10104
10105        if ($result->numRows() === 1) {
10106            $row = $ilDB->fetchAssoc($result);
10107            $row['feedback'] = ilRTE::_replaceMediaObjectImageSrc($row['feedback'], 1);
10108        } else {
10109            $DIC->logger()->root()->warning("WARNING: Multiple feedback entries on tst_manual_fb for " .
10110                "active_fi = $active_id , question_fi = $question_id and pass = $pass");
10111        }
10112
10113        return $row;
10114    }
10115
10116    /**
10117     * Retrieves the manual feedback for a question in a test
10118     *
10119     * @param integer $question_id Question ID
10120     * @return array The feedback text
10121     * @access public
10122     */
10123    public static function getCompleteManualFeedback(int $question_id)
10124    {
10125        global $DIC;
10126
10127        $ilDB = $DIC->database();
10128        $feedback = array();
10129        $result = $ilDB->queryF(
10130            "SELECT * FROM tst_manual_fb WHERE question_fi = %s",
10131            array('integer'),
10132            array($question_id)
10133        );
10134
10135        while ($row = $ilDB->fetchAssoc($result)) {
10136            $active = $row['active_fi'];
10137            $pass = $row['pass'];
10138            $question = $row['question_fi'];
10139
10140            $row['feedback'] = ilRTE::_replaceMediaObjectImageSrc($row['feedback'], 1);
10141
10142            $feedback[$active][$pass][$question] = $row;
10143        }
10144
10145        return $feedback;
10146    }
10147
10148    /**
10149    * Saves the manual feedback for a question in a test
10150    * @param integer $active_id Active ID of the user
10151    * @param integer $question_id Question ID
10152    * @param integer $pass Pass number
10153    * @param string $feedback The feedback text
10154    * @param boolean $finalized In Feedback is final
10155    * @param boolean $is_single_feedback
10156    * @return boolean TRUE if the operation succeeds, FALSE otherwise
10157    * @access public
10158    */
10159    public function saveManualFeedback($active_id, $question_id, $pass, $feedback, $finalized = false, $is_single_feedback = false)
10160    {
10161        global $DIC;
10162
10163        $feedback_old = $this->getSingleManualFeedback($active_id, $question_id, $pass);
10164
10165        $finalized_record = (int) $feedback_old['finalized_evaluation'];
10166        if ($finalized_record === 0 || ($is_single_feedback && $finalized_record === 1)) {
10167            $DIC->database()->manipulateF(
10168                "DELETE FROM tst_manual_fb WHERE active_fi = %s AND question_fi = %s AND pass = %s",
10169                array('integer', 'integer', 'integer'),
10170                array($active_id, $question_id, $pass)
10171            );
10172
10173            $this->insertManualFeedback($active_id, $question_id, $pass, $feedback, $finalized, $feedback_old);
10174
10175            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
10176                $this->logManualFeedback($active_id, $question_id, $feedback);
10177            }
10178        }
10179
10180        return true;
10181    }
10182
10183    /**
10184     * Inserts a manual feedback into the DB
10185     *
10186     * @param integer $active_id Active ID of the user
10187     * @param integer $question_id Question ID
10188     * @param integer $pass Pass number
10189     * @param string  $feedback The feedback text
10190     * @param array  $feedback_old The feedback before update
10191     * @param boolean $finalized In Feedback is final
10192     */
10193    private function insertManualFeedback($active_id, $question_id, $pass, $feedback, $finalized, $feedback_old)
10194    {
10195        global $DIC;
10196
10197        $ilDB = $DIC->database();
10198        $ilUser = $DIC->user();
10199        $next_id = $ilDB->nextId('tst_manual_fb');
10200        $user = $ilUser->getId();
10201        $finalized_time = time();
10202
10203        $update_default = [
10204            'manual_feedback_id' => [ 'integer', $next_id],
10205            'active_fi' => [ 'integer', $active_id],
10206            'question_fi' => [ 'integer', $question_id],
10207            'pass' => [ 'integer', $pass],
10208            'feedback' => [ 'clob', ilRTE::_replaceMediaObjectImageSrc($feedback, 0)],
10209            'tstamp' => [ 'integer', time()]
10210        ];
10211
10212        if ($feedback_old['finalized_evaluation'] == 1) {
10213            $user = $feedback_old['finalized_by_usr_id'];
10214            $finalized_time = $feedback_old['finalized_tstamp'];
10215        }
10216
10217        if ($finalized === true || $feedback_old['finalized_evaluation'] == 1) {
10218            if (!array_key_exists('evaluated', $_POST)) {
10219                $update_default['finalized_evaluation'] = ['integer', 0];
10220                $update_default['finalized_by_usr_id'] = ['integer', 0];
10221                $update_default['finalized_tstamp'] = ['integer', 0];
10222            } else {
10223                $update_default['finalized_evaluation'] = ['integer', 1];
10224                $update_default['finalized_by_usr_id'] = ['integer', $user];
10225                $update_default['finalized_tstamp'] = ['integer', $finalized_time];
10226            }
10227        }
10228
10229        $ilDB->insert('tst_manual_fb', $update_default);
10230    }
10231
10232    /**
10233     * Creates a log for the manual feedback
10234     *
10235     * @param integer $active_id Active ID of the user
10236     * @param integer $question_id Question ID
10237     * @param string  $feedback The feedback text
10238     */
10239    private function logManualFeedback($active_id, $question_id, $feedback)
10240    {
10241        global $DIC;
10242
10243        $ilUser = $DIC->user();
10244        $lng = $DIC->language();
10245        $username = ilObjTestAccess::_getParticipantData($active_id);
10246
10247        $this->logAction(
10248            sprintf(
10249                $lng->txtlng('assessment', 'log_manual_feedback', ilObjAssessmentFolder::_getLogLanguage()),
10250                $ilUser->getFullname() . ' (' . $ilUser->getLogin() . ')',
10251                $username,
10252                assQuestion::_getQuestionTitle($question_id),
10253                $feedback
10254            )
10255        );
10256    }
10257
10258    /**
10259    * Returns if Javascript should be chosen for drag & drop actions
10260    * for the active user
10261    *
10262    * @return boolean TRUE if Javascript should be chosen, FALSE otherwise
10263    * @access public
10264    */
10265    public function getJavaScriptOutput()
10266    {
10267        return true;
10268
10269        //		global $DIC;
10270//		$ilUser = $DIC['ilUser'];
10271//		if (strcmp($_GET["tst_javascript"], "0") == 0) return FALSE;
10272//		if ($this->getForceJS()) return TRUE;
10273//		$assessmentSetting = new ilSetting("assessment");
10274//		return ($ilUser->getPref("tst_javascript") === FALSE) ? $assessmentSetting->get("use_javascript") : $ilUser->getPref("tst_javascript");
10275    }
10276
10277    public function &createTestSequence($active_id, $pass, $shuffle)
10278    {
10279        include_once "./Modules/Test/classes/class.ilTestSequence.php";
10280        $this->testSequence = new ilTestSequence($active_id, $pass, $this->isRandomTest());
10281    }
10282
10283    /**
10284    * Sets the test ID
10285    *
10286    * @param integer $a_id Test ID
10287    */
10288    public function setTestId($a_id)
10289    {
10290        $this->test_id = $a_id;
10291    }
10292
10293    /**
10294     * returns all test results for all participants
10295     *
10296     * @param array $partipants array of user ids
10297     * @param boolean if true, the result will be prepared for csv output (see processCSVRow)
10298     *
10299     * @return array of fields, see code for column titles
10300     */
10301    public function getDetailedTestResults($participants)
10302    {
10303        $results = array();
10304        if (count($participants)) {
10305            foreach ($participants as $active_id => $user_rec) {
10306                $row = array();
10307                $reached_points = 0;
10308                $max_points = 0;
10309                foreach ($this->questions as $value) {
10310                    $question = &ilObjTest::_instanciateQuestion($value);
10311                    if (is_object($question)) {
10312                        $max_points += $question->getMaximumPoints();
10313                        $reached_points += $question->getReachedPoints($active_id);
10314                        if ($max_points > 0) {
10315                            $percentvalue = $reached_points / $max_points;
10316                            if ($percentvalue < 0) {
10317                                $percentvalue = 0.0;
10318                            }
10319                        } else {
10320                            $percentvalue = 0;
10321                        }
10322                        if ($this->getAnonymity()) {
10323                            $user_rec['firstname'] = "";
10324                            $user_rec['lastname'] = $this->lng->txt("anonymous");
10325                        }
10326                        $row = array(
10327                            "user_id" => $user_rec['usr_id'],
10328                            "matriculation" => $user_rec['matriculation'],
10329                            "lastname" => $user_rec['lastname'],
10330                            "firstname" => $user_rec['firstname'],
10331                            "login" => $user_rec['login'],
10332                            "question_id" => $question->getId(),
10333                            "question_title" => $question->getTitle(),
10334                            "reached_points" => $reached_points,
10335                            "max_points" => $max_points
10336                        );
10337                        $results[] = $row;
10338                    }
10339                }
10340            }
10341        }
10342        return $results;
10343    }
10344
10345    /**
10346    * Get test Object ID for question ID
10347    */
10348    public static function _lookupTestObjIdForQuestionId($a_q_id)
10349    {
10350        global $DIC;
10351        $ilDB = $DIC['ilDB'];
10352
10353        $result = $ilDB->queryF(
10354            "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",
10355            array('integer'),
10356            array($a_q_id)
10357        );
10358        $rec = $ilDB->fetchAssoc($result);
10359        return $rec["obj_id"];
10360    }
10361
10362    /**
10363    * Checks wheather or not a question plugin with a given name is active
10364    *
10365    * @param string $a_pname The plugin name
10366    * @access public
10367    */
10368    public function isPluginActive($a_pname)
10369    {
10370        global $DIC;
10371        $ilPluginAdmin = $DIC['ilPluginAdmin'];
10372        if ($ilPluginAdmin->isActive(IL_COMP_MODULE, "TestQuestionPool", "qst", $a_pname)) {
10373            return true;
10374        } else {
10375            return false;
10376        }
10377    }
10378
10379    public function getPassed($active_id)
10380    {
10381        global $DIC;
10382        $ilDB = $DIC['ilDB'];
10383
10384        $result = $ilDB->queryF(
10385            "SELECT passed FROM tst_result_cache WHERE active_fi = %s",
10386            array('integer'),
10387            array($active_id)
10388        );
10389        if ($result->numRows()) {
10390            $row = $ilDB->fetchAssoc($result);
10391            return $row['passed'];
10392        } else {
10393            $counted_pass = ilObjTest::_getResultPass($active_id);
10394            $result_array = &$this->getTestResult($active_id, $counted_pass);
10395            return $result_array["test"]["passed"];
10396        }
10397    }
10398
10399    /**
10400    * Checks whether the certificate button could be shown on the info page or not
10401    *
10402    * @access public
10403    */
10404    public function canShowCertificate($testSession, $user_id, $active_id)
10405    {
10406        if ($this->canShowTestResults($testSession)) {
10407            $isComplete = false;
10408            $userCertificateRepository = new ilUserCertificateRepository($this->db, $this->log);
10409            try {
10410                $userCertificateRepository->fetchActiveCertificate($user_id, $this->getId());
10411                $isComplete = true;
10412            } catch (ilException $e) {
10413            }
10414
10415            if ($isComplete) {
10416                $vis = $this->getCertificateVisibility();
10417                $showcert = false;
10418                switch ($vis) {
10419                    case 0:
10420                        $showcert = true;
10421                        break;
10422                    case 1:
10423                        if ($this->getPassed($active_id)) {
10424                            $showcert = true;
10425                        }
10426                        break;
10427                    case 2:
10428                        $showcert = false;
10429                        break;
10430                }
10431                if ($showcert) {
10432                    return true;
10433                } else {
10434                    return false;
10435                }
10436            } else {
10437                return false;
10438            }
10439        } else {
10440            return false;
10441        }
10442    }
10443
10444    /**
10445     * Creates an associated array with all active id's for a given test and original question id
10446     */
10447    public function getParticipantsForTestAndQuestion($test_id, $question_id)
10448    {
10449        /** @var ilDBInterface $ilDB */
10450        global $DIC;
10451        $ilDB = $DIC['ilDB'];
10452
10453        $query = "
10454			SELECT tst_test_result.active_fi, tst_test_result.question_fi, tst_test_result.pass
10455			FROM tst_test_result
10456			INNER JOIN tst_active ON tst_active.active_id = tst_test_result.active_fi AND tst_active.test_fi = %s
10457			INNER JOIN qpl_questions ON qpl_questions.question_id = tst_test_result.question_fi
10458			LEFT JOIN usr_data ON usr_data.usr_id = tst_active.user_fi
10459			WHERE tst_test_result.question_fi = %s
10460			ORDER BY usr_data.lastname ASC, usr_data.firstname ASC
10461		";
10462
10463        $result = $ilDB->queryF(
10464            $query,
10465            array('integer', 'integer'),
10466            array($test_id, $question_id)
10467        );
10468        $foundusers = array();
10469        /** @noinspection PhpAssignmentInConditionInspection */
10470        while ($row = $ilDB->fetchAssoc($result)) {
10471            if ($this->getAccessFilteredParticipantList() && !$this->getAccessFilteredParticipantList()->isActiveIdInList($row["active_fi"])) {
10472                continue;
10473            }
10474
10475            if (!array_key_exists($row["active_fi"], $foundusers)) {
10476                $foundusers[$row["active_fi"]] = array();
10477            }
10478            array_push($foundusers[$row["active_fi"]], array("pass" => $row["pass"], "qid" => $row["question_fi"]));
10479        }
10480        return $foundusers;
10481    }
10482
10483    /**
10484    * Returns the aggregated test results
10485    *
10486    * @access public
10487    */
10488    public function getAggregatedResultsData()
10489    {
10490        $data = &$this->getCompleteEvaluationData();
10491        $foundParticipants = &$data->getParticipants();
10492        $results = array("overview" => array(), "questions" => array());
10493        if (count($foundParticipants)) {
10494            $results["overview"][$this->lng->txt("tst_eval_total_persons")] = count($foundParticipants);
10495            $total_finished = $data->getTotalFinishedParticipants();
10496            $results["overview"][$this->lng->txt("tst_eval_total_finished")] = $total_finished;
10497            $average_time = $this->evalTotalStartedAverageTime($data->getParticipantIds());
10498            $diff_seconds = $average_time;
10499            $diff_hours = floor($diff_seconds / 3600);
10500            $diff_seconds -= $diff_hours * 3600;
10501            $diff_minutes = floor($diff_seconds / 60);
10502            $diff_seconds -= $diff_minutes * 60;
10503            $results["overview"][$this->lng->txt("tst_eval_total_finished_average_time")] = sprintf("%02d:%02d:%02d", $diff_hours, $diff_minutes, $diff_seconds);
10504            $total_passed = 0;
10505            $total_passed_reached = 0;
10506            $total_passed_max = 0;
10507            $total_passed_time = 0;
10508            foreach ($foundParticipants as $userdata) {
10509                if ($userdata->getPassed()) {
10510                    $total_passed++;
10511                    $total_passed_reached += $userdata->getReached();
10512                    $total_passed_max += $userdata->getMaxpoints();
10513                    $total_passed_time += $userdata->getTimeOfWork();
10514                }
10515            }
10516            $average_passed_reached = $total_passed ? $total_passed_reached / $total_passed : 0;
10517            $average_passed_max = $total_passed ? $total_passed_max / $total_passed : 0;
10518            $average_passed_time = $total_passed ? $total_passed_time / $total_passed : 0;
10519            $results["overview"][$this->lng->txt("tst_eval_total_passed")] = $total_passed;
10520            $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);
10521            $average_time = $average_passed_time;
10522            $diff_seconds = $average_time;
10523            $diff_hours = floor($diff_seconds / 3600);
10524            $diff_seconds -= $diff_hours * 3600;
10525            $diff_minutes = floor($diff_seconds / 60);
10526            $diff_seconds -= $diff_minutes * 60;
10527            $results["overview"][$this->lng->txt("tst_eval_total_passed_average_time")] = sprintf("%02d:%02d:%02d", $diff_hours, $diff_minutes, $diff_seconds);
10528        }
10529
10530        foreach ($data->getQuestionTitles() as $question_id => $question_title) {
10531            $answered = 0;
10532            $reached = 0;
10533            $max = 0;
10534            foreach ($foundParticipants as $userdata) {
10535                for ($i = 0; $i <= $userdata->getLastPass(); $i++) {
10536                    if (is_object($userdata->getPass($i))) {
10537                        $question = &$userdata->getPass($i)->getAnsweredQuestionByQuestionId($question_id);
10538                        if (is_array($question)) {
10539                            $answered++;
10540                            $reached += $question["reached"];
10541                            $max += $question["points"];
10542                        }
10543                    }
10544                }
10545            }
10546            $percent = $max ? $reached / $max * 100.0 : 0;
10547            $results["questions"][$question_id] = array(
10548                $question_title,
10549                sprintf("%.2f", $answered ? $reached / $answered : 0) . " " . strtolower($this->lng->txt("of")) . " " . sprintf("%.2f", $answered ? $max / $answered : 0),
10550                sprintf("%.2f", $percent) . "%",
10551                $answered,
10552                sprintf("%.2f", $answered ? $reached / $answered : 0),
10553                sprintf("%.2f", $answered ? $max / $answered : 0),
10554                $percent / 100.0
10555            );
10556        }
10557        return $results;
10558    }
10559
10560    /**
10561    * Get zipped xml file for test
10562    */
10563    public function getXMLZip()
10564    {
10565        require_once 'Modules/Test/classes/class.ilTestExportFactory.php';
10566        $expFactory = new ilTestExportFactory($this);
10567        $test_exp = $expFactory->getExporter('xml');
10568        return $test_exp->buildExportFile();
10569    }
10570
10571    /**
10572    * Get mail notification settings
10573    */
10574    public function getMailNotification()
10575    {
10576        return $this->mailnotification;
10577    }
10578
10579    /**
10580    * Set mail notification settings
10581    *
10582    * @param $a_notification Mail notification setting
10583    */
10584    public function setMailNotification($a_notification)
10585    {
10586        $this->mailnotification = $a_notification;
10587    }
10588
10589    public function sendSimpleNotification($active_id)
10590    {
10591        include_once "./Modules/Test/classes/class.ilTestMailNotification.php";
10592
10593        $mail = new ilTestMailNotification();
10594        $owner_id = $this->getOwner();
10595        $usr_data = $this->userLookupFullName(ilObjTest::_getUserIdFromActiveId($active_id));
10596        $mail->sendSimpleNotification($owner_id, $this->getTitle(), $usr_data);
10597    }
10598
10599    /**
10600     * Gets additional user fields that should be shown in the user evaluation
10601     *
10602     * @return array An array containing the database fields that should be shown in the evaluation
10603     */
10604    public function getEvaluationAdditionalFields()
10605    {
10606        include_once "./Modules/Test/classes/class.ilObjTestGUI.php";
10607        include_once "./Modules/Test/classes/tables/class.ilEvaluationAllTableGUI.php";
10608        $table_gui = new ilEvaluationAllTableGUI(new ilObjTestGUI($this->getRefId()), 'outEvaluation', $this->getAnonymity());
10609        return $table_gui->getSelectedColumns();
10610    }
10611
10612    public function sendAdvancedNotification($active_id)
10613    {
10614        include_once "./Modules/Test/classes/class.ilTestMailNotification.php";
10615
10616        $mail = new ilTestMailNotification();
10617        $owner_id = $this->getOwner();
10618        $usr_data = $this->userLookupFullName(ilObjTest::_getUserIdFromActiveId($active_id));
10619
10620        $participantList = new ilTestParticipantList($this);
10621        $participantList->initializeFromDbRows($this->getTestParticipants());
10622
10623        require_once 'Modules/Test/classes/class.ilTestExportFactory.php';
10624        $expFactory = new ilTestExportFactory($this);
10625        $exportObj = $expFactory->getExporter('results');
10626        $exportObj->setForcedAccessFilteredParticipantList($participantList);
10627        $file = $exportObj->exportToExcel($deliver = false, 'active_id', $active_id, $passedonly = false);
10628        include_once "./Services/Mail/classes/class.ilFileDataMail.php";
10629        $fd = new ilFileDataMail(ANONYMOUS_USER_ID);
10630        $fd->copyAttachmentFile($file, "result_" . $active_id . ".xls");
10631        $file_names[] = "result_" . $active_id . ".xls";
10632
10633        $mail->sendAdvancedNotification($owner_id, $this->getTitle(), $usr_data, $file_names);
10634
10635        if (count($file_names)) {
10636            $fd->unlinkFiles($file_names);
10637            unset($fd);
10638            @unlink($file);
10639        }
10640    }
10641
10642    public function createRandomSolutions($number)
10643    {
10644        global $DIC;
10645        $ilDB = $DIC['ilDB'];
10646
10647        // 1. get a user
10648        $query = "SELECT usr_id FROM usr_data";
10649        $result = $ilDB->query($query);
10650        while ($data = $ilDB->fetchAssoc($result)) {
10651            $activequery = sprintf(
10652                "SELECT user_fi FROM tst_active WHERE test_fi = %s AND user_fi = %s",
10653                $ilDB->quote($this->getTestId()),
10654                $ilDB->quote($data['usr_id'])
10655            );
10656            $activeresult = $ilDB->query($activequery);
10657            if ($activeresult->numRows() == 0) {
10658                $user_id = $data['usr_id'];
10659                if ($user_id != 13) {
10660                    include_once "./Modules/Test/classes/class.ilTestSession.php";
10661                    $testSession = new ilTestSession();
10662                    $testSession->setRefId($this->getRefId());
10663                    $testSession->setTestId($this->getTestId());
10664                    $testSession->setUserId($user_id);
10665                    $testSession->saveToDb();
10666                    $passes = ($this->getNrOfTries()) ? $this->getNrOfTries() : 10;
10667                    $random = new \ilRandom();
10668                    $nr_of_passes = $random->int(1, $passes);
10669                    $active_id = $testSession->getActiveId();
10670                    for ($pass = 0; $pass < $nr_of_passes; $pass++) {
10671                        include_once "./Modules/Test/classes/class.ilTestSequence.php";
10672                        $testSequence = new ilTestSequence($active_id, $pass, $this->isRandomTest());
10673                        $testSequence->loadFromDb();
10674                        $testSequence->loadQuestions();
10675                        if (!$testSequence->hasSequence()) {
10676                            $testSequence->createNewSequence($this->getQuestionCount(), $shuffle);
10677                            $testSequence->saveToDb();
10678                        }
10679                        for ($seq = 1; $seq <= count($this->questions); $seq++) {
10680                            $question_id = $testSequence->getQuestionForSequence($seq);
10681                            $objQuestion = ilObjTest::_instanciateQuestion($question_id);
10682                            $assSettings = new ilSetting('assessment');
10683                            require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionProcessLockerFactory.php';
10684                            $processLockerFactory = new ilAssQuestionProcessLockerFactory($assSettings, $ilDB);
10685                            $processLockerFactory->setQuestionId($objQuestion->getId());
10686                            $processLockerFactory->setUserId($testSession->getUserId());
10687                            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
10688                            $processLockerFactory->setAssessmentLogEnabled(ilObjAssessmentFolder::_enabledAssessmentLogging());
10689                            $objQuestion->setProcessLocker($processLockerFactory->getLocker());
10690                            $objQuestion->createRandomSolution($testSession->getActiveId(), $pass);
10691                        }
10692                        $testSession->increasePass();
10693                        $testSession->setLastSequence(0);
10694                        $testSession->setLastFinishedPass($pass);
10695                        $testSession->setSubmitted(1);
10696                        $testSession->setSubmittedTimestamp(date('Y-m-d H:i:s'));
10697                        $testSession->saveToDb();
10698                    }
10699                    $number--;
10700                    if ($number == 0) {
10701                        return;
10702                    }
10703                }
10704            }
10705        }
10706    }
10707
10708    public function getResultsForActiveId($active_id)
10709    {
10710        global $DIC;
10711        $ilDB = $DIC['ilDB'];
10712
10713        $query = "
10714			SELECT		*
10715			FROM		tst_result_cache
10716			WHERE		active_fi = %s
10717		";
10718
10719        $result = $ilDB->queryF(
10720            $query,
10721            array('integer'),
10722            array($active_id)
10723        );
10724
10725        if (!$result->numRows()) {
10726            include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
10727
10728            assQuestion::_updateTestResultCache($active_id);
10729
10730            $query = "
10731				SELECT		*
10732				FROM		tst_result_cache
10733				WHERE		active_fi = %s
10734			";
10735
10736            $result = $ilDB->queryF(
10737                $query,
10738                array('integer'),
10739                array($active_id)
10740            );
10741        }
10742
10743        $row = $ilDB->fetchAssoc($result);
10744
10745        return $row;
10746    }
10747
10748    public function getMailNotificationType()
10749    {
10750        if ($this->mailnottype == 1) {
10751            return $this->mailnottype;
10752        } else {
10753            return 0;
10754        }
10755    }
10756
10757    public function setMailNotificationType($a_type)
10758    {
10759        if ($a_type == 1) {
10760            $this->mailnottype = 1;
10761        } else {
10762            $this->mailnottype = 0;
10763        }
10764    }
10765
10766    public function getExportSettings()
10767    {
10768        if ($this->exportsettings) {
10769            return $this->exportsettings;
10770        } else {
10771            return 0;
10772        }
10773    }
10774
10775    public function setExportSettings($a_settings)
10776    {
10777        if ($a_settings) {
10778            $this->exportsettings = $a_settings;
10779        } else {
10780            $this->exportsettings = 0;
10781        }
10782    }
10783
10784    public function getExportSettingsSingleChoiceShort()
10785    {
10786        if (($this->exportsettings & 1) > 0) {
10787            return true;
10788        } else {
10789            return false;
10790        }
10791    }
10792
10793    public function setExportSettingsSingleChoiceShort($a_settings)
10794    {
10795        if ($a_settings) {
10796            $this->exportsettings = $this->exportsettings | 1;
10797        } else {
10798            if ($this->getExportSettingsSingleChoiceShort()) {
10799                $this->exportsettings = $this->exportsettings ^ 1;
10800            }
10801        }
10802    }
10803
10804    public function getEnabledViewMode()
10805    {
10806        return $this->enabled_view_mode;
10807    }
10808
10809    public function setEnabledViewMode($mode)
10810    {
10811        $this->enabled_view_mode = $mode;
10812    }
10813
10814    public function setTemplate($template_id)
10815    {
10816        $this->template_id = (int) $template_id;
10817    }
10818
10819    public function getTemplate()
10820    {
10821        return $this->template_id;
10822    }
10823
10824    public function moveQuestionAfterOLD($previous_question_id, $new_question_id)
10825    {
10826        $new_array = array();
10827        $position = 1;
10828
10829        $query = 'SELECT question_fi  FROM tst_test_question WHERE test_fi = %s';
10830        $types = array('integer');
10831        $values = array($this->getTestId());
10832
10833        $new_question_id += 1;
10834
10835        global $DIC;
10836        $ilDB = $DIC['ilDB'];
10837        $inserted = false;
10838        $res = $ilDB->queryF($query, $types, $values);
10839        while ($row = $ilDB->fetchAssoc($res)) {
10840            $qid = $row['question_fi'];
10841
10842            if ($qid == $new_question_id) {
10843                continue;
10844            } elseif ($qid == $previous_question_id) {
10845                $new_array[$position++] = $qid;
10846                $new_array[$position++] = $new_question_id;
10847                $inserted = true;
10848            } else {
10849                $new_array[$position++] = $qid;
10850            }
10851        }
10852
10853        $update_query = 'UPDATE tst_test_question SET sequence = %s WHERE test_fi = %s AND question_fi = %s';
10854        $update_types = array('integer', 'integer', 'integer');
10855
10856        foreach ($new_array as $position => $qid) {
10857            $ilDB->manipulateF(
10858                $update_query,
10859                $update_types,
10860                $vals = array(
10861                            $position,
10862                            $this->getTestId(),
10863                            $qid
10864                        )
10865                );
10866        }
10867    }
10868
10869    public function isAnyInstantFeedbackOptionEnabled()
10870    {
10871        return (
10872                $this->getSpecificAnswerFeedback() || $this->getGenericAnswerFeedback() ||
10873                $this->getAnswerFeedbackPoints() || $this->getInstantFeedbackSolution()
10874            );
10875    }
10876
10877    public function getInstantFeedbackOptionsAsArray()
10878    {
10879        $values = array();
10880
10881        if ($this->getSpecificAnswerFeedback()) {
10882            $values[] = 'instant_feedback_specific';
10883        }
10884        if ($this->getGenericAnswerFeedback()) {
10885            $values[] = 'instant_feedback_generic';
10886        }
10887        if ($this->getAnswerFeedbackPoints()) {
10888            $values[] = 'instant_feedback_points';
10889        }
10890        if ($this->getInstantFeedbackSolution()) {
10891            $values[] = 'instant_feedback_solution';
10892        }
10893
10894        return $values;
10895    }
10896
10897    public function setInstantFeedbackOptionsByArray($options)
10898    {
10899        if (is_array($options)) {
10900            $this->setGenericAnswerFeedback(in_array('instant_feedback_generic', $options) ? 1 : 0);
10901            $this->setSpecificAnswerFeedback(in_array('instant_feedback_specific', $options) ? 1 : 0);
10902            $this->setAnswerFeedbackPoints(in_array('instant_feedback_points', $options) ? 1 : 0);
10903            $this->setInstantFeedbackSolution(in_array('instant_feedback_solution', $options) ? 1 : 0);
10904        } else {
10905            $this->setGenericAnswerFeedback(0);
10906            $this->setSpecificAnswerFeedback(0);
10907            $this->setAnswerFeedbackPoints(0);
10908            $this->setInstantFeedbackSolution(0);
10909        }
10910    }
10911
10912    public function setResultsPresentationOptionsByArray($options)
10913    {
10914        $setter = array(
10915                'pass_details' => 'setShowPassDetails',
10916                'solution_details' => 'setShowSolutionDetails',
10917                'solution_printview' => 'setShowSolutionPrintview',
10918                'solution_feedback' => 'setShowSolutionFeedback',
10919                'solution_answers_only' => 'setShowSolutionAnswersOnly',
10920                'solution_signature' => 'setShowSolutionSignature',
10921                'solution_suggested' => 'setShowSolutionSuggested',
10922                );
10923        foreach ($setter as $key => $setter) {
10924            if (in_array($key, $options)) {
10925                $this->$setter(1);
10926            } else {
10927                $this->$setter(0);
10928            }
10929        }
10930    }
10931
10932    public function getPoolUsage()
10933    {
10934        return (boolean) $this->poolUsage;
10935    }
10936
10937    public function setPoolUsage($usage)
10938    {
10939        $this->poolUsage = (boolean) $usage;
10940    }
10941
10942    /**
10943     * @return ilTestReindexedSequencePositionMap
10944     */
10945    public function reindexFixedQuestionOrdering()
10946    {
10947        global $DIC;
10948        $tree = $DIC['tree'];
10949        $db = $DIC['ilDB'];
10950        $pluginAdmin = $DIC['ilPluginAdmin'];
10951
10952        require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
10953        $qscFactory = new ilTestQuestionSetConfigFactory($tree, $db, $pluginAdmin, $this);
10954        $questionSetConfig = $qscFactory->getQuestionSetConfig();
10955
10956        /* @var ilTestFixedQuestionSetConfig $questionSetConfig */
10957        $reindexedSequencePositionMap = $questionSetConfig->reindexQuestionOrdering();
10958
10959        $this->loadQuestions();
10960
10961        return $reindexedSequencePositionMap;
10962    }
10963
10964    public function setQuestionOrderAndObligations($orders, $obligations)
10965    {
10966        global $DIC;
10967        $ilDB = $DIC['ilDB'];
10968
10969        asort($orders);
10970
10971        $i = 0;
10972
10973        foreach ($orders as $id => $position) {
10974            $i++;
10975
10976            $obligatory = (
10977                isset($obligations[$id]) && $obligations[$id] ? 1 : 0
10978            );
10979
10980            $query = "
10981				UPDATE		tst_test_question
10982				SET			sequence = %s,
10983							obligatory = %s
10984				WHERE		question_fi = %s
10985			";
10986
10987            $ilDB->manipulateF(
10988                $query,
10989                array('integer', 'integer', 'integer'),
10990                array($i, $obligatory, $id)
10991            );
10992        }
10993
10994        $this->loadQuestions();
10995    }
10996
10997    public function moveQuestionAfter($question_to_move, $question_before)
10998    {
10999        global $DIC;
11000        $ilDB = $DIC['ilDB'];
11001        //var_dump(func_get_args());
11002        if ($question_before) {
11003            $query = 'SELECT sequence, test_fi FROM tst_test_question WHERE question_fi = %s';
11004            $types = array('integer');
11005            $values = array($question_before);
11006            $rset = $ilDB->queryF($query, $types, $values);
11007        }
11008
11009        if (!$question_before || ($rset && !($row = $ilDB->fetchAssoc($rset)))) {
11010            $row = array(
11011            'sequence' => 0,
11012            'test_fi' => $this->getTestId(),
11013        );
11014        }
11015
11016        $update = 'UPDATE tst_test_question SET sequence = sequence + 1 WHERE sequence > %s AND test_fi = %s';
11017        $types = array('integer', 'integer');
11018        $values = array($row['sequence'], $row['test_fi']);
11019        $ilDB->manipulateF($update, $types, $values);
11020
11021        $update = 'UPDATE tst_test_question SET sequence = %s WHERE question_fi = %s';
11022        $types = array('integer', 'integer');
11023        $values = array($row['sequence'] + 1, $question_to_move);
11024        $ilDB->manipulateF($update, $types, $values);
11025
11026        $this->reindexFixedQuestionOrdering();
11027    }
11028
11029    public function hasQuestionsWithoutQuestionpool()
11030    {
11031        global $DIC;
11032        $ilDB = $DIC['ilDB'];
11033
11034        $questions = $this->getQuestionTitlesAndIndexes();
11035
11036        $IN_questions = $ilDB->in('q1.question_id', array_keys($questions), false, 'integer');
11037
11038        $query = "
11039			SELECT		count(q1.question_id) cnt
11040
11041			FROM		qpl_questions q1
11042
11043			INNER JOIN	qpl_questions q2
11044			ON			q2.question_id = q1.original_id
11045
11046			WHERE		$IN_questions
11047			AND		 	q1.obj_fi = q2.obj_fi
11048		";
11049
11050        $rset = $ilDB->query($query);
11051
11052        $row = $ilDB->fetchAssoc($rset);
11053
11054        return $row['cnt'] > 0;
11055    }
11056
11057    /**
11058     * Gather all finished tests for user
11059     *
11060     * @param int $a_user_id
11061     * @return array(test id => passed)
11062     */
11063    public static function _lookupFinishedUserTests($a_user_id)
11064    {
11065        global $DIC;
11066        $ilDB = $DIC['ilDB'];
11067
11068        $result = $ilDB->queryF(
11069            "SELECT test_fi,MAX(pass) AS pass FROM tst_active" .
11070            " JOIN tst_pass_result ON (tst_pass_result.active_fi = tst_active.active_id)" .
11071            " WHERE user_fi=%s" .
11072            " GROUP BY test_fi",
11073            array('integer', 'integer'),
11074            array($a_user_id, 1)
11075        );
11076        $all = array();
11077        while ($row = $ilDB->fetchAssoc($result)) {
11078            $obj_id = self::_getObjectIDFromTestID($row["test_fi"]);
11079            $all[$obj_id] = (bool) $row["pass"];
11080        }
11081        return $all;
11082    }
11083    public function getQuestions()
11084    {
11085        return $this->questions;
11086    }
11087
11088    public function isOnline()
11089    {
11090        return $this->online;
11091    }
11092
11093    public function setOnline($a_online = true)
11094    {
11095        $this->online = (bool) $a_online;
11096    }
11097
11098    /**
11099     * @return null
11100     */
11101    public function getOldOnlineStatus()
11102    {
11103        return $this->oldOnlineStatus;
11104    }
11105
11106    /**
11107     * @param null $oldOnlineStatus
11108     */
11109    public function setOldOnlineStatus($oldOnlineStatus)
11110    {
11111        $this->oldOnlineStatus = $oldOnlineStatus;
11112    }
11113
11114    public function setPrintBestSolutionWithResult($status)
11115    {
11116        $this->print_best_solution_with_result = (bool) $status;
11117    }
11118
11119    public function isBestSolutionPrintedWithResult()
11120    {
11121        return (bool) $this->print_best_solution_with_result;
11122    }
11123
11124    /**
11125     * returns the fact wether offering hints is enabled or not
11126     *
11127     * @return boolean
11128     */
11129    public function isOfferingQuestionHintsEnabled()
11130    {
11131        return $this->offeringQuestionHintsEnabled;
11132    }
11133
11134    /**
11135     * sets offering question hints enabled/disabled
11136     *
11137     * @param boolean $offeringQuestionHintsEnabled
11138     */
11139    public function setOfferingQuestionHintsEnabled($offeringQuestionHintsEnabled)
11140    {
11141        $this->offeringQuestionHintsEnabled = (bool) $offeringQuestionHintsEnabled;
11142    }
11143
11144    public function setActivationVisibility($a_value)
11145    {
11146        $this->activation_visibility = (bool) $a_value;
11147    }
11148
11149    public function getActivationVisibility()
11150    {
11151        return $this->activation_visibility;
11152    }
11153
11154    public function isActivationLimited()
11155    {
11156        return (bool) $this->activation_limited;
11157    }
11158
11159    public function setActivationLimited($a_value)
11160    {
11161        $this->activation_limited = (bool) $a_value;
11162    }
11163
11164    /* GET/SET for highscore feature */
11165
11166    /**
11167     * Sets if the highscore feature should be enabled.
11168     *
11169     * @param bool $a_enabled
11170     */
11171    public function setHighscoreEnabled($a_enabled)
11172    {
11173        $this->_highscore_enabled = (bool) $a_enabled;
11174    }
11175
11176    /**
11177     * Gets the setting which determines if the highscore feature is enabled.
11178     *
11179     * @return bool True, if highscore is enabled.
11180     */
11181    public function getHighscoreEnabled()
11182    {
11183        return (bool) $this->_highscore_enabled;
11184    }
11185
11186    /**
11187     * Sets if the highscores should be anonymized.
11188     *
11189     * Note: This setting will be overriden, if the test is globally anonymized.
11190     *
11191     * @param bool $a_anon
11192     */
11193    public function setHighscoreAnon($a_anon)
11194    {
11195        $this->_highscore_anon = (bool) $a_anon;
11196    }
11197
11198    /**
11199     * Gets if the highscores should be anonymized per setting.
11200     *
11201     * Note: This method will retrieve the setting as set by the user. If you want
11202     * to figure out, if the highscore is to be shown anonymized or not, with
11203     * consideration of the global anon switch you should @see isHighscoreAnon().
11204     *
11205     * @return bool True, if setting is to anonymize highscores.
11206     */
11207    public function getHighscoreAnon()
11208    {
11209        return (bool) $this->_highscore_anon;
11210    }
11211
11212    /**
11213     * Gets if the highscores should be displayed anonymized.
11214     *
11215     * Note: This method considers the global anonymity switch. If you need
11216     * access to the users setting, @see getHighscoreAnon()
11217     *
11218     * @return boolean True, if output is anonymized.
11219     */
11220    public function isHighscoreAnon()
11221    {
11222        if ($this->getAnonymity() == 1) {
11223            return true;
11224        } else {
11225            return (bool) $this->getHighscoreAnon();
11226        }
11227    }
11228
11229    /**
11230     * Sets if the date and time of the scores achievement should be displayed.
11231     *
11232     * @param bool $a_achieved_ts
11233     */
11234    public function setHighscoreAchievedTS($a_achieved_ts)
11235    {
11236        $this->_highscore_achieved_ts = (bool) $a_achieved_ts;
11237    }
11238
11239    /**
11240     * Returns if date and time of the scores achievement should be displayed.
11241     *
11242     * @return bool True, if column should be shown.
11243     */
11244    public function getHighscoreAchievedTS()
11245    {
11246        return (bool) $this->_highscore_achieved_ts;
11247    }
11248
11249    /**
11250     * Sets if the actual score should be displayed.
11251     *
11252     * @param bool $a_score
11253     */
11254    public function setHighscoreScore($a_score)
11255    {
11256        $this->_highscore_score = (bool) $a_score;
11257    }
11258
11259    /**
11260     * Gets if the score column should be shown.
11261     *
11262     * @return bool True, if score column should be shown.
11263     */
11264    public function getHighscoreScore()
11265    {
11266        return (bool) $this->_highscore_score;
11267    }
11268
11269    /**
11270     * Sets if the percentages of the scores pass should be shown.
11271     *
11272     * @param bool $a_percentage
11273     */
11274    public function setHighscorePercentage($a_percentage)
11275    {
11276        $this->_highscore_percentage = (bool) $a_percentage;
11277    }
11278
11279    /**
11280     * Gets if the percentage column should be shown.
11281     *
11282     * @return bool True, if percentage column should be shown.
11283     */
11284    public function getHighscorePercentage()
11285    {
11286        return (bool) $this->_highscore_percentage;
11287    }
11288
11289    /**
11290     * Sets if the number of requested hints should be shown.
11291     *
11292     * @param bool $a_hints
11293     */
11294    public function setHighscoreHints($a_hints)
11295    {
11296        $this->_highscore_hints = (bool) $a_hints;
11297    }
11298
11299    /**
11300     * Gets, if the column with the number of requested hints should be shown.
11301     *
11302     * @return bool True, if the hints-column should be shown.
11303     */
11304    public function getHighscoreHints()
11305    {
11306        return (bool) $this->_highscore_hints;
11307    }
11308
11309    /**
11310     * Sets if the workingtime of the scores should be shown.
11311     *
11312     * @param bool $a_wtime
11313     */
11314    public function setHighscoreWTime($a_wtime)
11315    {
11316        $this->_highscore_wtime = (bool) $a_wtime;
11317    }
11318
11319    /**
11320     * Gets if the column with the workingtime should be shown.
11321     *
11322     * @return bool True, if the workingtime column should be shown.
11323     */
11324    public function getHighscoreWTime()
11325    {
11326        return (bool) $this->_highscore_wtime;
11327    }
11328
11329    /**
11330     * Sets if the table with the own ranking should be shown.
11331     *
11332     * @param bool $a_own_table True, if table with own ranking should be shown.
11333     */
11334    public function setHighscoreOwnTable($a_own_table)
11335    {
11336        $this->_highscore_own_table = (bool) $a_own_table;
11337    }
11338
11339    /**
11340     * Gets if the own rankings table should be shown.
11341     *
11342     * @return bool True, if the own rankings table should be shown.
11343     */
11344    public function getHighscoreOwnTable()
11345    {
11346        return (bool) $this->_highscore_own_table;
11347    }
11348
11349    /**
11350     * Sets if the top-rankings table should be shown.
11351     *
11352     * @param bool $a_top_table
11353     */
11354    public function setHighscoreTopTable($a_top_table)
11355    {
11356        $this->_highscore_top_table = (bool) $a_top_table;
11357    }
11358
11359    /**
11360     * Gets, if the top-rankings table should be shown.
11361     *
11362     * @return bool True, if top-rankings table should be shown.
11363     */
11364    public function getHighscoreTopTable()
11365    {
11366        return (bool) $this->_highscore_top_table;
11367    }
11368
11369    /**
11370     * Sets the number of entries which are to be shown in the top-rankings
11371     * table.
11372     *
11373     * @param integer $a_top_num Number of entries in the top-rankings table.
11374     */
11375    public function setHighscoreTopNum($a_top_num)
11376    {
11377        $this->_highscore_top_num = (int) $a_top_num;
11378    }
11379
11380    /**
11381     * Gets the number of entries which are to be shown in the top-rankings table.
11382     * Default: 10 entries
11383     *
11384     * @param integer $a_retval Optional return value if nothing is set, defaults to 10.
11385     *
11386     * @return integer Number of entries to be shown in the top-rankings table.
11387     */
11388    public function getHighscoreTopNum($a_retval = 10)
11389    {
11390        $retval = $a_retval;
11391        if ((int) $this->_highscore_top_num != 0) {
11392            $retval = $this->_highscore_top_num;
11393        }
11394
11395        return $retval;
11396    }
11397
11398    /**
11399     * @return int
11400     */
11401    public function getHighscoreMode()
11402    {
11403        switch (true) {
11404            case $this->getHighscoreOwnTable() && $this->getHighscoreTopTable():
11405                return self::HIGHSCORE_SHOW_ALL_TABLES;
11406                break;
11407
11408            case $this->getHighscoreTopTable():
11409                return self::HIGHSCORE_SHOW_TOP_TABLE;
11410                break;
11411
11412            case $this->getHighscoreOwnTable():
11413            default:
11414                return self::HIGHSCORE_SHOW_OWN_TABLE;
11415                break;
11416        }
11417    }
11418
11419    /**
11420     * @param $mode int
11421     */
11422    public function setHighscoreMode($mode)
11423    {
11424        switch ($mode) {
11425            case self::HIGHSCORE_SHOW_ALL_TABLES:
11426                $this->setHighscoreTopTable(1);
11427                $this->setHighscoreOwnTable(1);
11428                break;
11429
11430            case self::HIGHSCORE_SHOW_TOP_TABLE:
11431                $this->setHighscoreTopTable(1);
11432                $this->setHighscoreOwnTable(0);
11433                break;
11434
11435            case self::HIGHSCORE_SHOW_OWN_TABLE:
11436            default:
11437                $this->setHighscoreTopTable(0);
11438                $this->setHighscoreOwnTable(1);
11439                break;
11440        }
11441    }
11442    /* End GET/SET for highscore feature*/
11443
11444    public function setSpecificAnswerFeedback($specific_answer_feedback)
11445    {
11446        switch ($specific_answer_feedback) {
11447            case 1:
11448                $this->specific_answer_feedback = 1;
11449                break;
11450            default:
11451                $this->specific_answer_feedback = 0;
11452                break;
11453        }
11454    }
11455
11456    public function getSpecificAnswerFeedback()
11457    {
11458        switch ($this->specific_answer_feedback) {
11459            case 1:
11460                return 1;
11461            default:
11462                return 0;
11463        }
11464    }
11465
11466    /**
11467     * sets obligations enabled/disabled
11468     *
11469     * @param boolean $obligationsEnabled
11470     */
11471    public function setObligationsEnabled($obligationsEnabled = true)
11472    {
11473        $this->obligationsEnabled = (bool) $obligationsEnabled;
11474    }
11475
11476    /**
11477     * returns the fact wether obligations are enabled or not
11478     *
11479     * @return boolean
11480     */
11481    public function areObligationsEnabled()
11482    {
11483        return (bool) $this->obligationsEnabled;
11484    }
11485
11486    /**
11487     * checks wether the obligation for question with given id is possible or not
11488     *
11489     * @param integer $questionId
11490     * @return boolean $obligationPossible
11491     */
11492    public static function isQuestionObligationPossible($questionId)
11493    {
11494        require_once('Modules/TestQuestionPool/classes/class.assQuestion.php');
11495
11496        $classConcreteQuestion = assQuestion::_getQuestionType($questionId);
11497
11498        assQuestion::_includeClass($classConcreteQuestion, 0);
11499
11500        // static binder is not at work yet (in PHP < 5.3)
11501        //$obligationPossible = $classConcreteQuestion::isObligationPossible();
11502        $obligationPossible = call_user_func(array($classConcreteQuestion, 'isObligationPossible'), $questionId);
11503
11504        return $obligationPossible;
11505    }
11506
11507    /**
11508     * checks wether the question with given id is marked as obligatory or not
11509     *
11510     * @param integer $questionId
11511     * @return boolean $obligatory
11512     */
11513    public static function isQuestionObligatory($question_id)
11514    {
11515        global $DIC;
11516        $ilDB = $DIC['ilDB'];
11517
11518        $rset = $ilDB->queryF('SELECT obligatory FROM tst_test_question WHERE question_fi = %s', array('integer'), array($question_id));
11519
11520        if ($row = $ilDB->fetchAssoc($rset)) {
11521            return (bool) $row['obligatory'];
11522        }
11523
11524        return false;
11525    }
11526
11527    /**
11528     * checks wether all questions marked as obligatory were answered
11529     * within the test pass with given testId, activeId and pass index
11530     *
11531     * @static
11532     * @access public
11533     * @global ilDBInterface $ilDB
11534     * @param integer $test_id
11535     * @param integer $active_id
11536     * @param integer $pass
11537     * @return boolean $allObligationsAnswered
11538     */
11539    public static function allObligationsAnswered($test_id, $active_id, $pass)
11540    {
11541        global $DIC;
11542        $ilDB = $DIC['ilDB'];
11543
11544        $rset = $ilDB->queryF(
11545            'SELECT obligations_answered FROM tst_pass_result WHERE active_fi = %s AND pass = %s',
11546            array('integer', 'integer'),
11547            array($active_id, $pass)
11548        );
11549
11550        if ($row = $ilDB->fetchAssoc($rset)) {
11551            return (bool) $row['obligations_answered'];
11552        }
11553
11554        return !self::hasObligations($test_id);
11555    }
11556
11557    /**
11558     * returns the fact wether the test with given test id
11559     * contains questions markes as obligatory or not
11560     *
11561     * @global ilDBInterface $ilDB
11562     * @param integer $test_id
11563     * @return boolean $hasObligations
11564     */
11565    public static function hasObligations($test_id)
11566    {
11567        global $DIC;
11568        $ilDB = $DIC['ilDB'];
11569
11570        $rset = $ilDB->queryF(
11571            'SELECT count(*) cnt FROM tst_test_question WHERE test_fi = %s AND obligatory = 1',
11572            array('integer'),
11573            array($test_id)
11574        );
11575
11576        $row = $ilDB->fetchAssoc($rset);
11577
11578        return (bool) $row['cnt'] > 0;
11579    }
11580
11581    public function setAutosave($autosave)
11582    {
11583        $this->autosave = $autosave;
11584    }
11585
11586    public function getAutosave()
11587    {
11588        return $this->autosave;
11589    }
11590
11591    public function setAutosaveIval($autosave_ival)
11592    {
11593        $this->autosave_ival = $autosave_ival;
11594    }
11595
11596    public function getAutosaveIval()
11597    {
11598        return $this->autosave_ival;
11599    }
11600
11601    /**
11602     * getter for the test setting passDeletionAllowed
11603     *
11604     * @return integer
11605     */
11606    public function isPassDeletionAllowed()
11607    {
11608        return $this->passDeletionAllowed;
11609    }
11610
11611    /**
11612     * setter for the test setting passDeletionAllowed
11613     *
11614     * @return integer
11615     */
11616    public function setPassDeletionAllowed($passDeletionAllowed)
11617    {
11618        $this->passDeletionAllowed = (bool) $passDeletionAllowed;
11619    }
11620
11621    #region Examview / PDF Examview
11622    /**
11623     * @param boolean $show_examview_html
11624     */
11625    public function setShowExamviewHtml($show_examview_html)
11626    {
11627        $this->show_examview_html = $show_examview_html;
11628    }
11629
11630    /**
11631     * @return boolean
11632     */
11633    public function getShowExamviewHtml()
11634    {
11635        return $this->show_examview_html;
11636    }
11637
11638    /**
11639     * @param boolean $show_examview_pdf
11640     */
11641    public function setShowExamviewPdf($show_examview_pdf)
11642    {
11643        $this->show_examview_pdf = $show_examview_pdf;
11644    }
11645
11646    /**
11647     * @return boolean
11648     */
11649    public function getShowExamviewPdf()
11650    {
11651        return $this->show_examview_pdf;
11652    }
11653
11654    /**
11655     * @param boolean $enable_examview
11656     */
11657    public function setEnableExamview($enable_examview)
11658    {
11659        $this->enable_examview = $enable_examview;
11660    }
11661
11662    /**
11663     * @return boolean
11664     */
11665    public function getEnableExamview()
11666    {
11667        return $this->enable_examview;
11668    }
11669
11670    #endregion
11671
11672    public function setActivationStartingTime($starting_time = null)
11673    {
11674        $this->activation_starting_time = $starting_time;
11675    }
11676
11677    public function setActivationEndingTime($ending_time = null)
11678    {
11679        $this->activation_ending_time = $ending_time;
11680    }
11681
11682    public function getActivationStartingTime()
11683    {
11684        return (strlen($this->activation_starting_time)) ? $this->activation_starting_time : null;
11685    }
11686
11687    public function getActivationEndingTime()
11688    {
11689        return (strlen($this->activation_ending_time)) ? $this->activation_ending_time : null;
11690    }
11691
11692    /**
11693     * Note, this function should only be used if absolutely necessary, since it perform joins on tables that
11694     * tend to grow huge and returns vast amount of data. If possible, use getStartingTimeOfUser($active_id) instead
11695     *
11696     * @return array
11697     */
11698    public function getStartingTimeOfParticipants()
11699    {
11700        global $DIC;
11701        $ilDB = $DIC['ilDB'];
11702
11703        $times = array();
11704        $result = $ilDB->queryF("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",
11705            array('integer'),
11706            array($this->getTestId())
11707        );
11708	while ($row = $ilDB->fetchAssoc($result)) {
11709            $times[$row['active_fi']] = $row['started'];
11710        }
11711        return $times;
11712    }
11713
11714    public function getTimeExtensionsOfParticipants()
11715    {
11716        global $DIC;
11717        $ilDB = $DIC['ilDB'];
11718
11719        $times = array();
11720        $result = $ilDB->queryF(
11721            "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",
11722            array('integer'),
11723            array($this->getTestId())
11724        );
11725        while ($row = $ilDB->fetchAssoc($result)) {
11726            $times[$row['active_fi']] = $row['additionaltime'];
11727        }
11728        return $times;
11729    }
11730
11731    public function getExtraTime($active_id)
11732    {
11733        global $DIC;
11734        $ilDB = $DIC['ilDB'];
11735
11736        $result = $ilDB->queryF(
11737            "SELECT additionaltime FROM tst_addtime WHERE active_fi = %s",
11738            array('integer'),
11739            array($active_id)
11740        );
11741        if ($result->numRows() > 0) {
11742            $row = $ilDB->fetchAssoc($result);
11743            return $row['additionaltime'];
11744        }
11745        return 0;
11746    }
11747
11748    public function addExtraTime($active_id, $minutes)
11749    {
11750        global $DIC; /* @var ILIAS\DI\Container $DIC */
11751
11752        require_once 'Modules/Test/classes/class.ilTestParticipantData.php';
11753        $participantData = new ilTestParticipantData($DIC->database(), $DIC->language());
11754
11755        $participantData->setParticipantAccessFilter(
11756            ilTestParticipantAccessFilter::getManageParticipantsUserFilter($this->getRefId())
11757        );
11758
11759        if ($active_id) {
11760            $participantData->setActiveIdsFilter(array($active_id));
11761        }
11762
11763        $participantData->load($this->getTestId());
11764
11765        foreach ($participantData->getActiveIds() as $active_id) {
11766            $result = $DIC->database()->queryF(
11767                "SELECT active_fi FROM tst_addtime WHERE active_fi = %s",
11768                array('integer'),
11769                array($active_id)
11770            );
11771
11772            if ($result->numRows() > 0) {
11773                $DIC->database()->manipulateF(
11774                    "DELETE FROM tst_addtime WHERE active_fi = %s",
11775                    array('integer'),
11776                    array($active_id)
11777                );
11778            }
11779
11780            $DIC->database()->manipulateF(
11781                "UPDATE tst_active SET tries = %s, submitted = %s, submittimestamp = %s WHERE active_id = %s",
11782                array('integer','integer','timestamp','integer'),
11783                array(0, 0, null, $active_id)
11784            );
11785
11786            $DIC->database()->manipulateF(
11787                "INSERT INTO tst_addtime (active_fi, additionaltime, tstamp) VALUES (%s, %s, %s)",
11788                array('integer','integer','integer'),
11789                array($active_id, $minutes, time())
11790            );
11791
11792            require_once 'Modules/Test/classes/class.ilObjAssessmentFolder.php';
11793            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
11794                $this->logAction(sprintf($this->lng->txtlng("assessment", "log_added_extratime", ilObjAssessmentFolder::_getLogLanguage()), $minutes, $active_id));
11795            }
11796        }
11797    }
11798
11799    /**
11800     * @param boolean $enable_archiving
11801     *
11802     * @return $this
11803     */
11804    public function setEnableArchiving($enable_archiving)
11805    {
11806        $this->enable_archiving = $enable_archiving;
11807        return $this;
11808    }
11809
11810    /**
11811     * @return boolean
11812     */
11813    public function getEnableArchiving()
11814    {
11815        return $this->enable_archiving;
11816    }
11817
11818    public function getMaxPassOfTest()
11819    {
11820        /**
11821         * @var $ilDB ilDBInterface
11822         */
11823        global $DIC;
11824        $ilDB = $DIC['ilDB'];
11825
11826        $query = '
11827			SELECT MAX(tst_pass_result.pass) + 1 max_res
11828			FROM tst_pass_result
11829			INNER JOIN tst_active ON tst_active.active_id = tst_pass_result.active_fi
11830			WHERE test_fi = ' . $ilDB->quote($this->getTestId(), 'integer') . '
11831		';
11832        $res = $ilDB->query($query);
11833        $data = $ilDB->fetchAssoc($res);
11834        return (int) $data['max_res'];
11835    }
11836
11837    /**
11838     * @param $active_id
11839     * @param $pass
11840     * @return array
11841     */
11842    public static function lookupExamId($active_id, $pass)
11843    {
11844        global $DIC;
11845        $ilDB = $DIC['ilDB'];
11846
11847        $exam_id_query = 'SELECT exam_id FROM tst_pass_result WHERE active_fi = %s AND pass = %s';
11848        $exam_id_result = $ilDB->queryF($exam_id_query, array( 'integer', 'integer' ), array( $active_id, $pass ));
11849        if ($ilDB->numRows($exam_id_result) == 1) {
11850            $exam_id_row = $ilDB->fetchAssoc($exam_id_result);
11851
11852            if ($exam_id_row['exam_id'] != null) {
11853                return $exam_id_row['exam_id'];
11854            }
11855        }
11856
11857        return null;
11858    }
11859
11860    /**
11861     * @param  $active_id
11862     * @param  $pass
11863     * @param  $test_obj_id
11864     * @return array
11865     */
11866    public static function buildExamId($active_id, $pass, $test_obj_id = null)
11867    {
11868        global $DIC;
11869        $ilSetting = $DIC['ilSetting'];
11870
11871        $inst_id = $ilSetting->get('inst_id', null);
11872
11873        if ($test_obj_id === null) {
11874            $obj_id = self::_getObjectIDFromActiveID($active_id);
11875        } else {
11876            $obj_id = $test_obj_id;
11877        }
11878
11879        $examId = 'I' . $inst_id . '_T' . $obj_id . '_A' . $active_id . '_P' . $pass;
11880
11881        return $examId;
11882    }
11883
11884    public function setShowExamIdInTestPassEnabled($show_exam_id_in_test_pass_enabled)
11885    {
11886        $this->show_exam_id_in_test_pass_enabled = $show_exam_id_in_test_pass_enabled;
11887    }
11888
11889    public function isShowExamIdInTestPassEnabled()
11890    {
11891        return $this->show_exam_id_in_test_pass_enabled;
11892    }
11893
11894    /**
11895     * @param boolean $show_exam_id
11896     */
11897    public function setShowExamIdInTestResultsEnabled($show_exam_id_in_test_results_enabled)
11898    {
11899        $this->show_exam_id_in_test_results_enabled = $show_exam_id_in_test_results_enabled;
11900    }
11901
11902    /**
11903     * @return boolean
11904     */
11905    public function isShowExamIdInTestResultsEnabled()
11906    {
11907        return $this->show_exam_id_in_test_results_enabled;
11908    }
11909
11910    /**
11911     * @param boolean $sign_submission
11912     */
11913    public function setSignSubmission($sign_submission)
11914    {
11915        $this->sign_submission = $sign_submission;
11916    }
11917
11918    /**
11919     * @return boolean
11920     */
11921    public function getSignSubmission()
11922    {
11923        return $this->sign_submission;
11924    }
11925
11926    /**
11927     * @param int availability of the special character selector
11928     */
11929    public function setCharSelectorAvailability($availability)
11930    {
11931        $this->char_selector_availability = (int) $availability;
11932    }
11933
11934    /**
11935     * @return int	availability of the special character selector
11936     */
11937    public function getCharSelectorAvailability()
11938    {
11939        return (int) $this->char_selector_availability;
11940    }
11941
11942    /**
11943     * @param string	definition of the special character selector
11944     */
11945    public function setCharSelectorDefinition($definition = '')
11946    {
11947        $this->char_selector_definition = $definition;
11948    }
11949
11950    /**
11951     * @return string	definition of the special character selector
11952     */
11953    public function getCharSelectorDefinition()
11954    {
11955        return $this->char_selector_definition;
11956    }
11957
11958
11959    /**
11960     * setter for question set type
11961     *
11962     * @param string $questionSetType
11963     */
11964    public function setQuestionSetType($questionSetType)
11965    {
11966        $this->questionSetType = $questionSetType;
11967    }
11968
11969    /**
11970     * getter for question set type
11971     *
11972     * @return string $questionSetType
11973     */
11974    public function getQuestionSetType()
11975    {
11976        return $this->questionSetType;
11977    }
11978
11979    /**
11980     * lookup-er for question set type
11981     *
11982     * @global ilDBInterface $ilDB
11983     * @param integer $objId
11984     * @return string $questionSetType
11985     */
11986    public static function lookupQuestionSetType($objId)
11987    {
11988        global $DIC;
11989        $ilDB = $DIC['ilDB'];
11990
11991        $query = "SELECT question_set_type FROM tst_tests WHERE obj_fi = %s";
11992
11993        $res = $ilDB->queryF($query, array('integer'), array($objId));
11994
11995        $questionSetType = null;
11996
11997        while ($row = $ilDB->fetchAssoc($res)) {
11998            $questionSetType = $row['question_set_type'];
11999        }
12000
12001        return $questionSetType;
12002    }
12003
12004    /**
12005     * Returns the fact wether this test is a fixed question set test or not
12006     *
12007     * @return boolean $isFixedTest
12008     */
12009    public function isFixedTest()
12010    {
12011        return $this->getQuestionSetType() == self::QUESTION_SET_TYPE_FIXED;
12012    }
12013
12014    /**
12015     * Returns the fact wether this test is a random questions test or not
12016     *
12017     * @return boolean $isRandomTest
12018     */
12019    public function isRandomTest()
12020    {
12021        return $this->getQuestionSetType() == self::QUESTION_SET_TYPE_RANDOM;
12022    }
12023
12024    /**
12025     * Returns the fact wether this test is a dynamic question set test or not
12026     *
12027     * @return boolean $isDynamicTest
12028     */
12029    public function isDynamicTest()
12030    {
12031        return $this->getQuestionSetType() == self::QUESTION_SET_TYPE_DYNAMIC;
12032    }
12033
12034    /**
12035     * Returns the fact wether the test with passed obj id is a random questions test or not
12036     *
12037     * @param integer $a_obj_id
12038     * @return boolean $isRandomTest
12039     * @deprecated
12040     */
12041    public static function _lookupRandomTest($a_obj_id)
12042    {
12043        return self::lookupQuestionSetType($a_obj_id) == self::QUESTION_SET_TYPE_RANDOM;
12044    }
12045
12046    public function getQuestionSetTypeTranslation(ilLanguage $lng, $questionSetType)
12047    {
12048        switch ($questionSetType) {
12049            case ilObjTest::QUESTION_SET_TYPE_FIXED:
12050                return $lng->txt('tst_question_set_type_fixed');
12051
12052            case ilObjTest::QUESTION_SET_TYPE_RANDOM:
12053                return $lng->txt('tst_question_set_type_random');
12054
12055            case ilObjTest::QUESTION_SET_TYPE_DYNAMIC:
12056                return $lng->txt('tst_question_set_type_dynamic');
12057        }
12058
12059        throw new ilTestException('invalid question set type value given: ' . $questionSetType);
12060    }
12061
12062    public function participantDataExist()
12063    {
12064        if ($this->participantDataExist === null) {
12065            $this->participantDataExist = (bool) $this->evalTotalPersons();
12066        }
12067
12068        return $this->participantDataExist;
12069    }
12070
12071    public function recalculateScores($preserve_manscoring = false)
12072    {
12073        require_once 'class.ilTestScoring.php';
12074        $scoring = new ilTestScoring($this);
12075        $scoring->setPreserveManualScores($preserve_manscoring);
12076        $scoring->recalculateSolutions();
12077    }
12078
12079    public static function getPoolQuestionChangeListeners(ilDBInterface $db, $poolObjId)
12080    {
12081        require_once 'Modules/Test/classes/class.ilObjTestDynamicQuestionSetConfig.php';
12082
12083        $questionChangeListeners = array(
12084            ilObjTestDynamicQuestionSetConfig::getPoolQuestionChangeListener($db, $poolObjId)
12085        );
12086
12087        return $questionChangeListeners;
12088    }
12089
12090    public static function getTestObjIdsWithActiveForUserId($userId)
12091    {
12092        global $DIC;
12093        $ilDB = $DIC['ilDB'];
12094
12095        $query = "
12096			SELECT obj_fi
12097			FROM tst_active
12098			INNER JOIN tst_tests
12099			ON test_id = test_fi
12100			WHERE user_fi = %s
12101		";
12102
12103        $res = $ilDB->queryF($query, array('integer'), array($userId));
12104
12105        $objIds = array();
12106
12107        while ($row = $ilDB->fetchAssoc($res)) {
12108            $objIds[] = (int) $row['obj_fi'];
12109        }
12110
12111        return $objIds;
12112    }
12113
12114    public function setSkillServiceEnabled($skillServiceEnabled)
12115    {
12116        $this->skillServiceEnabled = $skillServiceEnabled;
12117    }
12118
12119    public function isSkillServiceEnabled()
12120    {
12121        return $this->skillServiceEnabled;
12122    }
12123
12124    public function setResultFilterTaxIds($resultFilterTaxIds)
12125    {
12126        $this->resultFilterTaxIds = $resultFilterTaxIds;
12127    }
12128
12129    public function getResultFilterTaxIds()
12130    {
12131        return $this->resultFilterTaxIds;
12132    }
12133
12134    public function isSkillServiceToBeConsidered()
12135    {
12136        if (!$this->isSkillServiceEnabled()) {
12137            return false;
12138        }
12139
12140        if (!self::isSkillManagementGloballyActivated()) {
12141            return false;
12142        }
12143
12144        return true;
12145    }
12146
12147    private static $isSkillManagementGloballyActivated = null;
12148
12149    public static function isSkillManagementGloballyActivated()
12150    {
12151        if (self::$isSkillManagementGloballyActivated === null) {
12152            include_once 'Services/Skill/classes/class.ilSkillManagementSettings.php';
12153            $skmgSet = new ilSkillManagementSettings();
12154
12155            self::$isSkillManagementGloballyActivated = $skmgSet->isActivated();
12156        }
12157
12158        return self::$isSkillManagementGloballyActivated;
12159    }
12160
12161    public function setShowGradingStatusEnabled($showGradingStatusEnabled)
12162    {
12163        $this->showGradingStatusEnabled = $showGradingStatusEnabled;
12164    }
12165
12166    public function isShowGradingStatusEnabled()
12167    {
12168        return $this->showGradingStatusEnabled;
12169    }
12170
12171    public function setShowGradingMarkEnabled($showGradingMarkEnabled)
12172    {
12173        $this->showGradingMarkEnabled = $showGradingMarkEnabled;
12174    }
12175
12176
12177    public function isShowGradingMarkEnabled()
12178    {
12179        return $this->showGradingMarkEnabled;
12180    }
12181
12182    public function setFollowupQuestionAnswerFixationEnabled($followupQuestionAnswerFixationEnabled)
12183    {
12184        $this->followupQuestionAnswerFixationEnabled = $followupQuestionAnswerFixationEnabled;
12185    }
12186
12187    public function isFollowupQuestionAnswerFixationEnabled()
12188    {
12189        return $this->followupQuestionAnswerFixationEnabled;
12190    }
12191
12192    public function setInstantFeedbackAnswerFixationEnabled($instantFeedbackAnswerFixationEnabled)
12193    {
12194        $this->instantFeedbackAnswerFixationEnabled = $instantFeedbackAnswerFixationEnabled;
12195    }
12196
12197    public function isInstantFeedbackAnswerFixationEnabled()
12198    {
12199        return $this->instantFeedbackAnswerFixationEnabled;
12200    }
12201
12202    /**
12203     * @return boolean
12204     */
12205    public function isForceInstantFeedbackEnabled()
12206    {
12207        return $this->forceInstantFeedbackEnabled;
12208    }
12209
12210    /**
12211     * @param boolean $forceInstantFeedbackEnabled
12212     */
12213    public function setForceInstantFeedbackEnabled($forceInstantFeedbackEnabled)
12214    {
12215        $this->forceInstantFeedbackEnabled = $forceInstantFeedbackEnabled;
12216    }
12217
12218    public static function ensureParticipantsLastActivePassFinished($testObjId, $userId, $a_force_new_run = false)
12219    {
12220        global $DIC;
12221        $ilDB = $DIC['ilDB'];
12222        $lng = $DIC['lng'];
12223        $ilPluginAdmin = $DIC['ilPluginAdmin'];
12224
12225        /* @var ilObjTest $testOBJ */
12226
12227        $testOBJ = ilObjectFactory::getInstanceByRefId($testObjId, false);
12228
12229        $activeId = $testOBJ->getActiveIdOfUser($userId);
12230
12231        require_once 'Modules/Test/classes/class.ilTestSessionFactory.php';
12232        $testSessionFactory = new ilTestSessionFactory($testOBJ);
12233
12234        require_once 'Modules/Test/classes/class.ilTestSequenceFactory.php';
12235        $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $ilPluginAdmin, $testOBJ);
12236
12237        $testSession = $testSessionFactory->getSession($activeId);
12238        $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($activeId, $testSession->getPass());
12239        $testSequence->loadFromDb();
12240
12241        // begin-patch lok changed smeyer
12242        if ($a_force_new_run) {
12243            if ($testSequence->hasSequence()) {
12244                $testSession->increasePass();
12245            }
12246            $testSession->setLastSequence(0);
12247            $testSession->saveToDb();
12248        }
12249        // end-patch lok
12250    }
12251
12252    public static function isParticipantsLastPassActive($testRefId, $userId)
12253    {
12254        global $DIC;
12255        $ilDB = $DIC['ilDB'];
12256        $lng = $DIC['lng'];
12257        $ilPluginAdmin = $DIC['ilPluginAdmin'];
12258
12259        /* @var ilObjTest $testOBJ */
12260
12261        $testOBJ = ilObjectFactory::getInstanceByRefId($testRefId, false);
12262
12263
12264        $activeId = $testOBJ->getActiveIdOfUser($userId);
12265
12266        require_once 'Modules/Test/classes/class.ilTestSessionFactory.php';
12267        $testSessionFactory = new ilTestSessionFactory($testOBJ);
12268        // Added temporarily bugfix smeyer
12269        $testSessionFactory->reset();
12270
12271        require_once 'Modules/Test/classes/class.ilTestSequenceFactory.php';
12272        $testSequenceFactory = new ilTestSequenceFactory($ilDB, $lng, $ilPluginAdmin, $testOBJ);
12273
12274        $testSession = $testSessionFactory->getSession($activeId);
12275        $testSequence = $testSequenceFactory->getSequenceByActiveIdAndPass($activeId, $testSession->getPass());
12276        $testSequence->loadFromDb();
12277
12278        return $testSequence->hasSequence();
12279    }
12280
12281    /**
12282     * @return boolean
12283     */
12284    public function isTestFinalBroken()
12285    {
12286        return $this->testFinalBroken;
12287    }
12288
12289    /**
12290     * @param boolean $testFinalBroken
12291     */
12292    public function setTestFinalBroken($testFinalBroken)
12293    {
12294        $this->testFinalBroken = $testFinalBroken;
12295    }
12296
12297    public function adjustTestSequence()
12298    {
12299        /**
12300         * @var $ilDB ilDB
12301         */
12302        global $DIC;
12303        $ilDB = $DIC['ilDB'];
12304
12305        $query = "
12306			SELECT COUNT(test_question_id) cnt
12307			FROM tst_test_question
12308			WHERE test_fi = %s
12309			ORDER BY sequence
12310		";
12311
12312        $questRes = $ilDB->queryF($query, array('integer'), array($this->getTestId()));
12313
12314        $row = $ilDB->fetchAssoc($questRes);
12315        $questCount = $row['cnt'];
12316
12317        if ($this->getShuffleQuestions()) {
12318            $query = "
12319				SELECT tseq.*
12320				FROM tst_active tac
12321				INNER JOIN tst_sequence tseq
12322					ON tseq.active_fi = tac.active_id
12323				WHERE tac.test_fi = %s
12324			";
12325
12326            $partRes = $ilDB->queryF(
12327                $query,
12328                array('integer'),
12329                array($this->getTestId())
12330            );
12331
12332            while ($row = $ilDB->fetchAssoc($partRes)) {
12333                $sequence = @unserialize($row['sequence']);
12334
12335                if (!$sequence) {
12336                    $sequence = array();
12337                }
12338
12339                $sequence = array_filter($sequence, function ($value) use ($questCount) {
12340                    return $value <= $questCount;
12341                });
12342
12343                $num_seq = count($sequence);
12344                if ($questCount > $num_seq) {
12345                    $diff = $questCount - $num_seq;
12346                    for ($i = 1; $i <= $diff; $i++) {
12347                        $sequence[$num_seq + $i - 1] = $num_seq + $i;
12348                    }
12349                }
12350
12351                $new_sequence = serialize($sequence);
12352
12353                $ilDB->update('tst_sequence', array(
12354                    'sequence' => array('clob', $new_sequence)
12355                ), array(
12356                    'active_fi' => array('integer', $row['active_fi']),
12357                    'pass' => array('integer', $row['pass'])
12358                ));
12359            }
12360        } else {
12361            $new_sequence = serialize($questCount > 0 ? range(1, $questCount) : array());
12362
12363            $query = "
12364				SELECT tseq.*
12365				FROM tst_active tac
12366				INNER JOIN tst_sequence tseq
12367					ON tseq.active_fi = tac.active_id
12368				WHERE tac.test_fi = %s
12369			";
12370
12371            $part_rest = $ilDB->queryF(
12372                $query,
12373                array('integer'),
12374                array($this->getTestId())
12375            );
12376
12377            while ($row = $ilDB->fetchAssoc($part_rest)) {
12378                $ilDB->update('tst_sequence', array(
12379                    'sequence' => array('clob', $new_sequence)
12380                ), array(
12381                    'active_fi' => array('integer', $row['active_fi']),
12382                    'pass' => array('integer', $row['pass'])
12383                ));
12384            }
12385        }
12386    }
12387}
12388