Browse Source

Merge branch 'ilosada-7212b' into 1.9.x

Yannick Warnier 10 years ago
parent
commit
ac7abd9e3d

+ 228 - 0
main/exercice/calculated_answer.class.php

@@ -0,0 +1,228 @@
+<?php
+/* For licensing terms, see /license.txt */
+/**
+ *  Class CalculatedAnswer
+ *  This class contains calculated answer form and answer processing functions
+ *
+ *  @author Imanol Losada
+ *  @package chamilo.exercise
+ **/
+class CalculatedAnswer extends Question
+{
+    static $typePicture = 'calculated_answer.png';
+    static $explanationLangVar = 'CalculatedAnswer';
+
+    /**
+     * Constructor
+     */
+    public function CalculatedAnswer()
+    {
+        parent::question();
+        $this -> type = CALCULATED_ANSWER;
+        $this -> isContent = $this-> getIsContent();
+    }
+
+    /**
+     * function which redefines Question::createAnswersForm
+     * @param the formvalidator instance
+     */
+    function createAnswersForm($form)
+    {
+        $defaults = array();
+
+        if (!empty($this->id)) {
+            $objAnswer = new answer($this->id);
+            $preArray = explode('@@', $objAnswer->selectAnswer(1));
+            $defaults['formula'] = array_pop($preArray);
+
+            $defaults['answer'] = array_shift($preArray);
+            $defaults['answer'] = preg_replace("/\[.*\]/", "", $defaults['answer']);
+
+            $defaults['weighting'] = $this->weighting;
+
+        } else {
+            $defaults['answer'] = get_lang('DefaultTextInBlanks');
+        }
+        $lowestValue = "1.00";
+        $highestValue = "20.00";
+
+        // javascript //
+        echo '<script>
+
+            function parseTextNumber(textNumber, floatValue) {
+                if (textNumber.indexOf(".") > -1) {
+                    textNumber = parseFloat(textNumber);
+                    floatValue.exists = "true";
+                } else {
+                    textNumber = parseInt(textNumber);
+                }
+                return textNumber;
+            }
+
+            function updateRandomValue(element) {
+                // "floatValue" helps to distinguish between an integer (10) and a float with all 0 decimals (10.00)
+                var floatValue = { exists: "false" };
+                var index = (element.name).match(/\[[^\]]*\]/g);
+                var lowestValue = parseTextNumber(document.getElementById("lowestValue"+index).value, floatValue);
+                var highestValue = parseTextNumber(document.getElementById("highestValue"+index).value, floatValue);
+                var result = Math.random() * (highestValue - lowestValue) + lowestValue;
+                if (floatValue.exists == "true") {
+                    result = parseFloat(result).toFixed(2);
+                } else {
+                    result = parseInt(result);
+                }
+                document.getElementById("randomValue"+index).innerHTML = "'.get_lang("ExampleValue").': " + result;
+           }
+
+            function FCKeditor_OnComplete(editorInstance) {
+                if (window.attachEvent) {
+                    editorInstance.EditorDocument.attachEvent("onkeyup", updateBlanks) ;
+                } else {
+                    editorInstance.EditorDocument.addEventListener("keyup", updateBlanks, true);
+                }
+            }
+
+            var firstTime = true;
+
+            function updateBlanks() {
+                if (firstTime) {
+                    field = document.getElementById("answer");
+                    var answer = field.value;
+                } else {
+                    var oEditor = FCKeditorAPI.GetInstance("answer");
+                    //var answer =  oEditor.GetXHTML(true);
+                    var answer = oEditor.EditorDocument.body.innerHTML;
+                }
+                var blanks = answer.match(/\[[^\]]*\]/g);
+                var fields = "<div class=\"control-group\"><label class=\"control-label\">'.get_lang('VariableRanges').'</label><div class=\"controls\"><table>";
+                if (blanks!=null) {
+                    if (typeof updateBlanks.randomValues === "undefined") {
+                        updateBlanks.randomValues = [];
+                    }
+                    for (i=0 ; i<blanks.length ; i++){
+                        if (document.getElementById("lowestValue["+i+"]") && document.getElementById("highestValue["+i+"]")) {
+                            lowestValue = document.getElementById("lowestValue["+i+"]").value;
+                            highestValue = document.getElementById("highestValue["+i+"]").value;
+                        } else {
+                            lowestValue = '.$lowestValue.'.toFixed(2);
+                            highestValue = '.$highestValue.'.toFixed(2);
+                            for (j=0; j<blanks.length; j++) {
+                                updateBlanks.randomValues[j] = parseFloat(Math.random() * (highestValue - lowestValue) + lowestValue).toFixed(2);
+                            }
+                        }
+                        fields += "<tr><td><label>"+blanks[i]+"</label></td><td><input class=\"span1\" style=\"margin-left: 0em;\" size=\"5\" value=\""+lowestValue+"\" type=\"text\" id=\"lowestValue["+i+"]\" name=\"lowestValue["+i+"]\" onblur=\"updateRandomValue(this)\"/></td><td><input class=\"span1\" style=\"margin-left: 0em; width:80px;\" size=\"5\" value=\""+highestValue+"\" type=\"text\" id=\"highestValue["+i+"]\" name=\"highestValue["+i+"]\" onblur=\"updateRandomValue(this)\"/></td><td><label class=\"span3\" id=\"randomValue["+i+"]\"/>'.get_lang('ExampleValue').': "+updateBlanks.randomValues[i]+"</label></td></tr>";
+                    }
+                }
+                document.getElementById("blanks_weighting").innerHTML = fields + "</table></div></div>";
+                if (firstTime) {
+                    firstTime = false;
+                }
+            }
+
+            window.onload = updateBlanks;
+
+        </script>';
+
+        // answer
+        $form->addElement('label', null, '<br /><br />'.get_lang('TypeTextBelow').', '.get_lang('And').' '.get_lang('UseTagForBlank'));
+        $form->addElement('html_editor', 'answer', '<img src="../img/fill_field.png">','id="answer" cols="122" rows="6" onkeyup="javascript: updateBlanks(this);"', array('ToolbarSet' => 'TestQuestionDescription', 'Width' => '100%', 'Height' => '350'));
+
+        $form->addRule('answer', get_lang('GiveText'),'required');
+        $form->addRule('answer', get_lang('DefineBlanks'),'regex','/\[.*\]/');
+
+        $form->addElement('label', null, get_lang('IfYouWantOnlyIntegerValuesWriteBothLimitsWithoutDecimals'));
+        $form->addElement('html', '<div id="blanks_weighting"></div>');
+
+        $form->addElement('label', null, get_lang('FormulaExample').': &radic;<span style="text-decoration:overline;">&nbsp;x &divide y&nbsp;</span> &times e <sup>(ln(pi))</sup> = sqrt([x]/[y])*(e^(ln(pi)))');
+        $form->addElement('text', 'formula', get_lang('Formula'), array('id' => 'formula', 'class' => 'span4'));
+        $form->addRule('formula', get_lang('GiveFormula'), 'required');
+
+        $form->addElement('text', 'weighting', get_lang('Weighting'), array('id' => 'weighting', 'class' => 'span1'));
+        $form->setDefaults(array('weighting' => '10'));
+
+        $form->addElement('text', 'answerVariations', get_lang('AnswerVariations'), array('class' => 'span1'));
+        $form->addRule('answerVariations', get_lang('GiveAnswerVariations'),'required');
+        $form->setDefaults(array('answerVariations' => '1'));
+
+        global $text, $class;
+        // setting the save button here and not in the question class.php
+        $form->addElement('style_submit_button', 'submitQuestion', $text, 'class="'.$class.'"');
+
+        if (!empty($this->id)) {
+            $form -> setDefaults($defaults);
+        } else {
+            if ($this->isContent == 1) {
+                $form->setDefaults($defaults);
+            }
+        }
+    }
+
+    /**
+     * abstract function which creates the form to create / edit the answers of the question
+     * @param FormValidator $form
+     */
+    function processAnswersCreation($form)
+    {
+        global $charset;
+        $answer = $form->getSubmitValue('answer');
+        $formula = $form->getSubmitValue('formula');
+        $lowestValues = $form->getSubmitValue('lowestValue');
+        $highestValues = $form->getSubmitValue('highestValue');
+        $answerVariations = $form->getSubmitValue('answerVariations');
+        $this->weighting = $form->getSubmitValue('weighting');
+        //Remove previous answers
+        Database::delete("c_quiz_answer", array('question_id = ?' => $this->id));
+        // Create as many answers as $answerVariations
+        for ($j=0 ; $j < $answerVariations; $j++) {
+            $auxAnswer = $answer;
+            $auxFormula = $formula;
+            $nb = preg_match_all('/\[[^\]]*\]/', $auxAnswer, $blanks);
+            if ($nb > 0) {
+                for ($i=0 ; $i < $nb; ++$i) {
+                    $blankItem = $blanks[0][$i];
+                    $replace = array("[", "]");
+                    $newBlankItem = str_replace($replace, "", $blankItem);
+                    $newBlankItem = "[".trim($newBlankItem)."]";
+                    $randomValue = mt_rand($lowestValues[$i],$highestValues[$i]);
+                    //$randomValue = mt_rand($lowestValues[$i]*100,$highestValues[$i]*100)/100;
+                    $auxAnswer = str_replace($blankItem, $randomValue, $auxAnswer);
+                    $auxFormula = str_replace($blankItem, $randomValue, $auxFormula);
+                }
+                require_once(api_get_path(LIBRARY_PATH).'evalmath.class.php');
+                $math = new EvalMath();
+                $result = $math->evaluate($auxFormula);
+                $result = number_format($result, 2, ".", "");
+                // Remove decimal trailing zeros
+                $result = rtrim($result, "0");
+                // If it is an integer (ends in .00) remove the decimal point
+                if (mb_substr($result, -1) === ".") {
+                    $result = str_replace(".", "", $result);
+                }
+                // Attach formula
+                $auxAnswer .= " [".$result."]@@".$formula;
+            }
+            $this->save();
+            $objAnswer = new answer($this->id);
+            $objAnswer->createAnswer($auxAnswer, 1, '', $this->weighting, array());
+            $objAnswer->position = array();
+            $objAnswer->save();
+
+        }
+    }
+
+    /**
+     * @param null $feedback_type
+     * @param null $counter
+     * @param null $score
+     * @return null|string
+     */
+    function return_header($feedback_type = null, $counter = null, $score = null)
+    {
+        $header = parent::return_header($feedback_type, $counter, $score);
+        $header .= '<table class="'.$this->question_table_class .'">
+            <tr>
+                <th>'.get_lang("Answer").'</th>
+            </tr>';
+        return $header;
+    }
+}

