From 96bee2b33467a1ce54f88c77b8a867ef1f7a9a97 Mon Sep 17 00:00:00 2001
From: Jean-Michel Vedrine <vedrine@vedrine.org>
Date: Sun, 9 Sep 2012 16:54:35 +0200
Subject: First version of algebra question type initially written by Roger
 Moore for Moodle 2.3

---
 CHANGELOG.txt                                      |   33 +
 INSTALL.txt                                        |   48 +
 README.txt                                         |   23 +
 backup/moodle1/lib.php                             |   79 +
 .../moodle2/backup_qtype_algebra_plugin.class.php  |   63 +
 .../moodle2/restore_qtype_algebra_plugin.class.php |  106 ++
 db/install.xml                                     |   47 +
 db/upgrade.php                                     |   59 +
 db/upgradelib.php                                  |   71 +
 displayformula.php                                 |   48 +
 edit_algebra_form.php                              |  378 +++++
 lang/en/qtype_algebra.php                          |   91 ++
 lang/en/qtype_algebra_parser.php                   |   20 +
 lib.php                                            |   38 +
 parser.php                                         | 1721 ++++++++++++++++++++
 pix/icon.gif                                       |  Bin 0 -> 87 bytes
 question.php                                       |  356 ++++
 questiontype.php                                   |  551 +++++++
 renderer.php                                       |  162 ++
 sage_server.py                                     |   29 +
 settings.php                                       |   11 +
 version.php                                        |    5 +
 xmlrpc-utils.php                                   |  269 +++
 23 files changed, 4208 insertions(+)
 create mode 100644 CHANGELOG.txt
 create mode 100644 INSTALL.txt
 create mode 100644 README.txt
 create mode 100644 backup/moodle1/lib.php
 create mode 100644 backup/moodle2/backup_qtype_algebra_plugin.class.php
 create mode 100644 backup/moodle2/restore_qtype_algebra_plugin.class.php
 create mode 100644 db/install.xml
 create mode 100644 db/upgrade.php
 create mode 100644 db/upgradelib.php
 create mode 100644 displayformula.php
 create mode 100644 edit_algebra_form.php
 create mode 100644 lang/en/qtype_algebra.php
 create mode 100644 lang/en/qtype_algebra_parser.php
 create mode 100644 lib.php
 create mode 100644 parser.php
 create mode 100644 pix/icon.gif
 create mode 100644 question.php
 create mode 100644 questiontype.php
 create mode 100644 renderer.php
 create mode 100644 sage_server.py
 create mode 100644 settings.php
 create mode 100644 version.php
 create mode 100644 xmlrpc-utils.php

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
new file mode 100644
index 0000000..bb14cb4
--- /dev/null
+++ b/CHANGELOG.txt
@@ -0,0 +1,33 @@
+Version 0.0.4
+Improvements
+  - Export and import to Moodle XML format
+  - Backup and restore functions added
+Bug fixes
+  - Fixed parser problem with negative numbers
+Version 0.0.3
+Improvements
+  - Added danish localizations based on forum feedback: mltiplication now 
+    uses 'cdot' and decimal points are rendered as commas when Danish is
+    selected as a language
+Bug fixes
+  - Operator priority, BODMAS, not quite implemented correctly. */ and +- not
+    implemented as equal priority - now fixed
+Version 0.0.2
+Significant changes as a result of the first round of feedback!
+  - Renamed parser classes to conform to coding guidelines
+  - Moved all parser strings into a language pack
+  - Switched a lot of double quoted string to single as per guidelines
+  - added automatic formatted comments as required by coding guidelines
+  - changed treatment of variable names to help reduce confusion. Now
+    the first letter is treated as the name and the rest are subscripted.
+    Greek letter names are treated as a single character i.e. theta1 
+    becomes \theta_{1} in LaTeX.
+  - Added option to specify text which goes in front of response box
+  - Added support for specified variable names in the parser to improve
+    parsing in some situations e.g. 'xy' will now get treated as 'x * y' if
+    there are two variables 'x' and 'y' defined.
+Bug fixes
+  - fixed bug when evaluating special constants in the parser
+  - fixed incorrect rendering of sqrt in LaTeX by the parser
+  - fixed incorrect sage-server.py file in the ZIP
+Version 0.0.1 released
diff --git a/INSTALL.txt b/INSTALL.txt
new file mode 100644
index 0000000..da45f38
--- /dev/null
+++ b/INSTALL.txt
@@ -0,0 +1,48 @@
+INSTALLATION INSTRUCTIONS
+
+Before installation: Please note that this is ALPHA quality software at
+the moment. DO NOT install this on your production server - or at least
+don't blame me if you do and it all goes horribly wrong! 
+
+
+REQUIREMENTS
+
+To install the algebra based question type you will need the following:
+
+1) Already installed copy of Moodle 2.1 or higher. It may work with other
+   Moodle versions but this is the only one I have tested it with.
+   
+2) PHP5: The code uses exceptions and so requires PHP version 5 at a
+   minimum. I used this since Moodle 2.1 will require it so it should
+   not be an unusual requirement for long. I used PHP 5.3.5.
+   
+3) If you want to use the SAGE XML-RPC server you will also need XML-RPC
+   support in PHP5. Most installations include this but the MAMP 1.7.1
+   package does not. If you use MAMP you will need to download the MAMP 
+   source code from the MAMP website and recompile PHP making sure you
+   call the initial configure script with the flag "--with-xmlrpc".
+   
+4) If you want to use the SAGE XML-RPC server you will also need a copy of
+   SAGE which you can get from here: http://www.sagemath.org/. This should
+   be installed according to the instructions on their website.
+   
+
+INSTALLING
+
+1) Copy the contents of the "moodle" directory into your top level moodle
+   directory. Note that all the files are 'new' so there should be no files
+   overwritten. ('cp -iR' are good options to use).
+   
+2) Go to Site Administration > Notifications and your database should be
+   configured. You are now ready to write algebra based questions which
+   use the 'Evaluate' and 'Equivalent' comarison methods.
+   
+3) To run the SAGE XML-RPC server you will need an installed copy of SAGE
+   (http://www.sagemath.org/). Edit the first line of the 'sage-server.py'
+   file to point to your installed copy of the sage executable. Then simply
+   execute the sage-server.py script. It will run a very simple XML-RPC
+   server. If the machine you run your moodle server on is different from
+   the machine running your SAGE webserver you will need to edit line 191-
+   193 of the file question/type/algebra/question.php to point to the
+   XML-RPC server. The same applies if you edit the network port. (This
+   mechanism obviously needs to be improved!)
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..539a0e4
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,23 @@
+MOODLE ALGEBRA QUESTION TYPE
+Updated to Moodle 2.0 by Stefan Raffeiner <stefan.raffeiner (at) gmail.com>
+Updated to Moodle 2.1 by Jean-Michel Vedrine <vedrine (at) univ-st-etienne.fr>
+
+README BY ROGER MOORE:
+MOODLE ALGEBRA QUESTION TYPE
+
+These files implement a algebra based question type for Moodle.
+Installation instructions are found in the file INSTALL.
+
+The code has been tested and used for a large, introductory physics
+course (~120 students) at the University of Alberta for several
+terms now and is stable and suitable for use in a production
+environment.
+
+The code is all released under the GPL V3.
+
+Please send any bugs, comments, suggestions for new features etc.
+to me.
+
+Enjoy,
+
+Roger Moore <rwmoore (at) ualberta.ca>
diff --git a/backup/moodle1/lib.php b/backup/moodle1/lib.php
new file mode 100644
index 0000000..d9a6406
--- /dev/null
+++ b/backup/moodle1/lib.php
@@ -0,0 +1,79 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    qtype
+ * @subpackage algebra
+ * @copyright  2011 David Mudrak <david@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Multichoice question type conversion handler
+ */
+class moodle1_qtype_algebra_handler extends moodle1_qtype_handler {
+
+    /**
+     * @return array
+     */
+    public function get_question_subpaths() {
+        return array(
+            'ANSWERS/ANSWER',
+            'ALGEBRA',
+            'ALGEBRA/VARLIST/VARIABLE'
+        );
+    }
+
+    /**
+     * Appends the algebra specific information to the question
+     */
+    public function process_question(array $data, array $raw) {
+        // convert and write the answers first
+        if (isset($data['answers'])) {
+            $this->write_answers($data['answers'], $this->pluginname);
+        }
+		
+        // convert and write the algebra variables
+        if (isset($data['algebra'][0]['varlist']['variable'])) {
+            $variables   = $data['algebra'][0]['varlist']['variable'];
+        } else {
+            $variables   = array();
+        }
+		$this->xmlwriter->begin_tag('algebra_variables');
+        foreach ($variables as $variable) {
+            $this->xmlwriter->begin_tag('algebra_variable', array('id' => $this->converter->get_nextid()));
+            $this->xmlwriter->full_tag('name', $variable['name']);
+            $this->xmlwriter->full_tag('min', $variable['min']);
+			$this->xmlwriter->full_tag('max', $variable['max']);
+            $this->xmlwriter->end_tag('algebra_variable');
+        }
+        $this->xmlwriter->end_tag('algebra_variables');
+		
+		// and finally the algebra options
+		$options = $data['algebra'][0];
+		$this->xmlwriter->begin_tag('algebra', array('id' => $this->converter->get_nextid()));
+		$this->xmlwriter->full_tag('compareby', $options['compareby']);
+		$this->xmlwriter->full_tag('nchecks', $options['nchecks']);
+		$this->xmlwriter->full_tag('tolerance', $options['tolerance']);
+		$this->xmlwriter->full_tag('disallow', $options['disallow']);
+		$this->xmlwriter->full_tag('allowedfuncs', $options['allowedfuncs']);
+		$this->xmlwriter->full_tag('answerprefix', $options['answerprefix']);
+		$this->xmlwriter->end_tag('algebra');
+    }
+}
diff --git a/backup/moodle2/backup_qtype_algebra_plugin.class.php b/backup/moodle2/backup_qtype_algebra_plugin.class.php
new file mode 100644
index 0000000..9e08467
--- /dev/null
+++ b/backup/moodle2/backup_qtype_algebra_plugin.class.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * Moodle algebra question type class.
+ *
+ * @copyright &copy; 2010 Hon Wai, Lau
+ * @author Hon Wai, Lau <lau65536@gmail.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License version 3
+ * @package questionbank
+ * @subpackage questiontypes
+ * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Provides the information to backup algebra questions
+ */
+class backup_qtype_algebra_plugin extends backup_qtype_plugin {
+
+    /**
+     * Returns the qtype information to attach to question element
+     */
+    protected function define_question_plugin_structure() {
+
+        // Define the virtual plugin element with the condition to fulfill
+        $plugin = $this->get_plugin_element(null, '../../qtype', 'algebra');
+
+        // Create one standard named plugin element (the visible container)
+        $pluginwrapper = new backup_nested_element($this->get_recommended_name());
+
+        // connect the visible container ASAP
+        $plugin->add_child($pluginwrapper);
+		
+		// This qtype uses standard question_answers, add them here
+        // to the tree before any other information that will use them
+        $this->add_question_question_answers($pluginwrapper);
+
+        // Now create the qtype own structures
+		
+		$algebravariables = new backup_nested_element('algebra_variables');
+		
+		$algebravariable = new backup_nested_element('algebra_variable', array('id'), array(
+            'name', 'min', 'max'));
+			
+		$algebra = new backup_nested_element('algebra', array('id'), array(
+            'compareby', 'nchecks', 'tolerance',
+            'disallow', 'allowedfuncs', 'answerprefix'));
+
+        // Now the own qtype tree
+		$pluginwrapper->add_child($algebravariables);
+        $algebravariables->add_child($algebravariable);
+		$pluginwrapper->add_child($algebra);
+
+        // set source to populate the data
+        $algebra->set_source_table('question_algebra', array('questionid' => backup::VAR_PARENTID));
+		$algebravariable->set_source_table('question_algebra_variables', array('question' => backup::VAR_PARENTID));
+
+        // don't need to annotate ids nor files
+
+        return $plugin;
+    }
+}
diff --git a/backup/moodle2/restore_qtype_algebra_plugin.class.php b/backup/moodle2/restore_qtype_algebra_plugin.class.php
new file mode 100644
index 0000000..5c2d567
--- /dev/null
+++ b/backup/moodle2/restore_qtype_algebra_plugin.class.php
@@ -0,0 +1,106 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    moodlecore
+ * @subpackage backup-moodle2
+ * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one algebra qtype plugin
+ */
+class restore_qtype_algebra_plugin extends restore_qtype_plugin {
+
+    /**
+     * Returns the paths to be handled by the plugin at question level
+     */
+    protected function define_question_plugin_structure() {
+
+        $paths = array();
+		
+		// This qtype uses question_answers, add them
+        $this->add_question_question_answers($paths);
+
+        // Add own qtype stuff
+        $elename = 'algebravariable';
+        $elepath = $this->get_pathfor('/algebra_variables/algebra_variable'); // we used get_recommended_name() so this works
+        $paths[] = new restore_path_element($elename, $elepath);
+		$elename = 'algebra';
+        $elepath = $this->get_pathfor('/algebra'); // we used get_recommended_name() so this works
+        $paths[] = new restore_path_element($elename, $elepath);
+
+        return $paths; // And we return the interesting paths
+    }
+
+    /**
+     * Process the qtype/algebra element
+     */
+    public function process_algebra($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+        // Detect if the question is created or mapped
+        $oldquestionid   = $this->get_old_parentid('question');
+        $newquestionid   = $this->get_new_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        // If the question has been created by restore, we need to create its question_algebra too
+        if ($questioncreated) {
+            // Adjust some columns
+            $data->questionid = $newquestionid;
+            // Insert record
+            $newitemid = $DB->insert_record('question_algebra', $data);
+            // Create mapping (needed for decoding links)
+            $this->set_mapping('question_algebra', $oldid, $newitemid);
+        } else {
+            // Nothing to remap if the question already existed
+        }
+    }
+	
+	/**
+     * Process the qtype/algebravariable element
+     */
+    public function process_algebravariable($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Detect if the question is created or mapped
+        $oldquestionid   = $this->get_old_parentid('question');
+        $newquestionid   = $this->get_new_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        // If the question has been created by restore, we need to create its question_algebra_variables too
+        if ($questioncreated) {
+            // Adjust some columns
+            $data->question = $newquestionid;
+            // Insert record
+            $newitemid = $DB->insert_record('question_algebra_variables', $data);
+            // Create mapping
+            $this->set_mapping('question_algebra_variable', $oldid, $newitemid);
+        } else {
+            // Nothing to remap if the question already existed
+        }
+    }
+}
diff --git a/db/install.xml b/db/install.xml
new file mode 100644
index 0000000..51b8d85
--- /dev/null
+++ b/db/install.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="question/type/symbollic/db" VERSION="20080516" COMMENT="XMLDB file for Moodle question/type/algebra">
+  <TABLES>
+	<TABLE NAME="question_algebra" COMMENT="Options for algebra questions" NEXT="question_algebra_variables">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true"
+		       NEXT="questionid"/>
+        <FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0"
+		       SEQUENCE="false" PREVIOUS="id" NEXT="compareby"/>
+        <FIELD NAME="compareby" TYPE="char" LENGTH="20" NOTNULL="true" UNSIGNED="false" DEFAULT="evaluated"
+		       SEQUENCE="false" PREVIOUS="questionid" NEXT="nchecks"/>
+        <FIELD NAME="nchecks" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="10"
+		       SEQUENCE="false" PREVIOUS="compareby" NEXT="tolerance"/>
+        <FIELD NAME="tolerance" TYPE="float" NOTNULL="true" UNSIGNED="false"
+		       SEQUENCE="false" PREVIOUS="nchecks" NEXT="disallow"/>
+        <FIELD NAME="disallow" TYPE="text" LENGTH="small" NOTNULL="true" UNSIGNED="false"
+		       SEQUENCE="false" PREVIOUS="tolerance" NEXT="allowedfuncs"/>
+        <FIELD NAME="allowedfuncs" TYPE="text" LENGTH="small" NOTNULL="true" UNSIGNED="false"
+		       SEQUENCE="false" PREVIOUS="disallow" NEXT="answerprefix"/>
+        <FIELD NAME="answerprefix" TYPE="text" LENGTH="small" NOTNULL="true" UNSIGNED="false"
+		       SEQUENCE="false" PREVIOUS="allowedfuncs"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="questionid"/>
+        <KEY NAME="questionid" TYPE="foreign" FIELDS="questionid" REFTABLE="question" REFFIELDS="id" PREVIOUS="primary"/>
+      </KEYS>
+    </TABLE>
+	<TABLE NAME="question_algebra_variables" COMMENT="Variables for algebra questions" PREVIOUS="question_algebra">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true"
+		       NEXT="question"/>
+        <FIELD NAME="question" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0"
+		       SEQUENCE="false" PREVIOUS="id" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="30" NOTNULL="true" UNSIGNED="false"
+		       SEQUENCE="false" PREVIOUS="question" NEXT="min"/>
+        <FIELD NAME="min" TYPE="char" LENGTH="30" NOTNULL="true" UNSIGNED="false" DEFAULT="-"
+		       SEQUENCE="false" PREVIOUS="name" NEXT="max"/>
+        <FIELD NAME="max" TYPE="char" LENGTH="30" NOTNULL="true" UNSIGNED="false" DEFAULT="-"
+		       SEQUENCE="false" PREVIOUS="min"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="question"/>
+        <KEY NAME="question" TYPE="foreign" FIELDS="question" REFTABLE="question" REFFIELDS="id" PREVIOUS="primary"/>
+      </KEYS>
+    </TABLE>
+  </TABLES>
+</XMLDB>
diff --git a/db/upgrade.php b/db/upgrade.php
new file mode 100644
index 0000000..371150b
--- /dev/null
+++ b/db/upgrade.php
@@ -0,0 +1,59 @@
+<?php  // $Id: upgrade.php,v 1.1 2008/07/24 01:48:12 arborrow Exp $
+
+// This file keeps track of upgrades to 
+// the algebra qtype plugin
+//
+// Sometimes, changes between versions involve
+// alterations to database structures and other
+// major things that may break installations.
+//
+// The upgrade function in this file will attempt
+// to perform all the necessary actions to upgrade
+// your older installtion to the current version.
+//
+// If there's something it cannot do itself, it
+// will tell you what you need to do.
+//
+// The commands in here will all be database-neutral,
+// using the functions defined in lib/ddllib.php
+
+function xmldb_qtype_algebra_upgrade($oldversion=0) {
+
+    global $CFG, $THEME, $DB;
+	
+	$dbman = $DB->get_manager();
+
+/// And upgrade begins here. For each one, you'll need one 
+/// block of code similar to the next one. Please, delete 
+/// this comment lines once this file start handling proper
+/// upgrade code.
+
+    // Add the field to store the string which is placed in front of the answer
+    // box when the question is displayed
+    if ($oldversion < 2008061500) {
+        $table = new xmldb_table('question_algebra');
+        $field = new xmldb_field('answerprefix', XMLDB_TYPE_TEXT, 'small', null, XMLDB_NOTNULL, null, '', 'allowedfuncs');
+		if (!$dbman->field_exists($table, $field)) {
+			$dbman->add_field($table, $field);
+		}
+		upgrade_plugin_savepoint(true, 2008061500, 'qtype', 'algebra');
+    }
+
+    // Drop the answers and variables fields wich are totaly redundants	
+	if ($oldversion < 2011072800) {
+	    $table = new xmldb_table('question_algebra');
+        $field = new xmldb_field('answers');
+
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->drop_field($table, $field);
+        }
+		$field = new xmldb_field('variables');
+
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->drop_field($table, $field);
+        }
+		upgrade_plugin_savepoint(true, 2011072800, 'qtype', 'algebra');
+	}
+    return true;
+}
+
diff --git a/db/upgradelib.php b/db/upgradelib.php
new file mode 100644
index 0000000..2f05142
--- /dev/null
+++ b/db/upgradelib.php
@@ -0,0 +1,71 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Upgrade library code for the algebra question type.
+ *
+ * @package    qtype
+ * @subpackage algebra
+ * @copyright  2010 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Class for converting attempt data for algebra questions when upgrading
+ * attempts to the new question engine.
+ *
+ * This class is used by the code in question/engine/upgrade/upgradelib.php.
+ *
+ * @copyright  2010 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_algebra_qe2_attempt_updater extends question_qtype_attempt_updater {
+    public function right_answer() {
+        foreach ($this->question->options->answers as $ans) {
+            if ($ans->fraction > 0.999) {
+                return $ans->answer;
+            }
+        }
+    }
+
+    public function was_answered($state) {
+        return !empty($state->answer);
+    }
+
+    public function response_summary($state) {
+        if (!empty($state->answer)) {
+            return $state->answer;
+        } else {
+            return null;
+        }
+    }
+
+    public function set_first_step_data_elements($state, &$data) {
+    }
+
+    public function supply_missing_first_step_data(&$data) {
+    }
+
+    public function set_data_elements_for_step($state, &$data) {
+        if (!empty($state->answer)) {
+            $data['answer'] = $state->answer;
+        }
+    }
+}
diff --git a/displayformula.php b/displayformula.php
new file mode 100644
index 0000000..e5051e5
--- /dev/null
+++ b/displayformula.php
@@ -0,0 +1,48 @@
+<?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");
+global $PAGE;
+
+$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());
+}
+$formatoptions = new stdClass;
+$formatoptions->para = false;
+$PAGE->set_context(get_context_instance(CONTEXT_SYSTEM));
+$text   = format_text($texexp, FORMAT_MOODLE, $formatoptions);
+?>
+<html>
+	<head>
+		<title>Formula</title>
+		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+	</head>
+	<body bgcolor="#FFFFFF">
+		<?php echo $text; ?>
+	</body>
+</html>
diff --git a/edit_algebra_form.php b/edit_algebra_form.php
new file mode 100644
index 0000000..eae12bf
--- /dev/null
+++ b/edit_algebra_form.php
@@ -0,0 +1,378 @@
+<?php
+/**
+ * Defines the editing form for the algebra question type.
+ *
+ * @copyright &copy; 2008 Roger Moore
+ * @author Roger Moore <rwmoore@ualberta.ca>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package questionbank
+ * @subpackage questiontypes
+ */
+
+require_once($CFG->dirroot . '/question/type/edit_question_form.php');
+require_once($CFG->dirroot . '/question/type/algebra/questiontype.php');
+require_once($CFG->dirroot . '/question/type/algebra/parser.php');
+    
+// Override the default number of answers and the number to add to avoid clutter.
+// Algebra questions will likely not have huge number of different answers...
+define("SYMB_QUESTION_NUMANS_START", 2);
+define("SYMB_QUESTION_NUMANS_ADD", 1);
+
+// Override the default number of answers and the number to add to avoid clutter.
+// algebra questions will likely not have huge number of different answers...
+define("SYMB_QUESTION_NUMVAR_START", 2);
+define("SYMB_QUESTION_NUMVAR_ADD", 1);
+
+/**
+ * symoblic editing form definition.
+ */
+class qtype_algebra_edit_form extends question_edit_form {
+    /**
+     * Add question-type specific form fields.
+     *
+     * @param MoodleQuickForm $mform the form being built.
+     */
+    protected function definition_inner($mform) {
+		// Add the select control which will select the comparison type to use
+        $mform->addElement('select', 'compareby', get_string('compareby','qtype_algebra'), 
+						   array( "sage"  => get_string('comparesage', 'qtype_algebra'),
+								  "eval"  => get_string('compareeval', 'qtype_algebra'),
+								  "equiv" => get_string('compareequiv','qtype_algebra')
+								 ));
+        $mform->addHelpButton('compareby', 'compareby', 'qtype_algebra');
+		$mform->setDefault('compareby','eval');
+		
+
+		// Add the control to select the number of checks to perform
+        // First create an array with all the allowed values. We will then use this array
+        // with the array_combine function to create a single array where the keys are the
+        // same as the array values
+        $chk_array=array(  '1',   '2',   '3',   '5',   '7',
+                          '10',  '20',  '30',  '50',  '70',
+                         '100', '200', '300', '500', '700', '1000');
+        // Add the select element using the array_combine method discussed above
+        $mform->addElement('select', 'nchecks', get_string('nchecks','qtype_algebra'), 
+                            array_combine($chk_array,$chk_array));
+        $mform->addHelpButton('nchecks', 'nchecks', 'qtype_algebra');
+		// Set the default number of checks to perform
+        $mform->setDefault('nchecks','10');
+		
+
+        // Add the box to set the tolerance to use when performing evaluation checks
+        $mform->addElement('text', 'tolerance', get_string('tolerance','qtype_algebra'));
+		$mform->addHelpButton('tolerance', 'tolerance', 'qtype_algebra');
+        $mform->setType('tolerance', PARAM_NUMBER);
+        $mform->setDefault('tolerance','0.001');
+        
+        // Add an entry for the answer box prefix
+        $mform->addElement('text', 'answerprefix', get_string('answerprefix','qtype_algebra'),array('size'=>55));
+		$mform->addHelpButton('answerprefix', 'answerprefix', 'qtype_algebra');
+        $mform->setType('answerprefix', PARAM_RAW);
+        
+        // Add an entry for a disallowed expression
+        $mform->addElement('text', 'disallow', get_string('disallow','qtype_algebra'),array('size'=>55));
+		$mform->addHelpButton('disallow', 'disallow', 'qtype_algebra');
+        $mform->setType('disallow', PARAM_RAW);
+        
+        // Create an array which will store the function checkboxes
+        $func_group=array();
+        // Create an array to add spacers between the boxes
+        $spacers=array('<br>');
+        // Add the initial all functions box to the list of check boxes
+        $func_group[] =& $mform->createElement('checkbox','all','',get_string('allfunctions','qtype_algebra'));
+        // Create a checkbox element for each function understood by the parser
+        for($i=0;$i<count(qtype_algebra_parser::$functions);$i++) {
+            $func=qtype_algebra_parser::$functions[$i];
+            $func_group[] =& $mform->createElement('checkbox',$func,'',$func);
+            if(($i % 6) == 5) {
+                $spacers[]='<br>';
+            } else {
+                $spacers[]=str_repeat('&nbsp;',8-strlen($func));
+            }
+        }
+		// Create and add the group of function controls to the form
+        $mform->addGroup($func_group,'allowedfuncs',get_string('allowedfuncs','qtype_algebra'),$spacers,true);
+		$mform->addHelpButton('allowedfuncs', 'allowedfuncs', 'qtype_algebra');
+        $mform->disabledIf('allowedfuncs','allowedfuncs[all]','checked');
+        $mform->setDefault('allowedfuncs[all]','checked');
+        
+		$mform->addElement('static', 'variablesinstruct',
+                get_string('variables', 'qtype_algebra'),
+                get_string('filloutonevariable', 'qtype_algebra'));
+		$mform->closeHeaderBefore('variablesinstruct');
+		// Create the array for the list of variables used in the question
+		$repeated=array();
+		// Create the array for the list of repeated options used by the variable subforms
+        $repeatedoptions = array();
+		
+		// Add the form elements to enter the variables
+        $repeated[] =& $mform->createElement('header','variablehdr',get_string('variableno','qtype_algebra','{no}'));
+        //$repeatedoptions['variablehdr']['helpbutton'] = array('variable',get_string('variable','qtype_algebra'),
+        //                                                      'qtype_algebra');
+		$repeated[] =& $mform->createElement('text','variable',get_string('variablename','qtype_algebra'),array('size'=>20));
+        $mform->setType('variable', PARAM_RAW);
+		$repeated[] =& $mform->createElement('text','varmin',get_string('varmin','qtype_algebra'),array('size'=>20));
+		$mform->setType('varmin', PARAM_RAW);
+        $repeatedoptions['varmin']['default'] = '';
+		$repeated[] =& $mform->createElement('text','varmax',get_string('varmax','qtype_algebra'),array('size'=>20));
+		$mform->setType('varmax', PARAM_RAW);
+        $repeatedoptions['varmax']['default'] = '';
+
+		// Get the current number of variables defined, if any
+		if (isset($this->question->options)) {
+            $countvars = count($this->question->options->variables);
+        } else {
+            $countvars = 0;
+        }
+		// Come up with the number of variable entries to add to the form at the start
+        if ($this->question->formoptions->repeatelements){
+            $repeatsatstart = (SYMB_QUESTION_NUMVAR_START > ($countvars + SYMB_QUESTION_NUMVAR_ADD))?
+			SYMB_QUESTION_NUMVAR_START : ($countvars + SYMB_QUESTION_NUMVAR_ADD);
+        } else {
+            $repeatsatstart = $countvars;
+        }
+        $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 'novariables', 'addvariables',
+							   SYMB_QUESTION_NUMVAR_ADD, get_string('addmorevariableblanks', 'qtype_algebra'));
+        
+        $mform->addElement('static', 'answersinstruct',
+                get_string('correctanswers', 'qtype_algebra'),
+                get_string('filloutoneanswer', 'qtype_algebra'));
+        $mform->closeHeaderBefore('answersinstruct');
+
+        $this->add_per_answer_fields($mform, get_string('answerno', 'qtype_algebra', '{no}'),
+                question_bank::fraction_options(), SYMB_QUESTION_NUMANS_START, SYMB_QUESTION_NUMANS_ADD);
+
+        $this->add_interactive_settings();
+
+    }
+
+	protected function data_preprocessing($question) {
+        $question = parent::data_preprocessing($question);
+        $question = $this->data_preprocessing_answers($question);
+        $question = $this->data_preprocessing_hints($question);
+
+        if (!empty($question->options)) {
+            $question->compareby = $question->options->compareby;
+			$question->nchecks = $question->options->nchecks;
+			$question->tolerance = $question->options->tolerance;
+			$question->allowedfuncs = $question->options->allowedfuncs;
+			$question->disallow = $question->options->disallow;
+			$question->answerprefix = $question->options->answerprefix;
+        }
+
+        return $question;
+    }
+    /**
+     * Sets the existing values into the form for the question specific data.
+	 *
+	 * This method copies the data from the existing database record into the form fields as default
+	 * values for the various elements.
+     *
+     * @param $question the question object from the database being used to fill the form
+     */
+    function set_data($question) {
+		// Check to see if there are any existing question options, if not then just call
+		// the base class set data method and exit
+        if (!isset($question->options)) {
+			return parent::set_data($question);
+		}
+		
+		// Now we do exactly the same for the variables...
+		$vars = $question->options->variables;
+		// If we found any variables then loop over them using a numerical key to provide an index
+		// to the arrays we need to access in the form
+		if (count($vars)) {
+			$key = 0;
+			foreach ($vars as $var) {
+				// For every variable set the default values
+				$default_values['variable['.$key.']'] = $var->name;
+				// Only set the min and max defaults if this variable has a range
+				if($var->min!='') {
+					$default_values['varmin['.$key.']'] = $var->min;
+					$default_values['varmax['.$key.']'] = $var->max;
+				}
+				$key++;
+			}
+		}
+		        
+        // Add the default values for the allowed functions controls
+        // First check to see if there are any allowed functions defined
+        if(count($question->options->allowedfuncs)>0) {
+            // Clear the 'all functions' flag since functions are restricted
+            $default_values['allowedfuncs[all]']=0;
+            // Loop over all the functions which the parser understands
+            foreach(qtype_algebra_parser::$functions as $func) {
+                // For each function see if the function is in the allowed function
+                // list and if so set the check box otherwise remove the check box
+                if(in_array($func,$question->options->allowedfuncs)) {
+                    $default_values['allowedfuncs['.$func.']']=1;
+                } else {
+                    $default_values['allowedfuncs['.$func.']']=0;
+                }
+            }
+        }
+        // There are no allowed functions defined so all functions are allowed
+        else {
+            $default_values['allowedfuncs[all]']=1;
+        }
+        
+		// Add the default values to the question object in a form which the parent 
+		// set data method will be able to use to find the default values
+		$question = (object)((array)$question + $default_values);
+		
+		// Finally call the parent set data method to handle everything else
+        parent::set_data($question);
+    }
+	
+    /**
+     * 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??
+     */
+    public 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');
+                }
+                // If there is one check that it is a number
+                else if(!preg_match($renumber,$trimmin)) {
+                    $errors['varmin['.$key.']'] = get_string('notanumber','qtype_algebra');
+                }
+                if ($trimmax == '') {
+                    $errors['varmax['.$key.']'] = get_string('novarmax','qtype_algebra');
+                }
+                // If there is one check that it is a number
+                else if(!preg_match($renumber,$trimmax)) {
+                    $errors['varmax['.$key.']'] = get_string('notanumber','qtype_algebra');
+                }
+                // Check that the minimum is less that the maximum!
+                if ((float)$trimmin > (float)$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;
+    }
+	
+    public function qtype() {
+        return 'algebra';
+    }
+}
+
diff --git a/lang/en/qtype_algebra.php b/lang/en/qtype_algebra.php
new file mode 100644
index 0000000..814aea8
--- /dev/null
+++ b/lang/en/qtype_algebra.php
@@ -0,0 +1,91 @@
+<?php
+include('qtype_algebra_parser.php');
+
+$string['answermustbegiven'] = 'You must enter an answer if there is a grade or feedback.';
+$string['answerno'] = 'Answer {$a}';
+$string['addmoreanswerblanks'] = 'Blanks for {no} More Answers';
+$string['addmorevariableblanks'] = 'Blanks for {no} More Variables';
+$string['allfunctions'] = 'All Functions';
+$string['allowedfuncs'] = 'Allowed Functions';
+$string['allowedfuncs_help'] = '**NOT YET IMPLEMENTED**
+
+These controls can be used to restrict the functions which the students
+can use in their responses. If the "All" button is checked then
+there are no restrictions on functions which the students may use in
+their answers. This is the default case. To restrict the allowed functions
+uncheck the "All" box and select the functions you wish to allow.';
+$string['allowedfunctions'] = 'Allowed Functions';
+$string['answer'] = 'Answer: {$a}';
+$string['answerboxprefix'] = 'String with which to prefix the answer box when displaying the question';
+$string['answerprefix_help'] = 'The text entered here will be placed in front of the input box where
+students enter their answers. For example if a question is asking the form
+of a function, f(x), then the string "f(x) = " could be entered in this
+field.';
+$string['answerno'] = 'Answer {$a}';
+$string['answerprefix'] = 'Answer box prefix';
+$string['checktolerance'] = 'Check Tolerance';
+$string['compalgorithm'] = 'Comparison Algorithm';
+$string['compareby_help'] = 'This selects the method by which the students\' responses are compared
+to all the questions answers. The different possibilities are:
+
+SAGE: uses the Open Source <a href="http://www.sagemath.org/">SAGE</a>
+mathematics software to perform a full symbolic algebraic comparison. 
+
+Evaluation: This method generates random numbers for
+the question variables and then evaluates both the student response and the
+question\'s answer for that set of values.
+
+Equivalence: 
+This is the simplest of all the methods. It will only perform the most basic of
+comparisons between expressions.';
+$string['compareby'] = 'Comparison Algorithm';
+$string['comparesage'] = 'SAGE';
+$string['compareeval'] = 'Evaluation';
+$string['compareequiv'] = 'Equivalence';
+$string['correctanswers'] = 'Correct answers';
+$string['correctansweris'] = 'The correct answer is: {$a} giving ';
+$string['disallow'] = 'Disallowed Answer';
+$string['disallow_help'] = 'contains an expression which will be disallowed as an answer.
+Students entering an answers which matches this will be prevented from
+receiving any grade for the question even if the response would match
+a given answer for the question.';
+$string['disallowans'] = 'Disallowed Answer';
+$string['disallowanswer'] = 'Disallowed Answer';
+$string['editingalgebra'] = 'Editing an Algebra question';
+$string['evalchecks'] = 'Evaluation Checks';
+$string['filloutoneanswer'] = 'You must provide at least one possible answer. Answers left blank will not be used. \'*\' can be used as a wildcard to match any characters. The first matching answer will be used to determine the score and feedback. Only variables defined above are allowed';
+$string['filloutonevariable'] = 'You must provide at least one variable. All variables used by answers must be entered here. Minimum and a maximum values are only needed if the Evaluation comparison algorithm is used.';
+$string['illegalvarname'] = 'Illegal variable name \'{$a}\': same name as a parser function or special constant';
+$string['nchecks'] = 'Number of Evaluation Checks';
+$string['nchecks_help'] = 'Number of Evaluation Checks used in Evaluation Comparison Algorithm';
+$string['notanumber'] = 'Invalid value: a number is required';
+$string['notenoughvars'] = 'At least one variable is required for all algebra questions';
+$string['novarmax'] = 'No maximum bound specified for variable';
+$string['novarmin'] = 'No minimum bound specified for variable';
+$string['parseerror'] = 'Error parsing function: \'{$a}\'';
+$string['restoreqdbfailed'] = 'Restoring algebra question failed: database write error';
+$string['restorevardbfailed'] = 'Restoring algebra question variable failed: database write error';
+$string['tolerance'] = 'Tolerance for Evaluation Checks';
+$string['tolerance_help'] = 'Determines the maximum difference between numerical
+evaluations of the student response and question answers which will be
+allowed to count as matching.';
+$string['toleranceltzero'] = 'Tolerance must be greater than or equal to zero';
+$string['undefinedvar'] = 'Undefined variable(s) {$a} used in one or more answers';
+$string['unusedvar'] = 'This variable is not used by any answer';
+$string['variable'] = 'Variable Name';
+$string['variablename'] = 'Variable Name';
+$string['variableno'] = 'Variable {$a}';
+$string['variables'] = 'Variables';
+$string['varmin'] = 'Minimum Value';
+$string['varmingtmax'] = 'The minimum value must be less than the maximum value';
+$string['varmax'] = 'Maximum Value';
+
+$string['pluginnameadding'] = 'Adding an algebra question';
+$string['pluginnameediting'] = 'Editing an algebra question';
+$string['pluginname_link'] = 'question/type/algebra';
+$string['pluginname_help'] = 'Student enter a formula as response that include one or more variables. Correctness is evaluted using one of 3 differents methods';
+$string['pluginname'] = 'algebra';
+$string['pluginnamesummary'] = 'Student enter a formula that can include one or more variables. Correctness is evaluted using one of 3 differents methods.';
+$string['host'] = 'Host url of SAGE server';
+$string['port'] = 'Port of SAGE server';
+$string['uri'] = 'uri of SAGE server';
diff --git a/lang/en/qtype_algebra_parser.php b/lang/en/qtype_algebra_parser.php
new file mode 100644
index 0000000..d4f7241
--- /dev/null
+++ b/lang/en/qtype_algebra_parser.php
@@ -0,0 +1,20 @@
+<?php
+$string['badclosebracket'] = 'Invalid close bracket found';
+$string['badequivtype'] = 'Invalid type: can only compare parser terms with other parser terms';
+$string['badfuncargs'] = 'Invalid arguments for the function \'{$a}\'';
+$string['decimal'] = '.';
+$string['illegalplusminus'] = 'Found a + or - in an invalid location';
+$string['mismatchedbracket'] = 'Mismatched brackets: Open and close bracket pair not of same type \'$a\'';
+$string['mismatchedcloseb'] = 'Mismatched brackets: Close bracket without an open bracket found';
+$string['mismatchedopenb'] = 'Mismatched brackets: Open bracket without a close bracket found';
+$string['missingonearg'] = 'Syntax Error: Operator \'{$a}\' missing its argument';
+$string['missingtwoargs'] = 'Syntax Error: Operator \'{$a}\' requires two arguments';
+$string['morethantwoargs'] = 'Trying to compare a term with more than 2 arguments - no code to handle this case!';
+$string['multiply'] = '\\\\times';
+$string['nargswrong'] = 'Incorrect number of arguments for the term \'{$a}\'';
+$string['noevaluate'] = 'The evaluate method for term \'{$a}\' has not been implemented';
+$string['notopterm'] = 'Syntax Error: Unable to condense to a single, top level operator';
+$string['undeclaredvar'] = 'Undeclared variable \'{$a}\' found';
+$string['undefinedfunction'] = 'Undefined function \'{$a}\'';
+$string['undefinedvariable'] = 'Undefined variable \'{$a}\' found when numerically evaluating an expression';
+$string['unknownterm'] = 'Syntax Error: Unknown term found at \'{$a}\' in the expression';
diff --git a/lib.php b/lib.php
new file mode 100644
index 0000000..f14538d
--- /dev/null
+++ b/lib.php
@@ -0,0 +1,38 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Serve question type files
+ *
+ * @since      2.0
+ * @package    qtype
+ * @subpackage algebra
+ * @copyright  Dongsheng Cai <dongsheng@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Checks file access for algebra questions.
+ */
+function qtype_algebra_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
+    global $DB, $CFG;
+    require_once($CFG->libdir . '/questionlib.php');
+    question_pluginfile($course, $context, 'qtype_algebra', $filearea, $args, $forcedownload, $options);
+}
diff --git a/parser.php b/parser.php
new file mode 100644
index 0000000..263d951
--- /dev/null
+++ b/parser.php
@@ -0,0 +1,1721 @@
+<?php
+
+// Parser code for the Moodle Algebra question type
+// Moodle algebra question type class
+// Author: Roger Moore <rwmoore 'at' ualberta.ca>
+// License: GNU Public License version 3
+    
+    
+// From the PHP manual: check for the existance of lcfirst and
+// if not found create one.
+if(!function_exists('lcfirst')) {
+	/**
+	 * Make a string's first character lowercase
+	 *
+	 * @param string $str
+	 * @return string the resulting string.
+	 */
+	function lcfirst( $str ) {
+		$str[0] = strtolower($str[0]);
+		return (string)$str;
+	}
+}
+
+/**
+ * Helper function which will compare two strings using their length only.
+ *
+ * This function is intended for use in sorting arrays of strings by their string
+ * length. This is used to order arrays for regular expressions so that the longest
+ * expressions are checked first.
+ *
+ * @param $a first string to compare
+ * @param $b second string to compare
+ * @return -1 if $a is longer than $b, 0 if they are the same length and +1 if $a is shorter
+ */
+function qtype_algebra_parser_strlen_sort($a,$b) {
+    // Get the two string lengths once so we don't have to repeat the function call
+    $alen=strlen($a);
+    $blen=strlen($b);
+    // If the two lengths are equal return zero
+    if($alen==$blen) return 0;
+    // Otherwise return +1 if a>b or -1 if a<b
+    return ($alen>$blen) ? -1 : +1;
+}
+    
+
+/**
+ * Class which represents a single term in an algebraic expression.
+ *
+ * A single algebraic term is considered to be either an operation, for example addition,
+ * subtraction, raising to a power etc. or something operated on, such as a number or
+ * variable. Each type of term implements a subclass of this base class.
+ */
+class qtype_algebra_parser_term {
+    
+    /**
+     * Constructor for the generic parser term.
+     *
+     * This method is called by all subclasses to initialize the base class for use.
+     * It initializes the number of arguments required, the format strings to use
+     * when converting the term in various strng formats, the parser text associated
+     * with the term and whether the term is one which commutes.
+     *
+     * @param $nargs number of arguments which this type of term requires
+     * @param $formats an array of the format strings for this term keyed by type
+     * @param $text the text from the expression associated with the array
+     * @param $commutes if set to true then this term commutes (only for 2 argument terms)
+     */
+    function qtype_algebra_parser_term($nargs,$formats,$text='',$commutes=false) {
+        $this->_value=$text;
+        $this->_nargs=$nargs;
+        $this->_formats=$formats;
+        $this->_commutes=$commutes;
+    }
+
+    /**
+     * Generates the list of arguments needed when converting the term into a string.
+     *
+     * This method returns an array with the arguments needed when converting the term
+     * into a string. The arrys can then be used with a format string to generate the
+     * string representation. The method is recursive because it needs to convert the
+     * arguments of the term into strings and so it will walk down the parse tree.
+     *
+     * @param $method name of method to call to convert arguments into strings
+     * @return array of the arguments that, with a format string, can be passed to sprintf
+     */
+    function print_args($method) {
+        // Create an empty array to store the arguments in
+        $args=array();
+        // Handle zero argument terms differently by making the 
+	    // first 'argument' the value of the term itself
+        if($this->_nargs==0) {
+            $args[]=$this->_value;
+        } else {
+            foreach($this->_arguments as $arg) {
+                $args[]=$arg->$method();
+            }
+        }
+        // Return the array of arguments
+        return $args;
+    }
+    
+    /**
+     * Produces a 'prettified' string of the expression using the standard input syntax.
+     *
+     * This method will use the {@link print_args} method to convert the term and all its
+     * arguments into a string.
+     *
+     * @return input syntax format string of the expression
+     */
+    function str() {
+        // First check to see if the class has been given all the arguments
+		$this->check_arguments();
+        // Get an array of all the arguments except for the format string
+        $args=$this->print_args('str');
+	    // Insert the format string at the front of the argument array
+	    array_unshift($args,$this->_formats['str']);
+        // Call sprintf using the argument array as the arguments
+        return call_user_func_array('sprintf',$args);
+    }
+
+    /**
+     * Produces a LaTeX formatted string of the expression.
+     *
+     * This method will use the {@link print_args} method to convert the term and all its
+     * arguments into a LaTeX formatted string. This can then be given to the main Moodle
+     * engine, with TeX filter enabled, to produce a graphical representation of the
+     * expression.
+     *
+     * @return LaTeX format string of the expression
+     */
+    function tex() {
+        // First check to see if the class has been given all the arguments
+		$this->check_arguments();
+        // Get an array of all the arguments except for the format string
+        $args=$this->print_args('tex');
+	    // Insert the format string at the front of the argument array
+	    array_unshift($args,$this->_formats['tex']);
+        // Call sprintf using the argument array as the arguments
+        return call_user_func_array('sprintf',$args);
+    }
+    
+    /**
+     * Produces a SAGE formatted string of the expression.
+     *
+     * This method will use the {@link print_args} method to convert the term and all its
+     * arguments into a SAGE formatted string. This can then be passed to SAGE via XML-RPC
+     * for symbolic comparisons. The format is very similar to the {@link str} method but
+     * has all multiplications made explicit with an asterix.
+     *
+     * @return SAGE format string of the expression
+     */
+    function sage() {
+        // First check to see if the class has been given all the arguments
+		$this->check_arguments();
+        // Get an array of all the arguments except for the format string
+        $args=$this->print_args('sage');
+	    // Insert the format string at the front of the argument array. First we
+	    // check to see if there is a format element called 'sage' if not then we
+        // default to the standard string format
+		if(array_key_exists('sage',$this->_formats)) {
+            // Insert the sage format string at the front of the argument array
+            array_unshift($args,$this->_formats['sage']);
+		} else {
+            // Insert the normal format string at the front of the argument array
+            array_unshift($args,$this->_formats['str']);
+		}
+        // Call sprintf using the argument array as the arguments
+        return call_user_func_array('sprintf',$args);
+    }
+    
+    /**
+     * Returns the list of arguments for the term.
+     *
+     * This method provides access to the arguments of the term. Although this should
+     * ideally be private information it is needed in certain cases to determine
+     * how neighbouring terms should display themselves.
+     *
+     * @return array of arguments for this term
+     */
+    function arguments() {
+        return $this->_arguments;
+    }
+	
+    /**
+     * Sets the arguments of the term to the values in the given array.
+     *
+     * The code here overrides the base class's method. The code uses this method to actually
+     * set the arguments in the given array but a second stage to choose the format of the
+     * multiplication operator is required. This is because a 'x' symbol is required when
+     * multiplying two numbers. However this can be omitted when multiplying two variables,
+     * a variable and a function etc.
+     *
+     * @param $args array to set the arguments of the term to
+     */
+    function set_arguments($args) {
+        if (count($args)!=$this->_nargs) {
+            throw new Exception(get_string('nargswrong','qtype_algebra',$this->_value));
+        }
+        $this->_arguments=$args;
+    }
+	
+    /**
+     * Checks to ensure that the correct number of arguments are defined.
+     *
+     * Note that this method just checks for the number or arguments it does not check
+     * whether they are valid arguments. If the parameter passed is true (default value)
+     * an exception will be thrown if the correct number of arguments are not present. Otherwise
+     * the function returns false.
+     *
+     * @param $exc if true then an exception will be thrown if the number of arguments is incorrect
+     * @return true if the correct number of arguments are present, false otherwise
+     */
+	function check_arguments($exc=true) {
+		$retval=(count($this->_arguments)==$this->_nargs);
+		if($exc && !$retval) {
+           throw new Exception(get_string('nargswrong','qtype_algebra',$this->_value));
+        } else {
+			return $retval;
+        }
+	}
+
+    /**
+     * Returns a list of all the variable names found in the expression.
+     *
+     * This method uses the {@link collect} method to walk down the parse tree and collect
+     * a list of all the variables which the parser has found in the expression. The names
+     * of the variables are then returned.
+     *
+     * @return an array containing all the variables names in the expression
+     */
+	function get_variables() {
+		$list=array();
+		$this->collect($list,'qtype_algebra_parser_variable');
+		return array_keys($list);
+	}
+	
+    /**
+     * Returns a list of all the function names found in the expression.
+     *
+     * This method uses the {@link collect} method to walk down the parse tree and collect
+     * a list of all the functions which the parser has found in the expression. The names
+     * of the functions are then returned.
+     *
+     * @return an array containing all the function names used in the expression
+     */
+	function get_functions() {
+		$list=array();
+		$this->collect($list,'qtype_algebra_parser_function');
+		return array_keys($list);
+	}
+	
+    /**
+     * Collects all the terms of a given type with unique values in the parse tree
+     *
+     * This method walks recursively down the parse tree by calling itself for the arguments
+     * of the current term. The method simply adds the current term to the given imput array
+     * using a key set to the value of the term but only if the term matches the selected type.
+     * In this way terms only a single entry per term value is return which is the functionality
+     * required for the {@link get_variables} and {@link get_functions} methods.
+     *
+     * @param $list the array to add the term to if it matches the type
+     * @param $type the name of the type of term to collect.
+     * @return an array containing all the terms of the selected type keyed by their value
+     */
+	function collect(&$list,$type) {
+        // Add this class to the list if of the correct type
+        if(is_a($this,$type)) {
+            // Add a key to the array with the value of the term, this means
+            // that multiple terms with the same value will overwrite each
+            // other so only one will remain.
+            $list[$this->_value]=0;
+        }
+        // Now loop over all the argument for this term (if any) and check them
+		foreach($this->_arguments as $arg) {
+			// Collect terms from the arguments as well
+			$arg->collect($list,$type);
+		}
+	}
+    
+    /**
+     * Checks to see if this term is equal to another term ignoring arguments.
+     *
+     * This method compares the current term to another term. The default method simply compares
+     * the class of each term. Terms which require more than this, for example comparing values
+     * too, override this method in theor own classes.
+     *
+     * @param $term the term to compare to the current one
+     * @return true if the terms match, false otherwise
+     */
+    function equals($term) {
+        // Default method just checks to ensure that the Terms are both of the same type
+        return is_a($term,get_class($this));
+    }
+    
+    /**
+     * Compares this term, including any arguments, with another term.
+     *
+     * This method uses the {@link equals} method to see if the current and given term match.
+     * It then looks at any arguments which the two terms have and, recursively, calls their
+     * compare methods to determine if they also match. For terms with two arguments which
+     * also commute the reverse ordering of the arguments is also tried if the first order
+     * fails to match.
+     *
+     * @param $expr top level term of an expression to compare against
+     * @return true if the expressions match, false otherwise
+     */
+    function equivalent($expr) {
+        // Check that the argument is also a term
+        if(!is_a($expr,'qtype_algebra_parser_term')) {
+            throw new Exception(get_string('badequivtype','qtype_algebra'));
+        }
+        // Now check that this term is the same as the given term
+        if(!$this->equals($expr)) {
+            // Terms are not equal immediately return false since the two do not match
+            return false;
+        }
+        // Now compare the arguments recursively...
+        switch($this->_nargs) {
+            case 0:
+                // For zero arguments we already compared this class and found it the same so
+                // because there are no arguments to check we are equivalent!
+                return true;
+            case 1:
+                // For one argument we also need to compare the argument of each term
+                return $this->_arguments[0]->equivalent($expr->_arguments[0]);
+            case 2:
+                // Now it gets interesting. First we compare the two arguments in the same
+                // order and see what we get...
+                if($this->_arguments[0]->equivalent($expr->_arguments[0]) and
+                   $this->_arguments[1]->equivalent($expr->_arguments[1])) {
+                    // Both arguments are equivalent so we have a match
+                    return true;
+                }
+                // Otherwise if the operator commutes we can see if the first argument matches
+                // the second argument and vice versa
+                else if($this->_commutes and $this->_arguments[0]->equivalent($expr->_arguments[1]) and
+                        $this->_arguments[1]->equivalent($expr->_arguments[0])) {
+                    return true;
+                } else {
+                    return false;
+                }
+            default:
+                throw new Exception(get_string('morethantwoargs','qtype_algebra'));
+        }
+    }
+    
+    /**
+     * Returns the number of arguments required by the term.
+     *
+     * @return the number of arguments required by the term
+     */
+    function n_args() {
+        return $this->_nargs;
+    }
+    
+    /**
+     * Evaluates the term numerically using the given variable values.
+     *
+     * The given parameter array is keyed by the name of the variable and the numerical
+     * value to assign it is stored in the array value. This method is an abstract one
+     * which must be implemented by all subclasses. Failure to do so will generate an
+     * exception when the method is called.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */
+    function evaluate($params) {
+        throw new Exception(get_string('noevaluate','qtype_algebra',$this->_value));
+    }
+
+    /**
+     * Dumps the term and its arguments to standard out.
+     *
+     * This method will recursively call the entire parse tree attached to it and produce
+     * a nicely formatted dump of the term structure. This is mainly useful for debugging
+     * purposes.
+     *
+     * @param $indent string containing the indentation to use
+     * @param $params variable values to use if an evaluation is also desired
+     * @return a string indicating the type of the term
+     */    
+	function dump(&$params=array(),$indent='') {
+		echo "$indent<Term type '".get_class($this).'\' with value \''.$this->_value;
+        if(!empty($params)) {
+            echo ' eval=\''.$this->evaluate($params)."'>\n";
+        } else {
+            echo "'>\n";
+        }
+        foreach($this->_arguments as $arg) {
+            $arg->dump($params,$indent.'  ');
+        }
+	}
+    
+    /**
+     * Special casting operator method to convert the term object to a string.
+     *
+     * This is primarily a debug method. It is called when the term object is cast into a
+     * string, such as happens when echoing or printing it. It simply returns a string
+     * indicating the type of the parser term.
+     *
+     * @return a string indicating the type of the term
+     */    
+	function __toString() {
+		return '<Algebraic parser term of type \''.get_class($this).'\'>';
+	}
+        
+    // Member variables
+    var $_value;             // String of the actual term itself
+    var $_arguments=array(); // Array of arguments in class form
+    var $_formats;           // Array of format strings
+    var $_nargs;             // Number of arguments for this term
+}
+
+/**
+ * Class representing a null, or empty, term.
+ *
+ * This is the type of term returned when the parser is given an empty string to parse.
+ * It takes no arguments and will never be found in a parser tree. This term is solely
+ * to give a valid return type for an empty string condition and so avoids the need to
+ * throw an exception in such cases.
+ */
+class qtype_algebra_parser_nullterm extends qtype_algebra_parser_term {
+
+    /**
+     * Constructs an instance of a null term.
+     *
+     * Initializes a null term class. Since this class represents nothing no special
+     * initialization is required and no arguments are needed.
+     */
+	function qtype_algebra_parser_nullterm() {
+		parent::qtype_algebra_parser_term(self::NARGS,self::$formats,'');
+	}
+    
+    /**
+     * Returns the array of arguments needed to convert this class into a string.
+     *
+     * Since this class is represented by an empty string which has no formatting fields
+     * we override the base class method to return an empty array.
+     *
+     * @param $method name of method to call to convert arguments into strings
+     * @return array of the arguments that, with a format string, can be passed to sprintf
+     */
+	function print_args($method) {
+		return array();
+	}
+	
+    /**
+     * Evaluates the term numerically.
+     *
+     * Since this is an empty term we define the evaluation as zero regardless of the parameters.
+     *
+     * @param $params array of the variable values to use
+     */
+	function evaluate($params) {
+        // Return something which is not a number
+		return acos(2.0);
+	}
+	
+	// Static class properties
+    const NARGS=0;
+	private static $formats=array('str' => '',
+                                  'tex' => '');
+}
+	
+
+/**
+ * Class representing a number.
+ *
+ * All purely numerical quantities will be represented by this type of class. There are
+ * two basic types of numbers: non-exponential and exponential. Both types are handled by
+ * this single class.
+ */
+class qtype_algebra_parser_number extends qtype_algebra_parser_term {
+ 
+    /**
+     * Constructs an instance of a number term.
+     *
+     * This function initializes an instance of a number term using the string which
+     * matches the number's regular expression.
+     *
+     * @param $text string matching the number regular expression
+     */
+    function qtype_algebra_parser_number($text='') {
+        // Unfortunately PHP maths will only support a '.' as a decimal point and will not support
+        // ',' as used in Danish, French etc. To allow for this we always convert any commas into
+        // decimal points before we parse the string
+        $text=str_replace(',','.',$text);
+        $this->_sign='';
+        // Now determine whether this is in exponent form or just a plain number
+	    if(preg_match('/([\.0-9]+)E([-+]?\d+)/',$text,$m)) {
+	        $this->_base=$m[1];
+	        $this->_exp=$m[2];
+            $eformats=array('str' => '%sE%s',
+                            'tex' => '%s '.get_string('multiply','qtype_algebra').' 10^{%s}');
+	        parent::qtype_algebra_parser_term(self::NARGS,$eformats,$text);
+    	} else {
+	        $this->_base=$text;
+	        $this->_exp='';
+	        parent::qtype_algebra_parser_term(self::NARGS,self::$formats,$text);
+	    }
+    }
+    
+    /**
+     * Sets this number to be negative.
+     *
+     * This method will convert the number into a nagetive one. It is called when
+     * the parser finds a subtraction operator in front of the number which does
+     * not have a variable or another number preceding it.
+     */
+    function set_negative() {
+        // Prepend a minus sign to both the base and total value strings
+        $this->_base='-'.$this->_base;
+        $this->_value='-'.$this->_value;
+        $this->_sign='-';
+    }
+    
+    /**
+     * Checks to see if this number is equal to another number.
+     *
+     * This is a two step process. First we use the base class equals method to ensure
+     * that we are comparing two numbers. Then we check that the two have the same value.
+     *
+     * @param $expt the term to compare to the current one
+     * @return true if the terms match, false otherwise
+     */
+    function equals($expr) {
+        // Call the default method first to check type
+        if(parent::equals($expr)) {
+            return (float)$this->_value==(float)$expr->_value;
+        } else {
+            return false;
+        }
+    }
+    
+    /**
+     * Generates the list of arguments needed when converting the term into a string.
+     *
+     * For number terms there are two possible formats: those with an exponent and those
+     * without an exponent. This method determines which to use and then pushes the correct
+     * arguments into the array which is returned.
+     *
+     * @param $method name of method to call to convert arguments into strings
+     * @return array of the arguments that, with a format string, can be passed to sprintf
+     */
+    function print_args($method) {
+        // When displaying the number we need to worry about whether to use a decimal point
+        // or a comma depending on the language currently selected/ Do this by replacing the
+        // decimal point (which we have to use internally because of the PHP math standard)
+        // with the correct string from the language pack
+        $base=str_replace('.',get_string('decimal','qtype_algebra'),$this->_base);
+        // Put the base part of the number into the argument array
+        $args=array($base);
+        // Check to see if we have an exponent...
+        if($this->_exp) {
+            // ...we do so add it to the argument array as well
+	        $args[]=$this->_exp;
+        }
+        // Return the list of arguments
+        return $args;
+    }
+    
+    /**
+     * Evaluates the term numerically.
+     *
+     * All this method does is return the string representing the number cast as a double
+     * precision floating point variable.
+     *
+     * @param $params array of the variable values to use
+     */
+    function evaluate($params) {
+        return doubleval($this->_value);
+    }
+    
+    // Static class properties
+    const NARGS=0;
+    private static $formats=array('str' => '%s',
+                                  'tex' => '%s ');
+}
+
+/**
+ * Class representing a variable term in an algebraic expression.
+ *
+ * When the parser finds a text string which does not correspond to a function it creates
+ * this type of term and puts the contents of that text into it. Variables with names
+ * corresponding to the names of the greek letters are replaced by those letters when
+ * rendering the term in LaTeX. Other variables display their first letter with all
+ * subsequent letters being lowercase. This reduces confusion when rendering expressions
+ * consisting of multiplication of two variables.
+ */
+class qtype_algebra_parser_variable extends qtype_algebra_parser_term {
+    // Define the list of variable names which will be replaced by greek letters
+	public static $greek = array (
+		'alpha',
+		'beta',
+		'gamma',
+		'delta',
+		'epsilon',
+		'zeta',
+		'eta',
+		'theta',
+		'iota',
+		'kappa',
+		'lambda',
+		'mu',
+		'nu',
+		'xi',
+		'omicron',
+		'pi',
+		'rho',
+		'sigma',
+		'tau',
+		'upsilon',
+		'phi',
+		'chi',
+		'psi',
+		'omega'
+	 );
+	
+    /**
+     * Constructor for an algebraic term cass representing a variable.
+     *
+     * Initializes an instance of the variable term subclass. The method is given the text
+     * in the expression corresponding to the variable name. This is then parsed to get the
+     * variable name which is split into a base and subscript. If the start of the string
+     * matches the name of a greek letter this is taken as the base and the remainder as the
+     * subscript. Failing that either the subscript must be explicitly specified using an
+     * underscore character or the first character is taken as the base.
+     *
+     * @param $text text matching the variable name
+     */
+    function qtype_algebra_parser_variable($text) {
+        // Create the array to store the regular expression matches in
+        $m=array();
+        // Set the sign of the variable to be empty
+        $this->_sign='';
+        // Try to match the text to a greek letter
+        if(preg_match('/('.implode('|',self::$greek).')/A',$text,$m)) {
+            // Take the base name of the variable to be the greek letter
+            $this->_base=$m[1];
+            // Extract the remaining characters for use as the subscript
+            $this->_subscript=substr($text,strlen($m[1]));
+            // If the first letter of the subscript is an underscore then remove it
+            if($this->_subscript[0] == '_') {
+                $this->_subscript=substr($this->_subscript,1);
+            }
+            // Call the base class constructor with the variable text set to the combination of the
+            // base name and the subscript without an underscore between them
+            parent::qtype_algebra_parser_term(self::NARGS,self::$formats['greek'],
+                                              $this->_base.$this->_subscript);
+        }
+        // Otherwise we have a simple multi-letter variable name. Treat the fist letter as the base
+        // name and the rest as the subscript
+        else {
+            // Get the variable's base name
+            $this->_base=substr($text,0,1);
+            // Now set the subscript to the remaining letters
+            $this->_subscript=substr($text,1);
+            // If the first letter of the subscript is an underscore then remove it
+            if($this->_subscript[0] == '_') {
+                $this->_subscript=substr($this->_subscript,1);
+            }
+            // Call the base class constructor with the variable text set to the combination of the
+            // base name and the subscript without an underscore between them
+            parent::qtype_algebra_parser_term(self::NARGS,self::$formats['std'],
+                                              $this->_base.$this->_subscript);
+        }
+    }
+    
+    /**
+     * Sets this variable to be negative.
+     *
+     * This method will convert the number into a nagetive one. It is called when
+     * the parser finds a subtraction operator in front of the number which does
+     * not have a variable or another number preceding it.
+     */
+    function set_negative() {
+        // Set the sign to be a '-'
+        $this->_sign='-';
+    }
+    
+    /**
+     * Generates the list of arguments needed when converting the term into a string.
+     *
+     * The string of the variable depends solely on the name and subscript and hence these
+     * are the only two arguments returned in the array.
+     *
+     * @param $method name of method to call to convert arguments into strings
+     * @return array of the arguments that, with a format string, can be passed to sprintf
+     */
+    function print_args($method) {
+        return array($this->_sign,$this->_base,$this->_subscript);
+    }
+
+    /**
+     * Evaluates the number numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the number the
+     * class represents.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+        if($this->_sign=='-') {
+            $mult=-1;
+        } else {
+            $mult=1;
+        }
+        if(array_key_exists($this->_value,$params)) {
+            return $mult*doubleval($params[$this->_value]);
+        } else {
+            // Found an indefined variable. Cannot evaluate numerically so throw exception
+            throw new Exception(get_string('undefinedvariable','qtype_algebra',$this->_value));
+        }
+    }
+    
+    /**
+     * Checks to see if this variable is equal to another variable.
+     *
+     * This is a two step process. First we use the base class equals method to ensure
+     * that we are comparing two variables. Then we check that the two are the same variable.
+     *
+     * @param $expr the term to compare to the current one
+     * @return true if the terms match, false otherwise
+     */
+    function equals($expr) {
+        // Call the default method first to check type
+        if(parent::equals($expr)) {
+            return $this->_value==$expr->_value and $this->_sign==$expr->_sign;
+        } else {
+            return false;
+        }
+    }
+    
+    // Static class properties
+    const NARGS=0;
+    private static $formats=array(
+		'greek' =>  array('str' => '%s%s%s',
+                          'tex' => '%s\%s_{%s}'),
+		'std'   =>  array('str' => '%s%s%s',
+                          'tex' => '%s%s_{%s}')
+    );
+}
+
+
+/**
+ * Class representing a power operation in an algebraic expression.
+ *
+ * The parser creates an instance of this term when it finds a string matching the power
+ * operator's syntax. The string which corresponds to the term is passed to the constructor
+ * of this subclass.
+ */
+class qtype_algebra_parser_power extends qtype_algebra_parser_term {
+
+    /**
+     * Constructs an instance of a power operator term.
+     *
+     * This function initializes an instance of a power operator term using the string which
+     * matches the power operator expression. Since this is simply the character representing
+     * the operator it is not used except when producing a string representation of the term.
+     *
+     * @param $text string matching the term's regular expression
+     */
+    function qtype_algebra_parser_power($text) {
+        parent::qtype_algebra_parser_term(self::NARGS,self::$formats,$text);
+    }
+    
+    /**
+     * Evaluates the power operation numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the power
+     * operation. The method evaluates the two arguments of the term and then passes them to
+     * the 'pow' function from the maths library.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+		$this->check_arguments();
+		return pow(doubleval($this->_arguments[0]->evaluate($params)),
+				   doubleval($this->_arguments[1]->evaluate($params)));
+    }
+
+    // Static class properties
+    const NARGS=2;
+    private static $formats=array(
+        'str' => '%s^%s',
+        'tex' => '%s^{%s}'
+    );
+}
+
+
+/**
+ * Class representing a divide operation in an algebraic expression.
+ *
+ * The parser creates an instance of this term when it finds a string matching the divide
+ * operator's syntax. The string which corresponds to the term is passed to the constructor
+ * of this subclass.
+ */
+class qtype_algebra_parser_divide extends qtype_algebra_parser_term {
+        
+    /**
+     * Constructs an instance of a divide operator term.
+     *
+     * This function initializes an instance of a divide operator term using the string which
+     * matches the divide operator expression. Since this is simply the character representing
+     * the operator it is not used except when producing a string representation of the term.
+     *
+     * @param $text string matching the term's regular expression
+     */
+    function qtype_algebra_parser_divide($text) {
+        parent::qtype_algebra_parser_term(self::NARGS,self::$formats,$text);
+    }
+    
+    /**
+     * Evaluates the divide operation numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the divide
+     * operation. The method evaluates the two arguments of the term and then simply divides
+     * them to get the return value.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+		$this->check_arguments();
+        // Get the value we are trying to divide by
+        $divby=$this->_arguments[1]->evaluate($params);
+        // Check to see if this is zero
+        if($divby==0) {
+            // Check the sign of the other argument and use to determine whether we return
+            // plus or minus infinity
+            return INF*$this->_arguments[0]->evaluate($params);
+        } else {
+            return $this->_arguments[0]->evaluate($params)/$divby;
+        }
+    }
+
+    // Static class properties
+    const NARGS=2;
+    private static $formats=array(
+        'str' => '%s/%s',
+        'tex' => '\\frac{%s}{%s}'
+    );
+}
+
+
+/**
+ * Class representing a multiplication operation in an algebraic expression.
+ *
+ * The parser creates an instance of this term when it finds a string matching the multiplication
+ * operator's syntax. The string which corresponds to the term is passed to the constructor
+ * of this subclass.
+ */
+class qtype_algebra_parser_multiply extends qtype_algebra_parser_term {
+
+    /**
+     * Constructs an instance of a multiplication operator term.
+     *
+     * This function initializes an instance of a multiplication operator term using the string which
+     * matches the multiplication operator expression. Since this is simply the character representing
+     * the operator it is not used except when producing a string representation of the term.
+     *
+     * @param $text string matching the term's regular expression
+     */
+    function qtype_algebra_parser_multiply($text) {
+        $this->mformats=array('*' =>  array('str' => '%s*%s',
+                                            'tex' => '%s '.get_string('multiply','qtype_algebra').' %s'),
+                              '.' =>  array('str' => '%s %s',
+                                            'tex' => '%s %s',
+                                            'sage'=> '%s*%s')
+                              );
+        parent::qtype_algebra_parser_term(self::NARGS,$this->mformats['*'],$text,true);
+    }
+
+    /**
+     * Sets the arguments of the term to the values in the given array.
+     *
+     * This method sets the term's arguments to those in the given array.
+     *
+     * @param $args array to set the arguments of the term to
+     */
+    function set_arguments($args) {
+        // First perform default argument setting method. This will generate
+        // an error if there is a problem with the number of arguments
+        parent::set_arguments($args);
+        // Set the default explicit format
+        $this->_formats=$this->mformats['*'];
+        // Only allow the implicit multipication if the second argument is either a
+        // special, variable, function or bracket and not negative. In all other cases the operator must be
+        // explicitly written
+        if(is_a($args[1],'qtype_algebra_parser_bracket') or
+           is_a($args[1],'qtype_algebra_parser_variable') or
+           is_a($args[1],'qtype_algebra_parser_special') or
+           is_a($args[1],'qtype_algebra_parser_function')) {
+            if(!method_exists($args[1],'set_negative') or $args[1]->_sign=='') {
+                $this->_formats=$this->mformats['.'];
+            } 
+        }
+        // Check for one more special exemption: if the second argument is a power expression
+        // then we use the same criteria on the first argument of it
+        if(is_a($args[1],'qtype_algebra_parser_power')) {
+            // Get the arguments from the power term. Note we do not check these since
+            // power terms are parsed before multiplication ones and are required to
+            // have two arguments.
+            $powargs=$args[1]->arguments();
+            // Allow the implicit multipication if the power's first argument is either a
+            // special, variable, function or bracket and not negative.
+            if(is_a($powargs[0],'qtype_algebra_parser_bracket') or
+               is_a($powargs[0],'qtype_algebra_parser_variable') or
+               is_a($powargs[0],'qtype_algebra_parser_special') or
+               is_a($powargs[0],'qtype_algebra_parser_function')) {
+                if(!method_exists($powargs[0],'set_negative') or $powargs[0]->_sign=='') {
+                    $this->_formats=$this->mformats['.'];
+                } 
+            }
+        }
+    }
+    
+    /**
+     * Evaluates the multiplication operation numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the multiplication
+     * operation. The method evaluates the two arguments of the term and then simply multiplies
+     * them to get the return value.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+		$this->check_arguments();
+		return $this->_arguments[0]->evaluate($params)*
+			   $this->_arguments[1]->evaluate($params);
+    }
+
+    // Static class properties
+    const NARGS=2;
+}
+
+
+/**
+ * Class representing a addition operation in an algebraic expression.
+ *
+ * The parser creates an instance of this term when it finds a string matching the addition
+ * operator's syntax. The string which corresponds to the term is passed to the constructor
+ * of this subclass.
+ */
+class qtype_algebra_parser_add extends qtype_algebra_parser_term {
+
+    /**
+     * Constructs an instance of a addition operator term.
+     *
+     * This function initializes an instance of a addition operator term using the string which
+     * matches the addition operator expression. Since this is simply the character representing
+     * the operator it is not used except when producing a string representation of the term.
+     *
+     * @param $text string matching the term's regular expression
+     */
+    function qtype_algebra_parser_add($text) {
+        parent::qtype_algebra_parser_term(self::NARGS,self::$formats,$text,true);
+    }
+    
+    /**
+     * Evaluates the addition operation numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the addition
+     * operation. The method evaluates the two arguments of the term and then simply adds
+     * them to get the return value.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+		$this->check_arguments();
+		return $this->_arguments[0]->evaluate($params)+
+			   $this->_arguments[1]->evaluate($params);
+    }
+
+    // Static class properties
+    const NARGS=2;
+    private static $formats=array(
+        'str' => '%s+%s',
+        'tex' => '%s + %s'
+    );
+}
+
+
+/**
+ * Class representing a subtraction operation in an algebraic expression.
+ *
+ * The parser creates an instance of this term when it finds a string matching the subtraction
+ * operator's syntax. The string which corresponds to the term is passed to the constructor
+ * of this subclass.
+ */
+class qtype_algebra_parser_subtract extends qtype_algebra_parser_term {
+
+    /**
+     * Constructs an instance of a subtraction operator term.
+     *
+     * This function initializes an instance of a subtraction operator term using the string which
+     * matches the subtraction operator expression. Since this is simply the character representing
+     * the operator it is not used except when producing a string representation of the term.
+     *
+     * @param $text string matching the term's regular expression
+     */
+    function qtype_algebra_parser_subtract($text) {
+        parent::qtype_algebra_parser_term(self::NARGS,self::$formats,$text);
+    }
+    
+    /**
+     * Evaluates the subtraction operation numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the subtraction
+     * operation. The method evaluates the two arguments of the term and then simply subtracts
+     * them to get the return value.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+		$this->check_arguments();
+		return $this->_arguments[0]->evaluate($params)-
+			   $this->_arguments[1]->evaluate($params);
+    }
+
+    // Static class properties
+    const NARGS=2;
+    private static $formats=array(
+        'str' => '%s-%s',
+        'tex' => '%s - %s'
+    );
+}
+
+
+/**
+ * Class representing a special constant in an algebraic expression.
+ *
+ * The parser creates an instance of this term when it finds a string matching the a predefined
+ * special constant such as pi or 'e' (from natural logarithms).
+ */
+class qtype_algebra_parser_special extends qtype_algebra_parser_term {
+
+    /**
+     * Constructs an instance of a special constant term.
+     *
+     * This function initializes an instance of a special term using the string which
+     * matches the regular expression of a special constant.
+     *
+     * @param $text string matching a constant's regular expression
+     */
+    function qtype_algebra_parser_special($text) {
+        parent::qtype_algebra_parser_term(self::NARGS,self::$formats[$text],$text);
+        $this->_sign='';
+    }
+    
+    /**
+     * Sets this special to be negative.
+     *
+     * This method will convert the number into a nagetive one. It is called when
+     * the parser finds a subtraction operator in front of the number which does
+     * not have a variable or another number preceding it.
+     */
+    function set_negative() {
+        // Set the sign to be a '-'
+        $this->_sign='-';
+    }
+    
+    /**
+     * Evaluates the special constant numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the special
+     * constant which is defined by an internal switch based on the constant's name.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+        if($this->_sign=='-') {
+            $mult=-1;
+        } else {
+            $mult=1;
+        }
+        switch($this->_value) {
+            case 'pi':
+                return $mult*pi();
+            case 'e':
+                return $mult*exp(1);
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Returns the array of arguments needed to convert this special term into a string.
+     *
+     * The special term generally has a fixed, predefined formatting already hard coded so
+     * the only remaining variable is the sign of the term and this is what this method
+     * returns.
+     *
+     * @param $method name of method to call to convert arguments into strings
+     * @return array of the arguments that, with a format string, can be passed to sprintf
+     */
+	function print_args($method) {
+		return array($this->_sign);
+	}
+	
+    /**
+     * Checks to see if this constant is equal to another term.
+     *
+     * This is a two step process. First we use the base class equals method to ensure
+     * that we are comparing two variables. Then we check that the two are the same constant.
+     *
+     * @param $expr the term to compare to the current one
+     * @return true if the terms match, false otherwise
+     */
+    function equals($expr) {
+        // Call the default method first to check type
+        if(parent::equals($expr)) {
+            return $this->_value==$expr->_value and $this->_sign==$this->_sign;
+        } else {
+            return false;
+        }
+    }
+    
+    // Static class properties
+    const NARGS=0;
+    private static $formats=array(
+		'pi' =>  array(  'str' => '%spi',
+					     'tex' => '%s\\pi'),
+		'e'  =>  array(  'str' => '%se',
+						 'tex' => '%se')
+	);
+}
+
+
+/**
+ * Class representing a function in an algebraic expression.
+ *
+ * The parser creates an instance of this term when it finds a string matching the function's
+ * syntax. The string which corresponds to the term is passed to the constructor
+ * of this subclass.
+ */
+class qtype_algebra_parser_function extends qtype_algebra_parser_term {
+
+    /**
+     * Constructs an instance of a function term.
+     *
+     * This function initializes an instance of a function term using the string which
+     * matches the name of a function.
+     *
+     * @param $text string matching the function's regular expression
+     */
+    function qtype_algebra_parser_function($text) {
+        if(!function_exists($text) and !array_key_exists($text,self::$fnmap)) {
+            throw new Exception(get_string('undefinedfunction','qtype_algebra',$text));
+        }
+        $formats=array( 'str'   =>  '%s'.$text.'%s');
+		if(array_key_exists($text,self::$texmap)) {
+            $formats['tex']='%s'.self::$texmap[$text].' %s';
+		} else {
+            $formats['tex']='%s\\'.$text.' %s';
+		}
+        $this->_sign='';
+        parent::qtype_algebra_parser_term(self::NARGS,$formats,$text);
+    }
+
+    /**
+     * Sets this function to be negative.
+     *
+     * This method will convert the function into a negative one. It is called when
+     * the parser finds a subtraction operator in front of the function which does
+     * not have a variable or another number preceding it e.g. 3*-sin(x)
+     */
+    function set_negative() {
+        // Set the sign to be a '-'
+        $this->_sign='-';
+    }
+    
+    /**
+     * Sets the arguments of the term to the values in the given array.
+     *
+     * The code here overrides the base class's method. The code uses this method to actually
+     * set the arguments in the given array but a second stage to insert brackets around the
+     * function's argument is required.
+     *
+     * @param $args array to set the arguments of the term to
+     */
+    function set_arguments($args) {
+        if(count($args)!=$this->_nargs) {
+            throw new Exception(get_string('badfuncargs','qtype_algebra',$this->_value));
+        }
+        if(!is_a($args[0],'qtype_algebra_parser_bracket')) {
+            // Check to see if this function requires a special bracket
+            if(in_array($this->_value,self::$bracketmap)) {
+                $b=new qtype_algebra_parser_bracket('<');
+            }
+            // Does not require special brackets so create normal ones
+            else {
+                $b=new qtype_algebra_parser_bracket('(');
+            }
+            $b->set_arguments($args);
+            $this->_arguments=array($b);
+        }
+        // First term already a bracket
+        else {
+            // Check to see if we need a special bracket
+            if(in_array($this->_value,self::$bracketmap)) {
+                // Make the bracket special
+                $args[0]->make_special();
+            }
+            // Set the arguments to the given type
+            $this->_arguments=$args;
+        }
+    }
+
+    /**
+     * Generates the list of arguments needed when converting the term into a string.
+     *
+     * The string of the function depends solely on the function argument and the sign.
+     * The name has already been coded in at construction time.
+     *
+     * @param $method name of method to call to convert arguments into strings
+     * @return array of the arguments that, with a format string, can be passed to sprintf
+     */
+    function print_args($method) {
+        // First ensure that there are the correct number of arguments
+		$this->check_arguments();
+        return array($this->_sign,$this->_arguments[0]->$method());
+    }
+    
+    /**
+     * Evaluates the function numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the function.
+     * Each function name is first checked against an internal map to determine the corresponding
+     * PHP math function to call. If the function is not in the map it is assumed to already be
+     * the correct name for a PHP math function.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+        // First ensure that there are the correct number of arguments
+		$this->check_arguments();
+        // Get the correct sign to multiply the value by
+        if($this->_sign=='-') {
+            $mult=-1;
+        } else {
+            $mult=1;
+        }
+        // Check to see if there is an entry to map the function name to a PHP function 
+        if(array_key_exists($this->_value,self::$fnmap)) {
+            $func=self::$fnmap[$this->_value];
+            return $mult*$func($this->_arguments[0]->evaluate($params));
+        }
+        // No map entry so the function name must already be a PHP function...
+        else {
+            $tmp=$this->_value;
+            return $mult*$tmp($this->_arguments[0]->evaluate($params));
+        }
+    }
+
+    /**
+     * Checks to see if this function is equal to another term.
+     *
+     * This is a two step process. First we use the base class equals method to ensure
+     * that we are comparing two variables. Then we check that the two are the same constant.
+     *
+     * @param $expr the term to compare to the current one
+     * @return true if the terms match, false otherwise
+     */
+    function equals($expr) {
+        // Call the default method first to check type
+        if(parent::equals($expr)) {
+            return $this->_value==$expr->_value and $this->_sign==$this->_sign;
+        } else {
+            return false;
+        }
+    }
+    
+    // Static class properties
+    const NARGS=1;
+    public static $fnmap = array ('ln'  => 'log',
+                                  'log' => 'log10'
+                                  );
+	public static $texmap = array('asin' => '\\sin^{-1}',
+                                  'acos' => '\\cos^{-1}',
+                                  'atan' => '\\tan^{-1}',
+                                  'sqrt' => '\\sqrt'
+                                  );
+    // List of functions requiring special brackets
+    public static $bracketmap = array ('sqrt'
+                                       );    
+}
+
+
+/**
+ * Class representing a bracket operation in an algebraic expression.
+ *
+ * The parser creates an instance of this term when it finds a string matching the bracket
+ * operator's syntax. The string which corresponds to the term is passed to the constructor
+ * of this subclass. Note that a pair of brackets is treated as a single term. There are no
+ * separate open and close bracket operators.
+ */
+class qtype_algebra_parser_bracket extends qtype_algebra_parser_term {
+
+    function qtype_algebra_parser_bracket($text) {
+        parent::qtype_algebra_parser_term(self::NARGS,self::$formats[$text],$text);
+        $this->_open=$text;
+        switch($this->_open) {
+            case '(':
+                $this->_close=')';
+                break;
+            case '[':
+                $this->_close=']';
+                break;
+            case '{':
+                $this->_close='}';
+                break;
+            // Special kind of bracket. This behaves as normal brackets for a string but as invisible
+            // curly brackets '{}' with LaTeX.
+            case '<':
+                $this->_close='>';
+                break;
+        }
+    }
+            
+    /**
+     * Evaluates the bracket operation numerically.
+     *
+     * Overrides the base class method to simply return the numerical value of the bracket
+     * operation. The method evaluates the argument of the term, i.e. what is inside the
+     * brackets, and then returns the value.
+     *
+     * @param $params array of values keyed by variable name
+     * @return the numerical value of the term given the provided values for the variables
+     */    
+    function evaluate($params) {
+        if(count($this->_arguments)!=$this->_nargs) {
+            return 0;
+        }
+        return $this->_arguments[0]->evaluate($params);            
+    }
+
+    /**
+     * Set the bracket type to 'special'.
+     *
+     * The method converts the bracket to the special type. The special type appears as a
+     * normal bracket in string mode but produces the invisible curly brackets for LaTeX.
+     */
+    function make_special() {
+        $this->_open='<';
+        $this->_close='>';
+        // Call the base class constructor as if this were a new instance of the bracket
+        parent::qtype_algebra_parser_term(self::NARGS,self::$formats['<'],'<');
+    }
+    
+    // Member variables
+    var $_open='(';
+    var $_close=')';
+    
+    // Static class properties
+    const NARGS=1;
+    private static $formats=array(
+        '(' =>  array('str' => '(%s)',
+                      'tex' => '\\left( %s \\right)'),
+        '[' =>  array('str' => '[%s]',
+                      'tex' => '\\left[ %s \\right]'),
+        '{' =>  array('str' => '{%s}',
+                      'tex' => '\\left\\lbrace %s \\right\\rbrace'),
+        '<' =>  array('str' => '(%s)',
+                      'tex' => '{%s}')
+    );
+}
+
+
+
+/**
+ * The main parser class.
+ *
+ * This class implements the methods needed to parse an expression. It uses a series of
+ * regular expressions to indentify the different terms in the expression and then creates
+ * instances of the correct subclass to handle them.
+ */
+class qtype_algebra_parser {
+    // Special constants which the parser will understand
+    public static $specials = array (
+        'pi',
+        'e'
+    );
+    
+    // Functions which the parser will understand. These should all be standard PHP math functions.
+    public static $functions = array ('sqrt',
+                                      'ln',
+                                      'log',
+                                      'cosh',
+                                      'sinh',
+                                      'sin',
+                                      'cos',
+                                      'tan',
+                                      'asin',
+                                      'acos',
+                                      'atan'
+                                      );
+        
+    // Array to define the priority of the different operations. The parser implements the standard BODMAS priority:
+    // brackets, order (power), division, mulitplication, addition, subtraction
+    private static $priority = array (
+        array('qtype_algebra_parser_power'),
+        array('qtype_algebra_parser_function'),
+        array('qtype_algebra_parser_divide','qtype_algebra_parser_multiply'),
+        array('qtype_algebra_parser_add','qtype_algebra_parser_subtract')
+    );
+    
+    // Regular experssion to match an open bracket
+    private static $OPENB        = '/[\{\(\[]/A';
+    // Regular experssion to match a close bracket
+    private static $CLOSEB       = '/[\}\)\]]/A';
+    // Regular expression to match a plain float or integer number without exponent
+    private static $PLAIN_NUMBER = '(([0-9]+(\.|,)[0-9]*)|([0-9]+)|((\.|,)[0-9]+))';
+    // Regular expression to match a float or integer number with an exponent
+    private static $EXP_NUMBER   = '(([0-9]+(\.|,)[0-9]*)|([0-9]+)|((\.|,)[0-9]+))E([-+]?\d+)';
+    // Array to associate close brackets with the correct open bracket type
+    private static $BRACKET_MAP  = array(')' => '(', ']' => '[', '}' => '{');
+    
+    /**
+     * Constructor for the main parser class.
+     *
+     * This constructor initializes the token map of the main parser class. It constructs a map of 
+     * regular expressions to class types. As it parses a string it uses these regular expressions to
+     * find tokens in the input string which are then fed to the corresponding term class for
+     * interpretation.
+     */
+    function qtype_algebra_parser() {
+        $this->_tokens = array (
+            array ('/(\^|\*\*)/A',                       	 'qtype_algebra_parser_power'    ),
+            array ('/('.implode('|',self::$functions).')/A', 'qtype_algebra_parser_function'   ),
+            array ('/\//A',                               	 'qtype_algebra_parser_divide'   ),
+            array ('/\*/A',                              	 'qtype_algebra_parser_multiply' ),
+            array ('/\+/A',                              	 'qtype_algebra_parser_add'      ),
+            array ('/-/A',                               	 'qtype_algebra_parser_subtract' ),
+            array ('/('.implode('|',self::$specials).')/A',  'qtype_algebra_parser_special'  ),
+            array ('/('.self::$EXP_NUMBER.'|'.self::$PLAIN_NUMBER.')/A',	'qtype_algebra_parser_number'   ),
+            array ('/[A-Za-z][A-Za-z0-9_]*/A',           	 'qtype_algebra_parser_variable' )
+            );
+    }
+
+    /**
+     * Parses a given string containing an algebric epxression and returns the corresponding parse tree.
+     *
+     * This method loops over the string using the regular expressions in the token map to break down the
+     * string into tokens. These tokens are arranged into a structured stack, taking account of the
+     * bracket structure. Finally then method calls the {@link interpret} method to convert the structured
+     * token strings into a fully parsed term structure. The method can optionally be passed a list of
+     * variables which are used in the expression. If such a list is passed then the parser will attempt
+     * to match the current position in the string with one of these given variables before any other
+     * token. When passing a variable list a third parameter allows a choice of whether to allow additional
+     * undeclared variables. This defaults to false when a list of variables is passed and is ignored otherwise.
+     *
+     * @param $text string containing the expression to parse
+     * @param $variables array containing known variable names
+     * @param $undecvars whether to allow (true) undeclared variable names
+     * @return top term of the parsed expression
+     */
+    function parse($text,$variables=array(),$undecvars=false) {
+        // Create a regular expression to match the known variables if an array is specified
+        if(!empty($variables)) {
+            // Create an empty array to store the list of extra regular expressions to match
+            $reextra=array();
+            // Loop over all the variable names we are given
+            foreach($variables as $var) {
+                // Create a temporary varible term using the current name
+                $tmpvar=new qtype_algebra_parser_variable($var);
+                // If the variable name has a subscript then create a new regular expression to
+                // search for which includes an underscore
+                if(!empty($tmpvar->_subscript)) {
+                    $reextra[]=$tmpvar->_base.'_'.$tmpvar->_subscript;
+                }
+            }
+            // Merge the variable name array with the array of extra regular expressions to match
+            $variables=array_merge($variables,$reextra);
+            // Sort the array in order of increasing variable length in order to prevent 'x1' matching
+            // a variable 'x' before 'x1'. Do this using a helper function, which will compare two
+            // strings using their length only, and use this with the usort function.
+            usort($variables,'qtype_algebra_parser_strlen_sort');
+            // Generate a single regular expression which will match both all known variables
+            $revar='/('.implode('|',$variables).')/A';
+        } else {
+            $revar='';
+        }
+        $i=0;
+        // Create an array to store the parse tree
+        $tree=array();
+        // Create an array to act as a temporary storage stack. This stack is used to 
+        // push higher levels of the parse tree as it is assembled from the expression
+        $stack=array();
+        // Array used to store the match results from regular expression searches
+        $m=array();
+        // Loop over the expression string moving along it using the offset variable $i while
+        // there are still characters left to parse
+        while($i<strlen($text)) {
+            // Match any white space at the start of the string and 'remove' it by advancing
+            // the pointer by the length of the string matching the regular expression white
+            // space pattern
+            if(preg_match('/\s+/A',substr($text,$i),$m)) {
+                $i+=strlen($m[0]);
+                // Return to the start of the loop in case this was white space characters at
+                // the end of the string
+                continue;
+            }
+            // Since we don't have any white space the first thing we look for (top priority)
+            // are open brackets
+            if(preg_match(self::$OPENB,substr($text,$i),$m)) {
+                // Check for a non-operator and if one is found assume implicit multiplication
+                if(count($tree)>0 and (is_array($tree[count($tree)-1]) or
+                    (is_object($tree[count($tree)-1]) 
+                     and $tree[count($tree)-1]->n_args()==0))) {
+                    // Make the implicit assumption explicit by adding an appropriate
+                    // multiplication operator
+                    array_push($tree,new qtype_algebra_parser_multiply('*'));
+                }
+                // Push the current parse tree onto the stack
+                array_push($stack,$tree);
+                // Create a new parse tree starting with a bracket term
+                $tree=array(new qtype_algebra_parser_bracket($m[0]));
+                // Increment the string pointer by the length of the string that was matched
+                $i+=strlen($m[0]);
+                // Return to the start of the loop
+                continue;
+            }
+            // Now see if we have a close bracket here
+            if(preg_match(self::$CLOSEB,substr($text,$i),$m)) {
+                // First check that the current parse tree has at least one term
+                if(count($tree)==0) {
+                    throw new Exception(get_string('badclosebracket','qtype_algebra'));
+                }
+                // Now check that the current tree started with a bracket
+                if(!is_a($tree[0],'qtype_algebra_parser_bracket')) {
+                    throw new Exception(get_string('mismatchedcloseb','qtype_algebra'));
+                }
+                // Check that the open and close bracket are of the same type
+                else if($tree[0]->_value != self::$BRACKET_MAP[$m[0]]) {
+                    throw new Exception(get_string('mismatchedbracket','qtype_algebra',$tree[0]->_value.$m[0]));
+                }                
+                // Append the current tree to the tree one level up on the stack
+                array_push($stack[count($stack)-1],$tree);
+                // The new tree is the lowest level tree on the stack so we
+                // pop the new tree off the stack
+                $tree=array_pop($stack);
+                $i+=strlen($m[0]);
+                continue;
+            }
+            // If a list of predefined variables was given to the method then check for them here
+            if(!empty($revar) and preg_match($revar,substr($text,$i),$m)) {
+                // Check for a zero argument term or brackets preceding the variable and if there is one then
+                // add the implicit multiplication operation
+                if(count($tree)>0 and (is_array($tree[count($tree)-1]) or $tree[count($tree)-1]->n_args()==0)) {
+                    array_push($tree,new qtype_algebra_parser_multiply('*'));
+                }
+                // Increment the string index by the length of the variable's name
+                $i+=strlen($m[0]);
+                // Push a new variable term onto the parse tree
+                array_push($tree,new qtype_algebra_parser_variable($m[0]));
+                continue;
+            }
+            // Here we have not found any open or close brackets or known variables so we can
+            // parse the string for a normal token
+            foreach($this->_tokens as $token) {
+                //echo 'Looking for token ',$token[1],"\n";
+                if(preg_match($token[0],substr($text,$i),$m)) {
+                    //echo 'Found a ',$token[1],"!\n";
+                    // Check for a variable and throw an exception if undeclared variables are
+                    // not allowed and a list of defined variables was passed
+                    if(!empty($revar) and !$undecvars and $token[1]=='qtype_algebra_parser_variable') {
+                        throw new Exception(get_string('undeclaredvar','qtype_algebra',$m[0]));
+                    }
+                    // Check for a zero argument term preceding a variable, function or special and then
+                    // add the implicit multiplication
+                    if(count($tree)>0 and ($token[1]=='qtype_algebra_parser_variable' or
+                        $token[1]=='qtype_algebra_parser_function' or
+                        $token[1]=='qtype_algebra_parser_special') 
+                        and (is_array($tree[count($tree)-1]) or
+                        $tree[count($tree)-1]->n_args()==0)) {
+                        array_push($tree,new qtype_algebra_parser_multiply('*'));
+                    }
+                    $i+=strlen($m[0]);
+                    array_push($tree,new $token[1]($m[0]));
+                    continue 2;
+                }
+            }
+            throw new Exception(get_string('unknownterm','qtype_algebra',substr($text,$i)));
+        } // end while loop over tokens
+        // If all the open brackets have been closed then the stack will be empty and the
+        // tree will contain the entire parsed expression
+        if(count($stack)>0) {
+            throw new Exception(get_string('mismatchedopenb','qtype_algebra'));
+        }
+        //print_r($tree);
+        //print_r($stack);
+        return $this->interpret($tree);
+    }
+
+    /**
+     * Takes a structured token map and converts it into a parsed term structure.
+     *
+     * This is an internal method of the parser class and is called by the {@link parse}
+     * method. It performs the final stage of the parsing process and returns the fully
+     * parsed term structure.
+     *
+     * @param $tree structured token array
+     * @return top term of the fully parsed structure
+     */
+    function interpret($tree) {
+        // First check to see if we are passed anything at all. If not then simply
+        // return a qtype_algebra_parser_nullterm
+        if(count($tree)==0) {
+			return new qtype_algebra_parser_nullterm();
+        }
+        // Now we check to see if this tree is inside brackets. If so then
+		// we remove the bracket object from the tree and store it in a
+		// temporary variable. We will then parse the remainder of the tree
+		// and make the top level term the bracket's argument if applicable.
+        if(is_a($tree[0],'qtype_algebra_parser_bracket')) {
+            $bracket=array_splice($tree,0,1);
+            $bracket=$bracket[0];
+        } else {
+            $bracket='';
+        }
+        // Next we loop over the tree and look for arrays. These represent
+		// brackets inside our tree and so we need to process them first.
+        for($i=0;$i<count($tree);$i++) {
+            // Check for a list type if we find one then replace
+            // it with the interpreted term
+            if(is_array($tree[$i])) {
+                $tree[$i]=$this->interpret($tree[$i]);
+            }
+        }
+        // The next job is to check the subtraction operations to determine whether they are
+        // really subtraction operations or whether they are minus signs for negative numbers
+        $toremove=array();
+        for($i=0;$i<count($tree);$i++) {
+            // Check that this element is an addition or subtraction operator
+            if(is_a($tree[$i],'qtype_algebra_parser_subtract') or is_a($tree[$i],'qtype_algebra_parser_add')) {
+                // Check whether the precedding argument (if there is one) is a number or
+                // a variable. In either case this is a addition/subtraction operation so we continue
+                if($i>0 and (is_a($tree[$i-1],'qtype_algebra_parser_variable') or 
+                             is_a($tree[$i-1],'qtype_algebra_parser_number') or
+                             is_a($tree[$i-1],'qtype_algebra_parser_bracket'))) {
+                    continue;
+                }
+                // Otherwise we have found a minus sign indicating a positive or negative quantity...
+                else {
+                    // Check that we do have a number following otherwise generate an exception...
+                    if($i==(count($tree)-1) or !method_exists($tree[$i+1],'set_negative')) {
+                        throw new Exception(get_string('illegalplusminus','qtype_algebra'));
+                    }
+                    // If we have a subtract operation then we need to make the following number negative
+                    if(is_a($tree[$i],'qtype_algebra_parser_subtract')) {
+                        // Set the number to be negative
+                        $tree[$i+1]->set_negative();
+                    }
+                    // Add the term to the removal list
+                    $toremove[$i]=1;
+                }
+            }
+        }
+        // Remove the elements from the tree who's keys are found in the removal list
+        $tree=array_diff_key($tree,$toremove);
+        // Re-key the tree array so that the keys are sequential
+        $tree=array_values($tree);
+        foreach(self::$priority as $ops) {
+            $i=0;
+            //echo 'Looking for ',$ops,"\n";
+            while($i<count($tree)) {
+                if(in_array(get_class($tree[$i]),$ops)) {
+                    //echo 'Found a ',get_class($tree[$i]),"\n";
+                    if($tree[$i]->n_args()==1) {
+                        if(($i+1)<count($tree)) {
+                            $tree[$i]->set_arguments(array_splice($tree,$i+1,1));
+                            $i++;
+                            continue;
+                        } else {
+                            throw new Exception(get_string('missingonearg','qtype_algebra',$op));
+                        }
+                    } elseif($tree[$i]->n_args() == 2) {
+                        if($i>0 and $i<(count($tree)-1)) {
+                            $tree[$i]->set_arguments(array($tree[$i-1],
+                                                    $tree[$i+1]));
+                            array_splice($tree,$i+1,1);
+                            array_splice($tree,$i-1,1);
+                            continue;
+                        } else {
+                            throw new Exception(get_string('missingtwoargs','qtype_algebra',$op));
+                        }
+                    }
+                } else {
+                    $i++;
+                }
+            }
+        }
+		// If there are no terms in the parse tree then we were passed an empty string
+		// in which case we create a null term and return it
+		if(count($tree)==0) {
+			return new qtype_algebra_parser_nullterm();
+		} else if(count($tree)!=1) {
+            //print_r($tree);
+            throw new Exception(get_string('notopterm','qtype_algebra'));
+        }
+        if($bracket) {
+            $bracket->set_arguments(array($tree[0]));
+            return $bracket;
+        } else {
+            return $tree[0];
+        }
+    }
+}
+
+// Sort static arrays once here by inverse string length
+usort(qtype_algebra_parser_variable::$greek,'qtype_algebra_parser_strlen_sort');
+usort(qtype_algebra_parser::$functions,'qtype_algebra_parser_strlen_sort');
+
diff --git a/pix/icon.gif b/pix/icon.gif
new file mode 100644
index 0000000..0668e33
Binary files /dev/null and b/pix/icon.gif differ
diff --git a/question.php b/question.php
new file mode 100644
index 0000000..12d27dd
--- /dev/null
+++ b/question.php
@@ -0,0 +1,356 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * algebra answer question definition class.
+ *
+ * @package    qtype
+ * @subpackage algebra
+ * @author  Roger Moore <rwmoore 'at' ualberta.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/question/type/algebra/questiontype.php');
+require_once($CFG->dirroot . '/question/type/algebra/parser.php');
+require_once($CFG->dirroot . '/question/type/algebra/xmlrpc-utils.php');
+
+/**
+ * Represents an algebra question.
+ *
+ * @copyright  2009 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_algebra_question extends question_graded_by_strategy
+        implements question_response_answer_comparer {
+
+    /** @var array of question_answer. */
+    public $answers = array();
+	/** @var array of question_answer. */
+	public $variables = array();
+	public $compareby;
+	public $nchecks;
+	public $tolerance;
+	public $allowedfuncs;
+	public $disallow;
+	public $answerprefix;
+
+    public function __construct() {
+        parent::__construct(new question_first_matching_answer_grading_strategy($this));
+    }
+
+    public function get_expected_data() {
+        return array('answer' => PARAM_RAW_TRIMMED);
+    }
+
+    public function summarise_response(array $response) {
+        if (isset($response['answer'])) {
+            return $response['answer'];
+        } else {
+            return null;
+        }
+    }
+
+    public function is_complete_response(array $response) {
+        return array_key_exists('answer', $response) &&
+                ($response['answer'] || $response['answer'] === '0');
+    }
+
+    public function get_validation_error(array $response) {
+        if ($this->is_gradable_response($response)) {
+            return '';
+        }
+        return get_string('pleaseenterananswer', 'qtype_algebra');
+    }
+	
+		/**
+	  * 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
+	  * @return top term of the parse tree or a string if an exception is thrown
+	  */
+	public function parse_expression($expr) {
+        // 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;
+        }
+
+        // Create an array of variable names for the parser from the question if defined
+        $varnames=array();
+        if(isset($this->variables)) {
+            foreach($this->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;
+		}
+	}
+
+		/**
+	  * Parses the given expression with the parser if required.
+	  *
+	  * This method will parse the expression and return a TeX string
+      * or empty string 
+	  *
+      * @param $expr expression which will be parsed
+	  * @return top term of the parse tree or a string if an exception is thrown
+	  */
+	public function formated_expression($text) {
+
+        // Create an array of variable names for the parser from the question if defined
+        $varnames=array();
+        if(isset($this->variables)) {
+            foreach($this->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 {
+            $exp = $p->parse($text, $varnames);
+			return '$$'.$exp->tex().'$$';
+		} catch(Exception $e) {
+			return '';
+		}
+	}
+
+    public function is_same_response(array $prevresponse, array $newresponse) {
+        // Check that both states have valid responses
+        if (!isset($prevresponse['answer']) or !isset($newresponse['answer'])) {
+            // At last one of the states did not have a response set so return false by default
+            return false;
+        }
+		// Parse the previous response
+        $expr=$this->parse_expression($prevresponse['answer']);
+        // Parse the new response
+        $testexpr=$this->parse_expression($newresponse['answer']);
+		// The type of comparison done depends on the comparision algorithm selected by
+        // the question. Use the defined algorithm to select which comparison function
+        // to call...
+        if($this->compareby == 'sage') {
+            // Uses an XML-RPC server with SAGE to perform a full symbollic comparision
+            return self::test_response_by_sage($expr,$testexpr);
+        } else if($this->compareby == 'eval') {
+            // Tests the response by evaluating it for a certain range of each variable
+            return self::test_response_by_evaluation($expr,$testexpr);
+        } else {
+            // Tests the response by performing a simple parse tree equivalence algorithm
+            return self::test_response_by_equivalence($expr,$testexpr);
+        }
+    }
+
+    public function get_answers() {
+        return $this->answers;
+    }
+
+    public function compare_response_with_answer(array $response, question_answer $answer) {
+                // Deal with the match anything answer by returning true
+        if ($answer->answer == '*') {
+            return true;
+        }
+		$expr=$this->parse_expression($response['answer']);
+        // Check that there is a response and if not return false. We do this here
+        // because even an empty response should match a widlcard answer.
+        if(is_a($expr,'qtype_algebra_parser_nullterm')) {
+            return false;
+        }
+		
+		// Now parse the answer
+        $ansexpr=$this->parse_expression($answer->answer);
+		// The type of comparison done depends on the comparision algorithm selected by
+		// the question. Use the defined algorithm to select which comparison function
+		// to call...
+		if($this->compareby == 'sage') {
+			// Uses an XML-RPC server with SAGE to perform a full symbollic comparision
+			return self::test_response_by_sage($expr,$ansexpr);
+		} else if($this->compareby == 'eval') {
+			// Tests the response by evaluating it for a certain range of each variable
+			return self::test_response_by_evaluation($expr,$ansexpr);
+		} else {
+			// Tests the response by performing a simple parse tree equivalence algorithm
+			return self::test_response_by_equivalence($expr,$ansexpr);
+		}
+    }
+	
+    /**
+	 * Checks whether a response matches a given answer using SAGE
+	 *
+	 * This method will compare the given response to the given answer using the SAGE
+	 * open source algebra computation software. The software is run by a remote
+	 * XML-RPC server which is called with both the asnwer and the response and told to
+	 * compare the two algebraic expressions.
+	 *
+	 * @return boolean true if the response matches the answer, false otherwise
+	 */
+	function test_response_by_sage($response, $answer) {
+		// TODO: Store server information in the Moodle configuration
+		$request=array(
+					   'host'   => 'localhost',
+					   'port'   => 7777,
+					   'uri'    => ''
+		);
+		// Sets the name of the method to call to full_symbolic_compare
+		$request['method']='full_symbolic_compare';
+        // Get a list of all the variables to declare
+        $vars=$response->get_variables();
+        $vars=array_merge($vars,array_diff($vars,$answer->get_variables()));
+		// Sets the arguments to the sage string of the response and the list of variables
+		$request['args']=array($answer->sage(),$response->sage(),$vars);
+		// Calls the XML-RPC method on the server and returns the response
+		return xu_rpc_http_concise($request)==0;
+    }
+	
+    /**
+	 * Checks whether a response matches a given answer using an evaluation method
+	 *
+	 * This method will compare the given response to the given answer by evaluating both
+	 * for given values of the variables. Each variable must have a predefined range over
+	 * which it can be checked and then both expressions will be evalutated several times
+	 * using values randomly chosen to be within the range.
+	 *
+	 * @return boolean true if the response matches the answer, false otherwise
+	 */
+	function test_response_by_evaluation($response, $answer) {
+        // Flag used to denote mismatch in response and answer
+        $same=true;
+        // Run the evaluation loop 10 times with different random variables...
+        for($i=0;$i<$this->nchecks;$i++) {
+            // Create an array to store the values of all the variables
+            $values=array();
+            // Loop over all the variables in the question
+            foreach($this->variables as $var) {
+                // Set the value of the variable to a random number between the min and max
+                $values[$var->name]=$var->min+lcg_value()*abs($var->max-$var->min);
+            }
+            $resp_value=$response->evaluate($values);
+            $ans_value=$answer->evaluate($values);
+            // Return false if only one of the reponse or answer gives NaN
+            if(is_nan($resp_value) xor is_nan($ans_value)) {
+                return false;
+            }
+            // Return false if only one of the reponse or answer is infinite
+            if(is_infinite($resp_value) xor is_infinite($ans_value)) {
+                return false;
+            }
+            // Use the fractional difference method if the answer has a value
+            // which is clearly distinguishable from zero
+            if(abs($ans_value)>1e-300) {
+                // Get the difference between the response and answer evaluations
+                $diff=abs(($resp_value-$ans_value)/$ans_value);
+            }
+            // Otherwise use an arbitrary minimum value
+            else {
+                // Get the difference between the response and answer evaluations
+                $diff=abs(($resp_value-$ans_value)/1e-300);
+            }
+            // Check to see if the difference is greater than tolerance
+            if($diff > $this->tolerance) {
+                // Return false since the formulae have been shown not to be the same
+                return false;
+            }
+        }
+        // We made it through the loop so now return true
+		return true;
+	}
+
+    /**
+	 * Checks whether a response matches a given answer using a simple equivalence algorithm
+	 *
+	 * This method will compare the given response to the given answer by simply checking to
+	 * see if the two parse trees are equivalent. This allows for a slightly more sophisticated
+	 * check than a simple text compare but will not, neccessarily, catch two equivalent but
+	 * different algebraic expressions.
+	 *
+	 * @return boolean true if the response matches the answer, false otherwise
+	 */
+	public function test_response_by_equivalence($response, $answer) {
+		// Use the parser's equivalent method to see if the response is the same as the answer
+		return $response->equivalent($answer);
+	}
+
+    public function check_file_access($qa, $options, $component, $filearea,
+            $args, $forcedownload) {
+        if ($component == 'question' && $filearea == 'answerfeedback') {
+            $currentanswer = $qa->get_last_qt_var('answer');
+            $answer = $qa->get_question()->get_matching_answer(array('answer' => $currentanswer));
+            $answerid = reset($args); // itemid is answer id.
+            return $options->feedback && $answerid == $answer->id;
+
+        } else if ($component == 'question' && $filearea == 'hint') {
+            return $this->check_hint_file_access($qa, $options, $args);
+
+        } else {
+            return parent::check_file_access($qa, $options, $component, $filearea,
+                    $args, $forcedownload);
+        }
+    }
+}
+
+/**
+ * Class to represent an algebra question variable, loaded from the question_algebra_variables table
+ * in the database.
+ *
+ * @copyright  2009 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class question_variable {
+    /** @var integer the answer id. */
+    public $id;
+
+    /** @var string the name. */
+    public $name;
+
+    /** @var string minimum value. */
+    public $min = '-';
+
+    /** @var string maximum value. */
+    public $max = '-';
+
+    /**
+     * Constructor.
+     * @param int $id the variable.
+     * @param string $name the name.
+     * @param string $min the minimum value.
+     * @param string $maximum value.
+     */
+    public function __construct($id, $name, $min, $max) {
+        $this->id = $id;
+        $this->name = $name;
+        $this->min = $min;
+        $this->max = $max;
+    }
+}
diff --git a/questiontype.php b/questiontype.php
new file mode 100644
index 0000000..dfd9f3b
--- /dev/null
+++ b/questiontype.php
@@ -0,0 +1,551 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Question type class for the algebra question type.
+ *
+ * @package    qtype
+ * @subpackage algebra
+ * @author  Roger Moore <rwmoore 'at' ualberta.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/question/type/algebra/question.php');
+require_once($CFG->dirroot . '/question/type/algebra/parser.php');
+require_once($CFG->dirroot . '/question/type/algebra/xmlrpc-utils.php');
+
+/**
+ * ALGEBRA QUESTION TYPE CLASS
+ *
+ * @package questionbank
+ * @subpackage questiontypes
+ */
+
+class qtype_algebra extends question_type {
+
+    /**
+     * Defines the table which extends the question table. This allows the base questiontype
+     * to automatically save, backup and restore the extra fields.
+     *
+     * @return an array with the table name (first) and then the column names (apart from id and questionid)
+     */
+    public function extra_question_fields() {
+        return array('question_algebra',
+					 'compareby',        // Name of comparison algorithm to use
+                     'nchecks',          // Number of evaluate checks to make when comparing by evaluation
+                     'tolerance',        // Max. fractional difference allowed for evaluation checks
+                     'allowedfuncs',     // Comma separated list of functions allowed in responses
+                     'disallow',         // Response which may be correct but which is not allowed
+                     'answerprefix'      // String which is placed in front of the asnwer box
+                     );
+    }
+
+	public function questionid_column_name() {
+        return 'questionid';
+    }
+
+	public function move_files($questionid, $oldcontextid, $newcontextid) {
+        parent::move_files($questionid, $oldcontextid, $newcontextid);
+        $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid);
+    }
+
+    protected function delete_files($questionid, $contextid) {
+        parent::delete_files($questionid, $contextid);
+        $this->delete_files_in_answers($questionid, $contextid);
+    }
+	
+	public function delete_question($questionid, $contextid) {
+        global $DB;
+        $DB->delete_records('question_algebra', array('questionid' => $questionid));
+        $DB->delete_records('question_algebra_variables', array('question' => $questionid));
+
+        parent::delete_question($questionid, $contextid);
+    }
+
+	/**
+	 * Saves the questions variables to the database
+	 *
+	 * This is called by {@link save_question_options()} to save the variables of the question to
+	 * the database from the data in the submitted form. The method returns an array of the variables
+	 * IDs written to the database or, in the event of an error, throws an exception.
+	 *
+	 * @param object $question  This holds the information from the editing form,
+	 *                          it is not a standard question object.
+	 * @return array of variable object IDs
+	 */
+    public function save_question_variables($question) {
+		global $DB;
+		// Create the results class
+        $result = new stdClass;
+		// Get all the old answers from the database as an array
+        if (!$oldvars = $DB->get_records('question_algebra_variables', array('question' => $question->id), 'id ASC')) {
+            $oldvars = array();
+        }
+		// Create an array of the variable IDs for the question
+        $variables = array();
+		
+        // Loop over all the answers in the question form and write them to the database
+        foreach ($question->variable as $key => $varname) {
+			// Check to see that there is a variable and skip any which are empty
+            if ($varname == '') {
+                continue;
+            }
+            
+            // Get the old variable from the array and overwrite what is required, if there 
+            // is no old variable then we skip to the 'else' clause
+            if ($oldvar = array_shift($oldvars)) {  // Existing variable, so reuse it
+                $var = $oldvar;
+                $var->name = trim($varname);
+                $var->min  = trim($question->varmin[$key]);
+                $var->max  = trim($question->varmax[$key]);
+                // Update the record in the database to denote this change.
+                if (!$DB->update_record('question_algebra_variables', $var)) {
+                    throw new Exception("Could not update algebra question variable (id=$var->id)");
+                }
+            }
+            // This is a completely new variable so we have to create a new record
+            else {
+                $var = new stdClass;
+                $var->name     = trim($varname);
+                $var->question = $question->id;
+                $var->min      = trim($question->varmin[$key]);
+                $var->max      = trim($question->varmax[$key]);
+                // Insert a new record into the database table
+                if (!$var->id = $DB->insert_record('question_algebra_variables', $var)) {
+                    throw new Exception("Could not insert algebra question variable '$varname'!");
+                }
+            }
+            // Add the variable ID to the array of IDs
+            $variables[] = $var->id;
+        }   // end loop over variables
+
+		// Delete any left over old variables records.
+        foreach ($oldvars as $oldvar) {
+            $DB->delete_records('question_algebra_variables', array('id' => $oldvar->id));
+        }		
+		// Finally we are all done so return the result!
+		return $variables;
+	}
+	
+	/**
+	 * Saves the questions answers to the database
+	 *
+	 * This is called by {@link save_question_options()} to save the answers to the question to
+	 * the database from the data in the submitted form. This method should probably be in the 
+	 * questin base class rather than in the algebra subclass since the code is common to multiple
+	 * question types and originally comes from the shortanswer question type. The method returns
+     * a list of the answer ID written to the database or throws an exception if an error is detected.
+	 *
+	 * @param object $question  This holds the information from the editing form,
+	 *                          it is not a standard question object.
+	 * @return array of answer IDs which were written to the database
+	 */
+    public function save_question_answers($question) {
+		global $CFG, $DB;
+		
+		$context = $question->context;
+		// Create the results class
+        $result = new stdClass;
+		
+		// Get all the old answers from the database as an array
+        if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { //'question', $question->'id ASC')) {
+           $oldanswers = array();
+        }
+		// Create an array of the answer IDs for the question
+        $answers = array();
+		// Set the maximum answer fraction to be -1. We will check this at the end of our
+		// loop over the questions and if it is not 100% (=1.0) then we will flag an error
+        $maxfraction = -1;
+		
+        // Loop over all the answers in the question form and write them to the database
+        foreach ($question->answer as $key => $answerdata) {
+            // Check for, and ignore, completely blank answer from the form.
+            if (trim($answerdata) == '' && $question->fraction[$key] == 0 &&
+                    html_is_blank($question->feedback[$key]['text'])) {
+                continue;
+            }
+			// Update an existing answer if possible.
+            $answer = array_shift($oldanswers);
+            if (!$answer) {
+                $answer = new stdClass();
+                $answer->question = $question->id;
+                $answer->answer = '';
+                $answer->feedback = '';
+				$answer->feedbackformat = FORMAT_HTML;
+				if (!$answer->id = $DB->insert_record('question_answers', $answer)) {
+					throw new Exception("Could not create new algebra question answer");
+                }
+            }
+
+            $answer->answer   = trim($answerdata);
+            $answer->fraction = $question->fraction[$key];
+            $answer->feedback = $this->import_or_save_files($question->feedback[$key],
+                    $context, 'question', 'answerfeedback', $answer->id);
+			$answer->feedbackformat = $question->feedback[$key]['format'];
+			if (!$DB->update_record('question_answers', $answer)) {
+                    throw new Exception("Could not update algebra question answer (id=$answer->id)");
+            }
+            
+
+            $answers[] = $answer->id;
+            if ($question->fraction[$key] > $maxfraction) {
+                $maxfraction = $question->fraction[$key];
+            }
+        }     // end loop over answers
+		
+		// Perform sanity check on the maximum fractional grade which should be 100%
+        if ($maxfraction != 1) {
+            $maxfraction = $maxfraction * 100;
+            throw new Exception(get_string('fractionsnomax', 'quiz', $maxfraction));
+        }
+		
+		// Delete any left over old answer records.
+        $fs = get_file_storage();
+        foreach ($oldanswers as $oldanswer) {
+            $fs->delete_area_files($context->id, 'question', 'answerfeedback', $oldanswer->id);
+            $DB->delete_records('question_answers', array('id' => $oldanswer->id));
+        }
+
+		// Finally we are all done so return the result!
+		return $answers;
+	}
+	
+    /**
+	 * Saves question-type specific options
+	 *
+	 * This is called by {@link save_question()} to save the question-type specific data from a
+	 * submitted form. This method takes the form data and formats into the correct format for
+	 * writing to the database. It then calls the parent method to actually write the data.
+	 *
+	 * @param object $question  This holds the information from the editing form,
+	 *                          it is not a standard question object.
+	 * @return object $result->error or $result->noticeyesno or $result->notice
+	 */
+    public function save_question_options($question) {
+		// Start a try block to catch any exceptions generated when we attempt to parse and
+        // then add the answers and variables to the database
+        try {
+            // First write out all the variables associated with the question
+            $variables=$this->save_question_variables($question);            
+			
+            // Loop over all the answers in the question form and parse them to generate
+            // a parser string. This ensures a constant formatting is stored in the database
+            foreach ($question->answer as &$answer) {
+                $expr=$this->parse_expression($answer);
+                // TODO detect invalid answer and issue a warning
+                $answer=$expr->sage();
+            }
+
+            // Now we need to write out all the answers to the question to the database
+            $answers=$this->save_question_answers($question);
+
+        } catch (Exception $e) {
+            // Error when adding answers or variables to the database so create a result class
+            // and put the error string in the error member funtion and then return the class
+            // This keeps us compatible with the existing save_question_options methods.
+            $result=new stdClass;
+            $result->error=$e->getMessage();
+            return $result;
+        }
+		
+        // Process the allowed functions field. This code just sets up the variable, it is saved
+        // in the parent class' save_question_options method called at the end of this method
+        // Look for the 'all' option. If we find it then set the string to an empty value
+        if(array_key_exists('all',$question->allowedfuncs)) {
+            $question->allowedfuncs='';
+        }
+        // Not all functions are allowed so set allowed functions to those which are
+        else {
+            // Create a comma separated string of the function names which are stored in the
+            // keys of the array
+            $question->allowedfuncs=implode(',',array_keys($question->allowedfuncs));
+        }
+		
+		// Call the parent method to write the extensions fields to the database. This either returns null
+		// or an error object so if we get anything then return it otherwise return our existing
+        $parentresult = parent::save_question_options($question);
+        if ($parentresult !== null) {
+            // Parent function returns null if all is OK
+            return $parentresult;
+        }
+		// Otherwise just return true - this mimics the shortanswer return format
+		else {
+			return true;
+		}
+	}
+	
+    /**
+	 * Loads the question type specific options for the question.
+	 *
+	 * This function loads the compare algorithm type, disallowed strings and variables
+	 * into the class from the database table in which they are stored. It first uses the
+	 * parent class method to get the database information.
+	 *
+	 * @param object $question The question object for the question. This object
+	 *                         should be updated to include the question type
+	 *                         specific information (it is passed by reference).
+	 * @return bool            Indicates success or failure.
+	 */
+    public function get_question_options($question) {
+		// Get the information from the database table. If this fails then immediately bail.
+        // Note unlike the save_question_options base class method this method DOES get the question's
+        // answers along with any answer extensions
+        global $DB;
+		if(!parent::get_question_options($question)) {
+			return false;
+		}
+		// Check that we have answers and if not then bail since this question type requires answers
+		if(count($question->options->answers)==0) {
+			notify('Failed to load question answers from the table question_answers for questionid ' .
+				   $question->id);
+			return false;
+		}
+        // Now get the variables from the database as well
+        $question->options->variables = $DB->get_records('question_algebra_variables', array('question' => $question->id));
+			
+			
+			
+			//, 'id ASC');
+		// Check that we have variables and if not then bail since this question type requires variables
+		
+		if(count($question->options->variables)==0) {
+			notify('Failed to load question variables from the table question_algebra_variables '.
+                   "for questionid $question->id");
+			return false;
+		}
+		
+        
+        // Check to see if there are any allowed functions
+        if($question->options->allowedfuncs!='') {
+            // Extract the allowed functions as an array
+            $question->options->allowedfuncs=explode(',',$question->options->allowedfuncs);
+        }
+        // Otherwise just create an empty array
+        else {
+            $question->options->allowedfuncs=array();
+        }
+        
+        // Everything worked so return true
+        return true;
+	}
+
+    /**
+     * Imports the question from Moodle XML format.
+     *
+     * This method is called by the format class when importing an algebra question from the 
+     * Moodle XML format.
+     *
+     * @param $data structure containing the XML data
+     * @param $question question object to fill: ignored by this function (assumed to be null)
+     * @param $format format class importing the question
+     * @param $extra extra information (not required for importing this question in this format)
+     * @return text string containing the question data in XML format
+     */
+	public function import_from_xml($data, $question, qformat_xml $format, $extra=null) {
+	    if (!array_key_exists('@', $data)) {
+            return false;
+        }
+        if (!array_key_exists('type', $data['@'])) {
+            return false;
+        }
+        if ($data['@']['type'] == 'algebra') {
+			// Import the common question headers
+			$qo = $format->import_headers($data);
+			// Set the question type
+			$qo->qtype='algebra';
+        
+			$qo->compareby = $format->getpath($data, array('#','compareby',0,'#'),'eval');
+			$qo->tolerance = $format->getpath($data, array('#','tolerance',0,'#'),'0');
+			$qo->nchecks   = $format->getpath($data, array('#','nchecks',0,'#'),'10');
+			$qo->disallow  = $format->getpath($data, array('#','disallow',0,'#','text',0,'#'),'',true);
+			$allowedfuncs  = $format->getpath($data, array('#','allowedfuncs',0,'#'), '');
+			if($allowedfuncs=='') {
+				$qo->allowedfuncs=array('all' => 1);
+			}
+			// Need to separate the allowed functions into an array of strings and then
+			// flip the values of this array into the keys because this is what the
+            // save options method requires
+            else {
+                $qo->allowedfuncs=array_flip(explode(',',$allowedfuncs));
+            }
+            $qo->answerprefix = $format->getpath($data, array('#','answerprefix',0,'#','text',0,'#'),'',true);
+        
+            // Import all the answers
+            $answers = $data['#']['answer'];
+            $a_count = 0;
+            // Loop over each answer block found in the XML
+            foreach($answers as $answer) {
+                // Use the common answer import function in the format class to load the data
+                $ans = $format->import_answer($answer);
+                $qo->answer[$a_count] = $ans->answer['text'];
+                $qo->fraction[$a_count] = $ans->fraction;
+                $qo->feedback[$a_count] = $ans->feedback;
+                ++$a_count;
+            }
+        
+            // Import all the variables
+            $vars = $data['#']['variable'];  
+            $v_count = 0;
+            // Loop over each answer block found in the XML
+            foreach($vars as $var) {
+                $qo->variable[$v_count] = $format->getpath($var, array('@','name'),0);
+                $qo->varmin[$v_count]   = $format->getpath($var, array('#','min',0,'#'),'0',false,get_string('novarmin','qtype_algebra'));
+                $qo->varmax[$v_count]   = $format->getpath($var, array('#','max',0,'#'),'0',false,get_string('novarmax','qtype_algebra'));
+                ++$v_count;
+            }
+
+			$format->import_hints($qo, $data);
+
+            return $qo;
+		}
+		return false;
+    }
+    
+    
+    /**
+     * Exports the question to Moodle XML format.
+     *
+     * This method is called by the format class when exporting an algebra question into then
+     * Moodle XML format.
+     *
+     * @param $question question to be exported into XML format
+     * @param $format format class exporting the question
+     * @param $extra extra information (not required for exporting this question in this format)
+     * @return text string containing the question data in XML format
+     */
+	public function export_to_xml($question, qformat_xml $format, $extra=null) {
+        $expout='';
+        // Create a text string of the allowed functions from the array
+        $allowedfuncs=implode(',',$question->options->allowedfuncs);
+        // Write out all the extra fields belonging to the algebra question type
+        $expout .= "    <compareby>{$question->options->compareby}</compareby>\n";
+        $expout .= "    <tolerance>{$question->options->tolerance}</tolerance>\n";
+        $expout .= "    <nchecks>{$question->options->nchecks}</nchecks>\n";
+        $expout .= "    <disallow>".$format->writetext($question->options->disallow,1,true)."</disallow>\n";
+        $expout .= "    <allowedfuncs>$allowedfuncs</allowedfuncs>\n";
+        $expout .= "    <answerprefix>".$format->writetext($question->options->answerprefix,1,true).
+            "</answerprefix>\n";
+        // Write out all the answers
+		$expout .= $format->write_answers($question->options->answers);
+        // Loop over all the variables for the question and write out all their details
+        foreach ($question->options->variables as $var) {
+            $expout .= "<variable name=\"{$var->name}\">\n";
+            $expout .= "    <min>{$var->min}</min>\n";
+            $expout .= "    <max>{$var->max}</max>\n";
+            $expout .= "</variable>\n";
+        }
+        return $expout;
+    }
+	
+    // Gets all the question responses
+    public function get_all_responses(&$question, &$state) {
+        $result = new stdClass;
+        $answers = array();
+		// Loop over all the answers
+        if (is_array($question->options->answers)) {
+            foreach ($question->options->answers as $aid=>$answer) {
+                $r = new stdClass;
+                $r->answer = $answer->answer;
+                $r->credit = $answer->fraction;
+                $answers[$aid] = $r;
+            }
+        }
+        $result->id = $question->id;
+        $result->responses = $answers;
+        return $result;
+    }
+
+	/**
+	  * 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) {
+        // 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 empty array of variable names for the parser (no variable checking here as it is done in the form validation
+		// TODO see in case of import
+        $varnames=array();
+
+		// 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;
+		}
+	}
+	
+	protected function initialise_question_instance(question_definition $question, $questiondata) {
+        parent::initialise_question_instance($question, $questiondata);
+		$question->variables = array();
+        if (!empty($questiondata->options->variables)) {
+			foreach ($questiondata->options->variables as $v) {
+				$question->variables[$v->id] = new question_variable($v->id, $v->name, $v->min, $v->max);
+            }
+        }
+		$question->compareby = $questiondata->options->compareby;
+		$question->nchecks = $questiondata->options->nchecks;
+		$question->tolerance = $questiondata->options->tolerance;
+		$question->allowedfuncs = $questiondata->options->allowedfuncs;
+		$question->disallow = $questiondata->options->disallow;
+		$question->answerprefix = $questiondata->options->answerprefix;
+        $this->initialise_question_answers($question, $questiondata);
+    }
+
+    public function get_random_guess_score($questiondata) {
+        foreach ($questiondata->options->answers as $aid => $answer) {
+            if ('*' == trim($answer->answer)) {
+                return $answer->fraction;
+            }
+        }
+        return 0;
+    }
+
+    public function get_possible_responses($questiondata) {
+        $responses = array();
+
+        foreach ($questiondata->options->answers as $aid => $answer) {
+            $responses[$aid] = new question_possible_response($answer->answer,
+                    $answer->fraction);
+        }
+        $responses[null] = question_possible_response::no_response();
+
+        return array($questiondata->id => $responses);
+    }
+}
\ No newline at end of file
diff --git a/renderer.php b/renderer.php
new file mode 100644
index 0000000..4ffe674
--- /dev/null
+++ b/renderer.php
@@ -0,0 +1,162 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Algebra question renderer class.
+ *
+ * @package    qtype
+ * @subpackage algebra
+ * @author  Roger Moore <rwmoore 'at' ualberta.ca>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Generates the output for algebra questions.
+ *
+ * @copyright  2009 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_algebra_renderer extends qtype_renderer {
+    public function formulation_and_controls(question_attempt $qa,
+            question_display_options $options) {
+			global $CFG;
+
+        $question = $qa->get_question();
+
+        $currentanswer = $qa->get_last_qt_var('answer');
+
+        $inputname = $qa->get_qt_field_name('answer');
+
+		$nameprefix = str_replace(':', '_', $inputname); // valid javascript name
+        $inputattributes = array(
+            'type' => 'text',
+            'name' => $inputname,
+            'value' => $currentanswer,
+            'id' => $inputname,
+            'size' => 80,
+        );
+
+        if ($options->readonly) {
+            $inputattributes['readonly'] = 'readonly';
+        }
+
+        $feedbackimg = '';
+        if ($options->correctness) {
+            $answer = $question->get_matching_answer(array('answer' => $currentanswer));
+            if ($answer) {
+                $fraction = $answer->fraction;
+            } else {
+                $fraction = 0;
+            }
+            $inputattributes['class'] = $this->feedback_class($fraction);
+            $feedbackimg = $this->feedback_image($fraction);
+        }
+
+		$iframename = $nameprefix.'_if';
+		// Name of the javascript function which causes the entered formula to be rendered
+		$df_name = $nameprefix.'_display';
+        // Create an array of variable names to use when displaying the function entered
+        $varnames=array();
+        if($question and isset($question->variables)) {
+			$variables = $question->variables;
+            foreach($question->variables as $var) {
+                $varnames[]=$var->name;
+            }
+        }
+
+        $varnames=implode(',',$varnames);
+		// Javascript function which the button uses to display the rendering
+		// This function sents the source of the iframe to the 'displayformula.php' script giving
+		// it an argument of the formula entered by the student.
+		$displayfunction =
+			'function '.$df_name."() {\n".
+            '    var text="vars='.$varnames.'&expr="+escape(document.getElementsByName("'.$inputname.'")[0].value);'."\n".
+			"    if(text.length != 0) {\n".
+		    '      document.getElementsByName("'.$iframename.'")[0].src="'.
+			$CFG->wwwroot.'/question/type/algebra/displayformula.php?"+'.
+			'text.replace(/\+/g,"%2b")'."\n".
+			"    }\n".
+			"  }\n";
+
+        $questiontext = $question->format_questiontext($qa);
+
+        $input = html_writer::empty_tag('input', $inputattributes) . $feedbackimg;
+
+
+        $result = html_writer::tag('div', $questiontext, array('class' => 'qtext'));
+
+        $result .= html_writer::tag('script', $displayfunction, array('type'=>'text/javascript'));
+		
+        $result .= html_writer::start_tag('div', array('class' => 'ablock'));
+		$result .= html_writer::start_tag('div', array('class' => 'prompt', 'style' => 'vertical-align: top'));
+		if(isset($question->answerprefix) and !empty($question->answerprefix)) {
+              $opts=new StdClass;
+              $opts->para=false;
+			  $result .= html_writer::tag('div', format_text($question->answerprefix,FORMAT_MOODLE,$opts).$input, array('class' => 'answer'));
+        } else {
+            $result .= get_string('answer', 'qtype_algebra',
+				html_writer::tag('div', $input, array('class' => 'answer')));
+        }
+		$result .= html_writer::end_tag('div');
+        
+        $result .= html_writer::end_tag('div');
+
+
+        if ($qa->get_state() == question_state::$invalid) {
+            $result .= html_writer::nonempty_tag('div',
+                    $question->get_validation_error(array('answer' => $currentanswer)),
+                    array('class' => 'validationerror'));
+        }
+		$result .= html_writer::start_tag('div', array('class' => 'dispresponse'));
+		$result .= html_writer::empty_tag('input', array('type'=>'button', 'value'=>'Display Response', 'onclick'=>$df_name.'()'));
+		$result .= html_writer::start_tag('iframe', array('name'=>$iframename, 'width'=>'60%', 'height'=>60, 'align'=>'middle', 'src'=>''));
+		$result .= html_writer::end_tag('iframe');
+		$result .= html_writer::tag('script', $df_name.'();', array('type'=>'text/javascript'));
+		$result .= html_writer::end_tag('div');
+
+        return $result;
+    }
+
+    public function specific_feedback(question_attempt $qa) {
+        $question = $qa->get_question();
+
+        $answer = $question->get_matching_answer(array('answer' => $qa->get_last_qt_var('answer')));
+        if (!$answer || !$answer->feedback) {
+            return '';
+        }
+
+        return $question->format_text($answer->feedback, $answer->feedbackformat,
+                $qa, 'question', 'answerfeedback', $answer->id);
+    }
+
+    public function correct_response(question_attempt $qa) {
+        $question = $qa->get_question();
+
+        $answer = $question->get_matching_answer($question->get_correct_response());
+        if (!$answer) {
+            return '';
+        }
+        $formatoptions = new stdClass;
+        $formatoptions->para = false;
+        $formatoptions->clean = false;
+        $formattedanswer = format_text($question->formated_expression($answer->answer), FORMAT_MOODLE, $formatoptions);
+        return get_string('correctansweris', 'qtype_algebra', s($answer->answer)).  $formattedanswer;
+    }
+}
\ No newline at end of file
diff --git a/sage_server.py b/sage_server.py
new file mode 100644
index 0000000..8b41bf7
--- /dev/null
+++ b/sage_server.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env sage -python
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer
+
+from sage.all import Sage
+
+s=Sage()
+
+server=SimpleXMLRPCServer(("localhost",7777))
+server.register_introspection_functions()
+
+def full_symbolic_compare(expr1,expr2,vars):
+    varstr=",".join(vars)
+    print "Comparing %s to %s with variables %s" % (expr1,expr2,varstr)
+    s.eval('%s=var("%s")' % (varstr,varstr))
+    s.eval("_func=(%s)-(%s)" % (expr1,expr2))
+    result=s.eval("_func.simplify_full()")
+    if result=='0':
+        print "Equal"
+        return 0
+    else:
+        print "Not equal"
+        return 1
+
+server.register_function(full_symbolic_compare)
+
+print full_symbolic_compare('x^2+2*x+1', 'x^2+2*x+1', ['x'])
+
+server.serve_forever()
diff --git a/settings.php b/settings.php
new file mode 100644
index 0000000..a0f5423
--- /dev/null
+++ b/settings.php
@@ -0,0 +1,11 @@
+<?php
+defined('MOODLE_INTERNAL') || die;
+
+if ($ADMIN->fulltree) {
+    //host
+    $settings->add(new admin_setting_configtext('qtype_algebra_host', get_string('host', 'qtype_algebra'), '', 'localhost', PARAM_TEXT));
+    //port
+    $settings->add(new admin_setting_configtext('qtype_algebra_port', get_string('port', 'qtype_algebra'), '', 7777, PARAM_INT));
+	 //host
+    $settings->add(new admin_setting_configtext('qtype_algebra_uri', get_string('uri', 'qtype_algebra'), '', '', PARAM_TEXT));
+}	
diff --git a/version.php b/version.php
new file mode 100644
index 0000000..81af712
--- /dev/null
+++ b/version.php
@@ -0,0 +1,5 @@
+<?php
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version  = 2011072800;
+$plugin->requires = 2011060313;
diff --git a/xmlrpc-utils.php b/xmlrpc-utils.php
new file mode 100644
index 0000000..a671d75
--- /dev/null
+++ b/xmlrpc-utils.php
@@ -0,0 +1,269 @@
+<?php
+
+/*
+  This file is part of, or distributed with, libXMLRPC - a C library for 
+  xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2001 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+/* xmlrpc utilities (xu) 
+ * author: Dan Libby (dan@libby.com)
+ */
+
+// ensure extension is loaded.
+xu_load_extension();
+
+// a function to ensure the xmlrpc extension is loaded.
+// xmlrpc_epi_dir = directory where libxmlrpc.so.0 is located
+// xmlrpc_php_dir = directory where xmlrpc-epi-php.so is located
+function xu_load_extension($xmlrpc_php_dir="") {
+   $bSuccess=false;
+   if(!extension_loaded('xmlrpc')) {
+      $bSuccess = true;
+      putenv("LD_LIBRARY_PATH=/usr/lib/php4/apache/xmlrpc/");
+      if ($xmlrpc_php_dir) {
+         $xmlrpc_php_dir .= '/';
+      }
+      if (!extension_loaded("xmlrpc")) {
+         $bSuccess = dl($xmlrpc_php_dir . "xmlrpc-epi-php.so");
+      }
+   }
+   return $bSuccess;
+}
+
+/* generic function to call an http server with post method */
+function xu_query_http_post($request, $host, $uri, $port, $debug, 
+                            $timeout, $user, $pass, $secure=false) {
+   $response_buf = "";
+   if ($host && $uri && $port) {
+      $content_len = strlen($request);
+
+      $fsockopen = $secure ? "fsockopen_ssl" : "fsockopen";
+
+      dbg1("opening socket to host: $host, port: $port, uri: $uri", $debug);
+      $query_fd = $fsockopen($host, $port, $errno, $errstr, 10);
+
+      if ($query_fd) {
+
+         $auth = "";
+         if ($user) {
+            $auth = "Authorization: Basic " .
+                    base64_encode($user . ":" . $pass) . "\r\n";
+         }
+
+         $http_request = 
+         "POST $uri HTTP/1.0\r\n" .
+         "User-Agent: xmlrpc-epi-php/0.2 (PHP)\r\n" .
+         "Host: $host:$port\r\n" .
+         $auth .
+         "Content-Type: text/xml\r\n" .
+         "Content-Length: $content_len\r\n" . 
+         "\r\n" .
+         $request;
+
+         dbg1("sending http request:</h3> <xmp>\n$http_request\n</xmp>", $debug);
+
+         fputs($query_fd, $http_request, strlen($http_request));
+
+         dbg1("receiving response...", $debug);
+
+         $header_parsed = false;
+
+         $line = fgets($query_fd, 4096);
+         while ($line) {
+            if (!$header_parsed) {
+               if ($line === "\r\n" || $line === "\n") {
+                  $header_parsed = 1;
+               }
+               dbg2("got header - $line", $debug);
+            }
+            else {
+               $response_buf .= $line;
+            }
+            $line = fgets($query_fd, 4096);
+         }
+
+         fclose($query_fd);
+      }
+      else {
+         dbg1("socket open failed", $debug);
+      }
+   }
+   else {
+      dbg1("missing param(s)", $debug);
+   }
+
+   dbg1("got response:</h3>. <xmp>\n$response_buf\n</xmp>\n", $debug);
+
+   return $response_buf;
+}
+
+function xu_fault_code($code, $string) {
+   return array('faultCode' => $code,
+                'faultString' => $string);
+}
+
+
+function find_and_decode_xml($buf, $debug) {
+   if (strlen($buf)) {
+      $xml_begin = substr($buf, strpos($buf, "<?xml"));
+      if (strlen($xml_begin)) {
+
+         $retval = xmlrpc_decode($xml_begin);
+      }
+      else {
+         dbg1("xml start token not found", $debug);
+      }
+   }
+   else {
+      dbg1("no data", $debug);
+   }
+   return $retval;
+}
+
+ 
+/**
+ * @param params   a struct containing 3 or more of these key/val pairs:
+ * @param host		 remote host (required)
+ * @param uri		 remote uri	 (required)
+ * @param port		 remote port (required)
+ * @param method   name of method to call
+ * @param args	    arguments to send (parameters to remote xmlrpc server)
+ * @param debug	 debug level (0 none, 1, some, 2 more)
+ * @param timeout	 timeout in secs.  (0 = never)
+ * @param user		 user name for authentication.  
+ * @param pass		 password for authentication
+ * @param secure	 secure. wether to use fsockopen_ssl. (requires special php build).
+ * @param output	 array. xml output options. can be null.  details below:
+ *
+ *     output_type: return data as either php native data types or xml
+ *                  encoded. ifphp is used, then the other values are ignored. default = xml
+ *     verbosity:   determine compactness of generated xml. options are
+ *                  no_white_space, newlines_only, and pretty. default = pretty
+ *     escaping:    determine how/whether to escape certain characters. 1 or
+ *                  more values are allowed. If multiple, they need to be specified as
+ *                  a sub-array. options are: cdata, non-ascii, non-print, and
+ *                  markup. default = non-ascii | non-print | markup
+ *     version:     version of xml vocabulary to use. currently, three are
+ *                  supported: xmlrpc, soap 1.1, and simple. The keyword auto is also
+ *                  recognized to mean respond in whichever version the request came
+ *                  in. default = auto (when applicable), xmlrpc
+ *     encoding:    the encoding that the data is in. Since PHP defaults to
+ *                  iso-8859-1 you will usually want to use that. Change it if you know
+ *                  what you are doing. default=iso-8859-1
+ *
+ *   example usage
+ *
+ *                   $output_options = array('output_type' => 'xml',
+ *                                           'verbosity' => 'pretty',
+ *                                           'escaping' => array('markup', 'non-ascii', 'non-print'),
+ *                                           'version' => 'xmlrpc',
+ *                                           'encoding' => 'utf-8'
+ *                                         );
+ *                   or
+ *
+ *                   $output_options = array('output_type' => 'php');
+ */
+function xu_rpc_http_concise($params) {
+   $host = $uri = $port = $method = $args = $debug = null;
+   $timeout = $user = $pass = $secure = $debug = null;
+
+	extract($params);
+
+	// default values
+	if(!$port) {
+		$port = 80;
+	}
+	if(!$uri) {
+		$uri = '/';
+	}
+	if(!isset($output)) {
+		$output = array('version' => 'xmlrpc');
+	}
+
+   $response_buf = "";
+   if ($host && $uri && $port) {
+      $request_xml = xmlrpc_encode_request($method, $args, $output);
+      $response_buf = xu_query_http_post($request_xml, $host, $uri, $port, $debug,
+                                         $timeout, $user, $pass, $secure);
+
+      $retval = find_and_decode_xml($response_buf, $debug);
+   }
+   return $retval;
+}
+
+/* call an xmlrpc method on a remote http server. legacy support. */
+function xu_rpc_http($method, $args, $host, $uri="/", $port=80, $debug=false, 
+                     $timeout=0, $user=false, $pass=false, $secure=false) {
+	return xu_rpc_http_concise(
+		array(
+			method  => $method,
+			args    => $args,
+			host    => $host,
+			uri     => $uri,
+			port    => $port,
+			debug   => $debug,
+			timeout => $timeout,
+			user    => $user,
+			pass    => $pass,
+			secure  => $secure
+		));
+}
+
+
+
+function xu_is_fault($arg) {
+   // xmlrpc extension finally supports this.
+   return is_array($arg) ? xmlrpc_is_fault($arg) : false;
+}
+
+/* sets some http headers and prints xml */
+function xu_server_send_http_response($xml) {
+    header("Content-type: text/xml");
+    header("Content-length: " . strlen($xml) );
+    echo $xml;
+}
+
+
+function dbg($msg) {
+   echo "<h3>$msg</h3>"; flush();
+}
+function dbg1($msg, $debug_level) {
+   if ($debug_level >= 1) {
+      dbg($msg);
+   }
+}
+function dbg2($msg, $debug_level) {
+   if ($debug_level >= 2) {
+      dbg($msg);
+   }
+}
+
-- 
cgit v1.2.3