MDL-20636 More progress.

This commit is contained in:
Tim Hunt
2011-01-13 18:35:34 +00:00
parent b691dc8510
commit 06f8ed54fd
11 changed files with 399 additions and 142 deletions
+8
View File
@@ -77,6 +77,14 @@ abstract class question_bank {
return self::$questiontypes[$qtypename];
}
/**
* @param string $qtypename the internal name of a question type. For example multichoice.
* @return boolean whether users are allowed to create questions of this type.
*/
public static function qtype_enabled($qtypename) {
;
}
/**
* @param $qtypename the internal name of a question type, for example multichoice.
* @return string the human_readable name of this question type, from the language pack.
+52 -56
View File
@@ -34,6 +34,25 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_engine_data_mapper {
/**
* @var moodle_database normally points to global $DB, but I prefer not to
* use globals if I can help it.
*/
protected $db;
/**
* @param moodle_database $db a database connectoin. Defaults to global $DB.
*/
public function __construct($db = null) {
if (is_null($db)) {
global $DB;
new moodle_database;
$this->db = $DB;
} else {
$this->db = $db;
}
}
/**
* Store an entire {@link question_usage_by_activity} in the database,
* including all the question_attempts that comprise it.
@@ -45,10 +64,7 @@ class question_engine_data_mapper {
$record->component = addslashes($quba->get_owning_component());
$record->preferredbehaviour = addslashes($quba->get_preferred_behaviour());
$newid = insert_record('question_usages', $record);
if (!$newid) {
throw new Exception('Failed to save questions_usage_by_activity.');
}
$newid = $this->db->insert_record('question_usages', $record);
$quba->set_id_from_database($newid);
foreach ($quba->get_attempt_iterator() as $qa) {
@@ -74,10 +90,7 @@ class question_engine_data_mapper {
$record->rightanswer = addslashes($qa->get_right_answer_summary());
$record->responsesummary = addslashes($qa->get_response_summary());
$record->timemodified = time();
$record->id = insert_record('question_attempts', $record);
if (!$record->id) {
throw new Exception('Failed to save question_attempt ' . $qa->get_slot());
}
$record->id = $this->db->insert_record('question_attempts', $record);
foreach ($qa->get_step_iterator() as $seq => $step) {
$this->insert_question_attempt_step($step, $record->id, $seq);
@@ -98,18 +111,14 @@ class question_engine_data_mapper {
$record->timecreated = $step->get_timecreated();
$record->userid = $step->get_user_id();
$record->id = insert_record('question_attempt_steps', $record);
if (!$record->id) {
throw new Exception('Failed to save question_attempt_step' . $seq .
' for question attempt id ' . $questionattemptid);
}
$record->id = $this->db->insert_record('question_attempt_steps', $record);
foreach ($step->get_all_data() as $name => $value) {
$data = new stdClass;
$data->attemptstepid = $record->id;
$data->name = addslashes($name);
$data->value = addslashes($value);
insert_record('question_attempt_step_data', $data, false);
$this->db->insert_record('question_attempt_step_data', $data, false);
}
}
@@ -119,8 +128,7 @@ class question_engine_data_mapper {
* @param question_attempt_step the step that was loaded.
*/
public function load_question_attempt_step($stepid) {
global $CFG;
$records = get_records_sql("
$records = $this->db->get_records_sql("
SELECT
COALESCE(qasd.id, -1 * qas.id) AS id,
qas.id AS attemptstepid,
@@ -133,12 +141,12 @@ SELECT
qasd.name,
qasd.value
FROM {$CFG->prefix}question_attempt_steps qas
LEFT JOIN {$CFG->prefix}question_attempt_step_data qasd ON qasd.attemptstepid = qas.id
FROM {question_attempt_steps} qas
LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
WHERE
qas.id = $stepid
");
qas.id = :stepid
", array('stepid' => $stepid));
if (!$records) {
throw new Exception('Failed to load question_attempt_step ' . $stepid);
@@ -154,8 +162,7 @@ WHERE
* @param question_attempt the question attempt that was loaded.
*/
public function load_question_attempt($questionattemptid) {
global $CFG;
$records = get_records_sql("
$records = $this->db->get_records_sql("
SELECT
COALESCE(qasd.id, -1 * qas.id) AS id,
quba.preferredbehaviour,
@@ -180,17 +187,17 @@ SELECT
qasd.name,
qasd.value
FROM {$CFG->prefix}question_attempts qa
JOIN {$CFG->prefix}question_usages quba ON quba.id = qa.questionusageid
LEFT JOIN {$CFG->prefix}question_attempt_steps qas ON qas.questionattemptid = qa.id
LEFT JOIN {$CFG->prefix}question_attempt_step_data qasd ON qasd.attemptstepid = qas.id
FROM {question_attempts qa
JOIN {question_usages} quba ON quba.id = qa.questionusageid
LEFT JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
WHERE
qa.id = $questionattemptid
qa.id = :questionattemptid
ORDER BY
qas.sequencenumber
");
", array('questionattemptid' => $questionattemptid));
if (!$records) {
throw new Exception('Failed to load question_attempt ' . $questionattemptid);
@@ -208,8 +215,7 @@ ORDER BY
* @param question_usage_by_activity the usage that was loaded.
*/
public function load_questions_usage_by_activity($qubaid) {
global $CFG;
$records = get_records_sql("
$records = $this->db->get_records_sql("
SELECT
COALESCE(qasd.id, -1 * qas.id) AS id,
quba.id AS qubaid,
@@ -237,18 +243,18 @@ SELECT
qasd.name,
qasd.value
FROM {$CFG->prefix}question_usages quba
LEFT JOIN {$CFG->prefix}question_attempts qa ON qa.questionusageid = quba.id
LEFT JOIN {$CFG->prefix}question_attempt_steps qas ON qas.questionattemptid = qa.id
LEFT JOIN {$CFG->prefix}question_attempt_step_data qasd ON qasd.attemptstepid = qas.id
FROM {question_usages} quba
LEFT JOIN {question_attempts} qa ON qa.questionusageid = quba.id
LEFT JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
WHERE
quba.id = $qubaid
quba.id = :qubaid
ORDER BY
qa.slot,
qas.sequencenumber
");
", array('qubaid', $qubaid));
if (!$records) {
throw new Exception('Failed to load questions_usage_by_activity ' . $qubaid);
@@ -266,8 +272,6 @@ ORDER BY
* @return array of records. See the SQL in this function to see the fields available.
*/
public function load_questions_usages_latest_steps(qubaid_condition $qubaids, $slots) {
global $CFG;
list($slottest, $params) = get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot0000');
$records = get_records_sql("
@@ -293,17 +297,13 @@ SELECT
qas.userid
FROM {$qubaids->from_question_attempts('qa')}
JOIN {$CFG->prefix}question_attempt_steps qas ON
JOIN {question_attempt_steps} qas ON
qas.id = {$this->latest_step_for_qa_subquery()}
WHERE
{$qubaids->where()} AND
qa.slot $slottest
");
if (!$records) {
$records = array();
}
", $params + $qubaids->from_where_params());
return $records;
}
@@ -321,11 +321,9 @@ WHERE
* $manuallygraded and $all.
*/
public function load_questions_usages_question_state_summary(qubaid_condition $qubaids, $slots) {
global $CFG;
list($slottest, $params) = get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot0000');
$rs = get_recordset_sql("
$rs = $this->db->get_recordset_sql("
SELECT
qa.slot,
qa.questionid,
@@ -336,9 +334,9 @@ SELECT
COUNT(1) AS numattempts
FROM {$qubaids->from_question_attempts('qa')}
JOIN {$CFG->prefix}question_attempt_steps qas ON
JOIN {question_attempt_steps} qas ON
qas.id = {$this->latest_step_for_qa_subquery()}
JOIN {$CFG->prefix}question q ON q.id = qa.questionid
JOIN {question} q ON q.id = qa.questionid
WHERE
{$qubaids->where()} AND
@@ -356,7 +354,7 @@ ORDER BY
qa.questionid,
q.name,
q.id
");
", $params + $qubaids->from_where_params());
if (!$rs) {
throw new moodle_exception('errorloadingdata');
@@ -694,15 +692,13 @@ ORDER BY
* @param integer $sessionid the question_attempt id.
* @param boolean $newstate the new state of the flag. true = flagged.
*/
public function update_question_attempt_flag($qubaid, $questionid, $qaid, $newstate) {
if (!record_exists('question_attempts', 'id', $qaid,
'questionusageid', $qubaid, 'questionid', $questionid)) {
public function update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate) {
if (!$this->db->record_exists('question_attempts', array('id' => $qaid,
'questionusageid' => $qubaid, 'questionid' => $questionid, 'slot' => $slot))) {
throw new Exception('invalid ids');
}
if (!set_field('question_attempts', 'flagged', $newstate, 'id', $qaid)) {
throw new Exception('flag update failed');
}
$this->db->set_field('question_attempts', 'flagged', $newstate, array('id' => $qaid));
}
/**
+8 -7
View File
@@ -504,12 +504,12 @@ abstract class question_flags {
* @param object $user the user. If null, defaults to $USER.
* @return string that needs to be sent to question/toggleflag.php for it to work.
*/
protected static function get_toggle_checksum($qubaid, $questionid, $qaid, $user = null) {
protected static function get_toggle_checksum($qubaid, $questionid, $qaid, $slot, $user = null) {
if (is_null($user)) {
global $USER;
$user = $USER;
}
return md5($qubaid . "_" . $user->secret . "_" . $questionid . "_" . $qaid);
return md5($qubaid . "_" . $user->secret . "_" . $questionid . "_" . $qaid . "_" . $slot);
}
/**
@@ -521,8 +521,9 @@ abstract class question_flags {
$qaid = $qa->get_database_id();
$qubaid = $qa->get_usage_id();
$qid = $qa->get_question()->id;
$checksum = self::get_toggle_checksum($qubaid, $qid, $qaid);
return "qaid=$qaid&qubaid=$qubaid&qid=$qid&checksum=$checksum&sesskey=" . sesskey();
$slot = $qa->get_slot();
$checksum = self::get_toggle_checksum($qubaid, $qid, $qaid, $slot);
return "qaid=$qaid&qubaid=$qubaid&qid=$qid&slot=$slot&checksum=$checksum&sesskey=" . sesskey();
}
/**
@@ -535,18 +536,18 @@ abstract class question_flags {
* corresponding to the last three arguments.
* @param boolean $newstate the new state of the flag. true = flagged.
*/
public static function update_flag($qubaid, $questionid, $qaid, $checksum, $newstate) {
public static function update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate) {
// Check the checksum - it is very hard to know who a question session belongs
// to, so we require that checksum parameter is matches an md5 hash of the
// three ids and the users username. Since we are only updating a flag, that
// probably makes it sufficiently difficult for malicious users to toggle
// other users flags.
if ($checksum != question_flags::get_toggle_checksum($qubaid, $questionid, $qaid)) {
if ($checksum != question_flags::get_toggle_checksum($qubaid, $questionid, $qaid, $slot)) {
throw new Exception('checksum failure');
}
$dm = new question_engine_data_mapper();
$dm->update_question_attempt_flag($qubaid, $questionid, $qaid, $newstate);
$dm->update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate);
}
public static function initialise_js() {
+1 -1
View File
@@ -211,9 +211,9 @@ class core_question_renderer extends renderer_base {
// of a stupid IE bug: http://www.456bereastreet.com/archive/200802/beware_of_id_and_name_attribute_mixups_when_using_getelementbyid_in_internet_explorer/
$flagcontent = '<input type="hidden" name="' . $id . '" value="0" />' .
'<input type="checkbox" id="' . $id . 'checkbox" name="' . $id . '" value="1" ' . $checked . ' />' .
'<input type="hidden" value="' . s($postdata) . '" class="questionflagpostdata" />' .
'<label id="' . $id . 'label" for="' . $id . '">' . $this->get_flag_html(
$qa->is_flagged(), $id . 'img') . '</label>' . "\n" .
print_js_call('question_flag_changer.init_flag', array($id, $postdata, $qa->get_slot()), true);
break;
default:
$flagcontent = '';
+2 -3
View File
@@ -42,7 +42,6 @@ M.core_question_flags = {
Y.io(M.core_question_flags.actionurl , {method: 'POST', 'data': postdata});
M.core_question_flags.fire_listeners(postdata);
}, document.body, 'input.questionflagimage');
},
update_flag: function(input, image) {
@@ -56,8 +55,8 @@ M.core_question_flags = {
fire_listeners: function(postdata) {
for (var i = 0; i < M.core_question_flags.listeners.length; i++) {
M.core_question_flags.listeners[i](
postdata.match(/\baid=(\d+)\b/)[1],
postdata.match(/\bqid=(\d+)\b/)[1],
postdata.match(/\bqubaid=(\d+)\b/)[1],
postdata.match(/\bslot=(\d+)\b/)[1],
postdata.match(/\bnewstate=(\d+)\b/)[1]
);
}
+15 -16
View File
@@ -15,7 +15,7 @@
/**
* This file the Moodle question engine.
* JavaScript required by the question preview pop-up.
*
* @package moodlecore
* @subpackage questionengine
@@ -24,23 +24,22 @@
*/
M.core_question_preview = M.core_question_preview || {};
/**
* Initialise JavaScript-specific parts of the question preview popup.
*/
function question_preview_init(caption, addto) {
M.core_question_preview.init(Y) {
M.core_question_engine.init_form(Y, '#responseform');
// Add a close button to the window.
var button = document.createElement('input');
button.type = 'button';
button.value = caption;
var closebutton = Y.Node.create('<input type="button" />');
button.value = M.str.question.closepreview;
Y.one('#previewcontrols').append(closebutton);
Y.on('click', function() { window.close() }, closebutton);
YAHOO.util.Event.addListener(button, 'click', function() { window.close() });
var container = document.getElementById(addto);
container.appendChild(button);
// Make changint the settings disable all submit buttons, like clicking one of the
// question buttons does.
var form = document.getElementById('mform1');
YAHOO.util.Event.addListener(form, 'submit',
question_prevent_repeat_submission, document.body);
}
// Make changing the settings disable all submit buttons, like clicking one
// of the question buttons does.
Y.on('submit', M.core_question_engine.prevent_repeat_submission, '#mform1', null, Y)
}
+192 -2
View File
@@ -15,15 +15,177 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Library functions used by question/preview.php.
*
* @package core
* @package moodlecore
* @subpackage questionengine
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Settings form for the preview options.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class preview_options_form extends moodleform {
public function definition() {
$mform = $this->_form;
$hiddenofvisible = array(
question_display_options::HIDDEN => get_string('notshown', 'question'),
question_display_options::VISIBLE => get_string('shown', 'question'),
);
$mform->addElement('header', 'optionsheader', get_string('changeoptions', 'question'));
$behaviours = question_engine::get_behaviour_options($this->_customdata->get_preferred_behaviour());
$mform->addElement('select', 'behaviour', get_string('howquestionsbehave', 'question'), $behaviours);
$mform->setHelpButton('behaviour', array('howquestionsbehave', get_string('howquestionsbehave', 'question'), 'question'));
$mform->addElement('text', 'maxmark', get_string('markedoutof', 'question'), array('size' => '5'));
$mform->setType('maxmark', PARAM_NUMBER);
$mform->addElement('select', 'correctness', get_string('whethercorrect', 'question'), $hiddenofvisible);
$marksoptions = array(
question_display_options::HIDDEN => get_string('notshown', 'question'),
question_display_options::MAX_ONLY => get_string('showmaxmarkonly', 'question'),
question_display_options::MARK_AND_MAX => get_string('showmarkandmax', 'question'),
);
$mform->addElement('select', 'marks', get_string('marks', 'question'), $marksoptions);
$mform->addElement('select', 'markdp', get_string('decimalplacesingrades', 'question'),
question_engine::get_dp_options());
$mform->addElement('select', 'feedback', get_string('specificfeedback', 'question'), $hiddenofvisible);
$mform->addElement('select', 'generalfeedback', get_string('generalfeedback', 'question'), $hiddenofvisible);
$mform->addElement('select', 'rightanswer', get_string('rightanswer', 'question'), $hiddenofvisible);
$mform->addElement('select', 'history', get_string('responsehistory', 'question'), $hiddenofvisible);
$mform->addElement('submit', 'submit', get_string('restartwiththeseoptions', 'question'), $hiddenofvisible);
}
}
/**
* Displays question preview options as default and set the options
* Setting default, getting and setting user preferences in question preview options.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_preview_options extends question_display_options {
/** @var string the behaviour to use for this preview. */
public $behaviour;
/** @var number the maximum mark to use for this preview. */
public $maxmark;
/** @var string prefix to append to field names to get user_preference names. */
const OPTIONPREFIX = 'question_preview_options_';
/**
* Constructor.
*/
public function __construct($question) {
global $CFG;
$this->behaviour = 'deferredfeedback';
$this->maxmark = $question->defaultmark;
$this->correctness = self::VISIBLE;
$this->marks = self::MARK_AND_MAX;
$this->markdp = $CFG->quiz_decimalpoints;
$this->feedback = self::VISIBLE;
$this->numpartscorrect = $this->feedback;
$this->generalfeedback = self::VISIBLE;
$this->rightanswer = self::VISIBLE;
$this->history = self::HIDDEN;
$this->flags = self::HIDDEN;
$this->manualcomment = self::HIDDEN;
}
/**
* @return array names of the options we store in the user preferences table.
*/
protected function get_user_pref_fields() {
return array('behaviour', 'correctness', 'marks', 'markdp', 'feedback',
'generalfeedback', 'rightanswer', 'history');
}
/**
* @return array names and param types of the options we read from the request.
*/
protected function get_field_types() {
return array(
'behaviour' => PARAM_ALPHA,
'maxmark' => PARAM_NUMBER,
'correctness' => PARAM_BOOL,
'marks' => PARAM_INT,
'markdp' => PARAM_INT,
'feedback' => PARAM_BOOL,
'generalfeedback' => PARAM_BOOL,
'rightanswer' => PARAM_BOOL,
'history' => PARAM_BOOL,
);
}
/**
* Load the value of the options from the user_preferences table.
*/
public function load_user_defaults() {
foreach ($this->get_user_pref_fields() as $field) {
$this->$field = get_user_preferences(
self::OPTIONPREFIX . $field, $this->$field);
}
$this->numpartscorrect = $this->feedback;
}
/**
* Save a change to the user's preview options to the database.
* @param object $newoptions
*/
public function save_user_preview_options($newoptions) {
foreach ($this->get_user_pref_fields() as $field) {
if (isset($newoptions->$field)) {
set_user_preference(self::OPTIONPREFIX . $field, $newoptions->$field);
}
}
}
/**
* Set the value of any fields included in the request.
*/
public function set_from_request() {
foreach ($this->get_field_types() as $field => $type) {
$this->$field = optional_param($field, $this->$field, $type);
}
$this->numpartscorrect = $this->feedback;
}
/**
* @return string URL fragment. Parameters needed in the URL when continuing
* this preview.
*/
public function get_query_string() {
$querystring = array();
foreach ($this->get_field_types() as $field => $notused) {
if ($field == 'behaviour' || $field == 'maxmark') {
continue;
}
$querystring[] = $field . '=' . $this->$field;
}
return implode('&', $querystring);
}
}
/**
* Called via pluginfile.php -> question_pluginfile to serve files belonging to
* a question in a question_attempt when that attempt is a preview.
@@ -89,7 +251,7 @@ function question_preview_question_pluginfile($course, $context, $component,
$options = quiz_get_renderoptions($quiz, $attempt, $context, $state);
$options->noeditlink = true;
// XXX: mulitichoice type needs quiz id to get maxgrade
// TODO: mulitichoice type needs quiz id to get maxgrade
$options->quizid = 0;
if (!question_check_file_access($question, $state, $options, $context->id, $component,
@@ -106,3 +268,31 @@ function question_preview_question_pluginfile($course, $context, $component,
send_stored_file($file, 0, 0, $forcedownload);
}
/**
* The the URL to use for actions relating to this preview.
* @param integer $questionid the question being previewed.
* @param integer $qubaid the id of the question usage for this preview.
* @param question_preview_options $options the options in use.
*/
function question_preview_action_url($questionid, $qubaid,
question_preview_options $options) {
global $CFG;
$url = $CFG->wwwroot . '/question/preview.php?id=' . $questionid . '&previewid=' . $qubaid;
return $url . '&' . $options->get_query_string();
}
/**
* Delete the current preview, if any, and redirect to start a new preview.
* @param integer $previewid
* @param integer $questionid
* @param object $displayoptions
*/
function restart_preview($previewid, $questionid, $displayoptions) {
if ($previewid) {
begin_sql();
question_engine::delete_questions_usage_by_activity($previewid);
commit_sql();
}
redirect(question_preview_url($questionid, $displayoptions->behaviour, $displayoptions->maxmark, $displayoptions));
}
+75 -1
View File
@@ -1,5 +1,26 @@
M.core_question_engine = M.core_question_engine || {};
/**
* Flag used by M.core_question_engine.prevent_repeat_submission.
*/
M.core_question_engine.questionformalreadysubmitted = false;
/**
* Initialise a question submit button. This saves the scroll position and
* sets the fragment on the form submit URL so the page reloads in the right place.
* @param id the id of the button in the HTML.
* @param slot the number of the question_attempt within the usage.
*/
M.core_question_engine.init_submit_button(Y, button, slot) {
Y.on('click', function(e) {
var scrollpos = document.getElementById('scrollpos');
if (scrollpos) {
scrollpos.value = YAHOO.util.Dom.getDocumentScrollTop();
}
button.form.action = button.form.action + '#q' + slot;
}, button);
}
/**
* Initialise a form that contains questions printed using print_question.
* This has the effect of:
@@ -7,17 +28,70 @@ M.core_question_engine = M.core_question_engine || {};
* 2. Stopping enter from submitting the form (or toggling the next flag) unless
* keyboard focus is on the submit button or the flag.
* 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle
* the flags using Ajax.
* the flags using ajax.
* 4. Scroll to the position indicated by scrollpos= in the URL, if it is there.
* 5. Prevent the user from repeatedly submitting the form.
* @param Y the Yahoo object. Needs to have the DOM and Event modules loaded.
* @param form something that can be passed to Y.one, to find the form element.
*/
M.core_question_engine.init_form = function(Y, form) {
Y.one(form).setAttribute('autocomplete', 'off');
Y.on('submit', M.core_question_engine.prevent_repeat_submission, form, form, Y);
Y.on('key', function (e) {
if (!e.target.test('a') && !e.target.test('input[type=submit]') &&
!e.target.test('input[type=img]')) {
e.preventDefault();
}
}, form, 'press:13');
Y.one(form).all('.questionflagsavebutton').remove();
var matches = window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/, '$1');
if (matches) {
// onDOMReady is the effective one here. I am leaving the immediate call to
// window.scrollTo in case it reduces flicker.
window.scrollTo(0, matches[1]);
Y.on('domready', function() { window.scrollTo(0, matches[1]); });
// And the following horror is necessary to make it work in IE 8.
// Note that the class ie8 on body is only there in Moodle 2.0 and OU Moodle.
if (YAHOO.util.Dom.hasClass(document.body, 'ie')) {
question_force_ie_to_scroll(matches[1])
}
}
}
/**
* Event handler to stop the quiz form being submitted more than once.
* @param e the form submit event.
* @param form the form element.
*/
M.core_question_engine.prevent_repeat_submission(e, Y) {
if (M.core_question_engine.questionformalreadysubmitted) {
e.halt();
return;
}
setTimeout(function() {
Y.all('input[type=submit]').disabled = true;
}, 0);
M.core_question_engine.questionformalreadysubmitted = true;
}
/**
* Beat IE into submission.
* @param targetpos the target scroll position.
*/
M.core_question_engine.force_ie_to_scroll(targetpos) {
var hackcount = 25;
function do_scroll() {
window.scrollTo(0, targetpos);
hackcount -= 1;
if (hackcount > 0) {
setTimeout(do_scroll, 10);
}
}
Y.on('load', do_scroll, window);
}
+11 -23
View File
@@ -105,8 +105,7 @@ if ($id) {
$question->qtype = $qtype;
// Check that users are allowed to create this question type at the moment.
$allowedtypes = question_type_menu();
if (!isset($allowedtypes[$qtype])) {
if (!question_bank::qtype_enabled($qtype)) {
print_error('cannotenable', 'question', $returnurl, $qtype);
}
@@ -121,6 +120,8 @@ if ($id) {
print_error('notenoughdatatoeditaquestion', 'question', $returnurl);
}
$qtypeobj = question_bank::get_qtype($question->qtype);
// Validate the question category.
if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
print_error('categorydoesnotexist', 'question', $returnurl);
@@ -164,23 +165,13 @@ if ($id) {
}
// Validate the question type.
if (!isset($QTYPES[$question->qtype])) {
print_error('unknownquestiontype', 'question', $returnurl, $question->qtype);
}
$PAGE->set_pagetype('question-type-' . $question->qtype);
// Create the question editing form.
if ($wizardnow!=='' && !$movecontext){
if (!method_exists($QTYPES[$question->qtype], 'next_wizard_form')){
print_error('missingimportantcode', 'question', $returnurl, 'wizard form definition');
} else {
$mform = $QTYPES[$question->qtype]->next_wizard_form('question.php', $question, $wizardnow, $formeditable);
}
$mform = $qtypeobj->next_wizard_form('question.php', $question, $wizardnow, $formeditable);
} else {
$mform = $QTYPES[$question->qtype]->create_editing_form('question.php', $question, $category, $contexts, $formeditable);
}
if ($mform === null) {
print_error('missingimportantcode', 'question', $returnurl, 'question editing form definition for "'.$question->qtype.'"');
$mform = $qtypeobj->create_editing_form('question.php', $question, $category, $contexts, $formeditable);
}
$toform = fullclone($question); // send the question object and a few more parameters to the form
$toform->category = "$category->id,$category->contextid";
@@ -202,15 +193,13 @@ $toform->inpopup = $inpopup;
$mform->set_data($toform);
if ($mform->is_cancelled()){
if ($mform->is_cancelled()) {
if ($inpopup) {
close_window();
} else {
if (!empty($question->id)) {
$returnurl->param('lastchanged', $question->id);
}
redirect($returnurl->out(false));
}
} else if ($fromform = $mform->get_data()) {
/// If we are saving as a copy, break the connection to the old question.
if (!empty($fromform->makecopy)) {
@@ -248,7 +237,7 @@ if ($mform->is_cancelled()){
} else {
// We are acutally saving the question.
$question = $QTYPES[$question->qtype]->save_question($question, $fromform);
$question = $qtypeobj->save_question($question, $fromform);
if (!empty($CFG->usetags) && isset($fromform->tags)) {
// A wizardpage from multipe pages questiontype like calculated may not
// allow editing the question tags, hence the isset($fromform->tags) test.
@@ -257,7 +246,7 @@ if ($mform->is_cancelled()){
}
}
if (($QTYPES[$question->qtype]->finished_edit_wizard($fromform)) || $movecontext) {
if (($qtypeobj->finished_edit_wizard($fromform)) || $movecontext) {
if ($inpopup) {
echo $OUTPUT->notification(get_string('changessaved'), '');
close_window(3);
@@ -291,7 +280,7 @@ if ($mform->is_cancelled()){
}
} else {
$streditingquestion = $QTYPES[$question->qtype]->get_heading();
$streditingquestion = $qtypeobj->get_heading();
$PAGE->set_title($streditingquestion);
$PAGE->set_heading($COURSE->fullname);
if ($cm !== null) {
@@ -315,7 +304,6 @@ if ($mform->is_cancelled()){
// Display a heading, question editing form and possibly some extra content needed for
// for this question type.
$QTYPES[$question->qtype]->display_question_editing_page($mform, $question, $wizardnow);
$qtypeobj->display_question_editing_page($mform, $question, $wizardnow);
echo $OUTPUT->footer();
}
+4 -4
View File
@@ -162,11 +162,11 @@ DONE question/file.php | 171 +- | but this fil
DONE question/move_form.php | 32 +-
DONE question/preview.js | 47 +
question/preview.php | 408 ++--
question/previewlib.php | 214 ++
question/qengine.js | 181 ++
question/question.php | 3 +-
DONE question/previewlib.php | 214 ++
DONE question/qengine.js | 181 ++
DONE question/question.php | 3 +-
question/restorelib.php | 88 +-
question/toggleflag.php | 49 +
DONE question/toggleflag.php | 49 +
question/behaviour/behaviourbase.php | 627 +++++
question/behaviour/rendererbase.php | 200 ++
+31 -29
View File
@@ -1,47 +1,49 @@
<?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/>.
/**
* Used by ajax calls to toggle the flagged state of a question in an attempt.
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package questionbank
*
* @package moodlecore
* @subpackage questionengine
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('AJAX_SCRIPT', true);
require_once('../config.php');
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot . '/question/engine/lib.php');
// Parameters
$sessionid = required_param('qsid', PARAM_INT);
$attemptid = required_param('aid', PARAM_INT);
$qaid = required_param('qaid', PARAM_INT);
$qubaid = required_param('qubaid', PARAM_INT);
$questionid = required_param('qid', PARAM_INT);
$slot = required_param('slot', PARAM_INT);
$newstate = required_param('newstate', PARAM_BOOL);
$checksum = required_param('checksum', PARAM_ALPHANUM);
// Check user is logged in.
require_login();
// Check the sesskey.
if (!confirm_sesskey()) {
echo 'sesskey failure';
}
// Check the checksum - it is very hard to know who a question session belongs
// to, so we require that checksum parameter is matches an md5 hash of the
// three ids and the users username. Since we are only updating a flag, that
// probably makes it sufficiently difficult for malicious users to toggle
// other users flags.
if ($checksum != md5($attemptid . "_" . $USER->secret . "_" . $questionid . "_" . $sessionid)) {
echo 'checksum failure';
}
require_sesskey();
// Check that the requested session really exists
$questionsession = $DB->get_record('question_sessions', array('id' => $sessionid,
'attemptid' => $attemptid, 'questionid' => $questionid));
if (!$questionsession) {
echo 'invalid ids';
}
// Now change state
if (!question_update_flag($sessionid, $newstate)) {
echo 'update failed';
}
question_flags::update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate);
echo 'OK';