+ 113 - 11
main/exercice/exercise.class.php

@@ -1736,7 +1736,7 @@ class Exercise
         return $id;
     }
 
-    public function show_button($question_id, $questionNum, $questions_in_media = array()) {
+    public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '') {
         global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
 
         $nbrQuestions = $this->get_count_question_list();
@@ -1779,7 +1779,7 @@ class Exercise
                         $questions_in_media = "['".implode("','",$questions_in_media)."']";
                         $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_question_list('.$questions_in_media.'); ">'.$label.'</a>';
                     } else {
-                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_now('.$question_id.'); ">'.$label.'</a>';
+                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_now('.$question_id.', \'\', \''.$currentAnswer.'\'); ">'.$label.'</a>';
                     }
                     $all_button .= '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
 
@@ -2123,7 +2123,7 @@ class Exercise
             error_log('$answerType: '.$answerType);
         }
 
-        if ($answerType == FREE_ANSWER || $answerType == ORAL_EXPRESSION) {
+        if ($answerType == FREE_ANSWER || $answerType == ORAL_EXPRESSION || $answerType == CALCULATED_ANSWER) {
             $nbrAnswers = 1;
         }
 
@@ -2521,6 +2521,102 @@ class Exercise
                         }
                     }
                     break;
+                // for calculated answer
+                case CALCULATED_ANSWER:
+                    $answer = $objAnswerTmp->selectAnswer($_SESSION['calculatedAnswerId'][$questionId]);
+                    $preArray = explode('@@', $answer);
+                    $last = count($preArray) - 1;
+                    $answer = '';
+                    for ($k = 0; $k < $last; $k++) {
+                        $answer .= $preArray[$k];
+                    }
+                    $answerWeighting = array($answerWeighting);
+                    // we save the answer because it will be modified
+                    $temp = $answer;
+                    $answer = '';
+                    $j = 0;
+                    //initialise answer tags
+                    $userTags = $correctTags = $realText = array();
+                    // the loop will stop at the end of the text
+                    while (1) {
+                        // quits the loop if there are no more blanks (detect '[')
+                        if (($pos = api_strpos($temp, '[')) === false) {
+                            // adds the end of the text
+                            $answer = $temp;
+                            $realText[] = $answer;
+                            break; //no more "blanks", quit the loop
+                        }
+                        // adds the piece of text that is before the blank
+                        //and ends with '[' into a general storage array
+                        $realText[] = api_substr($temp, 0, $pos +1);
+                        $answer .= api_substr($temp, 0, $pos +1);
+                        //take the string remaining (after the last "[" we found)
+                        $temp = api_substr($temp, $pos +1);
+                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
+                        if (($pos = api_strpos($temp, ']')) === false) {
+                            // adds the end of the text
+                            $answer .= $temp;
+                            break;
+                        }
+                        if ($from_database) {
+                            $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
+                                          WHERE
+                                            exe_id = '".$exeId."' AND
+                                            question_id= '".Database::escape_string($questionId)."'";
+                            $resfill = Database::query($queryfill);
+                            $str = Database::result($resfill, 0, 'answer');
+                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
+                            $str = str_replace('\r\n', '', $str);
+                            $choice = $arr[1];
+                            if (isset($choice[$j])) {
+                                $tmp = api_strrpos($choice[$j], ' / ');
+                                $choice[$j] = api_substr($choice[$j], 0, $tmp);
+                                $choice[$j] = trim($choice[$j]);
+                                // Needed to let characters ' and " to work as part of an answer
+                                $choice[$j] = stripslashes($choice[$j]);
+                            } else {
+                                $choice[$j] = null;
+                            }
+                        } else {
+                            // This value is the user input, not escaped while correct answer is escaped by fckeditor
+                            $choice[$j] = api_htmlentities(trim($choice[$j]));
+                        }
+                        $userTags[] = $choice[$j];
+                        //put the contents of the [] answer tag into correct_tags[]
+                        $correctTags[] = api_substr($temp, 0, $pos);
+                        $j++;
+                        $temp = api_substr($temp, $pos +1);
+                    }
+                    $answer = '';
+                    $realCorrectTags = $correctTags;
+                    for ($i = 0; $i < count($realCorrectTags); $i++) {
+                        if ($i == 0) {
+                            $answer .= $realText[0];
+                        }
+                        // Needed to parse ' and " characters
+                        $userTags[$i] = stripslashes($userTags[$i]);
+                        if ($correctTags[$i] == $userTags[$i]) {
+                            // gives the related weighting to the student
+                            $questionScore += $answerWeighting[$i];
+                            // increments total score
+                            $totalScore += $answerWeighting[$i];
+                            // adds the word in green at the end of the string
+                            $answer .= $correctTags[$i];
+                        } elseif (!empty($userTags[$i])) {
+                            // else if the word entered by the student IS NOT the same as the one defined by the professor
+                            // adds the word in red at the end of the string, and strikes it
+                            $answer .= '<font color="red"><s>' . $userTags[$i] . '</s></font>';
+                        } else {
+                            // adds a tabulation if no word has been typed by the student
+                            $answer .= ''; // remove &nbsp; that causes issue
+                        }
+                        // adds the correct word, followed by ] to close the blank
+                        $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>]';
+                        if (isset($realText[$i +1])) {
+                            $answer .= $realText[$i +1];
+                        }
+                    }
+                    break;
                 // for free answer
                 case FREE_ANSWER:
                     if ($from_database) {
@@ -2745,32 +2841,36 @@ class Exercise
                             //if ($origin != 'learnpath') {
                             ExerciseShowFunctions::display_unique_or_multiple_answer($feedback_type, $answerType, $studentChoice, $answer, $answerComment, $answerCorrect, 0, 0, 0, $results_disabled);
                             //}
-                        } elseif($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
+                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
                             //if ($origin!='learnpath') {
                             ExerciseShowFunctions::display_multiple_answer_true_false($feedback_type, $answerType, $studentChoice, $answer, $answerComment, $answerCorrect,0,$questionId,0, $results_disabled);
                             //}
-                        } elseif($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ) {
+                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ) {
                             //	if ($origin!='learnpath') {
                             ExerciseShowFunctions::display_multiple_answer_combination_true_false($feedback_type, $answerType, $studentChoice, $answer, $answerComment, $answerCorrect,0,0,0, $results_disabled);
                             //}
-                        } elseif($answerType == FILL_IN_BLANKS) {
+                        } elseif ($answerType == FILL_IN_BLANKS) {
                             //if ($origin!='learnpath') {
                             ExerciseShowFunctions::display_fill_in_blanks_answer($feedback_type, $answer,0,0);
                             //	}
-                        } elseif($answerType == FREE_ANSWER) {
+                        } elseif ($answerType == CALCULATED_ANSWER) {
+                            //if ($origin!='learnpath') {
+                            ExerciseShowFunctions::display_calculated_answer($feedback_type, $answer,0,0);
+                            //  }
+                        } elseif ($answerType == FREE_ANSWER) {
                             //if($origin != 'learnpath') {
                             ExerciseShowFunctions::display_free_answer($feedback_type, $choice, $exeId, $questionId, $questionScore);
                             //}
-                        } elseif($answerType == ORAL_EXPRESSION) {
+                        } elseif ($answerType == ORAL_EXPRESSION) {
                             // to store the details of open questions in an array to be used in mail
                             //if ($origin != 'learnpath') {
                             ExerciseShowFunctions::display_oral_expression_answer($feedback_type, $choice, 0, 0, $nano);
                             //}
-                        } elseif($answerType == HOT_SPOT) {
+                        } elseif ($answerType == HOT_SPOT) {
                             //if ($origin != 'learnpath') {
                             ExerciseShowFunctions::display_hotspot_answer($feedback_type, $answerId, $answer, $studentChoice, $answerComment, $results_disabled);
                             //	}
-                        } elseif($answerType == HOT_SPOT_ORDER) {
+                        } elseif ($answerType == HOT_SPOT_ORDER) {
                             //if ($origin != 'learnpath') {
                             ExerciseShowFunctions::display_hotspot_order_answer($feedback_type, $answerId, $answer, $studentChoice, $answerComment);
                             //}
@@ -2964,6 +3064,9 @@ class Exercise
                         case FILL_IN_BLANKS:
                             ExerciseShowFunctions::display_fill_in_blanks_answer($feedback_type, $answer,$exeId,$questionId);
                             break;
+                        case CALCULATED_ANSWER:
+                            ExerciseShowFunctions::display_calculated_answer($feedback_type, $answer, $exeId, $questionId);
+                            break;
                         case FREE_ANSWER:
                             echo ExerciseShowFunctions::display_free_answer($feedback_type, $choice, $exeId, $questionId, $questionScore);
                             break;
@@ -3197,7 +3300,6 @@ class Exercise
             'threadhold2'=>$threadhold2,
             'threadhold3'=> $threadhold3,
         );
