1<?php
2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4include_once "./Modules/TestQuestionPool/classes/class.assQuestion.php";
5include_once "./Modules/TestQuestionPool/classes/class.assFormulaQuestionResult.php";
6include_once "./Modules/TestQuestionPool/classes/class.assFormulaQuestionVariable.php";
7include_once "./Modules/TestQuestionPool/classes/class.ilUnitConfigurationRepository.php";
8include_once "./Modules/Test/classes/inc.AssessmentConstants.php";
9include_once "./Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php";
10require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php';
11
12/**
13 * Class for single choice questions
14 * assFormulaQuestion is a class for single choice questions.
15 * @author        Helmut Schottmüller <helmut.schottmueller@mac.com>
16 * @version       $Id: class.assFormulaQuestion.php 1236 2010-02-15 15:44:16Z hschottm $
17 * @ingroup       ModulesTestQuestionPool
18 */
19class assFormulaQuestion extends assQuestion implements iQuestionCondition
20{
21    private $variables;
22    private $results;
23    private $resultunits;
24
25    /**
26     * @var ilUnitConfigurationRepository
27     */
28    private $unitrepository;
29
30    /**
31     * assFormulaQuestion constructor
32     * The constructor takes possible arguments an creates an instance of the assFormulaQuestion object.
33     * @param string  $title    A title string to describe the question
34     * @param string  $comment  A comment string to describe the question
35     * @param string  $author   A string containing the name of the questions author
36     * @param integer $owner    A numerical ID to identify the owner/creator
37     * @param string  $question The question string of the single choice question
38     * @access public
39     * @see    assQuestion:assQuestion()
40     */
41    public function __construct(
42        $title = "",
43        $comment = "",
44        $author = "",
45        $owner = -1,
46        $question = ""
47    ) {
48        parent::__construct($title, $comment, $author, $owner, $question);
49        $this->variables = array();
50        $this->results = array();
51        $this->resultunits = array();
52        $this->unitrepository = new ilUnitConfigurationRepository(0);
53    }
54
55    public function clearVariables()
56    {
57        $this->variables = array();
58    }
59
60    public function getVariables()
61    {
62        return $this->variables;
63    }
64
65    public function getVariable($variable)
66    {
67        if (array_key_exists($variable, $this->variables)) {
68            return $this->variables[$variable];
69        }
70        return null;
71    }
72
73    public function addVariable($variable)
74    {
75        $this->variables[$variable->getVariable()] = $variable;
76    }
77
78    public function clearResults()
79    {
80        $this->results = array();
81    }
82
83    public function getResults()
84    {
85        return $this->results;
86    }
87
88    public function getResult($result)
89    {
90        if (array_key_exists($result, $this->results)) {
91            return $this->results[$result];
92        }
93        return null;
94    }
95
96    public function addResult($result)
97    {
98        $this->results[$result->getResult()] = $result;
99    }
100
101    public function addResultUnits($result, $unit_ids)
102    {
103        $this->resultunits[$result->getResult()] = array();
104        if ((!is_object($result)) || (!is_array($unit_ids))) {
105            return;
106        }
107        foreach ($unit_ids as $id) {
108            if (is_numeric($id) && ($id > 0)) {
109                $this->resultunits[$result->getResult()][$id] = $this->getUnitrepository()->getUnit($id);
110            }
111        }
112    }
113
114    public function addResultUnit($result, $unit)
115    {
116        if (is_object($result) && is_object($unit)) {
117            if (!is_array($this->resultunits[$result->getResult()])) {
118                $this->resultunits[$result->getResult()] = array();
119            }
120            $this->resultunits[$result->getResult()][$unit->getId()] = $unit;
121        }
122    }
123
124    public function getResultUnits($result)
125    {
126        if (array_key_exists($result->getResult(), $this->resultunits)) {
127            return $this->resultunits[$result->getResult()];
128        } else {
129            return array();
130        }
131    }
132
133    public function hasResultUnit($result, $unit_id)
134    {
135        if (array_key_exists($result->getResult(), $this->resultunits)) {
136            if (array_key_exists($unit_id, $this->resultunits[$result->getResult()])) {
137                return true;
138            }
139        }
140
141        return false;
142    }
143
144    public function parseQuestionText()
145    {
146        $this->clearResults();
147        $this->clearVariables();
148        if (preg_match_all("/(\\\$v\\d+)/ims", $this->getQuestion(), $matches)) {
149            foreach ($matches[1] as $variable) {
150                $varObj = new assFormulaQuestionVariable($variable, 0, 0, null, 0);
151                $this->addVariable($varObj);
152            }
153        }
154
155        if (preg_match_all("/(\\\$r\\d+)/ims", $this->getQuestion(), $rmatches)) {
156            foreach ($rmatches[1] as $result) {
157                $resObj = new assFormulaQuestionResult($result, null, null, 0, -1, null, 1, 1, true);
158                $this->addResult($resObj);
159            }
160        }
161    }
162
163    public function checkForDuplicateVariables()
164    {
165        if (preg_match_all("/(\\\$v\\d+)/ims", $this->getQuestion(), $matches)) {
166            if ((count(array_unique($matches[1]))) != count($matches[1])) {
167                return false;
168            }
169        }
170        return true;
171    }
172
173    public function checkForDuplicateResults()
174    {
175        if (preg_match_all("/(\\\$r\\d+)/ims", $this->getQuestion(), $rmatches)) {
176            if ((count(array_unique($rmatches[1]))) != count($rmatches[1])) {
177                return false;
178            }
179        }
180        return true;
181    }
182
183    /**
184     * @param string $questionText
185     * @return assFormulaQuestionResult[] $resObjects
186     */
187    public function fetchAllResults($questionText)
188    {
189        $resObjects = array();
190        $matches = null;
191
192        if (preg_match_all("/(\\\$r\\d+)/ims", $questionText, $matches)) {
193            foreach ($matches[1] as $resultKey) {
194                $resObjects[] = $this->getResult($resultKey);
195            }
196        }
197
198        return $resObjects;
199    }
200
201    /**
202     * @param string $questionText
203     * @return assFormulaQuestionVariable[] $varObjects
204     */
205    public function fetchAllVariables($questionText)
206    {
207        $varObjects = array();
208        $matches = null;
209
210        if (preg_match_all("/(\\\$v\\d+)/ims", $questionText, $matches)) {
211            foreach ($matches[1] as $variableKey) {
212                $varObjects[] = $this->getVariable($variableKey);
213            }
214        }
215
216        return $varObjects;
217    }
218
219    /**
220     * @param array $userSolution
221     * @return bool
222     */
223    public function hasRequiredVariableSolutionValues(array $userSolution)
224    {
225        foreach ($this->fetchAllVariables($this->getQuestion()) as $varObj) {
226            if (!isset($userSolution[$varObj->getVariable()])) {
227                return false;
228            }
229
230            if (!strlen($userSolution[$varObj->getVariable()])) {
231                return false;
232            }
233        }
234
235        return true;
236    }
237
238    /**
239     * @return array $initialVariableSolutionValues
240     */
241    public function getInitialVariableSolutionValues()
242    {
243        foreach ($this->fetchAllResults($this->getQuestion()) as $resObj) {
244            $resObj->findValidRandomVariables($this->getVariables(), $this->getResults());
245        }
246
247        $variableSolutionValues = array();
248
249        foreach ($this->fetchAllVariables($this->getQuestion()) as $varObj) {
250            $variableSolutionValues[$varObj->getVariable()] = $varObj->getValue();
251        }
252
253        return $variableSolutionValues;
254    }
255
256    /**
257     * @param array $userdata
258     * @param bool $graphicalOutput
259     * @param bool $forsolution
260     * @param bool $result_output
261     * @param ilAssQuestionPreviewSession|null $previewSession
262     * @return bool|mixed|string
263     */
264    public function substituteVariables(array $userdata, $graphicalOutput = false, $forsolution = false, $result_output = false)
265    {
266        if ((count($this->results) == 0) && (count($this->variables) == 0)) {
267            return false;
268        }
269
270        $text = $this->getQuestion();
271
272        foreach ($this->fetchAllVariables($this->getQuestion()) as $varObj) {
273            if (isset($userdata[$varObj->getVariable()]) && strlen($userdata[$varObj->getVariable()])) {
274                $varObj->setValue($userdata[$varObj->getVariable()]);
275            }
276
277            $unit = (is_object($varObj->getUnit())) ? $varObj->getUnit()->getUnit() : "";
278            $val = (strlen($varObj->getValue()) > 8) ? strtoupper(sprintf("%e", $varObj->getValue())) : $varObj->getValue();
279
280            $text = preg_replace("/\\$" . substr($varObj->getVariable(), 1) . "(?![0-9]+)/", $val . " " . $unit . "\\1", $text);
281        }
282
283        if (preg_match_all("/(\\\$r\\d+)/ims", $this->getQuestion(), $rmatches)) {
284            foreach ($rmatches[1] as $result) {
285                $resObj = $this->getResult($result);
286                $value = "";
287                $frac_helper = '';
288                $user_data[$result]['result_type'] = $resObj->getResultType();
289
290                if (
291                    $resObj->getResultType() == assFormulaQuestionResult::RESULT_FRAC ||
292                    $resObj->getResultType() == assFormulaQuestionResult::RESULT_CO_FRAC
293                ) {
294                    $is_frac = true;
295                }
296                if (is_array($userdata)) {
297                    if (is_array($userdata[$result])) {
298                        if (false && $forsolution && $result_output) { // fix for mantis #25956
299                            $value_org = $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId());
300                            $value = sprintf("%." . $resObj->getPrecision() . "f", $value_org);
301                            if ($is_frac) {
302                                $value = assFormulaQuestionResult::convertDecimalToCoprimeFraction($value_org);
303                                if (is_array($value)) {
304                                    $frac_helper = $value[1];
305                                    $value = $value[0];
306                                }
307                            }
308                        } else {
309                            if ($forsolution) {
310                                $value = $userdata[$result]["value"];
311                            } else {
312                                $value = ' value="' . $userdata[$result]["value"] . '"';
313                            }
314                        }
315                    }
316                } else {
317                    if ($forsolution) {
318                        $value = $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId());
319                        $value = sprintf("%." . $resObj->getPrecision() . "f", $value);
320
321                        if ($is_frac) {
322                            $value = assFormulaQuestionResult::convertDecimalToCoprimeFraction($value);
323                            if (is_array($value)) {
324                                $frac_helper = $value[1];
325                                $value = $value[0];
326                            }
327                            $value = ' value="' . $value . '"';
328                        }
329                    } else {
330                        // Precision fix for Preview by tjoussen
331                        // If all default values are set, this function is called in getPreview
332                        $use_precision = !($userdata == null && $graphicalOutput == false && $forsolution == false && $result_output == false);
333
334                        $val = $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId(), $use_precision);
335
336                        if ($resObj->getResultType() == assFormulaQuestionResult::RESULT_FRAC
337                            || $resObj->getResultType() == assFormulaQuestionResult::RESULT_CO_FRAC) {
338                            $val = $resObj->convertDecimalToCoprimeFraction($val);
339                            if (is_array($val)) {
340                                $frac_helper = $val[1];
341                                $val = $val[0];
342                            }
343                        } else {
344                            $val = sprintf("%." . $resObj->getPrecision() . "f", $val);
345                            $val = (strlen($val) > 8) ? strtoupper(sprintf("%e", $val)) : $val;
346                        }
347                        $value = ' value="' . $val . '"';
348                    }
349                }
350
351                if ($forsolution) {
352                    $input = '<span class="ilc_qinput_TextInput solutionbox">' . ilUtil::prepareFormOutput($value) . '</span>';
353                } else {
354                    $input = '<input class="ilc_qinput_TextInput" type="text" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" name="result_' . $result . '"' . $value . ' />';
355                }
356
357                $units = "";
358                if (count($this->getResultUnits($resObj)) > 0) {
359                    if ($forsolution) {
360                        if (is_array($userdata)) {
361                            foreach ($this->getResultUnits($resObj) as $unit) {
362                                if ($userdata[$result]["unit"] == $unit->getId()) {
363                                    $units = $unit->getUnit();
364                                }
365                            }
366                        } else {
367                            if ($resObj->getUnit()) {
368                                $units = $resObj->getUnit()->getUnit();
369                            }
370                        }
371                    } else {
372                        $units = '<select name="result_' . $result . '_unit">';
373                        $units .= '<option value="-1">' . $this->lng->txt("select_unit") . '</option>';
374                        foreach ($this->getResultUnits($resObj) as $unit) {
375                            $units .= '<option value="' . $unit->getId() . '"';
376                            if ((is_array($userdata[$result])) && (strlen($userdata[$result]["unit"]))) {
377                                if ($userdata[$result]["unit"] == $unit->getId()) {
378                                    $units .= ' selected="selected"';
379                                }
380                            }
381                            $units .= '>' . $unit->getUnit() . '</option>';
382                        }
383                        $units .= '</select>';
384                    }
385                } else {
386                    $units = "";
387                }
388                switch ($resObj->getResultType()) {
389                    case assFormulaQuestionResult::RESULT_DEC:
390                        $units .= ' ' . $this->lng->txt('expected_result_type') . ': ' . $this->lng->txt('result_dec');
391                        break;
392                    case assFormulaQuestionResult::RESULT_FRAC:
393                        if (strlen($frac_helper)) {
394                            $units .= ' &asymp; ' . $frac_helper . ', ';
395                        } elseif (is_array($userdata) && isset($userdata[$result]) && strlen($userdata[$result]["frac_helper"])) {
396                            if (!preg_match('-/-', $value)) {
397                                $units .= ' &asymp; ' . $userdata[$result]["frac_helper"] . ', ';
398                            }
399                        }
400                        $units .= ' ' . $this->lng->txt('expected_result_type') . ': ' . $this->lng->txt('result_frac');
401                        break;
402                    case assFormulaQuestionResult::RESULT_CO_FRAC:
403                        if (strlen($frac_helper)) {
404                            $units .= ' &asymp; ' . $frac_helper . ', ';
405                        } elseif (is_array($userdata) && isset($userdata[$result]) && strlen($userdata[$result]["frac_helper"])) {
406                            if (!preg_match('-/-', $value)) {
407                                $units .= ' &asymp; ' . $userdata[$result]["frac_helper"] . ', ';
408                            }
409                        }
410                        $units .= ' ' . $this->lng->txt('expected_result_type') . ': ' . $this->lng->txt('result_co_frac');
411                        break;
412                    case assFormulaQuestionResult::RESULT_NO_SELECTION:
413                        break;
414                }
415                $checkSign = "";
416                if ($graphicalOutput) {
417                    $resunit = null;
418                    $user_value = '';
419                    if (is_array($userdata) && is_array($userdata[$result])) {
420                        if ($userdata[$result]["unit"] > 0) {
421                            $resunit = $this->getUnitrepository()->getUnit($userdata[$result]["unit"]);
422                        }
423
424                        if (isset($userdata[$result]["value"])) {
425                            $user_value = $userdata[$result]["value"];
426                        }
427                    }
428
429                    $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output_solution_image.html", true, true, 'Modules/TestQuestionPool');
430
431                    if ($resObj->isCorrect($this->getVariables(), $this->getResults(), $user_value, $resunit)) {
432                        $template->setCurrentBlock("icon_ok");
433                        $template->setVariable("ICON_OK", ilUtil::getImagePath("icon_ok.svg"));
434                        $template->setVariable("TEXT_OK", $this->lng->txt("answer_is_right"));
435                        $template->parseCurrentBlock();
436                    } else {
437                        $template->setCurrentBlock("icon_not_ok");
438                        $template->setVariable("ICON_NOT_OK", ilUtil::getImagePath("icon_not_ok.svg"));
439                        $template->setVariable("TEXT_NOT_OK", $this->lng->txt("answer_is_wrong"));
440                        $template->parseCurrentBlock();
441                    }
442                    $checkSign = $template->get();
443                }
444                $resultOutput = "";
445                if ($result_output) {
446                    $template = new ilTemplate("tpl.il_as_qpl_formulaquestion_output_solution_result.html", true, true, 'Modules/TestQuestionPool');
447
448                    if (is_array($userdata)) {
449                        $found = $resObj->getResultInfo($this->getVariables(), $this->getResults(), $userdata[$resObj->getResult()]["value"], $userdata[$resObj->getResult()]["unit"], $this->getUnitrepository()->getUnits());
450                    } else {
451                        $found = $resObj->getResultInfo($this->getVariables(), $this->getResults(), $resObj->calculateFormula($this->getVariables(), $this->getResults(), parent::getId()), is_object($resObj->getUnit()) ? $resObj->getUnit()->getId() : null, $this->getUnitrepository()->getUnits());
452                    }
453                    $resulttext = "(";
454                    if ($resObj->getRatingSimple()) {
455                        if ($frac_helper) {
456                            $resulttext .= "n/a";
457                        } else {
458                            $resulttext .= $found['points'] . " " . (($found['points'] == 1) ? $this->lng->txt('point') : $this->lng->txt('points'));
459                        }
460                    } else {
461                        $resulttext .= $this->lng->txt("rated_sign") . " " . (($found['sign']) ? $found['sign'] : 0) . " " . (($found['sign'] == 1) ? $this->lng->txt('point') : $this->lng->txt('points')) . ", ";
462                        $resulttext .= $this->lng->txt("rated_value") . " " . (($found['value']) ? $found['value'] : 0) . " " . (($found['value'] == 1) ? $this->lng->txt('point') : $this->lng->txt('points')) . ", ";
463                        $resulttext .= $this->lng->txt("rated_unit") . " " . (($found['unit']) ? $found['unit'] : 0) . " " . (($found['unit'] == 1) ? $this->lng->txt('point') : $this->lng->txt('points'));
464                    }
465
466                    $resulttext .= ")";
467                    $template->setVariable("RESULT_OUTPUT", $resulttext);
468
469                    $resultOutput = $template->get();
470                }
471                $text = preg_replace("/\\\$" . substr($result, 1) . "(?![0-9]+)/", $input . " " . $units . " " . $checkSign . " " . $resultOutput . " " . "\\1", $text);
472            }
473        }
474        return $text;
475    }
476
477    /**
478     * Check if advanced rating can be used for a result. This is only possible if there is exactly
479     * one possible correct unit for the result, otherwise it is impossible to determine wheather the
480     * unit is correct or the value.
481     * @return boolean True if advanced rating could be used, false otherwise
482     */
483    public function canUseAdvancedRating($result)
484    {
485        $result_units = $this->getResultUnits($result);
486        $resultunit = $result->getUnit();
487        $similar_units = 0;
488        foreach ($result_units as $unit) {
489            if (is_object($resultunit)) {
490                if ($resultunit->getId() != $unit->getId()) {
491                    if ($resultunit->getBaseUnit() && $unit->getBaseUnit()) {
492                        if ($resultunit->getBaseUnit() == $unit->getBaseUnit()) {
493                            return false;
494                        }
495                    }
496                    if ($resultunit->getBaseUnit()) {
497                        if ($resultunit->getBaseUnit() == $unit->getId()) {
498                            return false;
499                        }
500                    }
501                    if ($unit->getBaseUnit()) {
502                        if ($unit->getBaseUnit() == $resultunit->getId()) {
503                            return false;
504                        }
505                    }
506                }
507            }
508        }
509        return true;
510    }
511
512    /**
513     * Returns true, if the question is complete for use
514     * @return boolean True, if the single choice question is complete for use, otherwise false
515     */
516    public function isComplete()
517    {
518        if (($this->title) and ($this->author) and ($this->question) and ($this->getMaximumPoints() > 0)) {
519            return true;
520        } else {
521            return false;
522        }
523    }
524
525    /**
526     * Saves a assFormulaQuestion object to a database
527     * @access public
528     */
529    public function saveToDb($original_id = "")
530    {
531        global $DIC;
532        $ilDB = $DIC['ilDB'];
533
534        $this->saveQuestionDataToDb($original_id);
535        // save variables
536        $affectedRows = $ilDB->manipulateF(
537            "
538		DELETE FROM il_qpl_qst_fq_var
539		WHERE question_fi = %s",
540            array("integer"),
541            array($this->getId())
542        );
543
544        foreach ($this->variables as $variable) {
545            $next_id = $ilDB->nextId('il_qpl_qst_fq_var');
546            $ilDB->insert(
547                'il_qpl_qst_fq_var',
548                array(
549                'variable_id' => array('integer', $next_id),
550                'question_fi' => array('integer', $this->getId()),
551                'variable' => array('text', $variable->getVariable()),
552                'range_min' => array('float', ((strlen($variable->getRangeMin())) ? $variable->getRangeMin() : 0.0)),
553                'range_max' => array('float', ((strlen($variable->getRangeMax())) ? $variable->getRangeMax() : 0.0)),
554                'unit_fi' => array('integer', (is_object($variable->getUnit()) ? (int) $variable->getUnit()->getId() : 0)),
555                'varprecision' => array('integer', (int) $variable->getPrecision()),
556                'intprecision' => array('integer', (int) $variable->getIntprecision()),
557                'range_min_txt' => array('text', $variable->getRangeMinTxt()),
558                'range_max_txt' => array('text', $variable->getRangeMaxTxt())
559            )
560            );
561        }
562        // save results
563        $affectedRows = $ilDB->manipulateF(
564            "DELETE FROM il_qpl_qst_fq_res WHERE question_fi = %s",
565            array("integer"),
566            array($this->getId())
567        );
568
569        foreach ($this->results as $result) {
570            $next_id = $ilDB->nextId('il_qpl_qst_fq_res');
571            if (is_object($result->getUnit())) {
572                $tmp_result_unit = $result->getUnit()->getId();
573            } else {
574                $tmp_result_unit = null;
575            }
576
577            $formula = str_replace(",", ".", $result->getFormula());
578
579            $ilDB->insert("il_qpl_qst_fq_res", array(
580                "result_id" => array("integer", $next_id),
581                "question_fi" => array("integer", $this->getId()),
582                "result" => array("text", $result->getResult()),
583                "range_min" => array("float", ((strlen($result->getRangeMin())) ? $result->getRangeMin() : 0)),
584                "range_max" => array("float", ((strlen($result->getRangeMax())) ? $result->getRangeMax() : 0)),
585                "tolerance" => array("float", ((strlen($result->getTolerance())) ? $result->getTolerance() : 0)),
586                "unit_fi" => array("integer", (int) $tmp_result_unit),
587                "formula" => array("clob", $formula),
588                "resprecision" => array("integer", $result->getPrecision()),
589                "rating_simple" => array("integer", ($result->getRatingSimple()) ? 1 : 0),
590                "rating_sign" => array("float", ($result->getRatingSimple()) ? 0 : $result->getRatingSign()),
591                "rating_value" => array("float", ($result->getRatingSimple()) ? 0 : $result->getRatingValue()),
592                "rating_unit" => array("float", ($result->getRatingSimple()) ? 0 : $result->getRatingUnit()),
593                "points" => array("float", $result->getPoints()),
594                "result_type" => array('integer', (int) $result->getResultType()),
595                "range_min_txt" => array("text", $result->getRangeMinTxt()),
596                "range_max_txt" => array("text", $result->getRangeMaxTxt())
597
598            ));
599        }
600        // save result units
601        $affectedRows = $ilDB->manipulateF(
602            "DELETE FROM il_qpl_qst_fq_res_unit WHERE question_fi = %s",
603            array("integer"),
604            array($this->getId())
605        );
606        foreach ($this->results as $result) {
607            foreach ($this->getResultUnits($result) as $unit) {
608                $next_id = $ilDB->nextId('il_qpl_qst_fq_res_unit');
609                $affectedRows = $ilDB->manipulateF(
610                    "INSERT INTO il_qpl_qst_fq_res_unit (result_unit_id, question_fi, result, unit_fi) VALUES (%s, %s, %s, %s)",
611                    array('integer', 'integer', 'text', 'integer'),
612                    array(
613                        $next_id,
614                        $this->getId(),
615                        $result->getResult(),
616                        $unit->getId()
617                    )
618                );
619            }
620        }
621
622        parent::saveToDb();
623    }
624
625    /**
626     * Loads a assFormulaQuestion object from a database
627     * @param integer $question_id A unique key which defines the question in the database
628     */
629    public function loadFromDb($question_id)
630    {
631        global $DIC;
632        $ilDB = $DIC['ilDB'];
633
634        $result = $ilDB->queryF(
635            "SELECT qpl_questions.* FROM qpl_questions WHERE question_id = %s",
636            array('integer'),
637            array($question_id)
638        );
639        if ($result->numRows() == 1) {
640            $data = $ilDB->fetchAssoc($result);
641            $this->setId($question_id);
642            $this->setTitle($data["title"]);
643            $this->setComment($data["description"]);
644            $this->setSuggestedSolution($data["solution_hint"]);
645            $this->setPoints($data['points']);
646            $this->setOriginalId($data["original_id"]);
647            $this->setObjId($data["obj_fi"]);
648            $this->setAuthor($data["author"]);
649            $this->setOwner($data["owner"]);
650
651            try {
652                $this->setLifecycle(ilAssQuestionLifecycle::getInstance($data['lifecycle']));
653            } catch (ilTestQuestionPoolInvalidArgumentException $e) {
654                $this->setLifecycle(ilAssQuestionLifecycle::getDraftInstance());
655            }
656
657            try {
658                $this->setAdditionalContentEditingMode($data['add_cont_edit_mode']);
659            } catch (ilTestQuestionPoolException $e) {
660            }
661
662            $this->unitrepository = new ilUnitConfigurationRepository($question_id);
663
664            include_once("./Services/RTE/classes/class.ilRTE.php");
665            $this->setQuestion(ilRTE::_replaceMediaObjectImageSrc($data["question_text"], 1));
666            $this->setEstimatedWorkingTime(substr($data["working_time"], 0, 2), substr($data["working_time"], 3, 2), substr($data["working_time"], 6, 2));
667
668            // load variables
669            $result = $ilDB->queryF(
670                "SELECT * FROM il_qpl_qst_fq_var WHERE question_fi = %s",
671                array('integer'),
672                array($question_id)
673            );
674            if ($result->numRows() > 0) {
675                while ($data = $ilDB->fetchAssoc($result)) {
676                    $varObj = new assFormulaQuestionVariable($data["variable"], $data["range_min"], $data["range_max"], $this->getUnitrepository()->getUnit($data["unit_fi"]), $data["varprecision"], $data["intprecision"]);
677                    $varObj->setRangeMinTxt($data['range_min_txt']);
678                    $varObj->setRangeMaxTxt($data['range_max_txt']);
679                    $this->addVariable($varObj);
680                }
681            }
682            // load results
683            $result = $ilDB->queryF(
684                "SELECT * FROM il_qpl_qst_fq_res WHERE question_fi = %s",
685                array('integer'),
686                array($question_id)
687            );
688            if ($result->numRows() > 0) {
689                while ($data = $ilDB->fetchAssoc($result)) {
690                    $resObj = new assFormulaQuestionResult($data["result"], $data["range_min"], $data["range_max"], $data["tolerance"], $this->getUnitrepository()->getUnit($data["unit_fi"]), $data["formula"], $data["points"], $data["resprecision"], $data["rating_simple"], $data["rating_sign"], $data["rating_value"], $data["rating_unit"]);
691                    $resObj->setResultType($data['result_type']);
692                    $resObj->setRangeMinTxt($data['range_min_txt']);
693                    $resObj->setRangeMaxTxt($data['range_max_txt']);
694                    $this->addResult($resObj);
695                }
696            }
697
698            // load result units
699            $result = $ilDB->queryF(
700                "SELECT * FROM il_qpl_qst_fq_res_unit WHERE question_fi = %s",
701                array('integer'),
702                array($question_id)
703            );
704            if ($result->numRows() > 0) {
705                while ($data = $ilDB->fetchAssoc($result)) {
706                    $unit = $this->getUnitrepository()->getUnit($data["unit_fi"]);
707                    $resObj = $this->getResult($data["result"]);
708                    $this->addResultUnit($resObj, $unit);
709                }
710            }
711        }
712        parent::loadFromDb($question_id);
713    }
714
715    /**
716     * Duplicates an assFormulaQuestion
717     * @access public
718     */
719    public function duplicate($for_test = true, $title = "", $author = "", $owner = "", $testObjId = null)
720    {
721        if ($this->id <= 0) {
722            // The question has not been saved. It cannot be duplicated
723            return;
724        }
725        // duplicate the question in database
726        $this_id = $this->getId();
727        $thisObjId = $this->getObjId();
728
729        $clone = $this;
730        include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
731        $original_id = assQuestion::_getOriginalId($this->id);
732        $clone->id = -1;
733
734        if ((int) $testObjId > 0) {
735            $clone->setObjId($testObjId);
736        }
737
738        if ($title) {
739            $clone->setTitle($title);
740        }
741
742        if ($author) {
743            $clone->setAuthor($author);
744        }
745        if ($owner) {
746            $clone->setOwner($owner);
747        }
748
749        if ($for_test) {
750            $clone->saveToDb($original_id);
751        } else {
752            $clone->saveToDb();
753        }
754
755        $clone->unitrepository->cloneUnits($this_id, $clone->getId());
756
757        // copy question page content
758        $clone->copyPageOfQuestion($this_id);
759        // copy XHTML media objects
760        $clone->copyXHTMLMediaObjectsOfQuestion($this_id);
761        $clone->onDuplicate($thisObjId, $this_id, $clone->getObjId(), $clone->getId());
762
763        return $clone->id;
764    }
765
766    /**
767     * Copies an assFormulaQuestion object
768     * @access public
769     */
770    public function copyObject($target_questionpool_id, $title = "")
771    {
772        if ($this->id <= 0) {
773            // The question has not been saved. It cannot be duplicated
774            return;
775        }
776        // duplicate the question in database
777        $clone = $this;
778        include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
779        $original_id = assQuestion::_getOriginalId($this->id);
780        $clone->id = -1;
781        $source_questionpool_id = $this->getObjId();
782        $clone->setObjId($target_questionpool_id);
783        if ($title) {
784            $clone->setTitle($title);
785        }
786        $clone->saveToDb();
787
788        $clone->unitrepository->cloneUnits($original_id, $clone->getId());
789
790        // copy question page content
791        $clone->copyPageOfQuestion($original_id);
792        // copy XHTML media objects
793        $clone->copyXHTMLMediaObjectsOfQuestion($original_id);
794
795        $clone->onCopy($source_questionpool_id, $original_id, $clone->getObjId(), $clone->getId());
796
797        return $clone->id;
798    }
799
800    public function createNewOriginalFromThisDuplicate($targetParentId, $targetQuestionTitle = "")
801    {
802        if ($this->id <= 0) {
803            // The question has not been saved. It cannot be duplicated
804            return;
805        }
806
807        include_once("./Modules/TestQuestionPool/classes/class.assQuestion.php");
808
809        $sourceQuestionId = $this->id;
810        $sourceParentId = $this->getObjId();
811
812        // duplicate the question in database
813        $clone = $this;
814        $clone->id = -1;
815
816        $clone->setObjId($targetParentId);
817
818        if ($targetQuestionTitle) {
819            $clone->setTitle($targetQuestionTitle);
820        }
821
822        $clone->saveToDb();
823        // copy question page content
824        $clone->copyPageOfQuestion($sourceQuestionId);
825        // copy XHTML media objects
826        $clone->copyXHTMLMediaObjectsOfQuestion($sourceQuestionId);
827
828        $clone->onCopy($sourceParentId, $sourceQuestionId, $clone->getObjId(), $clone->getId());
829
830        return $clone->id;
831    }
832
833    /**
834     * Returns the maximum points, a learner can reach answering the question
835     * @see $points
836     */
837    public function getMaximumPoints()
838    {
839        $points = 0;
840        foreach ($this->results as $result) {
841            $points += $result->getPoints();
842        }
843        return $points;
844    }
845
846    /**
847     * Returns the points, a learner has reached answering the question
848     * The points are calculated from the given answers.
849     *
850     * @param integer $user_id The database ID of the learner
851     * @param integer $test_id The database Id of the test containing the question
852     * @access public
853     */
854    public function calculateReachedPoints($active_id, $pass = null, $authorizedSolution = true, $returndetails = false)
855    {
856        if (is_null($pass)) {
857            $pass = $this->getSolutionMaxPass($active_id);
858        }
859        $solutions = &$this->getSolutionValues($active_id, $pass, $authorizedSolution);
860        $user_solution = array();
861        foreach ($solutions as $idx => $solution_value) {
862            if (preg_match("/^(\\\$v\\d+)$/", $solution_value["value1"], $matches)) {
863                $user_solution[$matches[1]] = $solution_value["value2"];
864                $varObj = $this->getVariable($solution_value["value1"]);
865                $varObj->setValue($solution_value["value2"]);
866            } elseif (preg_match("/^(\\\$r\\d+)$/", $solution_value["value1"], $matches)) {
867                if (!array_key_exists($matches[1], $user_solution)) {
868                    $user_solution[$matches[1]] = array();
869                }
870                $user_solution[$matches[1]]["value"] = $solution_value["value2"];
871            } elseif (preg_match("/^(\\\$r\\d+)_unit$/", $solution_value["value1"], $matches)) {
872                if (!array_key_exists($matches[1], $user_solution)) {
873                    $user_solution[$matches[1]] = array();
874                }
875                $user_solution[$matches[1]]["unit"] = $solution_value["value2"];
876            }
877        }
878        //vd($this->getResults());
879        $points = 0;
880        foreach ($this->getResults() as $result) {
881            //vd($user_solution[$result->getResult()]["value"]);
882            $points += $result->getReachedPoints($this->getVariables(), $this->getResults(), $user_solution[$result->getResult()]["value"], $user_solution[$result->getResult()]["unit"], $this->unitrepository->getUnits());
883        }
884
885        return $points;
886    }
887
888    public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSession $previewSession)
889    {
890        $user_solution = $previewSession->getParticipantsSolution();
891
892        $points = 0;
893        foreach ($this->getResults() as $result) {
894            $v = isset($user_solution[$result->getResult()]) ? $user_solution[$result->getResult()] : null;
895            $u = isset($user_solution[$result->getResult() . '_unit']) ? $user_solution[$result->getResult() . '_unit'] : null;
896
897            $points += $result->getReachedPoints(
898                $this->getVariables(),
899                $this->getResults(),
900                $v,
901                $u,
902                $this->unitrepository->getUnits()
903            );
904        }
905
906        $reachedPoints = $this->deductHintPointsFromReachedPoints($previewSession, $points);
907
908        return $this->ensureNonNegativePoints($reachedPoints);
909    }
910
911    protected function isValidSolutionResultValue($submittedValue)
912    {
913        $submittedValue = str_replace(',', '.', $submittedValue);
914
915        if (is_numeric($submittedValue)) {
916            return true;
917        }
918
919        if (preg_match('/^[-+]{0,1}\d+\/\d+$/', $submittedValue)) {
920            return true;
921        }
922
923        return false;
924    }
925
926    /**
927     * Saves the learners input of the question to the database
928     * @param integer $test_id The database id of the test containing this question
929     * @return boolean Indicates the save status (true if saved successful, false otherwise)
930     * @access public
931     * @see    $answers
932     */
933    public function saveWorkingData($active_id, $pass = null, $authorized = true)
934    {
935        global $DIC;
936        $ilDB = $DIC['ilDB'];
937
938        if (is_null($pass)) {
939            include_once "./Modules/Test/classes/class.ilObjTest.php";
940            $pass = ilObjTest::_getPass($active_id);
941        }
942
943        $entered_values = false;
944
945        $this->getProcessLocker()->executeUserSolutionUpdateLockOperation(function () use (&$entered_values, $ilDB, $active_id, $pass, $authorized) {
946            $solutionSubmit = $this->getSolutionSubmit();
947            foreach ($solutionSubmit as $key => $value) {
948                $matches = null;
949                if (preg_match("/^result_(\\\$r\\d+)$/", $key, $matches)) {
950                    if (strlen($value)) {
951                        $entered_values = true;
952                    }
953
954                    $queryResult = "SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND authorized = %s  AND " . $ilDB->like('value1', 'clob', $matches[1]);
955
956                    if ($this->getStep() !== null) {
957                        $queryResult .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
958                    }
959
960                    $result = $ilDB->queryF(
961                        $queryResult,
962                        array('integer', 'integer', 'integer', 'integer'),
963                        array($active_id, $pass, $this->getId(), (int) $authorized)
964                    );
965                    if ($result->numRows()) {
966                        while ($row = $ilDB->fetchAssoc($result)) {
967                            $ilDB->manipulateF(
968                                "DELETE FROM tst_solutions WHERE solution_id = %s AND authorized = %s",
969                                array('integer', 'integer'),
970                                array($row['solution_id'], (int) $authorized)
971                            );
972                        }
973                    }
974
975                    $this->saveCurrentSolution($active_id, $pass, $matches[1], str_replace(",", ".", $value), $authorized);
976                } elseif (preg_match("/^result_(\\\$r\\d+)_unit$/", $key, $matches)) {
977                    $queryResultUnit = "SELECT solution_id FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND authorized = %s AND " . $ilDB->like('value1', 'clob', $matches[1] . "_unit");
978
979                    if ($this->getStep() !== null) {
980                        $queryResultUnit .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
981                    }
982
983                    $result = $ilDB->queryF(
984                        $queryResultUnit,
985                        array('integer', 'integer', 'integer', 'integer'),
986                        array($active_id, $pass, $this->getId(), (int) $authorized)
987                    );
988                    if ($result->numRows()) {
989                        while ($row = $ilDB->fetchAssoc($result)) {
990                            $ilDB->manipulateF(
991                                "DELETE FROM tst_solutions WHERE solution_id = %s AND authorized = %s",
992                                array('integer', 'integer'),
993                                array($row['solution_id'], (int) $authorized)
994                            );
995                        }
996                    }
997
998                    $this->saveCurrentSolution($active_id, $pass, $matches[1] . "_unit", $value, $authorized);
999                }
1000            }
1001        });
1002
1003        if ($entered_values) {
1004            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1005            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
1006                assQuestion::logAction($this->lng->txtlng("assessment", "log_user_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1007            }
1008        } else {
1009            include_once("./Modules/Test/classes/class.ilObjAssessmentFolder.php");
1010            if (ilObjAssessmentFolder::_enabledAssessmentLogging()) {
1011                assQuestion::logAction($this->lng->txtlng("assessment", "log_user_not_entered_values", ilObjAssessmentFolder::_getLogLanguage()), $active_id, $this->getId());
1012            }
1013        }
1014
1015        return true;
1016    }
1017
1018    // fau: testNav - overridden function lookupForExistingSolutions (specific for formula question: don't lookup variables)
1019    /**
1020     * Lookup if an authorized or intermediate solution exists
1021     * @param 	int 		$activeId
1022     * @param 	int 		$pass
1023     * @return 	array		['authorized' => bool, 'intermediate' => bool]
1024     */
1025    public function lookupForExistingSolutions($activeId, $pass)
1026    {
1027        global $DIC;
1028        $ilDB = $DIC['ilDB'];
1029
1030        $return = array(
1031            'authorized' => false,
1032            'intermediate' => false
1033        );
1034
1035        $query = "
1036			SELECT authorized, COUNT(*) cnt
1037			FROM tst_solutions
1038			WHERE active_fi = " . $ilDB->quote($activeId, 'integer') . "
1039			AND question_fi = " . $ilDB->quote($this->getId(), 'integer') . "
1040			AND pass = " . $ilDB->quote($pass, 'integer') . "
1041			AND value1 like '\$r%'
1042			AND value2 is not null
1043			AND value2 <> ''
1044		";
1045
1046        if ($this->getStep() !== null) {
1047            $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
1048        }
1049
1050        $query .= "
1051			GROUP BY authorized
1052		";
1053
1054        $result = $ilDB->query($query);
1055
1056        while ($row = $ilDB->fetchAssoc($result)) {
1057            if ($row['authorized']) {
1058                $return['authorized'] = $row['cnt'] > 0;
1059            } else {
1060                $return['intermediate'] = $row['cnt'] > 0;
1061            }
1062        }
1063        return $return;
1064    }
1065    // fau.
1066
1067    // fau: testNav - Remove an existing solution (specific for formula question: don't delete variables)
1068    /**
1069     * Remove an existing solution without removing the variables
1070     * @param 	int 		$activeId
1071     * @param 	int 		$pass
1072     * @return int
1073     */
1074    public function removeExistingSolutions($activeId, $pass)
1075    {
1076        global $DIC;
1077        $ilDB = $DIC['ilDB'];
1078
1079        $query = "
1080			DELETE FROM tst_solutions
1081			WHERE active_fi = " . $ilDB->quote($activeId, 'integer') . "
1082			AND question_fi = " . $ilDB->quote($this->getId(), 'integer') . "
1083			AND pass = " . $ilDB->quote($pass, 'integer') . "
1084			AND value1 like '\$r%'
1085		";
1086
1087        if ($this->getStep() !== null) {
1088            $query .= " AND step = " . $ilDB->quote((int) $this->getStep(), 'integer') . " ";
1089        }
1090
1091        return $ilDB->manipulate($query);
1092    }
1093    // fau.
1094
1095    protected function savePreviewData(ilAssQuestionPreviewSession $previewSession)
1096    {
1097        $userSolution = $previewSession->getParticipantsSolution();
1098
1099        foreach ($this->getSolutionSubmit() as $key => $val) {
1100            $matches = null;
1101
1102            if (preg_match("/^result_(\\\$r\\d+)$/", $key, $matches)) {
1103                $userSolution[$matches[1]] = $val;
1104            } elseif (preg_match("/^result_(\\\$r\\d+)_unit$/", $key, $matches)) {
1105                $userSolution[$matches[1] . "_unit"] = $val;
1106            }
1107        }
1108
1109        $previewSession->setParticipantsSolution($userSolution);
1110    }
1111
1112    /**
1113     * Returns the question type of the question
1114     * @return string The question type of the question
1115     */
1116    public function getQuestionType()
1117    {
1118        return "assFormulaQuestion";
1119    }
1120
1121    /**
1122     * Returns the name of the additional question data table in the database
1123     * @return string The additional table name
1124     */
1125    public function getAdditionalTableName()
1126    {
1127        return "";
1128    }
1129
1130    /**
1131     * Returns the name of the answer table in the database
1132     * @return string The answer table name
1133     */
1134    public function getAnswerTableName()
1135    {
1136        return "";
1137    }
1138
1139    /**
1140     * Deletes datasets from answers tables
1141     * @param integer $question_id The question id which should be deleted in the answers table
1142     * @access public
1143     */
1144    public function deleteAnswers($question_id)
1145    {
1146        global $DIC;
1147        $ilDB = $DIC['ilDB'];
1148
1149        $affectedRows = $ilDB->manipulateF(
1150            "DELETE FROM il_qpl_qst_fq_var WHERE question_fi = %s",
1151            array('integer'),
1152            array($question_id)
1153        );
1154
1155        $affectedRows = $ilDB->manipulateF(
1156            "DELETE FROM il_qpl_qst_fq_res WHERE question_fi = %s",
1157            array('integer'),
1158            array($question_id)
1159        );
1160
1161        $affectedRows = $ilDB->manipulateF(
1162            "DELETE FROM il_qpl_qst_fq_res_unit WHERE question_fi = %s",
1163            array('integer'),
1164            array($question_id)
1165        );
1166
1167        $affectedRows = $ilDB->manipulateF(
1168            "DELETE FROM il_qpl_qst_fq_ucat WHERE question_fi = %s",
1169            array('integer'),
1170            array($question_id)
1171        );
1172
1173        $affectedRows = $ilDB->manipulateF(
1174            "DELETE FROM il_qpl_qst_fq_unit WHERE question_fi = %s",
1175            array('integer'),
1176            array($question_id)
1177        );
1178    }
1179
1180    /**
1181     * Collects all text in the question which could contain media objects
1182     * which were created with the Rich Text Editor
1183     */
1184    public function getRTETextWithMediaObjects()
1185    {
1186        $text = parent::getRTETextWithMediaObjects();
1187        return $text;
1188    }
1189
1190    /**
1191     * {@inheritdoc}
1192     */
1193    public function setExportDetailsXLS($worksheet, $startrow, $active_id, $pass)
1194    {
1195        parent::setExportDetailsXLS($worksheet, $startrow, $active_id, $pass);
1196
1197        $solution = $this->getSolutionValues($active_id, $pass);
1198
1199        $i = 1;
1200        foreach ($solution as $solutionvalue) {
1201            $worksheet->setCell($startrow + $i, 0, $solutionvalue["value1"]);
1202            $worksheet->setBold($worksheet->getColumnCoord(0) . ($startrow + $i));
1203            if (strpos($solutionvalue["value1"], "_unit")) {
1204                $unit = $this->getUnitrepository()->getUnit($solutionvalue["value2"]);
1205                if (is_object($unit)) {
1206                    $worksheet->setCell($startrow + $i, 1, $unit->getUnit());
1207                }
1208            } else {
1209                $worksheet->setCell($startrow + $i, 1, $solutionvalue["value2"]);
1210            }
1211            if (preg_match("/(\\\$v\\d+)/", $solutionvalue["value1"], $matches)) {
1212                $var = $this->getVariable($solutionvalue["value1"]);
1213                if (is_object($var) && (is_object($var->getUnit()))) {
1214                    $worksheet->setCell($startrow + $i, 2, $var->getUnit()->getUnit());
1215                }
1216            }
1217            $i++;
1218        }
1219
1220        return $startrow + $i + 1;
1221    }
1222
1223    /**
1224     * Returns the best solution for a given pass of a participant
1225     * @return array An associated array containing the best solution
1226     * @access public
1227     */
1228    public function getBestSolution($solutions)
1229    {
1230        $user_solution = array();
1231
1232        foreach ($solutions as $idx => $solution_value) {
1233            if (preg_match("/^(\\\$v\\d+)$/", $solution_value["value1"], $matches)) {
1234                $user_solution[$matches[1]] = $solution_value["value2"];
1235                $varObj = $this->getVariable($matches[1]);
1236                $varObj->setValue($solution_value["value2"]);
1237            } elseif (preg_match("/^(\\\$r\\d+)$/", $solution_value["value1"], $matches)) {
1238                if (!array_key_exists($matches[1], $user_solution)) {
1239                    $user_solution[$matches[1]] = array();
1240                }
1241                $user_solution[$matches[1]]["value"] = $solution_value["value2"];
1242            } elseif (preg_match("/^(\\\$r\\d+)_unit$/", $solution_value["value1"], $matches)) {
1243                if (!array_key_exists($matches[1], $user_solution)) {
1244                    $user_solution[$matches[1]] = array();
1245                }
1246                $user_solution[$matches[1]]["unit"] = $solution_value["value2"];
1247            }
1248        }
1249        foreach ($this->getResults() as $result) {
1250            $resVal = $result->calculateFormula($this->getVariables(), $this->getResults(), parent::getId(), false);
1251
1252            if (is_object($result->getUnit())) {
1253                $user_solution[$result->getResult()]["unit"] = $result->getUnit()->getId();
1254                $user_solution[$result->getResult()]["value"] = $resVal;
1255            } elseif ($result->getUnit() == null) {
1256                $unit_factor = 1;
1257                // there is no fix result_unit, any "available unit" is accepted
1258
1259                $available_units = $result->getAvailableResultUnits(parent::getId());
1260                $result_name = $result->getResult();
1261
1262                if ($available_units[$result_name] != null) {
1263                    $check_unit = in_array($user_solution[$result_name]['unit'], $available_units[$result_name]);
1264                }
1265
1266                if ($check_unit == true) {
1267                    //get unit-factor
1268                    $unit_factor = assFormulaQuestionUnit::lookupUnitFactor($user_solution[$result_name]['unit']);
1269                }
1270
1271                try {
1272                    $user_solution[$result->getResult()]["value"] = ilMath::_div($resVal, $unit_factor, 55);
1273                } catch (ilMathDivisionByZeroException $ex) {
1274                    $user_solution[$result->getResult()]["value"] = 0;
1275                }
1276            }
1277            if ($result->getResultType() == assFormulaQuestionResult::RESULT_CO_FRAC
1278                || $result->getResultType() == assFormulaQuestionResult::RESULT_FRAC) {
1279                $value = assFormulaQuestionResult::convertDecimalToCoprimeFraction($resVal);
1280                if (is_array($value)) {
1281                    $user_solution[$result->getResult()]["value"] = $value[0];
1282                    $user_solution[$result->getResult()]["frac_helper"] = $value[1];
1283                } else {
1284                    $user_solution[$result->getResult()]["value"] = $value;
1285                    $user_solution[$result->getResult()]["frac_helper"] = null;
1286                }
1287            } else {
1288                $user_solution[$result->getResult()]["value"] = ilMath::_div(
1289                    $user_solution[$result->getResult()]["value"],
1290                    1,
1291                    $result->getPrecision()
1292                );
1293            }
1294        }
1295        return $user_solution;
1296    }
1297
1298    public function setId($id = -1)
1299    {
1300        parent::setId($id);
1301        $this->unitrepository->setConsumerId($this->getId());
1302    }
1303
1304    /**
1305     * Object getter
1306     */
1307    public function __get($value)
1308    {
1309        switch ($value) {
1310            case "resultunits":
1311                return $this->resultunits;
1312                break;
1313            default:
1314                return parent::__get($value);
1315                break;
1316        }
1317    }
1318
1319    /**
1320     * @param \ilUnitConfigurationRepository $unitrepository
1321     */
1322    public function setUnitrepository($unitrepository)
1323    {
1324        $this->unitrepository = $unitrepository;
1325    }
1326
1327    /**
1328     * @return \ilUnitConfigurationRepository
1329     */
1330    public function getUnitrepository()
1331    {
1332        return $this->unitrepository;
1333    }
1334
1335    /**
1336     * @return array
1337     */
1338    protected function getSolutionSubmit()
1339    {
1340        $solutionSubmit = array();
1341        foreach ($_POST as $k => $v) {
1342            if (preg_match("/^result_(\\\$r\\d+)$/", $k)) {
1343                $solutionSubmit[$k] = $v;
1344            } elseif (preg_match("/^result_(\\\$r\\d+)_unit$/", $k)) {
1345                $solutionSubmit[$k] = $v;
1346            }
1347        }
1348        return $solutionSubmit;
1349    }
1350
1351    public function validateSolutionSubmit()
1352    {
1353        foreach ($this->getSolutionSubmit() as $key => $value) {
1354            if (preg_match("/^result_(\\\$r\\d+)$/", $key)) {
1355                if (strlen($value) && !$this->isValidSolutionResultValue($value)) {
1356                    ilUtil::sendFailure($this->lng->txt("err_no_numeric_value"), true);
1357                    return false;
1358                }
1359            } elseif (preg_match("/^result_(\\\$r\\d+)_unit$/", $key)) {
1360                continue;
1361            }
1362        }
1363
1364        return true;
1365    }
1366
1367    /**
1368     * Get all available operations for a specific question
1369     *
1370     * @param $expression
1371     *
1372     * @internal param string $expression_type
1373     * @return array
1374     */
1375    public function getOperators($expression)
1376    {
1377        require_once "./Modules/TestQuestionPool/classes/class.ilOperatorsExpressionMapping.php";
1378        return ilOperatorsExpressionMapping::getOperatorsByExpression($expression);
1379    }
1380
1381    /**
1382     * Get all available expression types for a specific question
1383     * @return array
1384     */
1385    public function getExpressionTypes()
1386    {
1387        return array(
1388            iQuestionCondition::PercentageResultExpression,
1389            iQuestionCondition::NumericResultExpression,
1390            iQuestionCondition::EmptyAnswerExpression,
1391        );
1392    }
1393
1394    /**
1395     * Get the user solution for a question by active_id and the test pass
1396     *
1397     * @param int $active_id
1398     * @param int $pass
1399     *
1400     * @return ilUserQuestionResult
1401     */
1402    public function getUserQuestionResult($active_id, $pass)
1403    {
1404        /** @var ilDBInterface $ilDB */
1405        global $DIC;
1406        $ilDB = $DIC['ilDB'];
1407        $result = new ilUserQuestionResult($this, $active_id, $pass);
1408
1409        $maxStep = $this->lookupMaxStep($active_id, $pass);
1410
1411        if ($maxStep !== null) {
1412            $data = $ilDB->queryF(
1413                "SELECT value1, value2 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s AND step = %s",
1414                array("integer", "integer", "integer",'integer'),
1415                array($active_id, $pass, $this->getId(), $maxStep)
1416            );
1417        } else {
1418            $data = $ilDB->queryF(
1419                "SELECT value1, value2 FROM tst_solutions WHERE active_fi = %s AND pass = %s AND question_fi = %s",
1420                array("integer", "integer", "integer"),
1421                array($active_id, $pass, $this->getId())
1422            );
1423        }
1424
1425        while ($row = $ilDB->fetchAssoc($data)) {
1426            if (strstr($row["value1"], '$r') && $row["value2"] != null) {
1427                $result->addKeyValue(str_replace('$r', "", $row["value1"]), $row["value2"]);
1428            }
1429        }
1430
1431        $points = $this->calculateReachedPoints($active_id, $pass);
1432        $max_points = $this->getMaximumPoints();
1433
1434        $result->setReachedPercentage(($points / $max_points) * 100);
1435
1436        return $result;
1437    }
1438
1439    /**
1440     * If index is null, the function returns an array with all anwser options
1441     * Else it returns the specific answer option
1442     *
1443     * @param null|int $index
1444     *
1445     * @return array|ASS_AnswerSimple
1446     */
1447    public function getAvailableAnswerOptions($index = null)
1448    {
1449        if ($index !== null) {
1450            return $this->getResult('$r' . ($index + 1));
1451        } else {
1452            return $this->getResults();
1453        }
1454    }
1455}
1456