Browse Source

Add calculated answers feature. Doesn't include icons. - refs #7212

Imanol Losada 10 years ago
parent
commit
a2ff44cf69

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

@@ -0,0 +1,228 @@
+<?php
+/* For licensing terms, see /license.txt */
+/**
+ *
+ *  Class CalculatedAnswer
+ *
+ *  @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;
+    }
+}

+ 106 - 4
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) {
@@ -2757,6 +2853,10 @@ class Exercise
                             //if ($origin!='learnpath') {
                             ExerciseShowFunctions::display_fill_in_blanks_answer($feedback_type, $answer,0,0);
                             //	}
+                        } 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);
@@ -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')
     );
 

+ 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
+
+/*
+================================================================================
+
+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

+ 25 - 11
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";
@@ -1242,13 +1259,13 @@ $YouMustAcceptLicence = "You must accept the licence";
 $SelectOne = "Select one";
 $ContactInformationHasBeenSent = "Contact information has been sent";
 $UserInactivedSinceX = "User inactive since %s";
-$ContactInformationDescription = "Dear user,<br />
-<br />You are about to start using one of the best open-source e-learning platform on the market. Like many other open-source project, this project is backed up by a large community of students, teachers, developers and content creators who would like to promote the project better.<br />
-<br />
-By knowing a little bit more about you, one of our most important users, who will manage this e-learning system, we will be able to let people know that our software is used and let you know when we organize events that might be relevant to you.<br />
-<br />
-By filling this form, you accept that the Chamilo association or its members might send you information by e-mail about important events or updates in the Chamilo software or community. This will help the community grow as an organized entity where information flow, with a permanent respect of your time and your privacy.<br />
-<br />
+$ContactInformationDescription = "Dear user,<br />
+<br />You are about to start using one of the best open-source e-learning platform on the market. Like many other open-source project, this project is backed up by a large community of students, teachers, developers and content creators who would like to promote the project better.<br />
+<br />
+By knowing a little bit more about you, one of our most important users, who will manage this e-learning system, we will be able to let people know that our software is used and let you know when we organize events that might be relevant to you.<br />
+<br />
+By filling this form, you accept that the Chamilo association or its members might send you information by e-mail about important events or updates in the Chamilo software or community. This will help the community grow as an organized entity where information flow, with a permanent respect of your time and your privacy.<br />
+<br />
 Please note that you are <b>not required</b> to fill this form. If you want to remain anonymous, we will loose the opportunity to offer you all the privileges of being a registered portal administrator, but we will respect your decision. Simply leave this form empty and click \"Next\".<br /><br />";
 $CompanyActivity = "Your company's activity";
 $DateUnLock = "Unlock date";
@@ -1456,7 +1473,7 @@ $SaveForNow = "Save and continue later";
 $NoQuicktime = "Your browser does not have the QuickTime plugin installed. You can still use the platform, but to run a larger number of media file types, we suggest you might want to install it.";
 $NoJavaSun = "Your browser doesn't seem to have the Sun Java plugin installed. You can still use the platform, but you will lose a few of its capabilities.";
 $NoJava = "Your browser does not support Java";
-$JavaSun24 = "Your browser has a Java version not supported by this tool.
+$JavaSun24 = "Your browser has a Java version not supported by this tool.
 To use it you have to install a Java Sun version higher than 24";
 $NoMessageAnywere = "If you do not want to see this message again during this session, click here";
 $Attempts = "Attempts";
@@ -1621,7 +1638,4 @@ $DataTableSearch = "Search";
 $HideColumn = "Hide column";
 $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";
 ?>

+ 44 - 17
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";
@@ -1052,7 +1082,7 @@ $YouCanAccessTheExercise = "Ir a la prueba";
 $YouHaveBeenRegisteredToCourseX = "Ha sido inscrito en el curso %s";
 $DashboardPluginsHaveBeenUpdatedSucesslly = "Los plugins del panel de control han sido actualizados correctamente";
 $LoginEnter = "Entrar";
-$AttendanceSheetDescription = "Las listas de asistencia permiten registrar las faltas de asistencia de los estudiantes. En caso de ausencia de un estudiante, el profesor deberá registrarlo manualmente en la casilla correspondiente.
+$AttendanceSheetDescription = "Las listas de asistencia permiten registrar las faltas de asistencia de los estudiantes. En caso de ausencia de un estudiante, el profesor deberá registrarlo manualmente en la casilla correspondiente.
 Es posible crear más de una lista de asistencia por cada curso; así por ejemplo, podrá registrar separadamente la asistencia a las clases teóricas y prácticas.";
 $ThereAreNoRegisteredLearnersInsidetheCourse = "No hay estudiantes inscritos en este curso";
 $GoToAttendanceCalendarList = "Ir al calendario de asistencia";
@@ -1230,14 +1260,14 @@ $YouMustAcceptLicence = "Debe aceptar la licencia para poder usar este software"
 $SelectOne = "Seleccione uno";
 $ContactInformationHasBeenSent = "Información de contacto enviada";
 $UserInactivedSinceX = "Usuario inactivo desde %s";
-$ContactInformationDescription = "Estimado usuario,
-
-está a punto de instalar una de las mejores plataformas e-learning de código abierto que existen en el mercado. Al igual de muchos otros proyectos de código abierto, Chamilo está respaldado por una amplia comunidad de profesores, estudiantes, desarrolladores y creadores de contenido.
-
-Si sabemos algo más de quien va a gestionar este sistema e-learning, podremos dar a conocer a otros que nuestro software lo utiliza y a usted podremos informarle sobre eventos que pueden ser de su interés.
-
-Cumplimentar este formulario, implica la aceptación de que la asociación Chamilo o sus miembros puedan enviarle información por correo electrónico sobre eventos importantes o actualizaciones en el software Chamilo. Esto ayudará a crecer a la comunidad como una entidad organizada, donde el flujo de información, se haga con respeto permanente a su tiempo y su privacidad.
-
+$ContactInformationDescription = "Estimado usuario,
+
+está a punto de instalar una de las mejores plataformas e-learning de código abierto que existen en el mercado. Al igual de muchos otros proyectos de código abierto, Chamilo está respaldado por una amplia comunidad de profesores, estudiantes, desarrolladores y creadores de contenido.
+
+Si sabemos algo más de quien va a gestionar este sistema e-learning, podremos dar a conocer a otros que nuestro software lo utiliza y a usted podremos informarle sobre eventos que pueden ser de su interés.
+
+Cumplimentar este formulario, implica la aceptación de que la asociación Chamilo o sus miembros puedan enviarle información por correo electrónico sobre eventos importantes o actualizaciones en el software Chamilo. Esto ayudará a crecer a la comunidad como una entidad organizada, donde el flujo de información, se haga con respeto permanente a su tiempo y su privacidad.
+
 De cualquier forma, tenga en cuenta que no tiene la obligación de rellenar este formulario. Si desea permanecer en el anonimato, perderemos la oportunidad de ofrecerle todos los privilegios de ser un administrador de portal registrado, pero respetaremos su decisión. Basta con dejar vacío este formulario y hacer clic en \"Siguiente\" para seguir instalando Chamilo.";
 $CompanyActivity = "Sector";
 $DateUnLock = "Desbloquear fecha";
@@ -1437,11 +1467,11 @@ $CertificateOnlineLink = "Vínculo al certificado en línea";
 $NewExercises = "Nuevo ejercicio";
 $MyAverage = "Mi promedio";
 $AllAttempts = "Todos los intentos";
-$NoCookies = "Las cookies no están activadas en su navegador.
+$NoCookies = "Las cookies no están activadas en su navegador.
 Chamilo utiliza \"cookies\" para almacenar sus datos de conexión, por lo que no le será posible entrar si las cookies no están habilitadas. Por favor, cambie la configuración de su navegador y recargue esta página.";
-$NoJavascript = "Su navegador no tiene activado JavaScript.
+$NoJavascript = "Su navegador no tiene activado JavaScript.
 Chamilo se sirve de JavaScript para proporcionar un interfaz más dinámico. Es probable que muchas prestaciones sigan funcionando pero otras no lo harán, especialmente las relacionadas con la usabilidad. Le recomendamos que cambie la configuración de su navegador y recargue esta página.";
-$NoFlash = "Su navegador no tiene activado el soporte de Flash.
+$NoFlash = "Su navegador no tiene activado el soporte de Flash.
 Chamilo sólo se apoya en Flash para algunas de sus funciones por lo que su ausencia no le impedirá continuar. Pero si quiere beneficiarse del conjunto de las herramientas de Chamilo, le recomendamos que instale-active el plugin de Flash y reinicialice su navegador.";
 $Attempt = "Intento";
 $SaveForNow = "Guardar y continuar más tarde";
@@ -1522,7 +1552,7 @@ $LoginX = "Nombre de usuario: %s";
 $ChatConnected = "Chat (Conectado)";
 $ChatDisconnected = "Chat (Desconectado)";
 $ThingsToDo = "Primeras actividades sugeridas";
-$WamiFlashDialog = "Se mostrará un cuadro de diálogo en el que se le pedirá permiso para poder acceder al micrófono, responda afirmativamente y cierre el cuadro de diálogo (si no desea que vuelva a aparecer, antes de cerrar marque la opción
+$WamiFlashDialog = "Se mostrará un cuadro de diálogo en el que se le pedirá permiso para poder acceder al micrófono, responda afirmativamente y cierre el cuadro de diálogo (si no desea que vuelva a aparecer, antes de cerrar marque la opción
 recordar)";
 $WamiStartRecorder = "Inicie la grabación pulsando el micrófono y deténgala pulsándolo de nuevo. Cada vez que haga esto se generará un archivo.";
 $InputNameHere = "Escriba el nombre aquí";
@@ -1613,7 +1643,4 @@ $DataTableSearch = "Buscar";
 $HideColumn = "Ocultar columna";
 $DisplayColumn = "Mostrar columna";
 $LegalAgreementAccepted = "Condiciones legales aceptadas";
-$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.";
 ?>