-
         if ($from == 'exercise_result') {
             // if answer is hotspot. To the difference of exercise_show.php, we use the results from the session (from_db=0)
             // TODO Change this, because it is wrong to show the user some results that haven't been stored in the database yet

+ 93 - 1
main/exercice/exercise.lib.php

@@ -534,8 +534,100 @@ function showQuestion(
                             $attributes
                         ), $answer);*/
                 }
-    			$s .= $answer;
+                $s .= $answer;
 
+            } elseif ($answerType == CALCULATED_ANSWER) {
+                /*
+                 * In the CALCULATED_ANSWER test
+                 * you mustn't have [ and ] in the textarea
+                 * you mustn't have @@ in the textarea
+                 * the text to find mustn't be empty or contains only spaces
+                 * the text to find mustn't contains HTML tags
+                 * the text to find mustn't contains char "
+                 */
+                if ($origin !== null) {
+                    global $exe_id;
+                    $trackAttempts = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
+                    $sqlTrackAttempt = 'SELECT answer FROM '.$trackAttempts.' WHERE exe_id='.$exe_id.' AND question_id='.$questionId;
+                    $rsLastAttempt = Database::query($sqlTrackAttempt);
+                    $rowLastAttempt = Database::fetch_array($rsLastAttempt);
+                    $answer = $rowLastAttempt['answer'];
+                    if (empty($answer)) {
+                        $_SESSION['calculatedAnswerId'][$questionId] = mt_rand(1, $nbrAnswers);
+                        $answer = $objAnswerTmp->selectAnswer($_SESSION['calculatedAnswerId'][$questionId]);
+                    }
+                }
+                list($answer) = explode('@@', $answer);
+                // $correctAnswerList array of array with correct anwsers 0=> [0=>[\p] 1=>[plop]]
+                api_preg_match_all('/\[[^]]+\]/', $answer, $correctAnswerList);
+                // get student answer to display it if student go back to previous calculated answer question in a test
+                if (isset($user_choice[0]['answer'])) {
+                    api_preg_match_all('/\[[^]]+\]/', $answer, $studentAnswerList);
+                    $studentAnswerListTobecleaned = $studentAnswerList[0];
+                    $studentAnswerList = array();
+
+                    for ($i=0; $i < count($studentAnswerListTobecleaned); $i++) {
+                        $answerCorrected = $studentAnswerListTobecleaned[$i];
+                        $answerCorrected = api_preg_replace('| / <font color="green"><b>.*$|', '', $answerCorrected);
+                        $answerCorrected = api_preg_replace('/^\[/', '', $answerCorrected);
+                        $answerCorrected = api_preg_replace('|^<font color="red"><s>|', '', $answerCorrected);
+                        $answerCorrected = api_preg_replace('|</s></font>$|', '', $answerCorrected);
+                        $answerCorrected = '['.$answerCorrected.']';
+                        $studentAnswerList[] = $answerCorrected;
+                    }
+                }
+                // If display preview of answer in test view for exemple, set the student answer to the correct answers
+                if ($debug_mark_answer) {
+                    // contain the rights answers surronded with brackets
+                    $studentAnswerList = $correctAnswerList[0];
+                }
+                /*
+                Split the response by bracket
+                tabComments is an array with text surrounding the text to find
+                we add a space before and after the answerQuestion to be sure to
+                have a block of text before and after [xxx] patterns
+                so we have n text to find ([xxx]) and n+1 block of texts before,
+                between and after the text to find
+                */
+                $tabComments = api_preg_split('/\[[^]]+\]/', ' '.$answer.' ');
+                if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
+                    $answer = "";
+                    $i = 0;
+                    foreach ($studentAnswerList as $studentItem) {
+                        // remove surronding brackets
+                        $studentResponse = api_substr($studentItem, 1, api_strlen($studentItem) - 2);
+                        $size = strlen($studentItem);
+                        $attributes['class'] = detectInputAppropriateClass($size);
+
+                        $answer .= $tabComments[$i].
+                            Display::input(
+                                'text',
+                                "choice[$questionId][]",
+                                $studentResponse,
+                                $attributes
+                            );
+                        $i++;
+                    }
+                    $answer .= $tabComments[$i];
+                } else {
+                    // display exercise with empty input fields
+                    // every [xxx] are replaced with an empty input field
+                    foreach ($correctAnswerList[0] as $item) {
+                        $size = strlen($item);
+                        $attributes['class'] = detectInputAppropriateClass($size);
+                        $answer = str_replace(
+                            $item,
+                            Display::input('text', "choice[$questionId][]", '', $attributes),
+                            $answer
+                        );
+                    }
+                }
+                if ($origin !== null) {
+                    $s = $answer;
+                    break;
+                } else {
+                    $s .= $answer;
+                }
             } elseif ($answerType == MATCHING) {
     			// matching type, showing suggestions and answers
     			// TODO: replace $answerId by $numAnswer

+ 5 - 3
main/exercice/exercise_submit.php

@@ -82,6 +82,7 @@ $choice = empty($choice) ? isset($_REQUEST['choice2']) ? $_REQUEST['choice2'] :
 
 //From submit modal
 $current_question = isset($_REQUEST['num']) ? intval($_REQUEST['num']) : null;
+$currentAnswer = isset($_REQUEST['num_answer']) ? intval($_REQUEST['num_answer']) : null;
 
 //Error message
 $error = '';
@@ -415,7 +416,7 @@ if ($formSent && isset($_POST)) {
                     if (isset($exe_id)) {
                     	//Manage the question and answer attempts
                         if ($debug) { error_log('8.3. manage_answer exe_id: '.$exe_id.' - $questionId: '.$questionId.' Choice'.print_r($choice,1)); }
-                    	$objExercise->manage_answer(
+                        $objExercise->manage_answer(
                             $exe_id,
                             $questionId,
                             $choice,
@@ -424,7 +425,8 @@ if ($formSent && isset($_POST)) {
                             true,
                             false,
                             false,
-                            $objExercise->propagate_neg
+                            $objExercise->propagate_neg,
+                            array()
                         );
                     }
                     //END of saving and qualifying
@@ -442,7 +444,6 @@ if ($formSent && isset($_POST)) {
     Session::write('exerciseResultCoordinates',$exerciseResultCoordinates);
 
     // if all questions on one page OR if it is the last question (only for an exercise with one question per page)
-
     if ($objExercise->type == ALL_ON_ONE_PAGE || $current_question >= $question_count) {
         if (api_is_allowed_to_session_edit()) {
             // goes to the script that will show the result of the exercise
@@ -973,6 +974,7 @@ if (!empty($error)) {
          <input type="hidden" name="formSent"				value="1" />
          <input type="hidden" name="exerciseId" 			value="'.$exerciseId . '" />
          <input type="hidden" name="num" 					value="'.$current_question.'" id="num_current_id" />
+         <input type="hidden" name="num_answer"             value="'.$currentAnswer.'" id="num_current_answer_id" />
          <input type="hidden" name="exe_id" 				value="'.$exe_id . '" />
          <input type="hidden" name="origin" 				value="'.$origin . '" />
          <input type="hidden" name="learnpath_id" 			value="'.$learnpath_id . '" />

+ 2 - 0
main/exercice/question.class.php

@@ -17,6 +17,7 @@ define('MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE',    12);
 define('ORAL_EXPRESSION',                           13);
 define('GLOBAL_MULTIPLE_ANSWER',                    14);
 define('MEDIA_QUESTION',                            15);
+define('CALCULATED_ANSWER',                         16);
 
 //Some alias used in the QTI exports
 define('MCUA',				1);
@@ -68,6 +69,7 @@ abstract class Question
         MULTIPLE_ANSWER_TRUE_FALSE =>   array('multiple_answer_true_false.class.php', 'MultipleAnswerTrueFalse'),
         MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE =>   array('multiple_answer_combination_true_false.class.php', 'MultipleAnswerCombinationTrueFalse'),
         GLOBAL_MULTIPLE_ANSWER =>		array('global_multiple_answer.class.php' , 'GlobalMultipleAnswer'),
+        CALCULATED_ANSWER =>			array('calculated_answer.class.php' , 'CalculatedAnswer')
         //MEDIA_QUESTION =>               array('media_question.class.php' , 'MediaQuestion')
     );
 

BIN
main/img/calculated_answer.png


BIN
main/img/icons/128/calculated_answer.png


BIN
main/img/icons/32/calculated_answer.png


BIN
main/img/icons/64/calculated_answer.png


+ 772 - 0
main/img/icons/svg/calculated_answer.svg

@@ -0,0 +1,772 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="124"
+   height="124"
+   id="svg7613"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="database.svg">
+  <defs
+     id="defs7615">
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4799"
+       id="radialGradient7559"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.55758631,0,0,0.57073983,33.783815,180.81737)"
+       cx="55.21796"
+       cy="458.46527"
+       fx="55.21796"
+       fy="458.46527"
+       r="15.213428" />
+    <linearGradient
+       id="linearGradient4799">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4799-9"
+       id="radialGradient7557"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.55758631,0,0,0.57073983,104.95785,180.81737)"
+       cx="55.21796"
+       cy="458.46527"
+       fx="55.21796"
+       fy="458.46527"
+       r="15.213428" />
+    <linearGradient
+       id="linearGradient4799-9">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-0" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-9" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4799-6">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-8" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-96" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4799-2">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-4" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-8" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4799-1">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-49" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-98" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3609">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop3611" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop3613" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4068">
+      <stop
+         style="stop-color:#eaeae9;stop-opacity:1;"
+         offset="0"
+         id="stop4070" />
+      <stop
+         style="stop-color:#a5a7a2;stop-opacity:1;"
+         offset="1"
+         id="stop4072" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient8239">
+      <stop
+         style="stop-color:#eaeae9;stop-opacity:1;"
+         offset="0"
+         id="stop8241" />
+      <stop
+         style="stop-color:#a5a7a2;stop-opacity:1;"
+         offset="1"
+         id="stop8243" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3281">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3283" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3285" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4608">
+      <stop
+         style="stop-color:#eeeeec"
+         offset="0"
+         id="stop4610" />
+      <stop
+         style="stop-color:#8a8c87;stop-opacity:1;"
+         offset="1"
+         id="stop4612" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4099">
+      <stop
+         style="stop-color:#6f726f;stop-opacity:1;"
+         offset="0"
+         id="stop4101" />
+      <stop
+         style="stop-color:#6f726f;stop-opacity:0;"
+         offset="1"
+         id="stop4103" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3510">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3512" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3514" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3674">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="0"
+         id="stop3676" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="1"
+         id="stop3678" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3676">
+      <stop
+         style="stop-color:#eeeeec"
+         offset="0"
+         id="stop4022" />
+      <stop
+         style="stop-color:#555753"
+         offset="1"
+         id="stop3680" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient1557">
+      <stop
+         style="stop-color:#2e3436;stop-opacity:1;"
+         offset="0"
+         id="stop1559" />
+      <stop
+         style="stop-color:#888a85"
+         offset="1"
+         id="stop1561" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3730">
+      <stop
+         style="stop-color:#eeeeec"
+         offset="0"
+         id="stop3732" />
+      <stop
+         style="stop-color:#888a85"
+         offset="1"
+         id="stop3734" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3628">
+      <stop
+         style="stop-color:#d3d7cf"
+         offset="0"
+         id="stop3630" />
+      <stop
+         style="stop-color:#555753"
+         offset="1"
+         id="stop3632" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3692">
+      <stop
+         style="stop-color:#2e3436;stop-opacity:1;"
+         offset="0"
+         id="stop3694" />
+      <stop
+         style="stop-color:#babdb6"
+         offset="1"
+         id="stop3696" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3670">
+      <stop
+         style="stop-color:#d3d7cf"
+         offset="0"
+         id="stop3672" />
+      <stop
+         style="stop-color:#555753"
+         offset="1"
+         id="stop3674" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3498">
+      <stop
+         style="stop-color:#3465a4;stop-opacity:1;"
+         offset="0"
+         id="stop3500" />
+      <stop
+         style="stop-color:#3465a4;stop-opacity:0;"
+         offset="1"
+         id="stop3502" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient8294">
+      <stop
+         style="stop-color:#3465a4;stop-opacity:1;"
+         offset="0"
+         id="stop8296" />
+      <stop
+         style="stop-color:#3465a4;stop-opacity:0;"
+         offset="1"
+         id="stop8298" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient8301">
+      <stop
+         style="stop-color:#3465a4;stop-opacity:1;"
+         offset="0"
+         id="stop8303" />
+      <stop
+         style="stop-color:#3465a4;stop-opacity:0;"
+         offset="1"
+         id="stop8305" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4799-0"
+       id="radialGradient7555"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.55758631,0,0,0.57073983,388.7386,180.81737)"
+       cx="55.21796"
+       cy="458.46527"
+       fx="55.21796"
+       fy="458.46527"
+       r="15.213428" />
+    <linearGradient
+       id="linearGradient4799-0">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-3" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-97" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4799-20">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-43" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-95" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4799-4"
+       id="radialGradient7519"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.55758631,0,0,0.57073983,530.50531,180.81737)"
+       cx="55.21796"
+       cy="458.46527"
+       fx="55.21796"
+       fy="458.46527"
+       r="15.213428" />
+    <linearGradient
+       id="linearGradient4799-4">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-2" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-5" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4799-98"
+       id="radialGradient7517"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.55758631,0,0,0.57073983,601.39167,180.81737)"
+       cx="55.21796"
+       cy="458.46527"
+       fx="55.21796"
+       fy="458.46527"
+       r="15.213428" />
+    <linearGradient
+       id="linearGradient4799-98">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-1" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-0" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4799-8"
+       id="radialGradient7515"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.55758631,0,0,0.57073983,672.34994,180.81737)"
+       cx="55.21796"
+       cy="458.46527"
+       fx="55.21796"
+       fy="458.46527"
+       r="15.213428" />
+    <linearGradient
+       id="linearGradient4799-8">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-17" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-80" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4799-5">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-26" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-56" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4799-25"
+       id="radialGradient7501"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.55758631,0,0,0.57073983,814.69797,180.81737)"
+       cx="55.21796"
+       cy="458.46527"
+       fx="55.21796"
+       fy="458.46527"
+       r="15.213428" />
+    <linearGradient
+       id="linearGradient4799-25">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-5" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-1" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6507"
+       id="linearGradient7473"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4802018,0,0,0.4802018,816.32197,40.971943)"
+       x1="237.87701"
+       y1="853.43646"
+       x2="238.79231"
+       y2="877.03088" />
+    <linearGradient
+       id="linearGradient6507"
+       inkscape:collect="always">
+      <stop
+         id="stop6509"
+         offset="0"
+         style="stop-color:#000000;stop-opacity:1" />
+      <stop
+         id="stop6511"
+         offset="1"
+         style="stop-color:#b3b3b3;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6471"
+       id="linearGradient7475"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0.11085463,0.01271462)"
+       x1="118.98001"
+       y1="881.33508"
+       x2="126.49492"
+       y2="881.33508" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient6471">
+      <stop
+         style="stop-color:#030303;stop-opacity:1"
+         offset="0"
+         id="stop6473" />
+      <stop
+         style="stop-color:#4c4c4c;stop-opacity:1"
+         offset="1"
+         id="stop6475" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6422"
+       id="linearGradient7477"
+       gradientUnits="userSpaceOnUse"
+       x1="125.8298"
+       y1="561.69379"
+       x2="152.55438"
+       y2="577.74768" />
+    <linearGradient
+       id="linearGradient6422">
+      <stop
+         style="stop-color:#151515;stop-opacity:1;"
+         offset="0"
+         id="stop6424" />
+      <stop
+         style="stop-color:#787878;stop-opacity:1;"
+         offset="1"
+         id="stop6426" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6436"
+       id="radialGradient7479"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.64523018,0.76398823,-6.3282656,5.3445692,3681.5246,-2603.7602)"
+       cx="146.55011"
+       cy="573.54309"
+       fx="146.55011"
+       fy="573.54309"
+       r="15.560146" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient6436">
+      <stop
+         style="stop-color:#e6e6e6;stop-opacity:1"
+         offset="0"
+         id="stop6438" />
+      <stop
+         style="stop-color:#999999;stop-opacity:1"
+         offset="1"
+         id="stop6440" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6461"
+       id="linearGradient7481"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4802018,0,0,0.4802018,816.32197,40.971943)"
+       x1="230.73572"
+       y1="903.21411"
+       x2="233.44475"
+       y2="911.66388" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient6461">
+      <stop
+         style="stop-color:#434343;stop-opacity:1;"
+         offset="0"
+         id="stop6463" />
+      <stop
+         style="stop-color:#434343;stop-opacity:0;"
+         offset="1"
+         id="stop6465" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6535"
+       id="linearGradient7483"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.39731574,0,0,0.39731574,904.7542,130.91293)"
+       x1="92.852379"
+       y1="851.14813"
+       x2="85.733353"
+       y2="851.09729" />
+    <linearGradient
+       id="linearGradient6535"
+       inkscape:collect="always">
+      <stop
+         id="stop6537"
+         offset="0"
+         style="stop-color:#4d4d4d;stop-opacity:1" />
+      <stop
+         id="stop6539"
+         offset="1"
+         style="stop-color:#666666;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6529"
+       id="linearGradient7485"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.16774085,0,0,0.48511497,925.53916,56.129219)"
+       x1="92.852379"
+       y1="851.14813"
+       x2="85.733353"
+       y2="851.09729" />
+    <linearGradient
+       id="linearGradient6529"
+       inkscape:collect="always">
+      <stop
+         id="stop6531"
+         offset="0"
+         style="stop-color:#333333;stop-opacity:1" />
+      <stop
+         id="stop6533"
+         offset="1"
+         style="stop-color:#b3b3b3;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6535"
+       id="linearGradient7487"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.39731574,0,0,0.39731574,904.7542,130.91293)"
+       x1="92.852379"
+       y1="851.14813"
+       x2="85.733353"
+       y2="851.09729" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6529"
+       id="linearGradient7489"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.16774085,0,0,0.48511497,925.53916,56.129219)"
+       x1="92.852379"
+       y1="851.14813"
+       x2="85.733353"
+       y2="851.09729" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6535"
+       id="linearGradient7491"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.39731574,0,0,0.39731574,904.7542,130.91293)"
+       x1="92.852379"
+       y1="851.14813"
+       x2="85.733353"
+       y2="851.09729" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6529"
+       id="linearGradient7493"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.16774085,0,0,0.48511497,925.53916,56.129219)"
+       x1="92.852379"
+       y1="851.14813"
+       x2="85.733353"
+       y2="851.09729" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6535"
+       id="linearGradient7495"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.39731574,0,0,0.39731574,904.7542,130.91293)"
+       x1="92.852379"
+       y1="851.14813"
+       x2="85.733353"
+       y2="851.09729" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6529"
+       id="linearGradient7497"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.16774085,0,0,0.48511497,925.53916,56.129219)"
+       x1="92.852379"
+       y1="851.14813"
+       x2="85.733353"
+       y2="851.09729" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4799-59"
+       id="radialGradient7611"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.55758631,0,0,0.57073983,969.0517,180.81737)"
+       cx="55.21796"
+       cy="458.46527"
+       fx="55.21796"
+       fy="458.46527"
+       r="15.213428" />
+    <linearGradient
+       id="linearGradient4799-59">
+      <stop
+         style="stop-color:#ffde00;stop-opacity:1;"
+         offset="0"
+         id="stop4801-24" />
+      <stop
+         style="stop-color:#ffa700;stop-opacity:1;"
+         offset="1"
+         id="stop4803-14" />
+    </linearGradient>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.0887097"
+     inkscape:cx="62"
+     inkscape:cy="73.285552"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1871"
+     inkscape:window-height="1056"
+     inkscape:window-x="1415"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7618">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Capa 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-928.36218)">
+    <g
+       id="calculated_answer"
+       inkscape:label="#g7597"
+       inkscape:export-filename="/home/aragonc/Escritorio/icons_answer/32/calculated_answer.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90"
+       transform="matrix(1.9374998,0,0,1.9374998,-1863.871,196.307)">
+      <g
+         inkscape:label="#g7464"
+         transform="translate(-23.000061,-11.662994)"
+         id="asas">
+        <rect
+           y="389.7164"
+           x="985.58063"
+           height="64"
+           width="64"
+           id="rect6243"
+           style="opacity:0;fill:#ffffff;fill-opacity:1;stroke:none" />
+        <rect
+           ry="3.2374587"
+           y="395.62646"
+           x="997.15076"
+           height="53.322857"
+           width="42.888599"
+           id="rect6245"
+           style="fill:#2d404e;fill-opacity:1;stroke:none" />
+        <rect
+           style="fill:#ffffff;fill-opacity:1;stroke:#70acc7;stroke-opacity:1"
+           id="rect6247"
+           width="42.888599"
+           height="53.322857"
+           x="995.8692"
+           y="394.34494"
+           ry="3.2374587" />
+        <path
+           style="fill:#2f7ea6;fill-opacity:1;stroke:none"
+           d="m 1006.0248,428.26384 c 1.6796,-3.12861 2.7363,-9.78966 3.2187,-14.57402 l -2.6914,-0.0365 c -1.0256,-0.19548 -0.9469,-1.79344 0.073,-1.98351 l 2.8931,0.018 c 0.4578,-4.11051 1.2791,-6.24209 3.5474,-7.41351 2.4755,-1.2784 4.6106,1.14863 4.457,2.69375 -0.2549,2.56308 -4.1042,2.06539 -3.8692,-0.58777 -1.1673,0.2874 -1.5604,4.27294 -1.6374,5.36654 l 3.0527,0.0519 c 1.2331,0.12653 1.0604,2.00156 -0.142,2.00156 l -2.9295,-0.005 c -1.3004,10.3995 -2.2735,14.69564 -4.8739,17.15837 -1.3983,1.3243 -4.4076,-0.33202 -4.4792,-2.33417 -0.086,-2.39893 2.9045,-2.31327 2.8323,-0.15065 0.059,0.38801 0.3927,0.11262 0.548,-0.20494 z"
+           id="path6778"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cccccsscccccssccc" />
+        <text
+           xml:space="preserve"
+           style="font-size:11.53766727px;font-style:oblique;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ff6600;fill-opacity:1;stroke:none;font-family:Helvetica Rounded LT Std;-inkscape-font-specification:Helvetica Rounded LT Std Bold Oblique"
+           x="1015.799"
+           y="417.82117"
+           id="text6780"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan6782"
+             x="1015.799"
+             y="417.82117">(x)</tspan></text>
+        <path
+           sodipodi:nodetypes="ccccccccccccccccccccccccc"
+           inkscape:connector-curvature="0"
+           id="path6784"
+           d="m 999.0212,439.96834 -0.028,-5.07522 c 0.033,-1.11444 2.0468,-1.05051 2.1832,-0.0285 l 0,5.01854 3.3741,-3.4024 c 0.8279,-0.77673 2.0323,0.58656 1.3042,1.36097 l -3.1472,3.54417 4.4231,0.0284 c 1.2903,0.16752 1.1276,2.29901 0.057,2.38169 l -4.2813,-0.0284 3.2322,3.11887 c 0.7632,0.85437 -0.2549,2.33446 -1.3893,1.58778 l -3.6292,-3.48746 -0.028,5.01853 c 0.1283,1.06573 -2.0558,1.26709 -2.2683,0.0284 l 0.028,-4.76338 -3.2039,3.20394 c -1.1279,0.27764 -2.05381,-0.52426 -1.6161,-1.50274 l 3.4024,-3.26063 -4.90518,0.0568 c -1.20828,-0.23463 -1.17896,-2.22438 0.0284,-2.38168 l 5.01858,-0.0284 -3.5158,-3.4591 c -0.68409,-1.08866 0.4896,-2.19074 1.4176,-1.53108 z"
+           style="fill:url(#radialGradient7611);fill-opacity:1;stroke:#ff4100;stroke-width:0.83637941;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      </g>
+      <path
+         style="fill:#ffcc00;stroke:none"
+         d="m 996.6528,435.52065 18.6289,-21.66544 0.035,19.20347 c -0.1536,1.04704 -1.0015,2.40185 -2.8606,2.48542 z"
+         id="path7569"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc" />
+      <path
+         sodipodi:nodetypes="ccccc"
+         inkscape:connector-curvature="0"
+         id="path7567"
+         d="m 997.72768,435.522 17.55612,-20.41771 0.033,18.09752 c -0.1447,0.98675 -0.9438,2.26353 -2.6959,2.34229 z"
+         style="fill:#ff0000;stroke:none" />
+      <text
+         transform="matrix(0.6317534,-0.77516943,0.77516943,0.6317534,0,0)"
+         sodipodi:linespacing="125%"
+         id="text7561"
+         y="1054.7717"
+         x="298.08649"
+         style="font-size:7.01585102px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial Bold"
+         xml:space="preserve"><tspan
+           y="1054.7717"
+           x="298.08649"
+           id="tspan7563"
+           sodipodi:role="line">beta</tspan></text>
+    </g>
+  </g>
+</svg>

