1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once './Modules/Test/classes/inc.AssessmentConstants.php';
5require_once 'Modules/Test/classes/class.ilTestExpressPage.php';
6require_once 'Modules/TestQuestionPool/exceptions/class.ilTestQuestionPoolException.php';
7require_once 'Modules/Test/classes/class.ilTestExpressPage.php';
8require_once 'Modules/Test/classes/class.ilObjAssessmentFolder.php';
9
10/**
11* Basic GUI class for assessment questions
12*
13* The assQuestionGUI class encapsulates basic GUI functions for assessment questions.
14*
15* @author		Helmut Schottmüller <helmut.schottmueller@mac.com>
16* @author		Björn Heyser <bheyser@databay.de>
17* @version		$Id$
18* @ingroup		ModulesTestQuestionPool
19*/
20abstract class assQuestionGUI
21{
22    const FORM_MODE_EDIT = 'edit';
23    const FORM_MODE_ADJUST = 'adjust';
24
25    const FORM_ENCODING_URLENCODE = 'application/x-www-form-urlencoded';
26    const FORM_ENCODING_MULTIPART = 'multipart/form-data';
27
28    const SESSION_PREVIEW_DATA_BASE_INDEX = 'ilAssQuestionPreviewAnswers';
29
30    /**
31    * Question object
32    *
33    * A reference to the matching question object
34    *
35    * @var assQuestion
36    */
37    public $object;
38
39    /** @var ilGlobalTemplateInterface */
40    public $tpl;
41    public $lng;
42    public $error;
43    public $errormessage;
44
45    /**
46     * sequence number in test
47     */
48    public $sequence_no;
49    /**
50     * question count in test
51     */
52    public $question_count;
53
54    private $taxonomyIds = array();
55
56    private $targetGuiClass = null;
57
58    private $questionActionCmd = 'handleQuestionAction';
59
60    /**
61     * @var ilQuestionHeaderBlockBuilder
62     */
63    private $questionHeaderBlockBuilder;
64
65    /**
66     * @var ilTestQuestionNavigationGUI
67     */
68    private $navigationGUI;
69
70    const PRESENTATION_CONTEXT_TEST = 'pContextTest';
71    const PRESENTATION_CONTEXT_RESULTS = 'pContextResults';
72
73    /**
74     * @var string
75     */
76    private $presentationContext = null;
77
78    const RENDER_PURPOSE_PLAYBACK = 'renderPurposePlayback';
79    const RENDER_PURPOSE_DEMOPLAY = 'renderPurposeDemoplay';
80    const RENDER_PURPOSE_PREVIEW = 'renderPurposePreview';
81    const RENDER_PURPOSE_PRINT_PDF = 'renderPurposePrintPdf';
82    const RENDER_PURPOSE_INPUT_VALUE = 'renderPurposeInputValue';
83
84    /**
85     * @var string
86     */
87    private $renderPurpose = self::RENDER_PURPOSE_PLAYBACK;
88
89    const EDIT_CONTEXT_AUTHORING = 'authoring';
90    const EDIT_CONTEXT_ADJUSTMENT = 'adjustment';
91
92    /**
93     * @var string
94     */
95    private $editContext = self::EDIT_CONTEXT_AUTHORING;
96
97    // hey: prevPassSolutions - flag to indicate that a previous answer is shown
98    /**
99     * @var bool
100     */
101    private $previousSolutionPrefilled = false;
102    // hey.
103
104    /**
105     * @var \ilPropertyFormGUI
106     */
107    protected $editForm;
108
109    /**
110     * Prefer the intermediate solution for solution output
111     * @var bool
112     */
113    protected $use_intermediate_solution = false;
114
115    /**
116    * assQuestionGUI constructor
117    */
118    public function __construct()
119    {
120        global $DIC;
121        $lng = $DIC['lng'];
122        $tpl = $DIC['tpl'];
123        $ilCtrl = $DIC['ilCtrl'];
124
125        $this->lng = &$lng;
126        $this->tpl = &$tpl;
127        $this->ctrl = &$ilCtrl;
128        $this->ctrl->saveParameter($this, "q_id");
129        $this->ctrl->saveParameter($this, "prev_qid");
130        $this->ctrl->saveParameter($this, "calling_test");
131        $this->ctrl->saveParameter($this, "calling_consumer");
132        $this->ctrl->saveParameter($this, "consumer_context");
133        $this->ctrl->saveParameterByClass('ilAssQuestionPageGUI', 'test_express_mode');
134        $this->ctrl->saveParameterByClass('ilAssQuestionPageGUI', 'calling_consumer');
135        $this->ctrl->saveParameterByClass('ilAssQuestionPageGUI', 'consumer_context');
136        $this->ctrl->saveParameterByClass('ilobjquestionpoolgui', 'test_express_mode');
137        $this->ctrl->saveParameterByClass('ilobjquestionpoolgui', 'calling_consumer');
138        $this->ctrl->saveParameterByClass('ilobjquestionpoolgui', 'consumer_context');
139
140        include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
141        $this->errormessage = $this->lng->txt("fill_out_all_required_fields");
142
143        $this->selfassessmenteditingmode = false;
144        $this->new_id_listeners = array();
145        $this->new_id_listener_cnt = 0;
146
147        $this->navigationGUI = null;
148    }
149
150    /**
151     * this method can be overwritten per question type
152     *
153     * @return bool
154     */
155    public function hasInlineFeedback()
156    {
157        return false;
158    }
159
160    public function addHeaderAction()
161    {
162        global $DIC; /* @var ILIAS\DI\Container $DIC */
163
164        $DIC->ui()->mainTemplate()->setVariable(
165            "HEAD_ACTION",
166            $this->getHeaderAction()
167        );
168
169        $notesUrl = $this->ctrl->getLinkTargetByClass(
170            array("ilcommonactiondispatchergui", "ilnotegui"),
171            "",
172            "",
173            true,
174            false
175        );
176
177        ilNoteGUI::initJavascript($notesUrl, IL_NOTE_PUBLIC, $DIC->ui()->mainTemplate());
178
179        $redrawActionsUrl = $DIC->ctrl()->getLinkTarget($this, 'redrawHeaderAction', '', true);
180        $DIC->ui()->mainTemplate()->addOnLoadCode("il.Object.setRedrawAHUrl('$redrawActionsUrl');");
181    }
182
183    public function redrawHeaderAction()
184    {
185        global $DIC; /* @var ILIAS\DI\Container $DIC */
186        echo $this->getHeaderAction() . $DIC->ui()->mainTemplate()->getOnLoadCodeForAsynch();
187        exit;
188    }
189
190    public function getHeaderAction()
191    {
192        global $DIC; /* @var ILIAS\DI\Container $DIC */
193        /* @var ilObjectDataCache $ilObjDataCache */
194        $ilObjDataCache = $DIC['ilObjDataCache'];
195
196        $parentObjType = $ilObjDataCache->lookupType($this->object->getObjId());
197
198        $dispatcher = new ilCommonActionDispatcherGUI(
199            ilCommonActionDispatcherGUI::TYPE_REPOSITORY,
200            $DIC->access(),
201            $parentObjType,
202            $_GET["ref_id"],
203            $this->object->getObjId()
204        );
205
206        $dispatcher->setSubObject("quest", $this->object->getId());
207
208        $ha = $dispatcher->initHeaderAction();
209        $ha->enableComments(true, false);
210
211        return $ha->getHeaderAction($DIC->ui()->mainTemplate());
212    }
213
214    public function getNotesHTML()
215    {
216        $notesGUI = new ilNoteGUI($this->object->getObjId(), $this->object->getId(), 'quest');
217        $notesGUI->enablePublicNotes(true);
218        $notesGUI->enablePublicNotesDeletion(true);
219
220        return $notesGUI->getNotesHTML();
221    }
222
223    /**
224    * execute command
225    */
226    public function executeCommand()
227    {
228        global $DIC; /* @var \ILIAS\DI\Container $DIC */
229        $ilHelp = $DIC['ilHelp']; /* @var ilHelpGUI $ilHelp */
230        $ilHelp->setScreenIdComponent('qpl');
231
232        $cmd = $this->ctrl->getCmd("editQuestion");
233        $next_class = $this->ctrl->getNextClass($this);
234
235        $cmd = $this->getCommand($cmd);
236
237        switch ($next_class) {
238            case 'ilformpropertydispatchgui':
239                $form = $this->buildEditForm();
240
241                require_once 'Services/Form/classes/class.ilFormPropertyDispatchGUI.php';
242                $form_prop_dispatch = new ilFormPropertyDispatchGUI();
243                $form_prop_dispatch->setItem($form->getItemByPostVar(ilUtil::stripSlashes($_GET['postvar'])));
244                return $this->ctrl->forwardCommand($form_prop_dispatch);
245                break;
246
247            default:
248                $ret = $this->$cmd();
249                break;
250        }
251        return $ret;
252    }
253
254    public function getCommand($cmd)
255    {
256        return $cmd;
257    }
258
259    /**
260    * needed for page editor compliance
261    */
262    public function getType()
263    {
264        return $this->getQuestionType();
265    }
266
267    /**
268     * @return string
269     */
270    public function getPresentationContext()
271    {
272        return $this->presentationContext;
273    }
274
275    /**
276     * @param string $presentationContext
277     */
278    public function setPresentationContext($presentationContext)
279    {
280        $this->presentationContext = $presentationContext;
281    }
282
283    public function isTestPresentationContext()
284    {
285        return $this->getPresentationContext() == self::PRESENTATION_CONTEXT_TEST;
286    }
287
288    // hey: previousPassSolutions - setter/getter for Previous Solution Prefilled flag
289    /**
290     * @return boolean
291     */
292    public function isPreviousSolutionPrefilled()
293    {
294        return $this->previousSolutionPrefilled;
295    }
296
297    /**
298     * @param boolean $previousSolutionPrefilled
299     */
300    public function setPreviousSolutionPrefilled($previousSolutionPrefilled)
301    {
302        $this->previousSolutionPrefilled = $previousSolutionPrefilled;
303    }
304    // hey.
305
306    /**
307     * @return string
308     */
309    public function getRenderPurpose()
310    {
311        return $this->renderPurpose;
312    }
313
314    /**
315     * @param string $renderPurpose
316     */
317    public function setRenderPurpose($renderPurpose)
318    {
319        $this->renderPurpose = $renderPurpose;
320    }
321
322    public function isRenderPurposePrintPdf()
323    {
324        return $this->getRenderPurpose() == self::RENDER_PURPOSE_PRINT_PDF;
325    }
326
327    public function isRenderPurposePreview()
328    {
329        return $this->getRenderPurpose() == self::RENDER_PURPOSE_PREVIEW;
330    }
331
332    public function isRenderPurposeInputValue()
333    {
334        return $this->getRenderPurpose() == self::RENDER_PURPOSE_INPUT_VALUE;
335    }
336
337    public function isRenderPurposePlayback()
338    {
339        return $this->getRenderPurpose() == self::RENDER_PURPOSE_PLAYBACK;
340    }
341
342    public function isRenderPurposeDemoplay()
343    {
344        return $this->getRenderPurpose() == self::RENDER_PURPOSE_DEMOPLAY;
345    }
346
347    public function renderPurposeSupportsFormHtml()
348    {
349        if ($this->isRenderPurposePrintPdf()) {
350            return false;
351        }
352
353        if ($this->isRenderPurposeInputValue()) {
354            return false;
355        }
356
357        return true;
358    }
359
360    /**
361     * @return string
362     */
363    public function getEditContext()
364    {
365        return $this->editContext;
366    }
367
368    /**
369     * @param string $editContext
370     */
371    public function setEditContext($editContext)
372    {
373        $this->editContext = $editContext;
374    }
375
376    /**
377     * @param bool $isAuthoringEditContext
378     */
379    public function isAuthoringEditContext()
380    {
381        return $this->getEditContext() == self::EDIT_CONTEXT_AUTHORING;
382    }
383
384    /**
385     * @param bool $isAdjustmentEditContext
386     */
387    public function isAdjustmentEditContext()
388    {
389        return $this->getEditContext() == self::EDIT_CONTEXT_ADJUSTMENT;
390    }
391
392    public function setAdjustmentEditContext()
393    {
394        return $this->setEditContext(self::EDIT_CONTEXT_ADJUSTMENT);
395    }
396
397    /**
398     * @return ilTestQuestionNavigationGUI
399     */
400    public function getNavigationGUI()
401    {
402        return $this->navigationGUI;
403    }
404
405    /**
406     * @param ilTestQuestionNavigationGUI $navigationGUI
407     */
408    public function setNavigationGUI($navigationGUI)
409    {
410        $this->navigationGUI = $navigationGUI;
411    }
412
413    public function setTaxonomyIds($taxonomyIds)
414    {
415        $this->taxonomyIds = $taxonomyIds;
416    }
417
418    public function getTaxonomyIds()
419    {
420        return $this->taxonomyIds;
421    }
422
423    public function setTargetGui($linkTargetGui)
424    {
425        $this->setTargetGuiClass(get_class($linkTargetGui));
426    }
427
428    public function setTargetGuiClass($targetGuiClass)
429    {
430        $this->targetGuiClass = $targetGuiClass;
431    }
432
433    public function getTargetGuiClass()
434    {
435        return $this->targetGuiClass;
436    }
437
438    /**
439     * @param \ilQuestionHeaderBlockBuilder $questionHeaderBlockBuilder
440     */
441    public function setQuestionHeaderBlockBuilder($questionHeaderBlockBuilder)
442    {
443        $this->questionHeaderBlockBuilder = $questionHeaderBlockBuilder;
444    }
445
446    // fau: testNav - get the question header block bulder (for tweaking)
447    /**
448     * @return \ilQuestionHeaderBlockBuilder $questionHeaderBlockBuilder
449     */
450    public function getQuestionHeaderBlockBuilder()
451    {
452        return $this->questionHeaderBlockBuilder;
453    }
454    // fau.
455
456    public function setQuestionActionCmd($questionActionCmd)
457    {
458        $this->questionActionCmd = $questionActionCmd;
459
460        if (is_object($this->object)) {
461            $this->object->questionActionCmd = $questionActionCmd;
462        }
463    }
464
465    public function getQuestionActionCmd()
466    {
467        return $this->questionActionCmd;
468    }
469
470    /**
471     * Evaluates a posted edit form and writes the form data in the question object
472     * @return integer A positive value, if one of the required fields wasn't set, else 0
473     */
474    protected function writePostData($always = false)
475    {
476    }
477
478    /**
479    * output assessment
480    */
481    public function assessment()
482    {
483        /**
484         * @var $tpl ilGlobalTemplate
485         */
486        global $DIC;
487        $tpl = $DIC['tpl'];
488
489        require_once 'Modules/TestQuestionPool/classes/tables/class.ilQuestionCumulatedStatisticsTableGUI.php';
490        $stats_table = new ilQuestionCumulatedStatisticsTableGUI($this, 'assessment', '', $this->object);
491
492        require_once 'Modules/TestQuestionPool/classes/tables/class.ilQuestionUsagesTableGUI.php';
493        $usage_table = new ilQuestionUsagesTableGUI($this, 'assessment', '', $this->object);
494
495        $tpl->setContent(implode('<br />', array(
496            $stats_table->getHTML(),
497            $usage_table->getHTML()
498        )));
499    }
500
501    /**
502     * Creates a question gui representation and returns the alias to the question gui
503     * note: please do not use $this inside this method to allow static calls
504     *
505     * @param string $question_type The question type as it is used in the language database
506     * @param integer $question_id The database ID of an existing question to load it into assQuestionGUI
507     *
508     * @return assQuestionGUI The alias to the question object
509     */
510    public static function _getQuestionGUI($question_type, $question_id = -1)
511    {
512        global $DIC;
513        $ilCtrl = $DIC['ilCtrl'];
514        $ilDB = $DIC['ilDB'];
515        $lng = $DIC['lng'];
516
517        include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
518
519        if ((!$question_type) and ($question_id > 0)) {
520            $question_type = assQuestion::getQuestionTypeFromDb($question_id);
521        }
522
523        if (strlen($question_type) == 0) {
524            return null;
525        }
526
527        assQuestion::_includeClass($question_type, 1);
528
529        $question_type_gui = assQuestion::getGuiClassNameByQuestionType($question_type);
530        $question = new $question_type_gui();
531
532        $feedbackObjectClassname = assQuestion::getFeedbackClassNameByQuestionType($question_type);
533        $question->object->feedbackOBJ = new $feedbackObjectClassname($question->object, $ilCtrl, $ilDB, $lng);
534
535        if ($question_id > 0) {
536            $question->object->loadFromDb($question_id);
537        }
538
539        return $question;
540    }
541
542    /**
543     * @deprecated
544     */
545    public static function _getGUIClassNameForId($a_q_id)
546    {
547        include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
548        include_once "./Modules/TestQuestionPool/classes/class.assQuestionGUI.php";
549        $q_type = assQuestion::getQuestionTypeFromDb($a_q_id);
550        $class_name = assQuestionGUI::_getClassNameForQType($q_type);
551        return $class_name;
552    }
553
554    /**
555     * @deprecated
556     */
557    public static function _getClassNameForQType($q_type)
558    {
559        return $q_type . "GUI";
560    }
561
562    /**
563    * Creates a question gui representation
564    *
565    * Creates a question gui representation and returns the alias to the question gui
566    *
567    * @param string $question_type The question type as it is used in the language database
568    * @param integer $question_id The database ID of an existing question to load it into assQuestionGUI
569    * @return object The alias to the question object
570    * @access public
571     *
572     * @deprecated: WTF is this? GUIobject::question should be a GUIobject !? WTF is a question alias !?
573    */
574    public function &createQuestionGUI($question_type, $question_id = -1)
575    {
576        include_once "./Modules/TestQuestionPool/classes/class.assQuestionGUI.php";
577        $this->question = &assQuestionGUI::_getQuestionGUI($question_type, $question_id);
578    }
579
580    public function populateJavascriptFilesRequiredForWorkForm(ilGlobalTemplateInterface $tpl)
581    {
582        foreach ($this->getPresentationJavascripts() as $jsFile) {
583            $tpl->addJavaScript($jsFile);
584        }
585    }
586
587    public function getPresentationJavascripts()
588    {
589        return array();
590    }
591
592    /**
593    * get question template
594    */
595    public function getQuestionTemplate()
596    {
597        // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x (ilObjTestGUI::executeCommand, switch -> default case -> $this->prepareOutput(); already added a template to the CONTENT variable wrapped in a block named content)
598        if (!$this->tpl->blockExists('content')) {
599            $this->tpl->addBlockFile("CONTENT", "content", "tpl.il_as_qpl_content.html", "Modules/TestQuestionPool");
600        }
601        // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x (ilObjTestGUI::executeCommand, switch -> default case -> $this->prepareOutput(); already added a template to the STATUSLINE variable wrapped in a block named statusline)
602        if (!$this->tpl->blockExists('statusline')) {
603            $this->tpl->addBlockFile("STATUSLINE", "statusline", "tpl.statusline.html");
604        }
605        // @todo Björn: Maybe this has to be changed for PHP 7/ILIAS 5.2.x because ass[XYZ]QuestionGUI::editQuestion is called multiple times
606        if (!$this->tpl->blockExists('adm_content')) {
607            $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.il_as_question.html", "Modules/TestQuestionPool");
608        }
609    }
610
611    /**
612     * @param $form
613     */
614    protected function renderEditForm($form)
615    {
616        $this->getQuestionTemplate();
617        $this->tpl->setVariable("QUESTION_DATA", $form->getHTML());
618    }
619
620    /**
621    * Returns the ILIAS Page around a question
622    *
623    * @return string The ILIAS page content
624    * @access public
625    */
626    public function getILIASPage($html = "")
627    {
628        include_once("./Modules/TestQuestionPool/classes/class.ilAssQuestionPageGUI.php");
629        $page_gui = new ilAssQuestionPageGUI($this->object->getId());
630        $page_gui->setQuestionHTML(array($this->object->getId() => $html));
631        $presentation = $page_gui->presentation();
632        $presentation = preg_replace("/src=\"\\.\\//ims", "src=\"" . ILIAS_HTTP_PATH . "/", $presentation);
633        return $presentation;
634    }
635
636    /**
637    * output question page
638    */
639    public function outQuestionPage($a_temp_var, $a_postponed = false, $active_id = "", $html = "", $inlineFeedbackEnabled = false)
640    {
641        // hey: prevPassSolutions - add the "use previous answer"
642        // hey: prevPassSolutions - refactored identifiers
643        if ($this->object->getTestPresentationConfig()->isSolutionInitiallyPrefilled()) {
644            // hey
645            ilUtil::sendInfo($this->getPreviousSolutionProvidedMessage());
646            $html .= $this->getPreviousSolutionConfirmationCheckboxHtml();
647        } elseif // (!) --> if ($this->object->getTestQuestionConfig()->isUnchangedAnswerPossible())
648        // hey.
649// fau: testNav - add the "use unchanged answer checkbox"
650        // hey: prevPassSolutions - refactored identifiers
651        ($this->object->getTestPresentationConfig()->isUnchangedAnswerPossible()) {
652            // hey.
653            $html .= $this->getUseUnchangedAnswerCheckboxHtml();
654        }
655        // fau.
656
657        $this->lng->loadLanguageModule("content");
658
659        // fau: testNav - add question buttons below question, add actions menu
660        include_once("./Modules/TestQuestionPool/classes/class.ilAssQuestionPageGUI.php");
661        $page_gui = new ilAssQuestionPageGUI($this->object->getId());
662        $page_gui->setOutputMode("presentation");
663        $page_gui->setTemplateTargetVar($a_temp_var);
664
665        if ($this->getNavigationGUI()) {
666            $html .= $this->getNavigationGUI()->getHTML();
667            $page_gui->setQuestionActionsHTML($this->getNavigationGUI()->getActionsHTML());
668        }
669        // fau.
670
671        if (strlen($html)) {
672            if ($inlineFeedbackEnabled && $this->hasInlineFeedback()) {
673                $html = $this->buildFocusAnchorHtml() . $html;
674            }
675
676            $page_gui->setQuestionHTML(array($this->object->getId() => $html));
677        }
678
679        // fau: testNav - fill the header with subtitle blocks for question info an actions
680        $page_gui->setPresentationTitle($this->questionHeaderBlockBuilder->getPresentationTitle());
681        $page_gui->setQuestionInfoHTML($this->questionHeaderBlockBuilder->getQuestionInfoHTML());
682        // fau.
683
684        return $page_gui->presentation();
685    }
686
687    // fau: testNav - get the html of the "use unchanged answer checkbox"
688    protected function getUseUnchangedAnswerCheckboxHtml()
689    {
690        // hey: prevPassSolutions - use abstracted template to share with other purposes of this kind
691        $tpl = new ilTemplate('tpl.tst_question_additional_behaviour_checkbox.html', true, true, 'Modules/TestQuestionPool');
692        $tpl->setVariable('TXT_FORCE_FORM_DIFF_LABEL', $this->object->getTestPresentationConfig()->getUseUnchangedAnswerLabel());
693        // hey.
694        return $tpl->get();
695    }
696    // fau.
697
698    // hey: prevPassSolutions - build prev solution message / build "use previous answer checkbox" html
699    protected function getPreviousSolutionProvidedMessage()
700    {
701        return $this->lng->txt('use_previous_solution_advice');
702    }
703
704    protected function getPreviousSolutionConfirmationCheckboxHtml()
705    {
706        $tpl = new ilTemplate('tpl.tst_question_additional_behaviour_checkbox.html', true, true, 'Modules/TestQuestionPool');
707        // hey: prevPassSolutions - use abtract template
708        $tpl->setVariable('TXT_FORCE_FORM_DIFF_LABEL', $this->lng->txt('use_previous_solution'));
709        // hey.
710        return $tpl->get();
711    }
712    // hey.
713
714    /**
715    * cancel action
716    */
717    public function cancel()
718    {
719        if ($_GET["calling_test"]) {
720            $_GET["ref_id"] = $_GET["calling_test"];
721            ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $_GET["calling_test"]);
722        } elseif ($_GET["test_ref_id"]) {
723            $_GET["ref_id"] = $_GET["test_ref_id"];
724            ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $_GET["test_ref_id"]);
725        } else {
726            if ($_GET["q_id"] > 0) {
727                $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", $_GET["q_id"]);
728                $this->ctrl->redirectByClass("ilAssQuestionPageGUI", "edit");
729            } else {
730                $this->ctrl->redirectByClass("ilobjquestionpoolgui", "questions");
731            }
732        }
733    }
734
735    /**
736     * @param string $return_to
737     * @param string $return_to_feedback  ilAssQuestionFeedbackEditingGUI
738     */
739    public function originalSyncForm($return_to = "", $return_to_feedback = '')
740    {
741        if (strlen($return_to)) {
742            $this->ctrl->setParameter($this, "return_to", $return_to);
743        } elseif ($_REQUEST['return_to']) {
744            $this->ctrl->setParameter($this, "return_to", $_REQUEST['return_to']);
745        }
746        if (strlen($return_to_feedback)) {
747            $this->ctrl->setParameter($this, 'return_to_fb', 'true');
748        }
749
750        $this->ctrl->saveParameter($this, 'test_express_mode');
751
752        $template = new ilTemplate("tpl.il_as_qpl_sync_original.html", true, true, "Modules/TestQuestionPool");
753        $template->setVariable("BUTTON_YES", $this->lng->txt("yes"));
754        $template->setVariable("BUTTON_NO", $this->lng->txt("no"));
755        $template->setVariable("FORM_ACTION", $this->ctrl->getFormAction($this));
756        $template->setVariable("TEXT_SYNC", $this->lng->txt("confirm_sync_questions"));
757        $this->tpl->setVariable("ADM_CONTENT", $template->get());
758    }
759
760    public function sync()
761    {
762        $original_id = $this->object->original_id;
763        if ($original_id) {
764            $this->object->syncWithOriginal();
765        }
766        if (strlen($_GET["return_to"])) {
767            $this->ctrl->redirect($this, $_GET["return_to"]);
768        }
769        if (strlen($_REQUEST["return_to_fb"])) {
770            $this->ctrl->redirectByClass('ilAssQuestionFeedbackEditingGUI', 'showFeedbackForm');
771        } else {
772            if (isset($_GET['calling_consumer']) && (int) $_GET['calling_consumer']) {
773                $ref_id = (int) $_GET['calling_consumer'];
774                $consumer = ilObjectFactory::getInstanceByRefId($ref_id);
775                if ($consumer instanceof ilQuestionEditingFormConsumer) {
776                    ilUtil::redirect($consumer->getQuestionEditingFormBackTarget($_GET['consumer_context']));
777                }
778                require_once 'Services/Link/classes/class.ilLink.php';
779                ilUtil::redirect(ilLink::_getLink($ref_id));
780            }
781            $_GET["ref_id"] = $_GET["calling_test"];
782
783            if ($_REQUEST['test_express_mode']) {
784                ilUtil::redirect(ilTestExpressPage::getReturnToPageLink($this->object->getId()));
785            } else {
786                ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $_GET["calling_test"]);
787            }
788        }
789    }
790
791    public function cancelSync()
792    {
793        if (strlen($_GET["return_to"])) {
794            $this->ctrl->redirect($this, $_GET["return_to"]);
795        }
796        if (strlen($_REQUEST['return_to_fb'])) {
797            $this->ctrl->redirectByClass('ilAssQuestionFeedbackEditingGUI', 'showFeedbackForm');
798        } else {
799            if (isset($_GET['calling_consumer']) && (int) $_GET['calling_consumer']) {
800                $ref_id = (int) $_GET['calling_consumer'];
801                $consumer = ilObjectFactory::getInstanceByRefId($ref_id);
802                if ($consumer instanceof ilQuestionEditingFormConsumer) {
803                    ilUtil::redirect($consumer->getQuestionEditingFormBackTarget($_GET['consumer_context']));
804                }
805                require_once 'Services/Link/classes/class.ilLink.php';
806                ilUtil::redirect(ilLink::_getLink($ref_id));
807            }
808            $_GET["ref_id"] = $_GET["calling_test"];
809
810            if ($_REQUEST['test_express_mode']) {
811                ilUtil::redirect(ilTestExpressPage::getReturnToPageLink($this->object->getId()));
812            } else {
813                ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $_GET["calling_test"]);
814            }
815        }
816    }
817
818    /**
819    * save question
820    */
821    public function saveEdit()
822    {
823        global $DIC;
824        $ilUser = $DIC['ilUser'];
825
826        $result = $this->writePostData();
827        if ($result == 0) {
828            $ilUser->setPref("tst_lastquestiontype", $this->object->getQuestionType());
829            $ilUser->writePref("tst_lastquestiontype", $this->object->getQuestionType());
830            $this->object->saveToDb();
831            $originalexists = $this->object->_questionExists($this->object->original_id);
832            include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
833            if ($_GET["calling_test"] && $originalexists && assQuestion::_isWriteable($this->object->original_id, $ilUser->getId())) {
834                $this->ctrl->redirect($this, "originalSyncForm");
835            } elseif ($_GET["calling_test"]) {
836                $_GET["ref_id"] = $_GET["calling_test"];
837                ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $_GET["calling_test"]);
838                return;
839            } elseif ($_GET["test_ref_id"]) {
840                global $DIC;
841                $tree = $DIC['tree'];
842                $ilDB = $DIC['ilDB'];
843                $ilPluginAdmin = $DIC['ilPluginAdmin'];
844
845                include_once("./Modules/Test/classes/class.ilObjTest.php");
846                $_GET["ref_id"] = $_GET["test_ref_id"];
847                $test = new ilObjTest($_GET["test_ref_id"], true);
848
849                require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
850                $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $test);
851
852                $test->insertQuestion($testQuestionSetConfigFactory->getQuestionSetConfig(), $this->object->getId());
853
854                ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $_GET["test_ref_id"]);
855            } else {
856                $this->ctrl->setParameter($this, "q_id", $this->object->getId());
857                $this->editQuestion();
858                if (strcmp($_SESSION["info"], "") != 0) {
859                    ilUtil::sendSuccess($_SESSION["info"] . "<br />" . $this->lng->txt("msg_obj_modified"), false);
860                } else {
861                    ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), false);
862                }
863                $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", $this->object->getId());
864                $this->ctrl->redirectByClass("ilAssQuestionPageGUI", "edit");
865            }
866        }
867    }
868
869    /**
870    * save question
871    */
872    public function save()
873    {
874        global $DIC;
875        $ilUser = $DIC['ilUser'];
876        $old_id = $_GET["q_id"];
877        $result = $this->writePostData();
878
879        if ($result == 0) {
880            $ilUser->setPref("tst_lastquestiontype", $this->object->getQuestionType());
881            $ilUser->writePref("tst_lastquestiontype", $this->object->getQuestionType());
882            $this->object->saveToDb();
883            $originalexists = $this->object->_questionExistsInPool($this->object->original_id);
884
885
886            include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
887            if (($_GET["calling_test"] || (isset($_GET['calling_consumer']) && (int) $_GET['calling_consumer'])) && $originalexists && assQuestion::_isWriteable($this->object->original_id, $ilUser->getId())) {
888                ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
889                $this->ctrl->setParameter($this, 'return_to', 'editQuestion');
890                $this->ctrl->redirect($this, "originalSyncForm");
891                return;
892            } elseif ($_GET["calling_test"]) {
893                require_once 'Modules/Test/classes/class.ilObjTest.php';
894                $test = new ilObjTest($_GET["calling_test"]);
895                if (!assQuestion::_questionExistsInTest($this->object->getId(), $test->getTestId())) {
896                    global $DIC;
897                    $tree = $DIC['tree'];
898                    $ilDB = $DIC['ilDB'];
899                    $ilPluginAdmin = $DIC['ilPluginAdmin'];
900
901                    include_once("./Modules/Test/classes/class.ilObjTest.php");
902                    $_GET["ref_id"] = $_GET["calling_test"];
903                    $test = new ilObjTest($_GET["calling_test"], true);
904
905                    require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
906                    $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $test);
907
908                    $new_id = $test->insertQuestion(
909                        $testQuestionSetConfigFactory->getQuestionSetConfig(),
910                        $this->object->getId()
911                    );
912
913                    if (isset($_REQUEST['prev_qid'])) {
914                        $test->moveQuestionAfter($this->object->getId() + 1, $_REQUEST['prev_qid']);
915                    }
916
917                    $this->ctrl->setParameter($this, 'q_id', $new_id);
918                    $this->ctrl->setParameter($this, 'calling_test', $_GET['calling_test']);
919                    #$this->ctrl->setParameter($this, 'test_ref_id', false);
920                }
921                ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
922                $this->ctrl->redirect($this, 'editQuestion');
923            } else {
924                $this->callNewIdListeners($this->object->getId());
925
926                if ($this->object->getId() != $old_id) {
927                    // first save
928                    $this->ctrl->setParameterByClass($_GET["cmdClass"], "q_id", $this->object->getId());
929                    $this->ctrl->setParameterByClass($_GET["cmdClass"], "sel_question_types", $_GET["sel_question_types"]);
930                    ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
931
932                    //global $___test_express_mode;
933                    /**
934                     * in express mode, so add question to test directly
935                     */
936                    if ($_REQUEST['prev_qid']) {
937                        // @todo: bheyser/mbecker wtf? ..... thx@jposselt ....
938                        // mbecker: Possible fix: Just instantiate the obj?
939                        include_once("./Modules/Test/classes/class.ilObjTest.php");
940                        $test = new ilObjTest($_GET["ref_id"], true);
941                        $test->moveQuestionAfter($_REQUEST['prev_qid'], $this->object->getId());
942                    }
943                    if ( /*$___test_express_mode || */ $_REQUEST['express_mode']) {
944                        global $DIC;
945                        $tree = $DIC['tree'];
946                        $ilDB = $DIC['ilDB'];
947                        $ilPluginAdmin = $DIC['ilPluginAdmin'];
948
949                        include_once("./Modules/Test/classes/class.ilObjTest.php");
950                        $test = new ilObjTest($_GET["ref_id"], true);
951
952                        require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
953                        $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $test);
954
955                        $test->insertQuestion(
956                            $testQuestionSetConfigFactory->getQuestionSetConfig(),
957                            $this->object->getId()
958                        );
959
960                        require_once 'Modules/Test/classes/class.ilTestExpressPage.php';
961                        $_REQUEST['q_id'] = $this->object->getId();
962                        ilUtil::redirect(ilTestExpressPage::getReturnToPageLink());
963                    }
964
965                    $this->ctrl->redirectByClass($_GET["cmdClass"], "editQuestion");
966                }
967                if (strcmp($_SESSION["info"], "") != 0) {
968                    ilUtil::sendSuccess($_SESSION["info"] . "<br />" . $this->lng->txt("msg_obj_modified"), true);
969                } else {
970                    ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
971                }
972                $this->ctrl->redirect($this, 'editQuestion');
973            }
974        }
975    }
976
977    /**
978    * save question
979    */
980    public function saveReturn()
981    {
982        global $DIC;
983        $ilUser = $DIC['ilUser'];
984        $old_id = $_GET["q_id"];
985        $result = $this->writePostData();
986        if ($result == 0) {
987            $ilUser->setPref("tst_lastquestiontype", $this->object->getQuestionType());
988            $ilUser->writePref("tst_lastquestiontype", $this->object->getQuestionType());
989            $this->object->saveToDb();
990            $originalexists = $this->object->_questionExistsInPool($this->object->original_id);
991            include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
992            if (($_GET["calling_test"] || (isset($_GET['calling_consumer']) && (int) $_GET['calling_consumer'])) && $originalexists && assQuestion::_isWriteable($this->object->original_id, $ilUser->getId())) {
993                ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
994                $this->ctrl->setParameter($this, 'test_express_mode', $_REQUEST['test_express_mode']);
995                $this->ctrl->redirect($this, "originalSyncForm");
996                return;
997            } elseif ($_GET["calling_test"]) {
998                require_once 'Modules/Test/classes/class.ilObjTest.php';
999                $test = new ilObjTest($_GET["calling_test"]);
1000                #var_dump(assQuestion::_questionExistsInTest($this->object->getId(), $test->getTestId()));
1001                $q_id = $this->object->getId();
1002                if (!assQuestion::_questionExistsInTest($this->object->getId(), $test->getTestId())) {
1003                    global $DIC;
1004                    $tree = $DIC['tree'];
1005                    $ilDB = $DIC['ilDB'];
1006                    $ilPluginAdmin = $DIC['ilPluginAdmin'];
1007
1008                    include_once("./Modules/Test/classes/class.ilObjTest.php");
1009                    $_GET["ref_id"] = $_GET["calling_test"];
1010                    $test = new ilObjTest($_GET["calling_test"], true);
1011
1012                    require_once 'Modules/Test/classes/class.ilTestQuestionSetConfigFactory.php';
1013                    $testQuestionSetConfigFactory = new ilTestQuestionSetConfigFactory($tree, $ilDB, $ilPluginAdmin, $test);
1014
1015                    $new_id = $test->insertQuestion(
1016                        $testQuestionSetConfigFactory->getQuestionSetConfig(),
1017                        $this->object->getId()
1018                    );
1019
1020                    $q_id = $new_id;
1021                    if (isset($_REQUEST['prev_qid'])) {
1022                        $test->moveQuestionAfter($this->object->getId() + 1, $_REQUEST['prev_qid']);
1023                    }
1024
1025                    $this->ctrl->setParameter($this, 'q_id', $new_id);
1026                    $this->ctrl->setParameter($this, 'calling_test', $_GET['calling_test']);
1027                    #$this->ctrl->setParameter($this, 'test_ref_id', false);
1028                }
1029                ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
1030                if ( /*$___test_express_mode || */
1031                $_REQUEST['test_express_mode']
1032                ) {
1033                    ilUtil::redirect(ilTestExpressPage::getReturnToPageLink($q_id));
1034                } else {
1035                    ilUtil::redirect("ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=" . $_GET["calling_test"]);
1036                }
1037            } else {
1038                if ($this->object->getId() != $old_id) {
1039                    $this->callNewIdListeners($this->object->getId());
1040                    ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
1041                    $this->ctrl->redirectByClass("ilobjquestionpoolgui", "questions");
1042                }
1043                if (strcmp($_SESSION["info"], "") != 0) {
1044                    ilUtil::sendSuccess($_SESSION["info"] . "<br />" . $this->lng->txt("msg_obj_modified"), true);
1045                } else {
1046                    ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
1047                }
1048                $this->ctrl->redirectByClass("ilobjquestionpoolgui", "questions");
1049            }
1050        }
1051    }
1052
1053    /**
1054    * apply changes
1055    */
1056    public function apply()
1057    {
1058        $this->writePostData();
1059        $this->object->saveToDb();
1060        $this->ctrl->setParameter($this, "q_id", $this->object->getId());
1061        $this->editQuestion();
1062    }
1063
1064    /**
1065    * get context path in content object tree
1066    *
1067    * @param	int		$a_endnode_id		id of endnode
1068    * @param	int		$a_startnode_id		id of startnode
1069    */
1070    public function getContextPath($cont_obj, $a_endnode_id, $a_startnode_id = 1)
1071    {
1072        $path = "";
1073
1074        $tmpPath = $cont_obj->getLMTree()->getPathFull($a_endnode_id, $a_startnode_id);
1075
1076        // count -1, to exclude the learning module itself
1077        for ($i = 1; $i < (count($tmpPath) - 1); $i++) {
1078            if ($path != "") {
1079                $path .= " > ";
1080            }
1081
1082            $path .= $tmpPath[$i]["title"];
1083        }
1084
1085        return $path;
1086    }
1087
1088    public function setSequenceNumber($nr)
1089    {
1090        $this->sequence_no = $nr;
1091    }
1092
1093    public function getSequenceNumber()
1094    {
1095        return $this->sequence_no;
1096    }
1097
1098    public function setQuestionCount($a_question_count)
1099    {
1100        $this->question_count = $a_question_count;
1101    }
1102
1103    public function getQuestionCount()
1104    {
1105        return $this->question_count;
1106    }
1107
1108    public function getErrorMessage()
1109    {
1110        return $this->errormessage;
1111    }
1112
1113    public function setErrorMessage($errormessage)
1114    {
1115        $this->errormessage = $errormessage;
1116    }
1117
1118    public function addErrorMessage($errormessage)
1119    {
1120        $this->errormessage .= ((strlen($this->errormessage)) ? "<br />" : "") . $errormessage;
1121    }
1122
1123    public function outAdditionalOutput()
1124    {
1125    }
1126
1127    /**
1128    * Returns the question type string
1129    *
1130    * Returns the question type string
1131    *
1132    * @result string The question type string
1133    * @access public
1134    */
1135    public function getQuestionType()
1136    {
1137        return $this->object->getQuestionType();
1138    }
1139
1140    /**
1141    * Returns a HTML value attribute
1142    *
1143    * @param mixed $a_value A given text or value
1144    * @result string The value as HTML value attribute
1145    * @access public
1146    */
1147    public function getAsValueAttribute($a_value)
1148    {
1149        $result = "";
1150        if (strlen($a_value)) {
1151            $result = " value=\"$a_value\" ";
1152        }
1153        return $result;
1154    }
1155
1156    // scorm2004-start
1157    /**
1158    * Add a listener that is notified with the new question ID, when
1159    * a new question is saved
1160    */
1161    public function addNewIdListener(&$a_object, $a_method, $a_parameters = "")
1162    {
1163        $cnt = $this->new_id_listener_cnt;
1164        $this->new_id_listeners[$cnt]["object"] = &$a_object;
1165        $this->new_id_listeners[$cnt]["method"] = $a_method;
1166        $this->new_id_listeners[$cnt]["parameters"] = $a_parameters;
1167        $this->new_id_listener_cnt++;
1168    }
1169
1170    /**
1171    * Call the new id listeners
1172    */
1173    public function callNewIdListeners($a_new_id)
1174    {
1175        for ($i = 0; $i < $this->new_id_listener_cnt; $i++) {
1176            $this->new_id_listeners[$i]["parameters"]["new_id"] = $a_new_id;
1177            $object = &$this->new_id_listeners[$i]["object"];
1178            $method = $this->new_id_listeners[$i]["method"];
1179            $parameters = $this->new_id_listeners[$i]["parameters"];
1180            //var_dump($object);
1181            //var_dump($method);
1182            //var_dump($parameters);
1183
1184            $object->$method($parameters);
1185        }
1186    }
1187
1188    /**
1189    * Add the command buttons of a question properties form
1190    */
1191    public function addQuestionFormCommandButtons($form)
1192    {
1193        //if (!$this->object->getSelfAssessmentEditingMode() && !$_GET["calling_test"]) $form->addCommandButton("saveEdit", $this->lng->txt("save_edit"));
1194        if (!$this->object->getSelfAssessmentEditingMode()) {
1195            $form->addCommandButton("saveReturn", $this->lng->txt("save_return"));
1196        }
1197        $form->addCommandButton("save", $this->lng->txt("save"));
1198    }
1199
1200    /**
1201    * Add basic question form properties:
1202    * assessment: title, author, description, question, working time
1203    *
1204    * @return	int	Default Nr of Tries
1205    */
1206    public function addBasicQuestionFormProperties($form)
1207    {
1208        // title
1209        $title = new ilTextInputGUI($this->lng->txt("title"), "title");
1210        $title->setMaxLength(100);
1211        $title->setValue($this->object->getTitle());
1212        $title->setRequired(true);
1213        $form->addItem($title);
1214
1215        if (!$this->object->getSelfAssessmentEditingMode()) {
1216            // author
1217            $author = new ilTextInputGUI($this->lng->txt("author"), "author");
1218            $author->setValue($this->object->getAuthor());
1219            $author->setRequired(true);
1220            $form->addItem($author);
1221
1222            // description
1223            $description = new ilTextInputGUI($this->lng->txt("description"), "comment");
1224            $description->setValue($this->object->getComment());
1225            $description->setRequired(false);
1226            $form->addItem($description);
1227        } else {
1228            // author as hidden field
1229            $hi = new ilHiddenInputGUI("author");
1230            $author = ilUtil::prepareFormOutput($this->object->getAuthor());
1231            if (trim($author) == "") {
1232                $author = "-";
1233            }
1234            $hi->setValue($author);
1235            $form->addItem($hi);
1236        }
1237
1238        // lifecycle
1239        $lifecycle = new ilSelectInputGUI($this->lng->txt('qst_lifecycle'), 'lifecycle');
1240        $lifecycle->setOptions($this->object->getLifecycle()->getSelectOptions($this->lng));
1241        $lifecycle->setValue($this->object->getLifecycle()->getIdentifier());
1242        $form->addItem($lifecycle);
1243
1244        // questiontext
1245        $question = new ilTextAreaInputGUI($this->lng->txt("question"), "question");
1246        $question->setValue($this->object->getQuestion());
1247        $question->setRequired(true);
1248        $question->setRows(10);
1249        $question->setCols(80);
1250
1251        if (!$this->object->getSelfAssessmentEditingMode()) {
1252            if ($this->object->getAdditionalContentEditingMode() != assQuestion::ADDITIONAL_CONTENT_EDITING_MODE_PAGE_OBJECT) {
1253                $question->setUseRte(true);
1254                include_once "./Services/AdvancedEditing/classes/class.ilObjAdvancedEditing.php";
1255                $question->setRteTags(ilObjAdvancedEditing::_getUsedHTMLTags("assessment"));
1256                $question->addPlugin("latex");
1257                $question->addButton("latex");
1258                $question->addButton("pastelatex");
1259                $question->setRTESupport($this->object->getId(), "qpl", "assessment");
1260            }
1261        } else {
1262            require_once 'Modules/TestQuestionPool/classes/questions/class.ilAssSelfAssessmentQuestionFormatter.php';
1263            $question->setRteTags(ilAssSelfAssessmentQuestionFormatter::getSelfAssessmentTags());
1264            $question->setUseTagsForRteOnly(false);
1265        }
1266        $form->addItem($question);
1267
1268        if (!$this->object->getSelfAssessmentEditingMode()) {
1269            // duration
1270            $duration = new ilDurationInputGUI($this->lng->txt("working_time"), "Estimated");
1271            $duration->setShowHours(true);
1272            $duration->setShowMinutes(true);
1273            $duration->setShowSeconds(true);
1274            $ewt = $this->object->getEstimatedWorkingTime();
1275            $duration->setHours($ewt["h"]);
1276            $duration->setMinutes($ewt["m"]);
1277            $duration->setSeconds($ewt["s"]);
1278            $duration->setRequired(false);
1279            $form->addItem($duration);
1280        } else {
1281            // number of tries
1282            if (strlen($this->object->getNrOfTries())) {
1283                $nr_tries = $this->object->getNrOfTries();
1284            } else {
1285                $nr_tries = $this->object->getDefaultNrOfTries();
1286            }
1287            if ($nr_tries < 1) {
1288                $nr_tries = "";
1289            }
1290
1291            $ni = new ilNumberInputGUI($this->lng->txt("qst_nr_of_tries"), "nr_of_tries");
1292            $ni->setValue($nr_tries);
1293            $ni->setMinValue(0);
1294            $ni->setSize(5);
1295            $ni->setMaxLength(5);
1296            $form->addItem($ni);
1297        }
1298    }
1299
1300    protected function saveTaxonomyAssignments()
1301    {
1302        if (count($this->getTaxonomyIds())) {
1303            require_once 'Services/Taxonomy/classes/class.ilTaxAssignInputGUI.php';
1304
1305            foreach ($this->getTaxonomyIds() as $taxonomyId) {
1306                $postvar = "tax_node_assign_$taxonomyId";
1307
1308                $tax_node_assign = new ilTaxAssignInputGUI($taxonomyId, true, '', $postvar);
1309                // TODO: determine tst/qpl when tax assigns become maintainable within tests
1310                $tax_node_assign->saveInput("qpl", $this->object->getObjId(), "quest", $this->object->getId());
1311            }
1312        }
1313    }
1314
1315    protected function populateTaxonomyFormSection(ilPropertyFormGUI $form)
1316    {
1317        if (count($this->getTaxonomyIds())) {
1318            // this is needed by ilTaxSelectInputGUI in some cases
1319            require_once 'Services/UIComponent/Overlay/classes/class.ilOverlayGUI.php';
1320            ilOverlayGUI::initJavaScript();
1321
1322            $sectHeader = new ilFormSectionHeaderGUI();
1323            $sectHeader->setTitle($this->lng->txt('qpl_qst_edit_form_taxonomy_section'));
1324            $form->addItem($sectHeader);
1325
1326            require_once 'Services/Taxonomy/classes/class.ilTaxSelectInputGUI.php';
1327
1328            foreach ($this->getTaxonomyIds() as $taxonomyId) {
1329                $taxonomy = new ilObjTaxonomy($taxonomyId);
1330                $label = sprintf($this->lng->txt('qpl_qst_edit_form_taxonomy'), $taxonomy->getTitle());
1331                $postvar = "tax_node_assign_$taxonomyId";
1332
1333                $taxSelect = new ilTaxSelectInputGUI($taxonomy->getId(), $postvar, true);
1334                $taxSelect->setTitle($label);
1335
1336                require_once 'Services/Taxonomy/classes/class.ilTaxNodeAssignment.php';
1337                $taxNodeAssignments = new ilTaxNodeAssignment(ilObject::_lookupType($this->object->getObjId()), $this->object->getObjId(), 'quest', $taxonomyId);
1338                $assignedNodes = $taxNodeAssignments->getAssignmentsOfItem($this->object->getId());
1339
1340                $taxSelect->setValue(array_map(function ($assignedNode) {
1341                    return $assignedNode['node_id'];
1342                }, $assignedNodes));
1343                $form->addItem($taxSelect);
1344            }
1345        }
1346    }
1347
1348    /**
1349    * Returns the answer generic feedback depending on the results of the question
1350    *
1351    * @deprecated Use getGenericFeedbackOutput instead.
1352    * @param integer $active_id Active ID of the user
1353    * @param integer $pass Active pass
1354    * @return string HTML Code with the answer specific feedback
1355    * @access public
1356    */
1357    public function getAnswerFeedbackOutput($active_id, $pass)
1358    {
1359        return $this->getGenericFeedbackOutput($active_id, $pass);
1360    }
1361
1362    /**
1363     * Returns the answer specific feedback for the question
1364
1365     *
1366     * @param integer $active_id Active ID of the user
1367     * @param integer $pass Active pass
1368     * @return string HTML Code with the answer specific feedback
1369     * @access public
1370     */
1371    public function getGenericFeedbackOutput($active_id, $pass)
1372    {
1373        $output = "";
1374        include_once "./Modules/Test/classes/class.ilObjTest.php";
1375        $manual_feedback = ilObjTest::getManualFeedback($active_id, $this->object->getId(), $pass);
1376        if (strlen($manual_feedback)) {
1377            return $manual_feedback;
1378        }
1379        $correct_feedback = $this->object->feedbackOBJ->getGenericFeedbackTestPresentation($this->object->getId(), true);
1380        $incorrect_feedback = $this->object->feedbackOBJ->getGenericFeedbackTestPresentation($this->object->getId(), false);
1381        if (strlen($correct_feedback . $incorrect_feedback)) {
1382            $reached_points = $this->object->calculateReachedPoints($active_id, $pass);
1383            $max_points = $this->object->getMaximumPoints();
1384            if ($reached_points == $max_points) {
1385                $output = $correct_feedback;
1386            } else {
1387                $output = $incorrect_feedback;
1388            }
1389        }
1390        return $this->object->prepareTextareaOutput($output, true);
1391    }
1392
1393    public function getGenericFeedbackOutputForCorrectSolution()
1394    {
1395        return $this->object->prepareTextareaOutput(
1396            $this->object->feedbackOBJ->getGenericFeedbackTestPresentation($this->object->getId(), true),
1397            true
1398        );
1399    }
1400
1401    public function getGenericFeedbackOutputForIncorrectSolution()
1402    {
1403        return $this->object->prepareTextareaOutput(
1404            $this->object->feedbackOBJ->getGenericFeedbackTestPresentation($this->object->getId(), false),
1405            true
1406        );
1407    }
1408
1409    /**
1410     * Returns the answer specific feedback for the question
1411     *
1412     * This method should be overwritten by the actual question.
1413     *
1414     * @todo Mark this method abstract!
1415     * @param array $userSolution ($userSolution[<value1>] = <value2>)
1416     * @return string HTML Code with the answer specific feedback
1417     * @access public
1418     */
1419    abstract public function getSpecificFeedbackOutput($userSolution);
1420
1421    public function outQuestionType()
1422    {
1423        $count = $this->object->isInUse();
1424
1425        if ($this->object->_questionExistsInPool($this->object->getId()) && $count) {
1426            global $DIC;
1427            $rbacsystem = $DIC['rbacsystem'];
1428            if ($rbacsystem->checkAccess("write", $_GET["ref_id"])) {
1429                ilUtil::sendInfo(sprintf($this->lng->txt("qpl_question_is_in_use"), $count));
1430            }
1431        }
1432
1433        return assQuestion::_getQuestionTypeName($this->object->getQuestionType());
1434    }
1435
1436    public function showSuggestedSolution()
1437    {
1438        $this->suggestedsolution();
1439    }
1440
1441    /**
1442    * Allows to add suggested solutions for questions
1443    *
1444    * @access public
1445    */
1446    public function suggestedsolution()
1447    {
1448        global $DIC;
1449        $ilUser = $DIC['ilUser'];
1450        global $DIC;
1451        $ilAccess = $DIC['ilAccess'];
1452
1453        $save = (is_array($_POST["cmd"]) && array_key_exists("suggestedsolution", $_POST["cmd"])) ? true : false;
1454
1455        if ($save && $_POST["deleteSuggestedSolution"] == 1) {
1456            $this->object->deleteSuggestedSolutions();
1457            ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
1458            $this->ctrl->redirect($this, "suggestedsolution");
1459        }
1460
1461        $output = "";
1462        $solution_array = $this->object->getSuggestedSolution(0);
1463        $options = array(
1464            "lm" => $this->lng->txt("obj_lm"),
1465            "st" => $this->lng->txt("obj_st"),
1466            "pg" => $this->lng->txt("obj_pg"),
1467            "git" => $this->lng->txt("glossary_term"),
1468            "file" => $this->lng->txt("fileDownload"),
1469            "text" => $this->lng->txt("solutionText")
1470        );
1471
1472        if ((strcmp($_POST["solutiontype"], "file") == 0) && (strcmp($solution_array["type"], "file") != 0)) {
1473            $solution_array = array(
1474                "type" => "file"
1475            );
1476        } elseif ((strcmp($_POST["solutiontype"], "text") == 0) && (strcmp($solution_array["type"], "text") != 0)) {
1477            $oldOutputMode = $this->getRenderPurpose();
1478            $this->setRenderPurpose(self::RENDER_PURPOSE_INPUT_VALUE);
1479
1480            $solution_array = array(
1481                "type" => "text",
1482                "value" => $this->getSolutionOutput(0, null, false, false, true, false, true)
1483            );
1484            $this->setRenderPurpose($oldsaveSuggestedSolutionOutputMode);
1485        }
1486        if ($save && strlen($_POST["filename"])) {
1487            $solution_array["value"]["filename"] = $_POST["filename"];
1488        }
1489        if ($save && strlen($_POST["solutiontext"])) {
1490            $solution_array["value"] = $_POST["solutiontext"];
1491        }
1492        include_once("./Services/Form/classes/class.ilPropertyFormGUI.php");
1493        if (count($solution_array)) {
1494            $form = new ilPropertyFormGUI();
1495            $form->setFormAction($this->ctrl->getFormAction($this));
1496            $form->setTitle($this->lng->txt("solution_hint"));
1497            $form->setMultipart(true);
1498            $form->setTableWidth("100%");
1499            $form->setId("suggestedsolutiondisplay");
1500
1501            // suggested solution output
1502            include_once "./Modules/TestQuestionPool/classes/class.ilSolutionTitleInputGUI.php";
1503            $title = new ilSolutionTitleInputGUI($this->lng->txt("showSuggestedSolution"), "solutiontype");
1504            $template = new ilTemplate("tpl.il_as_qpl_suggested_solution_input_presentation.html", true, true, "Modules/TestQuestionPool");
1505            if (strlen($solution_array["internal_link"])) {
1506                $href = assQuestion::_getInternalLinkHref($solution_array["internal_link"]);
1507                $template->setCurrentBlock("preview");
1508                $template->setVariable("TEXT_SOLUTION", $this->lng->txt("suggested_solution"));
1509                $template->setVariable("VALUE_SOLUTION", " <a href=\"$href\" target=\"content\">" . $this->lng->txt("view") . "</a> ");
1510                $template->parseCurrentBlock();
1511            } elseif ((strcmp($solution_array["type"], "file") == 0) && (is_array($solution_array["value"]))) {
1512                $href = $this->object->getSuggestedSolutionPathWeb() . $solution_array["value"]["name"];
1513                $template->setCurrentBlock("preview");
1514                $template->setVariable("TEXT_SOLUTION", $this->lng->txt("suggested_solution"));
1515                $template->setVariable("VALUE_SOLUTION", " <a href=\"$href\" target=\"content\">" . ilUtil::prepareFormOutput((strlen($solution_array["value"]["filename"])) ? $solution_array["value"]["filename"] : $solution_array["value"]["name"]) . "</a> ");
1516                $template->parseCurrentBlock();
1517            }
1518            $template->setVariable("TEXT_TYPE", $this->lng->txt("type"));
1519            $template->setVariable("VALUE_TYPE", $options[$solution_array["type"]]);
1520            $title->setHtml($template->get());
1521            $deletesolution = new ilCheckboxInputGUI("", "deleteSuggestedSolution");
1522            $deletesolution->setOptionTitle($this->lng->txt("deleteSuggestedSolution"));
1523            $title->addSubItem($deletesolution);
1524            $form->addItem($title);
1525
1526            if (strcmp($solution_array["type"], "file") == 0) {
1527                // file
1528                $file = new ilFileInputGUI($this->lng->txt("fileDownload"), "file");
1529                $file->setRequired(true);
1530                $file->enableFileNameSelection("filename");
1531                //$file->setSuffixes(array("doc","xls","png","jpg","gif","pdf"));
1532                if ($_FILES["file"]["tmp_name"] && $file->checkInput()) {
1533                    if (!file_exists($this->object->getSuggestedSolutionPath())) {
1534                        ilUtil::makeDirParents($this->object->getSuggestedSolutionPath());
1535                    }
1536
1537                    $res = ilUtil::moveUploadedFile($_FILES["file"]["tmp_name"], $_FILES["file"]["name"], $this->object->getSuggestedSolutionPath() . $_FILES["file"]["name"]);
1538                    if ($res) {
1539                        ilUtil::renameExecutables($this->object->getSuggestedSolutionPath());
1540
1541                        // remove an old file download
1542                        if (is_array($solution_array["value"])) {
1543                            @unlink($this->object->getSuggestedSolutionPath() . $solution_array["value"]["name"]);
1544                        }
1545                        $file->setValue($_FILES["file"]["name"]);
1546                        $this->object->saveSuggestedSolution("file", "", 0, array("name" => $_FILES["file"]["name"], "type" => $_FILES["file"]["type"], "size" => $_FILES["file"]["size"], "filename" => $_POST["filename"]));
1547                        $originalexists = $this->object->_questionExistsInPool($this->object->original_id);
1548                        if (($_GET["calling_test"] || (isset($_GET['calling_consumer']) && (int) $_GET['calling_consumer'])) && $originalexists && assQuestion::_isWriteable($this->object->original_id, $ilUser->getId())) {
1549                            return $this->originalSyncForm("suggestedsolution");
1550                        } else {
1551                            ilUtil::sendSuccess($this->lng->txt("suggested_solution_added_successfully"), true);
1552                            $this->ctrl->redirect($this, "suggestedsolution");
1553                        }
1554                    } else {
1555                        // BH: $res as info string? wtf? it holds a bool or something else!!?
1556                        ilUtil::sendInfo($res);
1557                    }
1558                } else {
1559                    if (is_array($solution_array["value"])) {
1560                        $file->setValue($solution_array["value"]["name"]);
1561                        $file->setFilename((strlen($solution_array["value"]["filename"])) ? $solution_array["value"]["filename"] : $solution_array["value"]["name"]);
1562                    }
1563                }
1564                $form->addItem($file);
1565                $hidden = new ilHiddenInputGUI("solutiontype");
1566                $hidden->setValue("file");
1567                $form->addItem($hidden);
1568            } elseif (strcmp($solution_array["type"], "text") == 0) {
1569                $solutionContent = $solution_array['value'];
1570                $solutionContent = $this->object->fixSvgToPng($solutionContent);
1571                $solutionContent = $this->object->fixUnavailableSkinImageSources($solutionContent);
1572                $question = new ilTextAreaInputGUI($this->lng->txt("solutionText"), "solutiontext");
1573                $question->setValue($this->object->prepareTextareaOutput($solutionContent));
1574                $question->setRequired(true);
1575                $question->setRows(10);
1576                $question->setCols(80);
1577                $question->setUseRte(true);
1578                $question->addPlugin("latex");
1579                $question->addButton("latex");
1580                $question->setRTESupport($this->object->getId(), "qpl", "assessment");
1581                $hidden = new ilHiddenInputGUI("solutiontype");
1582                $hidden->setValue("text");
1583                $form->addItem($hidden);
1584                $form->addItem($question);
1585            }
1586            if ($ilAccess->checkAccess("write", "", $_GET['ref_id'])) {
1587                $form->addCommandButton('showSuggestedSolution', $this->lng->txt('cancel'));
1588                $form->addCommandButton('suggestedsolution', $this->lng->txt('save'));
1589            }
1590
1591            if ($save) {
1592                if ($form->checkInput()) {
1593                    switch ($solution_array["type"]) {
1594                        case "file":
1595                            $this->object->saveSuggestedSolution("file", "", 0, array(
1596                                "name" => $solution_array["value"]["name"],
1597                                "type" => $solution_array["value"]["type"],
1598                                "size" => $solution_array["value"]["size"],
1599                                "filename" => $_POST["filename"]
1600                            ));
1601                            break;
1602                        case "text":
1603                            $this->object->saveSuggestedSolution("text", "", 0, $solution_array["value"]);
1604                            break;
1605                    }
1606                    $originalexists = $this->object->_questionExistsInPool($this->object->original_id);
1607                    if (($_GET["calling_test"] || (isset($_GET['calling_consumer']) && (int) $_GET['calling_consumer'])) && $originalexists && assQuestion::_isWriteable($this->object->original_id, $ilUser->getId())) {
1608                        return $this->originalSyncForm("suggestedsolution");
1609                    } else {
1610                        ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"), true);
1611                        $this->ctrl->redirect($this, "suggestedsolution");
1612                    }
1613                }
1614            }
1615
1616            $output = $form->getHTML();
1617        }
1618
1619        $savechange = (strcmp($this->ctrl->getCmd(), "saveSuggestedSolution") == 0) ? true : false;
1620
1621        $changeoutput = "";
1622        if ($ilAccess->checkAccess("write", "", $_GET['ref_id'])) {
1623            $formchange = new ilPropertyFormGUI();
1624            $formchange->setFormAction($this->ctrl->getFormAction($this));
1625            $formchange->setTitle((count($solution_array)) ? $this->lng->txt("changeSuggestedSolution") : $this->lng->txt("addSuggestedSolution"));
1626            $formchange->setMultipart(false);
1627            $formchange->setTableWidth("100%");
1628            $formchange->setId("suggestedsolution");
1629
1630            $solutiontype = new ilRadioGroupInputGUI($this->lng->txt("suggestedSolutionType"), "solutiontype");
1631            foreach ($options as $opt_value => $opt_caption) {
1632                $solutiontype->addOption(new ilRadioOption($opt_caption, $opt_value));
1633            }
1634            if (count($solution_array)) {
1635                $solutiontype->setValue($solution_array["type"]);
1636            }
1637            $solutiontype->setRequired(true);
1638            $formchange->addItem($solutiontype);
1639
1640            $formchange->addCommandButton("saveSuggestedSolution", $this->lng->txt("select"));
1641
1642            if ($savechange) {
1643                $formchange->checkInput();
1644            }
1645            $changeoutput = $formchange->getHTML();
1646        }
1647
1648        $this->tpl->setVariable("ADM_CONTENT", $changeoutput . $output);
1649    }
1650
1651    public function outSolutionExplorer()
1652    {
1653        global $DIC;
1654        $tree = $DIC['tree'];
1655
1656        include_once("./Modules/TestQuestionPool/classes/class.ilSolutionExplorer.php");
1657        $type = $_GET["link_new_type"];
1658        $search = $_GET["search_link_type"];
1659        $this->ctrl->setParameter($this, "link_new_type", $type);
1660        $this->ctrl->setParameter($this, "search_link_type", $search);
1661        $this->ctrl->saveParameter($this, array("subquestion_index", "link_new_type", "search_link_type"));
1662
1663        ilUtil::sendInfo($this->lng->txt("select_object_to_link"));
1664
1665        $parent_ref_id = $tree->getParentId($_GET["ref_id"]);
1666        $exp = new ilSolutionExplorer($this->ctrl->getLinkTarget($this, 'suggestedsolution'), get_class($this));
1667        $exp->setExpand($_GET['expand_sol'] ? $_GET['expand_sol'] : $parent_ref_id);
1668        $exp->setExpandTarget($this->ctrl->getLinkTarget($this, 'outSolutionExplorer'));
1669        $exp->setTargetGet("ref_id");
1670        $exp->setRefId($_GET["ref_id"]);
1671        $exp->addFilter($type);
1672        $exp->setSelectableType($type);
1673        if (isset($_GET['expandCurrentPath']) && $_GET['expandCurrentPath']) {
1674            $exp->expandPathByRefId($parent_ref_id);
1675        }
1676
1677        // build html-output
1678        $exp->setOutput(0);
1679
1680        $template = new ilTemplate("tpl.il_as_qpl_explorer.html", true, true, "Modules/TestQuestionPool");
1681        $template->setVariable("EXPLORER_TREE", $exp->getOutput());
1682        $template->setVariable("BUTTON_CANCEL", $this->lng->txt("cancel"));
1683        $template->setVariable("FORMACTION", $this->ctrl->getFormAction($this, "suggestedsolution"));
1684        $this->tpl->setVariable("ADM_CONTENT", $template->get());
1685    }
1686
1687    public function saveSuggestedSolution()
1688    {
1689        global $DIC;
1690        $tree = $DIC['tree'];
1691
1692        include_once("./Modules/TestQuestionPool/classes/class.ilSolutionExplorer.php");
1693        switch ($_POST["solutiontype"]) {
1694            case "lm":
1695                $type = "lm";
1696                $search = "lm";
1697                break;
1698            case "git":
1699                $type = "glo";
1700                $search = "glo";
1701                break;
1702            case "st":
1703                $type = "lm";
1704                $search = "st";
1705                break;
1706            case "pg":
1707                $type = "lm";
1708                $search = "pg";
1709                break;
1710            case "file":
1711            case "text":
1712                return $this->suggestedsolution();
1713                break;
1714            default:
1715                return $this->suggestedsolution();
1716                break;
1717        }
1718        if (isset($_POST['solutiontype'])) {
1719            $this->ctrl->setParameter($this, 'expandCurrentPath', 1);
1720        }
1721        $this->ctrl->setParameter($this, "link_new_type", $type);
1722        $this->ctrl->setParameter($this, "search_link_type", $search);
1723        $this->ctrl->redirect($this, "outSolutionExplorer");
1724    }
1725
1726    public function cancelExplorer()
1727    {
1728        $this->ctrl->redirect($this, "suggestedsolution");
1729    }
1730
1731    public function outPageSelector()
1732    {
1733        require_once 'Modules/TestQuestionPool/classes/tables/class.ilQuestionInternalLinkSelectionTableGUI.php';
1734        require_once 'Modules/LearningModule/classes/class.ilLMPageObject.php';
1735        require_once 'Modules/LearningModule/classes/class.ilObjContentObjectGUI.php';
1736
1737        $this->ctrl->setParameter($this, 'q_id', $this->object->getId());
1738
1739        $cont_obj_gui = new ilObjContentObjectGUI('', $_GET['source_id'], true);
1740        $cont_obj = $cont_obj_gui->object;
1741        $pages = ilLMPageObject::getPageList($cont_obj->getId());
1742        $shownpages = array();
1743        $tree = $cont_obj->getLMTree();
1744        $chapters = $tree->getSubtree($tree->getNodeData($tree->getRootId()));
1745
1746        $rows = array();
1747
1748        foreach ($chapters as $chapter) {
1749            $chapterpages = $tree->getChildsByType($chapter['obj_id'], 'pg');
1750            foreach ($chapterpages as $page) {
1751                if ($page['type'] == $_GET['search_link_type']) {
1752                    array_push($shownpages, $page['obj_id']);
1753
1754                    if ($tree->isInTree($page['obj_id'])) {
1755                        $path_str = $this->getContextPath($cont_obj, $page['obj_id']);
1756                    } else {
1757                        $path_str = '---';
1758                    }
1759
1760                    $this->ctrl->setParameter($this, $page['type'], $page['obj_id']);
1761                    $rows[] = array(
1762                        'title' => $page['title'],
1763                        'description' => ilUtil::prepareFormOutput($path_str),
1764                        'text_add' => $this->lng->txt('add'),
1765                        'href_add' => $this->ctrl->getLinkTarget($this, 'add' . strtoupper($page['type']))
1766                    );
1767                }
1768            }
1769        }
1770        foreach ($pages as $page) {
1771            if (!in_array($page['obj_id'], $shownpages)) {
1772                $this->ctrl->setParameter($this, $page['type'], $page['obj_id']);
1773                $rows[] = array(
1774                    'title' => $page['title'],
1775                    'description' => '---',
1776                    'text_add' => $this->lng->txt('add'),
1777                    'href_add' => $this->ctrl->getLinkTarget($this, 'add' . strtoupper($page['type']))
1778                );
1779            }
1780        }
1781
1782        require_once 'Modules/TestQuestionPool/classes/tables/class.ilQuestionInternalLinkSelectionTableGUI.php';
1783        $table = new ilQuestionInternalLinkSelectionTableGUI($this, 'cancelExplorer', __METHOD__);
1784        $table->setTitle($this->lng->txt('obj_' . ilUtil::stripSlashes($_GET['search_link_type'])));
1785        $table->setData($rows);
1786
1787        $this->tpl->setContent($table->getHTML());
1788    }
1789
1790    public function outChapterSelector()
1791    {
1792        require_once 'Modules/TestQuestionPool/classes/tables/class.ilQuestionInternalLinkSelectionTableGUI.php';
1793        require_once 'Modules/LearningModule/classes/class.ilObjContentObjectGUI.php';
1794
1795        $this->ctrl->setParameter($this, 'q_id', $this->object->getId());
1796
1797        $cont_obj_gui = new ilObjContentObjectGUI('', $_GET['source_id'], true);
1798        $cont_obj = $cont_obj_gui->object;
1799        $ctree = $cont_obj->getLMTree();
1800        $nodes = $ctree->getSubtree($ctree->getNodeData($ctree->getRootId()));
1801
1802        $rows = array();
1803
1804        foreach ($nodes as $node) {
1805            if ($node['type'] == $_GET['search_link_type']) {
1806                $this->ctrl->setParameter($this, $node['type'], $node['obj_id']);
1807                $rows[] = array(
1808                    'title' => $node['title'],
1809                    'description' => '',
1810                    'text_add' => $this->lng->txt('add'),
1811                    'href_add' => $this->ctrl->getLinkTarget($this, 'add' . strtoupper($node['type']))
1812                );
1813            }
1814        }
1815
1816        $table = new ilQuestionInternalLinkSelectionTableGUI($this, 'cancelExplorer', __METHOD__);
1817        $table->setTitle($this->lng->txt('obj_' . ilUtil::stripSlashes($_GET['search_link_type'])));
1818        $table->setData($rows);
1819
1820        $this->tpl->setContent($table->getHTML());
1821    }
1822
1823    public function outGlossarySelector()
1824    {
1825        $this->ctrl->setParameter($this, 'q_id', $this->object->getId());
1826
1827        $glossary = new ilObjGlossary($_GET['source_id'], true);
1828        $terms = $glossary->getTermList();
1829
1830        $rows = array();
1831
1832        foreach ($terms as $term) {
1833            $this->ctrl->setParameter($this, 'git', $term['id']);
1834            $rows[] = array(
1835                'title' => $term['term'],
1836                'description' => '',
1837                'text_add' => $this->lng->txt('add'),
1838                'href_add' => $this->ctrl->getLinkTarget($this, 'addGIT')
1839            );
1840        }
1841
1842        $table = new ilQuestionInternalLinkSelectionTableGUI($this, 'cancelExplorer', __METHOD__);
1843        $table->setTitle($this->lng->txt('glossary_term'));
1844        $table->setData($rows);
1845
1846        $this->tpl->setContent($table->getHTML());
1847    }
1848
1849    public function linkChilds()
1850    {
1851        $this->ctrl->saveParameter($this, array("subquestion_index", "link_new_type", "search_link_type"));
1852        switch ($_GET["search_link_type"]) {
1853            case "pg":
1854                return $this->outPageSelector();
1855                break;
1856            case "st":
1857                return $this->outChapterSelector();
1858                break;
1859            case "glo":
1860                return $this->outGlossarySelector();
1861                break;
1862            case "lm":
1863                $subquestion_index = ($_GET["subquestion_index"] > 0) ? $_GET["subquestion_index"] : 0;
1864                $this->object->saveSuggestedSolution("lm", "il__lm_" . $_GET["source_id"], $subquestion_index);
1865                ilUtil::sendSuccess($this->lng->txt("suggested_solution_added_successfully"), true);
1866                $this->ctrl->redirect($this, "suggestedsolution");
1867                break;
1868        }
1869    }
1870
1871    public function addPG()
1872    {
1873        $subquestion_index = 0;
1874        if (strlen($_GET["subquestion_index"]) && $_GET["subquestion_index"] >= 0) {
1875            $subquestion_index = $_GET["subquestion_index"];
1876        }
1877        $this->object->saveSuggestedSolution("pg", "il__pg_" . $_GET["pg"], $subquestion_index);
1878        ilUtil::sendSuccess($this->lng->txt("suggested_solution_added_successfully"), true);
1879        $this->ctrl->redirect($this, "suggestedsolution");
1880    }
1881
1882    public function addST()
1883    {
1884        $subquestion_index = 0;
1885        if (strlen($_GET["subquestion_index"]) && $_GET["subquestion_index"] >= 0) {
1886            $subquestion_index = $_GET["subquestion_index"];
1887        }
1888        $this->object->saveSuggestedSolution("st", "il__st_" . $_GET["st"], $subquestion_index);
1889        ilUtil::sendSuccess($this->lng->txt("suggested_solution_added_successfully"), true);
1890        $this->ctrl->redirect($this, "suggestedsolution");
1891    }
1892
1893    public function addGIT()
1894    {
1895        $subquestion_index = 0;
1896        if (strlen($_GET["subquestion_index"]) && $_GET["subquestion_index"] >= 0) {
1897            $subquestion_index = $_GET["subquestion_index"];
1898        }
1899        $this->object->saveSuggestedSolution("git", "il__git_" . $_GET["git"], $subquestion_index);
1900        ilUtil::sendSuccess($this->lng->txt("suggested_solution_added_successfully"), true);
1901        $this->ctrl->redirect($this, "suggestedsolution");
1902    }
1903
1904    public function isSaveCommand()
1905    {
1906        return in_array($this->ctrl->getCmd(), array('save', 'saveEdit', 'saveReturn'));
1907    }
1908
1909    /**
1910     * extracts values of all constants of given class with given prefix as array
1911     * can be used to get all possible commands in case of these commands are defined as constants
1912     *
1913     * @param string $guiClassName
1914     * @param string $cmdConstantNameBegin
1915     * @return array
1916     */
1917    public static function getCommandsFromClassConstants($guiClassName, $cmdConstantNameBegin = 'CMD_')
1918    {
1919        $reflectionClass = new ReflectionClass($guiClassName);
1920
1921        $commands = null;
1922
1923        if ($reflectionClass instanceof ReflectionClass) {
1924            $commands = array();
1925
1926            foreach ($reflectionClass->getConstants() as $constName => $constValue) {
1927                if (substr($constName, 0, strlen($cmdConstantNameBegin)) == $cmdConstantNameBegin) {
1928                    $commands[] = $constValue;
1929                }
1930            }
1931        }
1932
1933        return $commands;
1934    }
1935
1936    public function setQuestionTabs()
1937    {
1938        global $DIC;
1939        $rbacsystem = $DIC['rbacsystem'];
1940        $ilTabs = $DIC['ilTabs'];
1941
1942        $ilTabs->clearTargets();
1943
1944        $this->ctrl->setParameterByClass("ilAssQuestionPageGUI", "q_id", $_GET["q_id"]);
1945        include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
1946        $q_type = $this->object->getQuestionType();
1947
1948        if (strlen($q_type)) {
1949            $classname = $q_type . "GUI";
1950            $this->ctrl->setParameterByClass(strtolower($classname), "sel_question_types", $q_type);
1951            $this->ctrl->setParameterByClass(strtolower($classname), "q_id", $_GET["q_id"]);
1952        }
1953
1954        if ($_GET["q_id"]) {
1955            if ($rbacsystem->checkAccess('write', $_GET["ref_id"])) {
1956                // edit page
1957                $ilTabs->addTarget(
1958                    "edit_page",
1959                    $this->ctrl->getLinkTargetByClass("ilAssQuestionPageGUI", "edit"),
1960                    array("edit", "insert", "exec_pg"),
1961                    "",
1962                    "",
1963                    $force_active
1964                );
1965            }
1966
1967            $this->addTab_QuestionPreview($ilTabs);
1968        }
1969        $force_active = false;
1970        if ($rbacsystem->checkAccess('write', $_GET["ref_id"])) {
1971            $url = "";
1972            if ($classname) {
1973                $url = $this->ctrl->getLinkTargetByClass($classname, "editQuestion");
1974            }
1975            $force_active = false;
1976            // edit question properties
1977            $ilTabs->addTarget(
1978                "edit_question",
1979                $url,
1980                $this->getEditQuestionTabCommands(),
1981                $classname,
1982                "",
1983                $force_active
1984            );
1985        }
1986
1987        // add tab for question feedback within common class assQuestionGUI
1988        $this->addTab_QuestionFeedback($ilTabs);
1989
1990        // add tab for question hint within common class assQuestionGUI
1991        $this->addTab_QuestionHints($ilTabs);
1992
1993        // add tab for question's suggested solution within common class assQuestionGUI
1994        $this->addTab_SuggestedSolution($ilTabs, $classname);
1995
1996        // Assessment of questions sub menu entry
1997        if ($_GET["q_id"]) {
1998            $ilTabs->addTarget(
1999                "statistics",
2000                $this->ctrl->getLinkTargetByClass($classname, "assessment"),
2001                array("assessment"),
2002                $classname,
2003                ""
2004            );
2005        }
2006
2007        $this->addBackTab($ilTabs);
2008    }
2009
2010    public function addTab_SuggestedSolution(ilTabsGUI $tabs, $classname)
2011    {
2012        if ($_GET["q_id"]) {
2013            $tabs->addTarget(
2014                "suggested_solution",
2015                $this->ctrl->getLinkTargetByClass($classname, "suggestedsolution"),
2016                array("suggestedsolution", "saveSuggestedSolution", "outSolutionExplorer", "cancel",
2017                    "addSuggestedSolution","cancelExplorer", "linkChilds", "removeSuggestedSolution"
2018                ),
2019                $classname,
2020                ""
2021            );
2022        }
2023    }
2024
2025    final public function getEditQuestionTabCommands()
2026    {
2027        return array_merge($this->getBasicEditQuestionTabCommands(), $this->getAdditionalEditQuestionCommands());
2028    }
2029
2030    protected function getBasicEditQuestionTabCommands()
2031    {
2032        return array('editQuestion', 'save', 'saveEdit', 'originalSyncForm');
2033    }
2034
2035    protected function getAdditionalEditQuestionCommands()
2036    {
2037        return array();
2038    }
2039
2040    /**
2041     * adds the feedback tab to ilTabsGUI
2042     *
2043     * @global ilCtrl $ilCtrl
2044     * @param ilTabsGUI $tabs
2045     */
2046    protected function addTab_QuestionFeedback(ilTabsGUI $tabs)
2047    {
2048        global $DIC;
2049        $ilCtrl = $DIC['ilCtrl'];
2050
2051        require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionFeedbackEditingGUI.php';
2052        $tabCommands = self::getCommandsFromClassConstants('ilAssQuestionFeedbackEditingGUI');
2053
2054        $tabLink = $ilCtrl->getLinkTargetByClass('ilAssQuestionFeedbackEditingGUI', ilAssQuestionFeedbackEditingGUI::CMD_SHOW);
2055
2056        $tabs->addTarget('tst_feedback', $tabLink, $tabCommands, $ilCtrl->getCmdClass(), '');
2057    }
2058
2059    /**
2060     * @param ilTabsGUI $tabs
2061     */
2062    protected function addTab_Units(ilTabsGUI $tabs)
2063    {
2064        /**
2065         * @var $ilCtrl ilCtrl
2066         */
2067        global $DIC;
2068        $ilCtrl = $DIC['ilCtrl'];
2069
2070        $tabs->addTarget('units', $ilCtrl->getLinkTargetByClass('ilLocalUnitConfigurationGUI', ''), '', 'illocalunitconfigurationgui');
2071    }
2072
2073    /**
2074     * adds the hints tab to ilTabsGUI
2075     *
2076     * @global ilCtrl $ilCtrl
2077     * @param ilTabsGUI $tabs
2078     */
2079    protected function addTab_QuestionHints(ilTabsGUI $tabs)
2080    {
2081        global $DIC;
2082        $ilCtrl = $DIC['ilCtrl'];
2083
2084        require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintsGUI.php';
2085
2086        switch ($ilCtrl->getCmdClass()) {
2087            case 'ilassquestionhintsgui':
2088
2089                $tabCommands = self::getCommandsFromClassConstants('ilAssQuestionHintsGUI');
2090                break;
2091
2092            case 'ilassquestionhintgui':
2093
2094                require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionHintGUI.php';
2095                $tabCommands = self::getCommandsFromClassConstants('ilAssQuestionHintGUI');
2096                break;
2097
2098            default:
2099
2100                $tabCommands = array();
2101        }
2102
2103        $tabLink = $ilCtrl->getLinkTargetByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST);
2104
2105        $tabs->addTarget('tst_question_hints_tab', $tabLink, $tabCommands, $ilCtrl->getCmdClass(), '');
2106    }
2107
2108    protected function addTab_QuestionPreview(ilTabsGUI $tabsGUI)
2109    {
2110        require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php';
2111
2112        $tabsGUI->addTarget(
2113            ilAssQuestionPreviewGUI::TAB_ID_QUESTION_PREVIEW,
2114            $this->ctrl->getLinkTargetByClass('ilAssQuestionPreviewGUI', ilAssQuestionPreviewGUI::CMD_SHOW),
2115            array(),
2116            array('ilAssQuestionPreviewGUI')
2117        );
2118    }
2119
2120    abstract public function getSolutionOutput(
2121        $active_id,
2122        $pass = null,
2123        $graphicalOutput = false,
2124        $result_output = false,
2125        $show_question_only = true,
2126        $show_feedback = false,
2127        $show_correct_solution = false,
2128        $show_manual_scoring = false,
2129        $show_question_text = true
2130    );
2131
2132    /**
2133     * Question type specific support of intermediate solution output
2134     * The function getSolutionOutput respects getUseIntermediateSolution()
2135     * @return bool
2136     */
2137    public function supportsIntermediateSolutionOutput()
2138    {
2139        return false;
2140    }
2141
2142    /**
2143     * Check if the question has an intermediate solution
2144     * @param int $activeId
2145     * @param int $passIndex
2146     * @return bool
2147     */
2148    public function hasIntermediateSolution($activeId, $passIndex)
2149    {
2150        $result = $this->object->lookupForExistingSolutions($activeId, $passIndex);
2151        return ($result['intermediate']);
2152    }
2153
2154    /**
2155     * Set to use the intermediate solution for solution output
2156     * @var bool $use
2157     */
2158    public function setUseIntermediateSolution($use)
2159    {
2160        $this->use_intermediate_solution = (bool) $use;
2161    }
2162
2163    /**
2164     * Get if intermediate solution should be used for solution output
2165     * @return bool
2166     */
2167    public function getUseIntermediateSolution()
2168    {
2169        return (bool) $this->use_intermediate_solution;
2170    }
2171
2172    protected function hasCorrectSolution($activeId, $passIndex)
2173    {
2174        $reachedPoints = $this->object->getAdjustedReachedPoints($activeId, $passIndex, true);
2175        $maximumPoints = $this->object->getMaximumPoints();
2176
2177        return $reachedPoints == $maximumPoints;
2178    }
2179
2180    public function isAutosaveable()
2181    {
2182        return $this->object->isAutosaveable();
2183    }
2184
2185    protected function writeQuestionGenericPostData()
2186    {
2187        $this->object->setTitle($_POST["title"]);
2188        $this->object->setAuthor($_POST["author"]);
2189        $this->object->setComment($_POST["comment"]);
2190        if ($this->object->getSelfAssessmentEditingMode()) {
2191            $this->object->setNrOfTries($_POST['nr_of_tries']);
2192        }
2193
2194        try {
2195            $lifecycle = ilAssQuestionLifecycle::getInstance($_POST['lifecycle']);
2196            $this->object->setLifecycle($lifecycle);
2197        } catch (ilTestQuestionPoolInvalidArgumentException $e) {
2198        }
2199
2200        $this->object->setQuestion(ilUtil::stripOnlySlashes($_POST['question'])); // ?
2201        $this->object->setEstimatedWorkingTime(
2202            $_POST["Estimated"]["hh"],
2203            $_POST["Estimated"]["mm"],
2204            $_POST["Estimated"]["ss"]
2205        );
2206    }
2207
2208    abstract public function getPreview($show_question_only = false, $showInlineFeedback = false);
2209
2210    /**
2211     * @param string		$formaction
2212     * @param integer		$active_id
2213     * @param integer|null 	$pass
2214     * @param bool 			$is_question_postponed
2215     * @param bool 			$user_post_solutions
2216     * @param bool 			$show_specific_inline_feedback
2217     */
2218    final public function outQuestionForTest(
2219        $formaction,
2220        $active_id,
2221        // hey: prevPassSolutions - pass will be always available from now on
2222        $pass,
2223        // hey.
2224        $is_question_postponed = false,
2225        $user_post_solutions = false,
2226        $show_specific_inline_feedback = false
2227    ) {
2228        $formaction = $this->completeTestOutputFormAction($formaction, $active_id, $pass);
2229
2230        $test_output = $this->getTestOutput(
2231            $active_id,
2232            $pass,
2233            $is_question_postponed,
2234            $user_post_solutions,
2235            $show_specific_inline_feedback
2236        );
2237
2238        $this->magicAfterTestOutput();
2239
2240        $this->tpl->setVariable("QUESTION_OUTPUT", $test_output);
2241        $this->tpl->setVariable("FORMACTION", $formaction);
2242        $this->tpl->setVariable("ENCTYPE", 'enctype="' . $this->getFormEncodingType() . '"');
2243        $this->tpl->setVariable("FORM_TIMESTAMP", time());
2244    }
2245
2246    // hey: prevPassSolutions - $pass will be passed always from now on
2247    protected function completeTestOutputFormAction($formAction, $active_id, $pass)
2248    // hey.
2249    {
2250        return $formAction;
2251    }
2252
2253    public function magicAfterTestOutput()
2254    {
2255        return;
2256    }
2257
2258    abstract public function getTestOutput(
2259        $active_id,
2260        $pass,
2261        $is_question_postponed,
2262        $user_post_solutions,
2263        $show_specific_inline_feedback
2264    );
2265
2266    public function getFormEncodingType()
2267    {
2268        return self::FORM_ENCODING_URLENCODE;
2269    }
2270
2271    /**
2272     * @param ilTabsGUI $ilTabs
2273     */
2274    protected function addBackTab(ilTabsGUI $ilTabs)
2275    {
2276        if (($_GET["calling_test"] > 0) || ($_GET["test_ref_id"] > 0)) {
2277            $ref_id = $_GET["calling_test"];
2278            if (strlen($ref_id) == 0) {
2279                $ref_id = $_GET["test_ref_id"];
2280            }
2281
2282            if (!$_GET['test_express_mode'] && !$GLOBALS['___test_express_mode']) {
2283                $ilTabs->setBackTarget($this->lng->txt("backtocallingtest"), "ilias.php?baseClass=ilObjTestGUI&cmd=questions&ref_id=$ref_id");
2284            } else {
2285                $link = ilTestExpressPage::getReturnToPageLink();
2286                $ilTabs->setBackTarget($this->lng->txt("backtocallingtest"), $link);
2287            }
2288        } elseif (isset($_GET['calling_consumer']) && (int) $_GET['calling_consumer']) {
2289            $ref_id = (int) $_GET['calling_consumer'];
2290            $consumer = ilObjectFactory::getInstanceByRefId($ref_id);
2291            if ($consumer instanceof ilQuestionEditingFormConsumer) {
2292                $ilTabs->setBackTarget($consumer->getQuestionEditingFormBackTargetLabel(), $consumer->getQuestionEditingFormBackTarget($_GET['consumer_context']));
2293            } else {
2294                require_once 'Services/Link/classes/class.ilLink.php';
2295                $ilTabs->setBackTarget($this->lng->txt("qpl"), ilLink::_getLink($ref_id));
2296            }
2297        } else {
2298            $ilTabs->setBackTarget($this->lng->txt("qpl"), $this->ctrl->getLinkTargetByClass("ilobjquestionpoolgui", "questions"));
2299        }
2300    }
2301
2302    /**
2303     * @var ilAssQuestionPreviewSession
2304     */
2305    private $previewSession;
2306
2307    /**
2308     * @param \ilAssQuestionPreviewSession $previewSession
2309     */
2310    public function setPreviewSession($previewSession)
2311    {
2312        $this->previewSession = $previewSession;
2313    }
2314
2315    /**
2316     * @return \ilAssQuestionPreviewSession
2317     */
2318    public function getPreviewSession()
2319    {
2320        return $this->previewSession;
2321    }
2322
2323    /**
2324     * @return ilPropertyFormGUI
2325     */
2326    protected function buildBasicEditFormObject()
2327    {
2328        require_once 'Services/Form/classes/class.ilPropertyFormGUI.php';
2329        $form = new ilPropertyFormGUI();
2330
2331        $form->setFormAction($this->ctrl->getFormAction($this));
2332
2333        $form->setId($this->getType());
2334        $form->setTitle($this->outQuestionType());
2335
2336        $form->setTableWidth('100%');
2337
2338        $form->setMultipart(true);
2339
2340        return $form;
2341    }
2342
2343    public function showHints()
2344    {
2345        global $DIC;
2346        $ilCtrl = $DIC['ilCtrl'];
2347        $ilCtrl->redirectByClass('ilAssQuestionHintsGUI', ilAssQuestionHintsGUI::CMD_SHOW_LIST);
2348    }
2349
2350    /**
2351     *
2352     */
2353    protected function buildEditForm()
2354    {
2355        $errors = $this->editQuestion(true); // TODO bheyser: editQuestion should be added to the abstract base class with a unified signature
2356        return $this->editForm;
2357    }
2358
2359    /**
2360     * @return string
2361     */
2362    public function buildFocusAnchorHtml()
2363    {
2364        return '<div id="focus"></div>';
2365    }
2366
2367    public function isAnswerFreuqencyStatisticSupported()
2368    {
2369        return true;
2370    }
2371
2372    public function getSubQuestionsIndex()
2373    {
2374        return array(0);
2375    }
2376
2377    public function getAnswersFrequency($relevantAnswers, $questionIndex)
2378    {
2379        return array();
2380    }
2381
2382    /**
2383     * @param $parentGui
2384     * @param $parentCmd
2385     * @param $relevantAnswers
2386     * @param $questionIndex
2387     * @return ilAnswerFrequencyStatisticTableGUI
2388     */
2389    public function getAnswerFrequencyTableGUI($parentGui, $parentCmd, $relevantAnswers, $questionIndex)
2390    {
2391        require_once 'Modules/TestQuestionPool/classes/tables/class.ilAnswerFrequencyStatisticTableGUI.php';
2392
2393        $table = new ilAnswerFrequencyStatisticTableGUI($parentGui, $parentCmd, $this->object);
2394        $table->setQuestionIndex($questionIndex);
2395        $table->setData($this->getAnswersFrequency($relevantAnswers, $questionIndex));
2396        $table->initColumns();
2397
2398        return $table;
2399    }
2400
2401    /**
2402     * @param ilPropertyFormGUI $form
2403     */
2404    public function prepareReprintableCorrectionsForm(ilPropertyFormGUI $form)
2405    {
2406    }
2407
2408    /**
2409     * @param ilPropertyFormGUI $form
2410     */
2411    public function populateCorrectionsFormProperties(ilPropertyFormGUI $form)
2412    {
2413    }
2414
2415    /**
2416     * @param ilPropertyFormGUI $form
2417     */
2418    public function saveCorrectionsFormProperties(ilPropertyFormGUI $form)
2419    {
2420    }
2421}
2422