<?php

// Moodle algebra question type class
// Author: Roger Moore <rwmoore 'at' ualberta.ca>
// License: GNU Public License version 3
/**
 * Script which converts the given formula text into LaTeX code and then 
 * displays the appropriate image file. It relies on the LaTeX filter to
 * be present.
 */
require_once '../../../config.php';
require_once "{$CFG->dirroot}/question/type/algebra/parser.php";
$p = new qtype_algebra_parser();
try {
    $query = urldecode($_SERVER['QUERY_STRING']);
    $m = array();
    if (!preg_match('/vars=([^&]*)&expr=(.*)$/A', $query, $m)) {
        throw new Exception('Invalid query string received from http server!');
    }
    $vars = explode(',', $m[1]);
    if (empty($m[2])) {
        $texexp = '';
    } else {
        $exp = $p->parse($m[2], $vars);
        $texexp = '$$' . $exp->tex() . '$$';
    }
} catch (Exception $e) {
    $texexp = get_string('parseerror', 'qtype_algebra', $e->getMessage());
}
$text = format_text($texexp);
?>
 /**
  * Parses the given expression with the parser if required.
  *
  * This method will check to see if the argument it is given is already a parsed
  * expression and if not will attempt to parse it.
  *
  * @param $expr expression which will be parsed
  * @param $question question containing the expression or null if none
  * @return top term of the parse tree or a string if an exception is thrown
  */
 function parse_expression($expr, &$question = null)
 {
     // Check to see if this is already a parsed expression
     if (is_a($expr, 'qtype_algebra_parser_term')) {
         // It is a parsed expression so simply return it
         return $expr;
     }
     // Check whether we have a state object or a simple string. If a state
     // then replace it with the response string
     if (isset($expr->responses[''])) {
         $expr = $expr->responses[''];
     }
     // Create an array of variable names for the parser from the question if defined
     $varnames = array();
     if ($question and isset($question->options->variables)) {
         foreach ($question->options->variables as $var) {
             $varnames[] = $var->name;
         }
     }
     // We now assume that we have a string to parse. Create a parser instance to
     // to this and return the parser expression at the top of the parse tree
     $p = new qtype_algebra_parser();
     // Perform the actual parsing inside a try-catch block so that any exceptions
     // can be caught and converted into errors
     try {
         return $p->parse($expr, $varnames);
     } catch (Exception $e) {
         // If the expression cannot be parsed then return a null term. This will
         // make Moodle treat the answer as wrong.
         // TODO: Would be nice to have support for 'invalid answer' in the quiz
         // engine since an unparseable response is usually caused by a silly typo
         return new qtype_algebra_parser_nullterm();
     }
 }
 /**
  * Validates the form data ensuring there are no obvious errors in the submitted data.
  *
  * This method performs some basic sanity checks on the form data before it gets converted
  * into a database record.
  *
  * @param $data the data from the form which needs to be checked
  * @param $files some files - I don't know what this is for! - files defined in the form??
  */
 function validation($data, $files)
 {
     // Call the base class validation method and keep any errors it generates
     $errors = parent::validation($data, $files);
     // Regular expression string to match a number
     $renumber = '/([+-]*(([0-9]+\\.[0-9]*)|([0-9]+)|(\\.[0-9]+))|' . '(([0-9]+\\.[0-9]*)|([0-9]+)|(\\.[0-9]+))E([-+]?\\d+))/A';
     // Perform sanity checks on the variables.
     $vars = $data['variable'];
     // Create an array of defined variables
     $varlist = array();
     foreach ($vars as $key => $var) {
         $trimvar = trim($var);
         $trimmin = trim($data['varmin'][$key]);
         $trimmax = trim($data['varmax'][$key]);
         // Check that there is a valid variable name otherwise skip
         if ($trimvar == '') {
             continue;
         }
         // Check that this variable does not have the same name as a function
         if (in_array($trimvar, qtype_algebra_parser::$functions) or in_array($trimvar, qtype_algebra_parser::$specials)) {
             $errors['variable[' . $key . ']'] = get_string('illegalvarname', 'qtype_algebra', $trimvar);
         }
         // Check that this variable has not been defined before
         if (in_array($trimvar, $varlist)) {
             $errors['variable[' . $key . ']'] = get_string('duplicatevar', 'qtype_algebra');
         } else {
             // Add the variable to the list of defined variables
             $varlist[] = $trimvar;
         }
         // If the comparison algorithm selected is evaluate then ensure that each variable
         // has a valid minimum and maximum defined. For the other types of comparison we can
         // ignore the range
         if ($data['compareby'] == 'eval') {
             // Check that a minimum has been defined
             if ($trimmin == '') {
                 $errors['varmin[' . $key . ']'] = get_string('novarmin', 'qtype_algebra');
             } else {
                 if (!preg_match($renumber, $trimmin)) {
                     $errors['varmin[' . $key . ']'] = get_string('notanumber', 'qtype_algebra');
                 }
             }
             if ($trimmax == '') {
                 $errors['varmax[' . $key . ']'] = get_string('novarmax', 'qtype_algebra');
             } else {
                 if (!preg_match($renumber, $trimmax)) {
                     $errors['varmax[' . $key . ']'] = get_string('notanumber', 'qtype_algebra');
                 }
             }
             // Check that the minimum is less that the maximum!
             if ((double) $trimmin > (double) $trimmax) {
                 $errors['varmin[' . $key . ']'] = get_string('varmingtmax', 'qtype_algebra');
             }
         }
         // end check for eval type
     }
     // end loop over variables
     // Check that at least one variable is defined
     if (count($varlist) == 0) {
         $errors['variable[0]'] = get_string('notenoughvars', 'qtype_algebra');
     }
     // Now perform the sanity checks on the answers
     // Create a parser which we will use to check that the answers are understandable
     $p = new qtype_algebra_parser();
     $answers = $data['answer'];
     $answercount = 0;
     $maxgrade = false;
     // Create an empty array to store the used variables
     $ansvars = array();
     // Create an empty array to store the used functions
     $ansfuncs = array();
     // Loop over all the answers in the form
     foreach ($answers as $key => $answer) {
         // Try to parse the answer string using the parser. If this fails it will
         // throw an exception which we catch to generate the associated error string
         // for the expression
         try {
             $expr = $p->parse($answer);
             // Add any new variables to the list we are keeping. First we get the list
             // of variables in this answer. Then we get the array of variables which are
             // in this answer that are not in any previous answer (using array_diff).
             // Finally we merge this difference array with the list of all variables so far
             $tmpvars = $expr->get_variables();
             $ansvars = array_merge($ansvars, array_diff($tmpvars, $ansvars));
             // Check that all the variables in this answer have been declared
             // Do this by looking for a non-empty array to be returned from the array_diff
             // between the list of all declared variables and the variables in this answer
             if ($d = array_diff($tmpvars, $varlist)) {
                 $errors['answer[' . $key . ']'] = get_string('undefinedvar', 'qtype_algebra', "'" . implode("', '", $d) . "'");
             }
             // Do the same for functions which we did for variables
             $ansfuncs = array_merge($ansfuncs, array_diff($expr->get_functions(), $ansfuncs));
             // Check that this is not an empty answer
             if (!is_a($expr, "qtype_algebra_parser_nullterm")) {
                 // Increase the number of answers
                 $answercount++;
                 // Check to see if the answer has the maximum grade
                 if ($data['fraction'][$key] == 1) {
                     $maxgrade = true;
                 }
             }
         } catch (Exception $e) {
             $errors['answer[' . $key . ']'] = $e->getMessage();
             // Return here because subsequent errors may be wrong due to not counting the answer
             // which just failed to parse
             return $errors;
         }
     }
     // Check that we have at least one answer!
     if ($answercount == 0) {
         $errors['answer[0]'] = get_string('notenoughanswers', 'quiz', 1);
     }
     // Check that at least one question has the maximum possible grade
     if ($maxgrade == false) {
         $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
     }
     // Check for variables which are defined but never used.
     // Do this by looking for a non-empty array to be returned from array_diff.
     if ($d = array_diff($varlist, $ansvars)) {
         // Loop over all the variables in the form
         foreach ($vars as $key => $var) {
             $trimvar = trim($var);
             // If the variable is in the unused array then add the error message to that variable
             if (in_array($trimvar, $d)) {
                 $errors['variable[' . $key . ']'] = get_string('unusedvar', 'qtype_algebra');
             }
         }
     }
     // Check that the tolerance is greater than or equal to zero
     if ($data['tolerance'] < 0) {
         $errors['tolerance'] = get_string('toleranceltzero', 'qtype_algebra');
     }
     return $errors;
 }