+ 3 - 3
main/inc/ajax/exercise.ajax.php

@@ -389,7 +389,8 @@ switch ($action) {
                         false,
                         true,
                         false,
-                        $objExercise->selectPropagateNeg()
+                        $objExercise->selectPropagateNeg(),
+                        array()
                     );
 
                     // Removing old score.
@@ -422,8 +423,7 @@ switch ($action) {
                     false,
                     false,
                     $objExercise->selectPropagateNeg(),
-                    $hotspot_delineation_result,
-                    true
+                    $hotspot_delineation_result
                 );
 
                 //  Adding the new score.

+ 390 - 0
main/inc/lib/evalmath.class.php

@@ -0,0 +1,390 @@
+<?php
+/* This library is licensed under New BSD. See below to check its terms.*/
+/*
+================================================================================
+
+EvalMath - PHP Class to safely evaluate math expressions
+Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
+
+================================================================================
+
+NAME
+    EvalMath - safely evaluate math expressions
+
+SYNOPSIS
+    <?
+      include('evalmath.class.php');
+      $m = new EvalMath;
+      // basic evaluation:
+      $result = $m->evaluate('2+2');
+      // supports: order of operation; parentheses; negation; built-in functions
+      $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
+      // create your own variables
+      $m->evaluate('a = e^(ln(pi))');
+      // or functions
+      $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
+      // and then use them
+      $result = $m->evaluate('3*f(42,a)');
+    ?>
+
+DESCRIPTION
+    Use the EvalMath class when you want to evaluate mathematical expressions
+    from untrusted sources.  You can define your own variables and functions,
+    which are stored in the object.  Try it, it's fun!
+
+METHODS
+    $m->evalute($expr)
+        Evaluates the expression and returns the result.  If an error occurs,
+        prints a warning and returns false.  If $expr is a function assignment,
+        returns true on success.
+
+    $m->e($expr)
+        A synonym for $m->evaluate().
+
+    $m->vars()
+        Returns an associative array of all user-defined variables and values.
+
+    $m->funcs()
+        Returns an array of all user-defined functions.
+
+PARAMETERS
+    $m->suppress_errors
+        Set to true to turn off warnings when evaluating expressions
+
+    $m->last_error
+        If the last evaluation failed, contains a string describing the error.
+        (Useful when suppress_errors is on).
+
+AUTHOR INFORMATION
+    Copyright 2005, Miles Kaufmann.
+
+LICENSE
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+
+    1   Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+    2.  Redistributions in binary form must reproduce the above copyright
+        notice, this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+    3.  The name of the author may not be used to endorse or promote
+        products derived from this software without specific prior written
+        permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+    POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+class EvalMath {
+
+    var $suppress_errors = false;
+    var $last_error = null;
+
+    var $v = array('e'=>2.71,'pi'=>3.14); // variables (and constants)
+    var $f = array(); // user-defined functions
+    var $vb = array('e', 'pi'); // constants
+    var $fb = array(  // built-in functions
+        'sin','sinh','arcsin','asin','arcsinh','asinh',
+        'cos','cosh','arccos','acos','arccosh','acosh',
+        'tan','tanh','arctan','atan','arctanh','atanh',
+        'sqrt','abs','ln','log');
+
+    function EvalMath() {
+        // make the variables a little more accurate
+        $this->v['pi'] = pi();
+        $this->v['e'] = exp(1);
+    }
+
+    function e($expr) {
+        return $this->evaluate($expr);
+    }
+
+    function evaluate($expr) {
+        $this->last_error = null;
+        $expr = trim($expr);
+        if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
+        //===============
+        // is it a variable assignment?
+        if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
+            if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
+                return $this->trigger("cannot assign to constant '$matches[1]'");
+            }
+            if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
+            $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
+            return $this->v[$matches[1]]; // and return the resulting value
+        //===============
+        // is it a function assignment?
+        } elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
+            $fnn = $matches[1]; // get the function name
+            if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
+                return $this->trigger("cannot redefine built-in function '$matches[1]()'");
+            }
+            $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
+            if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
+            for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
+                $token = $stack[$i];
+                if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {
+                    if (array_key_exists($token, $this->v)) {
+                        $stack[$i] = $this->v[$token];
+                    } else {
+                        return $this->trigger("undefined variable '$token' in function definition");
+                    }
+                }
+            }
+            $this->f[$fnn] = array('args'=>$args, 'func'=>$stack);
+            return true;
+        //===============
+        } else {
+            return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
+        }
+    }
+
+    function vars() {
+        $output = $this->v;
+        unset($output['pi']);
+        unset($output['e']);
+        return $output;
+    }
+
+    function funcs() {
+        $output = array();
+        foreach ($this->f as $fnn=>$dat)
+            $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
+        return $output;
+    }
+
+    //===================== HERE BE INTERNAL METHODS ====================\\
+
+    // Convert infix to postfix notation
+    function nfx($expr) {
+
+        $index = 0;
+        $stack = new EvalMathStack;
+        $output = array(); // postfix form of expression, to be passed to pfx()
+        $expr = trim(strtolower($expr));
+
+        $ops   = array('+', '-', '*', '/', '^', '_');
+        $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative operator?
+        $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2); // operator precedence
+
+        $expecting_op = false; // we use this in syntax-checking the expression
+                               // and determining when a - is a negation
+
+        if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr, $matches)) { // make sure the characters are all good
+            return $this->trigger("illegal character '{$matches[0]}'");
+        }
+
+        while(1) { // 1 Infinite Loop ;)
+            $op = substr($expr, $index, 1); // get the first character at the current index
+            // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
+            $ex = preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
+            //===============
+            if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
+                $stack->push('_'); // put a negation on the stack
+                $index++;
+            } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
+                return $this->trigger("illegal character '_'"); // but not in the input expression
+            //===============
+            } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
+                if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
+                    $op = '*'; $index--; // it's an implicit multiplication
+                }
+                // heart of the algorithm:
+                while($stack->count > 0 and ($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
+                    $output[] = $stack->pop(); // pop stuff off the stack into the output
+                }
+                // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
+                $stack->push($op); // finally put OUR operator onto the stack
+                $index++;
+                $expecting_op = false;
+            //===============
+            } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
+                while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
+                    if (is_null($o2)) return $this->trigger("unexpected ')'");
+                    else $output[] = $o2;
+                }
+                if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) { // did we just close a function?
+                    $fnn = $matches[1]; // get the function name
+                    $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
+                    $output[] = $stack->pop(); // pop the function and push onto the output
+                    if (in_array($fnn, $this->fb)) { // check the argument count
+                        if($arg_count > 1)
+                            return $this->trigger("too many arguments ($arg_count given, 1 expected)");
+                    } elseif (array_key_exists($fnn, $this->f)) {
+                        if ($arg_count != count($this->f[$fnn]['args']))
+                            return $this->trigger("wrong number of arguments ($arg_count given, " . count($this->f[$fnn]['args']) . " expected)");
+                    } else { // did we somehow push a non-function on the stack? this should never happen
+                        return $this->trigger("internal error");
+                    }
+                }
+                $index++;
+            //===============
+            } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
+                while (($o2 = $stack->pop()) != '(') {
+                    if (is_null($o2)) return $this->trigger("unexpected ','"); // oops, never had a (
+                    else $output[] = $o2; // pop the argument expression stuff and push onto the output
+                }
+                // make sure there was a function
+                if (!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches))
+                    return $this->trigger("unexpected ','");
+                $stack->push($stack->pop()+1); // increment the argument count
+                $stack->push('('); // put the ( back on, we'll need to pop back to it again
+                $index++;
+                $expecting_op = false;
+            //===============
+            } elseif ($op == '(' and !$expecting_op) {
+                $stack->push('('); // that was easy
+                $index++;
+                $allow_neg = true;
+            //===============
+            } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
+                $expecting_op = true;
+                $val = $match[1];
+                if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
+                    if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { // it's a func
+                        $stack->push($val);
+                        $stack->push(1);
+                        $stack->push('(');
+                        $expecting_op = false;
+                    } else { // it's a var w/ implicit multiplication
+                        $val = $matches[1];
+                        $output[] = $val;
+                    }
+                } else { // it's a plain old var or num
+                    $output[] = $val;
+                }
+                $index += strlen($val);
+            //===============
+            } elseif ($op == ')') { // miscellaneous error checking
+                return $this->trigger("unexpected ')'");
+            } elseif (in_array($op, $ops) and !$expecting_op) {
+                return $this->trigger("unexpected operator '$op'");
+            } else { // I don't even want to know what you did to get here
+                return $this->trigger("an unexpected error occured");
+            }
+            if ($index == strlen($expr)) {
+                if (in_array($op, $ops)) { // did we end with an operator? bad.
+                    return $this->trigger("operator '$op' lacks operand");
+                } else {
+                    break;
+                }
+            }
+            while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
+                $index++;                             // into implicit multiplication if no operator is there)
+            }
+
+        }
+        while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
+            if ($op == '(') return $this->trigger("expecting ')'"); // if there are (s on the stack, ()s were unbalanced
+            $output[] = $op;
+        }
+        return $output;
+    }
+
+    // evaluate postfix notation
+    function pfx($tokens, $vars = array()) {
+
+        if ($tokens == false) return false;
+
+        $stack = new EvalMathStack;
+
+        foreach ($tokens as $token) { // nice and easy
+            // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
+            if (in_array($token, array('+', '-', '*', '/', '^'))) {
+                if (is_null($op2 = $stack->pop())) return $this->trigger("internal error");
+                if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
+                switch ($token) {
+                    case '+':
+                        $stack->push($op1+$op2); break;
+                    case '-':
+                        $stack->push($op1-$op2); break;
+                    case '*':
+                        $stack->push($op1*$op2); break;
+                    case '/':
+                        if ($op2 == 0) return $this->trigger("division by zero");
+                        $stack->push($op1/$op2); break;
+                    case '^':
+                        $stack->push(pow($op1, $op2)); break;
+                }
+            // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
+            } elseif ($token == "_") {
+                $stack->push(-1*$stack->pop());
+            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
+            } elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a function!
+                $fnn = $matches[1];
+                if (in_array($fnn, $this->fb)) { // built-in function:
+                    if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
+                    $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
+                    if ($fnn == 'ln') $fnn = 'log';
+                    eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
+                } elseif (array_key_exists($fnn, $this->f)) { // user function
+                    // get args
+                    $args = array();
+                    for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
+                        if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger("internal error");
+                    }
+                    $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
+                }
+            // if the token is a number or variable, push it on the stack
+            } else {
+                if (is_numeric($token)) {
+                    $stack->push($token);
+                } elseif (array_key_exists($token, $this->v)) {
+                    $stack->push($this->v[$token]);
+                } elseif (array_key_exists($token, $vars)) {
+                    $stack->push($vars[$token]);
+                } else {
+                    return $this->trigger("undefined variable '$token'");
+                }
+            }
+        }
+        // when we're out of tokens, the stack should have a single element, the final result
+        if ($stack->count != 1) return $this->trigger("internal error");
+        return $stack->pop();
+    }
+
+    // trigger an error, but nicely, if need be
+    function trigger($msg) {
+        $this->last_error = $msg;
+        if (!$this->suppress_errors) trigger_error($msg, E_USER_WARNING);
+        return false;
+    }
+}
+
+// for internal use
+class EvalMathStack {
+
+    var $stack = array();
+    var $count = 0;
+
+    function push($val) {
+        $this->stack[$this->count] = $val;
+        $this->count++;
+    }
+
+    function pop() {
+        if ($this->count > 0) {
+            $this->count--;
+            return $this->stack[$this->count];
+        }
+        return null;
+    }
+
+    function last($n=1) {
+        if (isset($this->stack[$this->count-$n])) {
+          return $this->stack[$this->count-$n];
+        }
+        return;
+    }
+}

+ 33 - 0
main/inc/lib/exercise_show_functions.lib.php

@@ -50,6 +50,39 @@ class ExerciseShowFunctions
         }
 	}
 
+    /**
+     * Shows the answer to a calculated question, as HTML
+     * @param string    Answer text
+     * @param int       Exercise ID
+     * @param int       Question ID
+     * @return void
+     */
+    static function display_calculated_answer($feedback_type, $answer, $id, $questionId)
+    {
+        if (empty($id)) {
+            echo '<tr><td>'. (Security::remove_XSS($answer)).'</td></tr>';
+        } else {
+        ?>
+            <tr>
+                <td>
+                    <?php
+                    echo (Security::remove_XSS($answer));
+                    ?>
+                </td>
+
+            <?php
+            if (!api_is_allowed_to_edit(null,true) && $feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) { ?>
+                <td>
+                    <?php
+                    $comm = get_comments($id,$questionId);
+                    ?>
+                </td>
+            <?php } ?>
+            </tr>
+        <?php
+        }
+    }
+
 	/**
 	 * Shows the answer to a free-answer question, as HTML
 	 * @param string    Answer text

+ 19 - 2
main/lang/english/trad4all.inc.php

@@ -2,6 +2,23 @@
 /*
 for more information: see languages.txt in the lang folder.
 */
