MDL-20636 More progress.
This commit is contained in:
@@ -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
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user