/** True if module has custom completion rules */
define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
+ /** True if module has no 'view' page (like label) */
+ define('FEATURE_NO_VIEW_LINK', 'viewlink');
/** True if module supports outcomes */
define('FEATURE_IDNUMBER', 'idnumber');
/** True if module supports groups */
}
case PARAM_TAG:
- //as long as magic_quotes_gpc is used, a backslash will be a
- //problem, so remove *all* backslash.
- //$param = str_replace('\\', '', $param);
- //remove some nasties
+ // Please note it is not safe to use the tag name directly anywhere,
+ // it must be processed with s(), urlencode() before embedding anywhere.
+ // remove some nasties
$param = preg_replace('~[[:cntrl:]]|[<>`]~u', '', $param);
//convert many whitespace chars into one
$param = preg_replace('/\s+/', ' ', $param);
$param = $textlib->substr(trim($param), 0, TAG_MAX_LENGTH);
return $param;
-
case PARAM_TAGLIST:
$tags = explode(',', $param);
$result = array();
if ($cm->course != $course->id) {
throw new coding_exception('course and cm parameters in require_login() call do not match!!');
}
+ // make sure we have a $cm from get_fast_modinfo as this contains activity access details
+ if (!($cm instanceof cm_info)) {
+ // note: nearly all pages call get_fast_modinfo anyway and it does not make any
+ // db queries so this is not really a performance concern, however it is obviously
+ // better if you use get_fast_modinfo to get the cm before calling this.
+ $modinfo = get_fast_modinfo($course);
+ $cm = $modinfo->get_cm($cm->id);
+ }
$PAGE->set_cm($cm, $course); // set's up global $COURSE
$PAGE->set_pagelayout('incourse');
} else {
}
}
- // test visibility
- if ($cm && !$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $cmcontext)) {
+ // Check visibility of activity to current user; includes visible flag, groupmembersonly,
+ // conditional availability, etc
+ if ($cm && !$cm->uservisible) {
if ($preventredirect) {
throw new require_login_exception('Activity is hidden');
}
redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
}
- // groupmembersonly access control
- if (!empty($CFG->enablegroupmembersonly) and $cm and $cm->groupmembersonly and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
- if (isguestuser() or !groups_has_membership($cm)) {
- if ($preventredirect) {
- throw new require_login_exception('Not member of a group');
- }
- print_error('groupmembersonlyerror', 'group', $CFG->wwwroot.'/course/view.php?id='.$cm->course);
- }
- }
-
- // Conditional activity access control
- if (!empty($CFG->enableavailability) and $cm) {
- // TODO: this is going to work with login-as-user, sorry!
- // We cache conditional access in session
- if (!isset($SESSION->conditionaccessok)) {
- $SESSION->conditionaccessok = array();
- }
- // If you have been allowed into the module once then you are allowed
- // in for rest of session, no need to do conditional checks
- if (!array_key_exists($cm->id, $SESSION->conditionaccessok)) {
- // Get condition info (does a query for the availability table)
- require_once($CFG->libdir.'/conditionlib.php');
- $ci = new condition_info($cm, CONDITION_MISSING_EXTRATABLE);
- // Check condition for user (this will do a query if the availability
- // information depends on grade or completion information)
- if ($ci->is_available($junk) || has_capability('moodle/course:viewhiddenactivities', $cmcontext)) {
- $SESSION->conditionaccessok[$cm->id] = true;
- } else {
- print_error('activityiscurrentlyhidden');
- }
- }
- }
-
// Finally access granted, update lastaccess times
user_accesstime_log($course->id);
}
*/
function require_course_login($courseorid, $autologinguest = true, $cm = NULL, $setwantsurltome = true, $preventredirect = false) {
global $CFG, $PAGE, $SITE;
+ $issite = (is_object($courseorid) and $courseorid->id == SITEID)
+ or (!is_object($courseorid) and $courseorid == SITEID);
+ if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
+ // note: nearly all pages call get_fast_modinfo anyway and it does not make any
+ // db queries so this is not really a performance concern, however it is obviously
+ // better if you use get_fast_modinfo to get the cm before calling this.
+ if (is_object($courseorid)) {
+ $course = $courseorid;
+ } else {
+ $course = clone($SITE);
+ }
+ $modinfo = get_fast_modinfo($course);
+ $cm = $modinfo->get_cm($cm->id);
+ }
if (!empty($CFG->forcelogin)) {
// login required for both SITE and courses
require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
- } else if (!empty($cm) and !$cm->visible) {
+ } else if ($issite && !empty($cm) and !$cm->uservisible) {
// always login for hidden activities
require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
- } else if ((is_object($courseorid) and $courseorid->id == SITEID)
- or (!is_object($courseorid) and $courseorid == SITEID)) {
+ } else if ($issite) {
//login for SITE not required
if ($cm and empty($cm->visible)) {
// hidden activities are not accessible without login
$SESSION->logincount = 0;
}
- /**
- * Returns reference to full info about modules in course (including visibility).
- * Cached and as fast as possible (0 or 1 db query).
- *
- * @global object
- * @global object
- * @global object
- * @uses CONTEXT_MODULE
- * @uses MAX_MODINFO_CACHE_SIZE
- * @param mixed $course object or 'reset' string to reset caches, modinfo may be updated in db
- * @param int $userid Defaults to current user id
- * @return mixed courseinfo object or nothing if resetting
- */
- function &get_fast_modinfo(&$course, $userid=0) {
- global $CFG, $USER, $DB;
- require_once($CFG->dirroot.'/course/lib.php');
-
- if (!empty($CFG->enableavailability)) {
- require_once($CFG->libdir.'/conditionlib.php');
- }
-
- static $cache = array();
-
- if ($course === 'reset') {
- $cache = array();
- $nothing = null;
- return $nothing; // we must return some reference
- }
-
- if (empty($userid)) {
- $userid = $USER->id;
- }
-
- if (array_key_exists($course->id, $cache) and $cache[$course->id]->userid == $userid) {
- return $cache[$course->id];
- }
-
- if (empty($course->modinfo)) {
- // no modinfo yet - load it
- rebuild_course_cache($course->id);
- $course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
- }
-
- $modinfo = new stdClass();
- $modinfo->courseid = $course->id;
- $modinfo->userid = $userid;
- $modinfo->sections = array();
- $modinfo->cms = array();
- $modinfo->instances = array();
- $modinfo->groups = null; // loaded only when really needed - the only one db query
-
- $info = unserialize($course->modinfo);
- if (!is_array($info)) {
- // hmm, something is wrong - lets try to fix it
- rebuild_course_cache($course->id);
- $course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
- $info = unserialize($course->modinfo);
- if (!is_array($info)) {
- return $modinfo;
- }
- }
-
- if ($info) {
- // detect if upgrade required
- $first = reset($info);
- if (!isset($first->id)) {
- rebuild_course_cache($course->id);
- $course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
- $info = unserialize($course->modinfo);
- if (!is_array($info)) {
- return $modinfo;
- }
- }
- }
-
- $modlurals = array();
-
- // If we haven't already preloaded contexts for the course, do it now
- preload_course_contexts($course->id);
-
- foreach ($info as $mod) {
- if (empty($mod->name)) {
- // something is wrong here
- continue;
- }
- // reconstruct minimalistic $cm
- $cm = new stdClass();
- $cm->id = $mod->cm;
- $cm->instance = $mod->id;
- $cm->course = $course->id;
- $cm->modname = $mod->mod;
- $cm->idnumber = $mod->idnumber;
- $cm->name = $mod->name;
- $cm->visible = $mod->visible;
- $cm->sectionnum = $mod->section;
- $cm->groupmode = $mod->groupmode;
- $cm->groupingid = $mod->groupingid;
- $cm->groupmembersonly = $mod->groupmembersonly;
- $cm->indent = $mod->indent;
- $cm->completion = $mod->completion;
- $cm->extra = isset($mod->extra) ? $mod->extra : '';
- $cm->icon = isset($mod->icon) ? $mod->icon : '';
- $cm->iconcomponent = isset($mod->iconcomponent) ? $mod->iconcomponent : '';
- $cm->uservisible = true;
- if (!empty($CFG->enableavailability)) {
- // We must have completion information from modinfo. If it's not
- // there, cache needs rebuilding
- if(!isset($mod->availablefrom)) {
- debugging('enableavailability option was changed; rebuilding '.
- 'cache for course '.$course->id);
- rebuild_course_cache($course->id,true);
- // Re-enter this routine to do it all properly
- return get_fast_modinfo($course, $userid);
- }
- $cm->availablefrom = $mod->availablefrom;
- $cm->availableuntil = $mod->availableuntil;
- $cm->showavailability = $mod->showavailability;
- $cm->conditionscompletion = $mod->conditionscompletion;
- $cm->conditionsgrade = $mod->conditionsgrade;
- }
-
- // preload long names plurals and also check module is installed properly
- if (!isset($modlurals[$cm->modname])) {
- if (!file_exists("$CFG->dirroot/mod/$cm->modname/lib.php")) {
- continue;
- }
- $modlurals[$cm->modname] = get_string('modulenameplural', $cm->modname);
- }
- $cm->modplural = $modlurals[$cm->modname];
- $modcontext = get_context_instance(CONTEXT_MODULE,$cm->id);
-
- if (!empty($CFG->enableavailability)) {
- // Unfortunately the next call really wants to call
- // get_fast_modinfo, but that would be recursive, so we fake up a
- // modinfo for it already
- if (empty($minimalmodinfo)) { //TODO: this is suspicious (skodak)
- $minimalmodinfo = new stdClass();
- $minimalmodinfo->cms = array();
- foreach($info as $mod) {
- if (empty($mod->name)) {
- // something is wrong here
- continue;
- }
- $minimalcm = new stdClass();
- $minimalcm->id = $mod->cm;
- $minimalcm->name = $mod->name;
- $minimalmodinfo->cms[$minimalcm->id]=$minimalcm;
- }
- }
-
- // Get availability information
- $ci = new condition_info($cm);
- $cm->available = $ci->is_available($cm->availableinfo, true, $userid, $minimalmodinfo);
- } else {
- $cm->available = true;
- }
- if ((!$cm->visible or !$cm->available) and !has_capability('moodle/course:viewhiddenactivities', $modcontext, $userid)) {
- $cm->uservisible = false;
-
- } else if (!empty($CFG->enablegroupmembersonly) and !empty($cm->groupmembersonly)
- and !has_capability('moodle/site:accessallgroups', $modcontext, $userid)) {
- if (is_null($modinfo->groups)) {
- $modinfo->groups = groups_get_user_groups($course->id, $userid);
- }
- if (empty($modinfo->groups[$cm->groupingid])) {
- $cm->uservisible = false;
- }
- }
-
- if (!isset($modinfo->instances[$cm->modname])) {
- $modinfo->instances[$cm->modname] = array();
- }
- $modinfo->instances[$cm->modname][$cm->instance] =& $cm;
- $modinfo->cms[$cm->id] =& $cm;
-
- // reconstruct sections
- if (!isset($modinfo->sections[$cm->sectionnum])) {
- $modinfo->sections[$cm->sectionnum] = array();
- }
- $modinfo->sections[$cm->sectionnum][] = $cm->id;
-
- unset($cm);
- }
-
- unset($cache[$course->id]); // prevent potential reference problems when switching users
- $cache[$course->id] = $modinfo;
-
- // Ensure cache does not use too much RAM
- if (count($cache) > MAX_MODINFO_CACHE_SIZE) {
- reset($cache);
- $key = key($cache);
- unset($cache[$key]);
- }
-
- return $cache[$course->id];
- }
-
/**
* Determines if the currently logged in user is in editing mode.
* Note: originally this function had $userid parameter - it was not usable anyway
'webservice' => 'webservice',
'repository' => 'repository',
'portfolio' => 'portfolio',
- 'qtype' => 'question/type',
+ 'qbehaviour' => 'question/behaviour',
'qformat' => 'question/format',
+ 'qtype' => 'question/type',
'plagiarism' => 'plagiarism',
'theme' => 'theme'); // this is a bit hacky, themes may be in dataroot too
GROUP BY ip
HAVING COUNT(*) >= ?";
$params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
- if ($rs = $DB->get_recordset_sql($sql, $params)) {
- foreach ($rs as $iprec) {
- if (!empty($iprec->ip)) {
- set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
- }
+ $rs = $DB->get_recordset_sql($sql, $params);
+ foreach ($rs as $iprec) {
+ if (!empty($iprec->ip)) {
+ set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
}
- $rs->close();
}
+ $rs->close();
/// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
/// and insert them into the cache_flags temp table
GROUP BY info
HAVING count(*) >= ?";
$params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
- if ($rs = $DB->get_recordset_sql($sql, $params)) {
- foreach ($rs as $inforec) {
- if (!empty($inforec->info)) {
- set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
- }
+ $rs = $DB->get_recordset_sql($sql, $params);
+ foreach ($rs as $inforec) {
+ if (!empty($inforec->info)) {
+ set_cache_flag('login_failure_by_info', $inforec->info, '1', 0);
}
- $rs->close();
}
+ $rs->close();
/// Now, select all the login error logged records belonging to the ips and infos
/// since lastnotifyfailure, that we have stored in the cache_flags table
$count = 0;
$messages = '';
/// Iterate over the logs recordset
- if ($rs = $DB->get_recordset_sql($sql, $params)) {
- foreach ($rs as $log) {
- $log->time = userdate($log->time);
- $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
- $count++;
- }
- $rs->close();
+ $rs = $DB->get_recordset_sql($sql, $params);
+ foreach ($rs as $log) {
+ $log->time = userdate($log->time);
+ $messages .= get_string('notifyloginfailuresmessage','',$log)."\n";
+ $count++;
}
+ $rs->close();
/// If we haven't run in the last hour and
/// we have something useful to report and we
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
'qtype' => 'essay',
- 'defaultgrade' => 1,
- 'penalty' => 0.1,
+ 'defaultmark' => 1,
+ 'penalty' => 0.3333333,
'length' => 1,
'feedback' => array(
'text' => '',
'questiontextformat' => FORMAT_MOODLE,
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
- 'defaultgrade' => 1,
- 'penalty' => 0.1,
+ 'defaultmark' => 1,
+ 'penalty' => 0.3333333,
'length' => 1,
'qtype' => 'essay',
'options' => (object) array(
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_HTML,
'qtype' => 'match',
- 'defaultgrade' => 1,
- 'penalty' => 0.1,
+ 'defaultmark' => 1,
+ 'penalty' => 0.3333333,
'length' => 1,
'shuffleanswers' => '1',
'subquestions' => array(
'questiontextformat' => FORMAT_HTML,
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_HTML,
- 'defaultgrade' => 1,
- 'penalty' => 0.1,
+ 'defaultmark' => 1,
+ 'penalty' => 0.3333333,
'length' => 1,
'qtype' => 'match',
'options' => (object) array(
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
'qtype' => 'multichoice',
- 'defaultgrade' => 1,
- 'penalty' => 0.1,
+ 'defaultmark' => 1,
+ 'penalty' => 0.3333333,
'length' => 1,
'single' => 1,
'shuffleanswers' => '1',
'questiontextformat' => FORMAT_MOODLE,
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
- 'defaultgrade' => 1,
- 'penalty' => 0.1,
+ 'defaultmark' => 1,
+ 'penalty' => 0.3333333,
'length' => 1,
'qtype' => 'multichoice',
'options' => (object) array(
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
'qtype' => 'numerical',
- 'defaultgrade' => 1,
- 'penalty' => 0.1,
+ 'defaultmark' => 1,
+ 'penalty' => 0.3333333,
'length' => 1,
'answer' => array(
'3',
'questiontextformat' => FORMAT_MOODLE,
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
- 'defaultgrade' => 1,
+ 'defaultmark' => 1,
'penalty' => 1,
'length' => 1,
'qtype' => 'numerical',
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
'qtype' => 'shortanswer',
- 'defaultgrade' => 1,
- 'penalty' => 0.1,
+ 'defaultmark' => 1,
+ 'penalty' => 0.3333333,
'length' => 1,
'answer' => array(
'Frog',
'questiontextformat' => FORMAT_MOODLE,
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
- 'defaultgrade' => 1,
+ 'defaultmark' => 1,
'penalty' => 1,
'length' => 1,
'qtype' => 'shortanswer',
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
'qtype' => 'truefalse',
- 'defaultgrade' => 1,
+ 'defaultmark' => 1,
'penalty' => 1,
'length' => 1,
'correctanswer' => 0,
$this->assert(new CheckSpecifiedFieldsExpectation($expectedq), $q);
}
- 'defaultgrade' => 1,
+ public function test_import_truefalse_true_answer1() {
+ $gift = "// name 0-11
+ ::2-08 TSL::TSL is blablabla.{T}";
+ $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
+
+ $importer = new qformat_gift();
+ $q = $importer->readquestion($lines);
+
+ $expectedq = (object) array(
+ 'name' => '2-08 TSL',
+ 'questiontext' => "TSL is blablabla.",
+ 'questiontextformat' => FORMAT_MOODLE,
+ 'generalfeedback' => '',
+ 'generalfeedbackformat' => FORMAT_MOODLE,
+ 'qtype' => 'truefalse',
- 'defaultgrade' => 1,
++ 'defaultmark' => 1,
+ 'penalty' => 1,
+ 'length' => 1,
+ 'correctanswer' => 1,
+ 'feedbacktrue' => array(
+ 'text' => '',
+ 'format' => FORMAT_MOODLE,
+ 'files' => array(),
+ ),
+ 'feedbackfalse' => array(
+ 'text' => '',
+ 'format' => FORMAT_MOODLE,
+ 'files' => array(),
+ ),
+ );
+
+ $this->assert(new CheckSpecifiedFieldsExpectation($expectedq), $q);
+ }
+
+ public function test_import_truefalse_true_answer2() {
+ $gift = "// name 0-11
+ ::2-08 TSL::TSL is blablabla.{TRUE}";
+ $lines = preg_split('/[\\n\\r]/', str_replace("\r\n", "\n", $gift));
+
+ $importer = new qformat_gift();
+ $q = $importer->readquestion($lines);
+
+ $expectedq = (object) array(
+ 'name' => '2-08 TSL',
+ 'questiontext' => "TSL is blablabla.",
+ 'questiontextformat' => FORMAT_MOODLE,
+ 'generalfeedback' => '',
+ 'generalfeedbackformat' => FORMAT_MOODLE,
+ 'qtype' => 'truefalse',
++ 'defaultmark' => 1,
+ 'penalty' => 1,
+ 'length' => 1,
+ 'correctanswer' => 1,
+ 'feedbacktrue' => array(
+ 'text' => '',
+ 'format' => FORMAT_MOODLE,
+ 'files' => array(),
+ ),
+ 'feedbackfalse' => array(
+ 'text' => '',
+ 'format' => FORMAT_MOODLE,
+ 'files' => array(),
+ ),
+ );
+
+ $this->assert(new CheckSpecifiedFieldsExpectation($expectedq), $q);
+ }
+
public function test_export_truefalse() {
$qdata = (object) array(
'id' => 666 ,
'questiontextformat' => FORMAT_MOODLE,
'generalfeedback' => '',
'generalfeedbackformat' => FORMAT_MOODLE,
- 'defaultgrade' => 1,
+ 'defaultmark' => 1,
'penalty' => 1,
'length' => 1,
'qtype' => 'truefalse',
--- /dev/null
- return array('answer' => PARAM_TRIM);
+<?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/>.
+
+
+/**
+ * Numerical question definition class.
+ *
+ * @package qtype_numerical
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/numerical/questiontype.php');
+
+
+/**
+ * Represents a numerical question.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_numerical_question extends question_graded_by_strategy
+ implements question_response_answer_comparer {
+ /** @var array of question_answer. */
+ public $answers = array();
+ /** @var qtype_numerical_answer_processor */
+ public $ap;
+
+ 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 init_first_step(question_attempt_step $step) {
+ if ($step->has_qt_var('_separators')) {
+ list($point, $separator) = explode('$', $step->get_qt_var('_separators'));
+ $this->ap->set_characters($point, $separator);
+ } else {
+ $step->set_qt_var('_separators',
+ $this->ap->get_point() . '$' . $this->ap->get_separator());
+ }
+ }
+
+ 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' || $response['answer'] === 0);
+ }
+
+ public function get_validation_error(array $response) {
+ if ($this->is_gradable_response($response)) {
+ return '';
+ }
+ return get_string('pleaseenterananswer', 'qtype_numerical');
+ }
+
+ public function is_same_response(array $prevresponse, array $newresponse) {
+ return question_utils::arrays_same_at_key_missing_is_blank(
+ $prevresponse, $newresponse, 'answer');
+ }
+
+ public function get_answers() {
+ return $this->answers;
+ }
+
+ public function compare_response_with_answer(array $response, question_answer $answer) {
+ list($value, $unit) = $this->ap->apply_units($response['answer']);
+ return $answer->within_tolerance($value);
+ }
+}
+
+
+/**
+ * Subclass of {@link question_answer} with the extra information required by
+ * the numerical question type.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_numerical_answer extends question_answer {
+ /** @var float allowable margin of error. */
+ public $tolerance;
+ /** @var integer|string see {@link get_tolerance_interval()} for the meaning of this value. */
+ public $tolerancetype = 2;
+
+ public function __construct($id, $answer, $fraction, $feedback, $feedbackformat, $tolerance) {
+ parent::__construct($id, $answer, $fraction, $feedback, $feedbackformat);
+ $this->tolerance = abs($tolerance);
+ }
+
+ public function get_tolerance_interval() {
+ if ($this->answer === '*') {
+ throw new Exception('Cannot work out tolerance interval for answer *.');
+ }
+
+ // We need to add a tiny fraction depending on the set precision to make
+ // the comparison work correctly, otherwise seemingly equal values can
+ // yield false. See MDL-3225.
+ $tolerance = (float) $this->tolerance + pow(10, -1 * ini_get('precision'));
+
+ switch ($this->tolerancetype) {
+ case 1: case 'relative':
+ $range = abs($this->answer) * $tolerance;
+ return array($this->answer - $range, $this->answer + $range);
+
+ case 2: case 'nominal':
+ $tolerance = $this->tolerance + pow(10, -1 * ini_get('precision')) *
+ max(1, abs($this->answer));
+ return array($this->answer - $tolerance, $this->answer + $tolerance);
+
+ case 3: case 'geometric':
+ $quotient = 1 + abs($tolerance);
+ return array($this->answer / $quotient, $this->answer * $quotient);
+
+ default:
+ throw new Exception('Unknown tolerance type ' . $this->tolerancetype);
+ }
+ }
+
+ public function within_tolerance($value) {
+ if ($this->answer === '*') {
+ return true;
+ }
+ list($min, $max) = $this->get_tolerance_interval();
+ return $min <= $value && $value <= $max;
+ }
+}
--- /dev/null
- return array('answer' => PARAM_TRIM);
+<?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/>.
+
+
+/**
+ * Short answer question definition class.
+ *
+ * @package qtype_shortanswer
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Represents a short answer question.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_shortanswer_question extends question_graded_by_strategy
+ implements question_response_answer_comparer {
+ /** @var boolean whether answers should be graded case-sensitively. */
+ public $usecase;
+ /** @var array of question_answer. */
+ public $answers = array();
+
+ 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_shortanswer');
+ }
+
+ public function is_same_response(array $prevresponse, array $newresponse) {
+ return question_utils::arrays_same_at_key_missing_is_blank(
+ $prevresponse, $newresponse, 'answer');
+ }
+
+ public function get_answers() {
+ return $this->answers;
+ }
+
+ public function compare_response_with_answer(array $response, question_answer $answer) {
+ return self::compare_string_with_wildcard($response['answer'], $answer->answer, !$this->usecase);
+ }
+
+ public static function compare_string_with_wildcard($string, $pattern, $ignorecase) {
+ // Break the string on non-escaped asterisks.
+ $bits = preg_split('/(?<!\\\\)\*/', $pattern);
+ // Escape regexp special characters in the bits.
+ $excapedbits = array();
+ foreach ($bits as $bit) {
+ $excapedbits[] = preg_quote(str_replace('\*', '*', $bit));
+ }
+ // Put it back together to make the regexp.
+ $regexp = '|^' . implode('.*', $excapedbits) . '$|u';
+
+ // Make the match insensitive if requested to.
+ if ($ignorecase) {
+ $regexp .= 'i';
+ }
+
+ return preg_match($regexp, trim($string));
+ }
+
+ 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);
+ }
+ }
+}