+$UserNameHasDash = "The username cannot contain the '-' character";
+$IfYouWantOnlyIntegerValuesWriteBothLimitsWithoutDecimals = "If you want only integer values write both limits without decimals";
+$GiveAnswerVariations = "Please, write how many question variations you want";
+$AnswerVariations = "Question variations";
+$GiveFormula = "Please, write the formula";
+$Formula = "Formula";
+$FormulaExample = "Formula sample";
+$VariableRanges = "Variable ranges";
+$ExampleValue = "Range value";
+$CalculatedAnswer = "Calculated question";
+$UserIsCurrentlySubscribed = "The user is currently subscribed";
+$OnlyBestAttempts = "Only best attempts";
+$IncludeAllUsers = "Include all users";
+$HostingWarningReached = "Hosting warning reached";
+$SessionName = "Session name";
+$MobilePhoneNumberWrong = "Mobile phone number is incomplete or contains not valid characters";
+$CountryDialCode = "Include the country dial code";
 $FieldTypeMobilePhoneNumber = "Mobile phone number";
 $CheckUniqueEmail = "Check unique email";
 $EmailUsedTwice = "This email is not available";
@@ -1623,6 +1640,6 @@ $DisplayColumn = "Show column";
 $LegalAgreementAccepted = "Legal agreement accepted";
 $FieldTypeMobilePhoneNumber = "Mobile phone";
 $CountryDialCode = "Include the country dial code";
-$MobilePhoneNumberWrong = "Mobile phone number is incomplete or contains not valid characters";
+$MobilePhoneNumberWrong = "Mobile phone number is incomplete or contains invalid characters";
 $SessionName = "Session name";
-?>
+?>

+ 32 - 2
main/lang/spanish/trad4all.inc.php

@@ -2,10 +2,40 @@
 /*
 for more information: see languages.txt in the lang folder.
 */
+$UserNameHasDash = "El nombre de usuario no puede contener el caracter '-'";
+$IfYouWantOnlyIntegerValuesWriteBothLimitsWithoutDecimals = "Si desea sólo números enteros escriba ambos límites sin decimales";
+$GiveAnswerVariations = "Por favor, escriba cuántos problemas desea generar";
+$AnswerVariations = "Problemas a generar";
+$GiveFormula = "Por favor, escriba la fórmula";
+$Formula = "Fórmula";
+$FormulaExample = "Ejemplo de fórmula";
+$VariableRanges = "Rangos de las variables";
+$ExampleValue = "Valor del rango";
+$CalculatedAnswer = "Pregunta calculada";
+$UserIsCurrentlySubscribed = "El usuario está actualmente suscrito";
+$OnlyBestAttempts = "Solo los mejores intentos";
+$IncludeAllUsers = "Incluir todos los usuarios";
+$HostingWarningReached = "Limite de alojamiento alcanzado";
+$SessionName = "Nombre de la sesión";
+$MobilePhoneNumberWrong = "El número de móvil que ha escrito está incompleto o contiene caracteres no válidos";
+$CountryDialCode = "Incluya el prefijo de llamada del país";
 $FieldTypeMobilePhoneNumber = "Número de móvil";
 $CheckUniqueEmail = "Verificar que los correos sean únicos.";
-$EmailUsedTwice = "El correo electrónico ya esta registrado en el sistema.";
+$EmailUsedTwice = "Este correo electrónico ya está registrado en el sistema.";
+$TotalPostsInAllForums = "Total de posts en todos los foros";
+$AddMeAsCoach = "Agregarme como tutor";
+$AddMeAsTeacherInCourses = "Agregarme como profesor en los cursos importados.";
+$ExerciseProgressInfo = "Progreso de los ejercicios llevados por el alumno";
+$CourseTimeInfo = "Tiempo pasado en el curso";
+$ExerciseAverageInfo = "Media del mejor resultado para cada intento de ejercicio.";
+$ExtraDurationForUser = "Días de acceso adicionales para este usuario.";
 $UserXSessionY = "Usuario: %s - Sesión: %s";
+$DurationIsSameAsDefault = "La duración de la sesión es la misma que el valor por defecto. Ignorando.";
+$FirstAccessWasXSessionDurationYEndDateWasZ = "El primer acceso de este usuario a la sesión fue el %s. Con una duración de %s días, el acceso a esta sesión expiró el %s.";
+$FirstAccessWasXSessionDurationYEndDateInZDays = "El primer acceso de este usuario a esta sesión fue el %s. Con una duración de %s días, la fecha de fin está configurada dentro de %s días.";
+$UserNeverAccessedSessionDefaultDurationIsX = "Este usuario nunca ha accedido a esta sesión antes. La duración está configurada a %s días (desde la fecha de primer acceso).";
+$SessionDurationEdit = "Editar duración de sesión";
+$EditUserSessionDuration = "Editar la duración de la sesión del usuario";
 $SessionDurationXDaysLeft = "Esta sesión tiene una duración máxima. Solo quedan %s días.";
 $NextTopic = "Próximo tema";
 $CurrentTopic = "Tema actual";
@@ -1617,4 +1647,4 @@ $FieldTypeMobilePhoneNumber = "Número de móvil";
 $CountryDialCode = "Incluya el prefijo de llamada del país";
 $MobilePhoneNumberWrong = "El número de móvil que ha escrito está incompleto o contiene caracteres no válidos.";
 $SessionName = "Nombre de la sesión";
-?>
+?>