$string['movelinksonly'] = 'Just change where links point to, do not move or copy files.';
$string['moveq'] = 'Move question(s)';
$string['moveqtoanothercontext'] = 'Move question to another context.';
+$string['moveto'] = 'Move to >>';
$string['movingcategory'] = 'Moving category';
$string['movingcategoryandfiles'] = 'Are you sure you want to move category {$a->name} and all child categories to context for "{$a->contextto}"?<br /> We have detected {$a->urlcount} files linked from questions in {$a->fromareaname}, would you like to copy or move these to {$a->toareaname}?';
$string['movingcategorynofiles'] = 'Are you sure you want to move category "{$a->name}" and all child categories to context for "{$a->contextto}"?';
$string['unknownquestion'] = 'Unknown question: {$a}.';
$string['unknownquestioncatregory'] = 'Unknown question category: {$a}.';
$string['whethercorrect'] = 'Whether correct';
+$string['withselected'] = 'With selected';
$string['xoutofmax'] = '{$a->mark} out of {$a->max}';
$string['yougotnright'] = 'You have correctly selected {$a->num}.';
*/
function _tidy_question($question, $loadtags = false) {
global $CFG;
- if (question_bank::is_qtype_installed($question->qtype)) {
+ if (!question_bank::is_qtype_installed($question->qtype)) {
$question->questiontext = html_writer::tag('p', get_string('warningmissingtype',
'qtype_missingtype')) . $question->questiontext;
}
}
/**
- * Returns a comma separated list of ids of the category and all subcategories
+ * @return array of question category ids of the category and all subcategories.
*/
function question_categorylist($categoryid) {
global $DB;
- // returns a comma separated list of ids of the category and all subcategories
- $categorylist = $categoryid;
- if ($subcategories = $DB->get_records('question_categories', array('parent' => $categoryid), 'sortorder ASC', 'id, 1')) {
- foreach ($subcategories as $subcategory) {
- $categorylist .= ','. question_categorylist($subcategory->id);
- }
+ $subcategories = $DB->get_records('question_categories', array('parent' => $categoryid), 'sortorder ASC', 'id, 1');
+
+ $categorylist = array($categoryid);
+ foreach ($subcategories as $subcategory) {
+ $categorylist = array_merge($categorylist, question_categorylist($subcategory->id));
}
+
return $categorylist;
}
// Process any data that was submitted.
if (data_submitted() && confirm_sesskey()) {
if (optional_param('submit', false, PARAM_BOOL)) {
- // TODO better error handling.
$transaction = $DB->start_delegated_transaction();
$attemptobj->process_all_actions(time());
$transaction->allow_commit();
$string['modulename_help'] = 'The quiz module enables the teacher to design and set quizzes consisting of multiple choice, true-false, matching and other question types. Each attempt is automatically marked, and the teacher can choose whether to give feedback and/or show correct answers.';
$string['modulenameplural'] = 'Quizzes';
$string['moveselectedonpage'] = 'Move selected questions to page: {$a}';
-$string['moveto'] = 'Move to >>';
$string['multianswer'] = 'Embedded answers (cloze)';
$string['multichoice'] = 'Multiple choice';
$string['multipleanswers'] = 'Choose at least one answer.';
$string['wheregrade'] = 'Where\'s my grade?';
$string['wildcard'] = 'Wild card';
$string['windowclosing'] = 'This window will close shortly.';
-$string['withselected'] = 'With selected';
$string['withsummary'] = 'with summary statistics';
$string['wronguse'] = 'You can not use this page like that';
$string['xhtml'] = 'XHTML';
function quiz_delete_all_attempts($quiz) {
global $CFG, $DB;
require_once($CFG->libdir . '/questionlib.php');
- // TODO remove $CFG->prefix
- question_engine::delete_questions_usage_by_activities("{$CFG->prefix}question_usages.id IN (
- SELECT uniqueid FROM {$CFG->prefix}quiz_attempts WHERE quiz = $quizid)");
+ question_engine::delete_questions_usage_by_activities("{question_usages}.id IN (
+ SELECT uniqueid FROM {quiz_attempts} WHERE quiz = :quizid)", array('quizid' => $quizid));
$DB->delete_records('quiz_attempts', array('quiz' => $quiz->id));
$DB->delete_records('quiz_grades', array('quiz' => $quiz->id));
}
SELECT uniqueid
FROM {quiz_attempts} quiza
JOIN {quiz} quiz ON quiza.quiz = quiz.id
- WHERE quiz.course = ?)', array($data->courseid));
+ WHERE quiz.course = :courseid)', array('courseid' => $data->courseid));
$DB->delete_records_select('quiz_attempts',
'quiz IN (SELECT id FROM {quiz} WHERE course = ?)', array($data->courseid));
/// Functions to do with quiz layout and pages ////////////////////////////////
-/**
- * Returns a comma separated list of question ids for the current page
- *
- * @param string $layout the string representing the quiz layout. Each page is represented as a
- * comma separated list of question ids and 0 indicating page breaks.
- * So 5,2,0,3,0 means questions 5 and 2 on page 1 and question 3 on page 2
- * @param int $page the number of the current page.
- * @return string comma separated list of question ids
- */
-function quiz_questions_on_page($layout, $page) {
- $pages = explode(',0', $layout);
- // TODO is this still used.
- return trim($pages[$page], ',');
-}
-
/**
* Returns a comma separated list of question ids for the quiz
*
* @return int The number of questions in the quiz.
*/
function quiz_number_of_questions_in_quiz($layout) {
- // TODO is this still used.
$layout = quiz_questions_in_quiz(quiz_clean_layout($layout));
$count = substr_count($layout, ',');
if ($layout !== '') {
return $count;
}
-/**
- * Returns the first question number for the current quiz page
- *
- * @param string $quizlayout The string representing the layout for the whole quiz
- * @param string $pagelayout The string representing the layout for the current page
- * @return int the number of the first question
- */
-function quiz_first_questionnumber($quizlayout, $pagelayout) {
- // TODO is this still used.
- // this works by finding all the questions from the quizlayout that
- // come before the current page and then adding up their lengths.
- global $CFG, $DB;
- $start = strpos($quizlayout, ','.$pagelayout.',')-2;
- if ($start > 0) {
- $prevlist = substr($quizlayout, 0, $start);
- list($usql, $params) = $DB->get_in_or_equal(explode(',', $prevlist));
- return $DB->get_field_sql("SELECT sum(length)+1 FROM {question}
- WHERE id $usql", $params);
- } else {
- return 1;
- }
-}
-
/**
* Re-paginates the quiz layout
*
* Empty means all users.
*/
protected function delete_selected_attempts($quiz, $cm, $attemptids, $allowed) {
+ global $DB;
+
foreach ($attemptids as $attemptid) {
- $attempt = get_record('quiz_attempts', 'id', $attemptid);
+ $attempt = $DB->get_record('quiz_attempts', array('id' => $attemptid));
if (!$attempt || $attempt->quiz != $quiz->id || $attempt->preview != 0) {
// Ensure the attempt exists, and belongs to this quiz. If not skip.
continue;
$this->sql->fields .= ",\n$fields";
$this->sql->from .= "\nLEFT JOIN $inlineview ON " .
- "$alias.questionusageid = quiza.uniqueid AND $alias.slot = $slot";
- // TODO params
+ "$alias.questionusageid = quiza.uniqueid AND $alias.slot = :{$alias}slot";
+ $this->sql->params[$alias . 'slot'] = $slot;
}
/**
*/
class mod_quiz_report_overview_settings extends moodleform {
- function definition() {
- global $COURSE; // TODO get rid of this.
+ public function definition() {
$mform = $this->_form;
$mform->addElement('header', 'preferencespage', get_string('preferencespage', 'quiz_overview'));
if (!$this->_customdata['currentgroup']) {
$options[QUIZ_REPORT_ATTEMPTS_ALL] = get_string('optallattempts','quiz_overview');
}
- if ($this->_customdata['currentgroup'] || $COURSE->id != SITEID) {
+ if ($this->_customdata['currentgroup'] || !is_inside_frontpage($this->_customdata['context'])) {
$options[QUIZ_REPORT_ATTEMPTS_ALL_STUDENTS] = get_string('optallstudents','quiz_overview', $studentsstring);
$options[QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH] =
get_string('optattemptsonly','quiz_overview', $studentsstring);
$qmsubselect = quiz_report_qm_filter_select($quiz);
$mform = new mod_quiz_report_overview_settings($reporturl,
- array('qmsubselect' => $qmsubselect, 'quiz' => $quiz, 'currentgroup' => $currentgroup, 'context'=>$this->context));
+ array('qmsubselect' => $qmsubselect, 'quiz' => $quiz,
+ 'currentgroup' => $currentgroup, 'context' => $this->context));
if ($fromform = $mform->get_data()) {
$regradeall = false;
global $DB;
if ($userids) {
- list($usql, $params) = $DB->get_in_or_equal($userids);
+ list($usql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'u000000');
$usql = "qg.userid $usql AND";
} else {
- $usql ='';
+ $usql = '';
$params = array();
}
$sql = "
SELECT
- FLOOR(qg.grade/$bandwidth) AS band,
+ FLOOR(qg.grade / :bandwidth1) AS band,
COUNT(1) AS num
FROM {quiz_grades} qg
WHERE
$usql
- qg.quiz = ?
+ qg.quiz = :quizid
GROUP BY
- FLOOR(qg.grade/$bandwidth)
+ FLOOR(qg.grade / :bandwidth2)
ORDER BY
band";
-// TODO bandiwth with a placeholder.
- $params[] = $quizid;
+
+ $params['quizid'] = $quizid;
+ $params['bandwidth1'] = $bandwidth;
+ $params['bandwidth2'] = $bandwidth;
$data = $DB->get_records_sql_menu($sql, $params);
$qmsubselect = quiz_report_qm_filter_select($quiz);
$mform = new mod_quiz_report_responses_settings($reporturl,
- array('qmsubselect' => $qmsubselect, 'quiz' => $quiz, 'currentgroup' => $currentgroup));
+ array('qmsubselect' => $qmsubselect, 'quiz' => $quiz,
+ 'currentgroup' => $currentgroup, 'context' => $this->context));
if ($fromform = $mform->get_data()) {
$attemptsmode = $fromform->attemptsmode;
*/
class mod_quiz_report_responses_settings extends moodleform {
- function definition() {
- global $COURSE; // TODO get rid of this.
+ public function definition() {
$mform = $this->_form;
$mform->addElement('header', 'preferencespage', get_string('preferencespage', 'quiz_overview'));
if (!$this->_customdata['currentgroup']) {
$options[QUIZ_REPORT_ATTEMPTS_ALL] = get_string('optallattempts','quiz_overview');
}
- if ($this->_customdata['currentgroup'] || $COURSE->id != SITEID) {
+ if ($this->_customdata['currentgroup'] || !is_inside_frontpage($this->_customdata['context'])) {
$options[QUIZ_REPORT_ATTEMPTS_ALL_STUDENTS] = get_string('optallstudents','quiz_overview', $studentsstring);
$options[QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH] =
get_string('optattemptsonly','quiz_overview', $studentsstring);
require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());
$attemptobj->check_review_capability();
+echo $OUTPUT->header();
+
// Check permissions.
if ($attemptobj->is_own_attempt()) {
if (!$attemptobj->is_finished()) {
- echo $OUTPUT->header();
echo $OUTPUT->notification(get_string('cannotreviewopen', 'quiz'));
echo $OUTPUT->close_window_button();
echo $OUTPUT->footer();
- die;
+ die();
} else if (!$options->responses) {
$accessmanager = $attemptobj->get_access_manager(time());
- echo $OUTPUT->header();
echo $OUTPUT->notification($accessmanager->cannot_review_message($attemptobj->get_review_options()));
echo $OUTPUT->close_window_button();
echo $OUTPUT->footer();
- die;
+ die();
}
} else if (!$attemptobj->is_review_allowed()) {
}
// If they are not enrolled in this course in a good enough role, tell them to enrol.
-// TODO, review this.
if (!($canattempt || $canpreview || $canreviewmine)) {
echo $OUTPUT->box('<p>' . get_string('youneedtoenrol', 'quiz') . "</p>\n\n<p>" .
$OUTPUT->continue_button($CFG->wwwroot . '/course/view.php?id=' . $course->id) .
*/
public function render(question_display_options $options, $number,
core_question_renderer $qoutput, qtype_renderer $qtoutput) {
- $behaviouroutput = $this->get_renderer();
+ $behaviouroutput = $this->get_renderer($qoutput->get_page());
$options = clone($options);
$this->adjust_display_options($options);
return $qoutput->question($this->qa, $behaviouroutput, $qtoutput, $options, $number);
}
/**
+ * @param moodle_page $page the page to render for.
* @return qbehaviour_renderer get the appropriate renderer to use for this model.
*/
- public function get_renderer() {
- global $PAGE;
- return $PAGE->get_renderer(get_class($this));
+ public function get_renderer(moodle_page $page) {
+ return $page->get_renderer(get_class($this));
}
/**
*/
class qbehaviour_missing_test extends UnitTestCase {
public function test_missing_cannot_start() {
- $qa = new question_attempt(test_question_maker::make_a_truefalse_question(), 0, 0);
+ $qa = new question_attempt(test_question_maker::make_a_truefalse_question(), 0);
$behaviour = new qbehaviour_missing($qa, 'deferredfeedback');
$this->expectException();
$behaviour->init_first_step(new question_attempt_step(array()));
}
public function test_missing_cannot_process() {
- $qa = new question_attempt(test_question_maker::make_a_truefalse_question(), 0, 0);
+ $qa = new question_attempt(test_question_maker::make_a_truefalse_question(), 0);
$behaviour = new qbehaviour_missing($qa, 'deferredfeedback');
$this->expectException();
$behaviour->process_action(new question_attempt_pending_step(array()));
}
public function test_missing_cannot_get_min_grade() {
- $qa = new question_attempt(test_question_maker::make_a_truefalse_question(), 0, 0);
+ $qa = new question_attempt(test_question_maker::make_a_truefalse_question(), 0);
$behaviour = new qbehaviour_missing($qa, 'deferredfeedback');
$this->expectException();
$behaviour->get_min_fraction();
$opaquestate = qtype_opaque_update_state($this->qa, $pendingstep);
} catch (SoapFault $sf) {
print_object($sf);
- return question_attempt::DISCARD; // TODO
+ return question_attempt::DISCARD; // TODO better Opaque error handling.
}
if ($opaquestate->resultssequencenumber != $this->qa->get_num_steps()) {
$npsql = " and parent='0' ";
}
- // get (list) of categories
+ // Get list of categories
if ($recurse) {
$categorylist = question_categorylist($category->id);
- }
- else {
- $categorylist = $category->id;
+ } else {
+ $categorylist = array($category->id);
}
// get the list of questions for the category
- list ($usql, $params) = $DB->get_in_or_equal(explode(',', $categorylist));
- if ($questions = $DB->get_records_select("question","category $usql $npsql", $params, "qtype, name ASC")) {
+ list($usql, $params) = $DB->get_in_or_equal($categorylist);
+ if ($questions = $DB->get_records_select('question', "category $usql $npsql", $params, 'qtype, name')) {
// iterate through questions, getting stuff we need
foreach($questions as $question) {
}
if ($recurse) {
- $categoryids = explode(',', question_categorylist($category->id));
+ $categoryids = question_categorylist($category->id);
} else {
$categoryids = array($category->id);
}
$cmoptions->hasattempts = !empty($this->quizhasattempts);
$strselectall = get_string('selectall');
- $strselectnone = get_string('selectnone');
+ $strselectnone = get_string('deselectall');
$strdelete = get_string('delete');
list($categoryid, $contextid) = explode(',', $categoryandcontext);
}
if ($canmoveall && count($addcontexts)) {
- echo '<input type="submit" name="move" value="'.get_string('moveto')."\" />\n";
+ echo '<input type="submit" name="move" value="'.get_string('moveto', 'question')."\" />\n";
question_category_select_menu($addcontexts, false, 0, "$category->id,$category->contextid");
}
function question_edit_setup($edittab, $baseurl, $requirecmid = false, $requirecourseid = true) {
global $DB, $PAGE;
- //$thispageurl is used to construct urls for all question edit pages we link to from this page. It contains an array
- //of parameters that are passed from page to page.
-// $thispageurl = new moodle_url($PAGE->url); //TODO: this looks dumb, because this method is called BEFORE $PAGE->set_page() !!!!
$thispageurl = new moodle_url($baseurl);
$thispageurl->remove_all_params(); // We are going to explicity add back everything important - this avoids unwanted params from being retained.
foreach (get_plugin_list('qtype') as $plugin => $notused) {
try {
$qtypes[$plugin] = self::get_qtype($plugin);
- } catch (Exception $e) {
- // TODO ingore, but review this later.
+ } catch (coding_exception $e) {
+ // Catching coding_exceptions here means that incompatible
+ // question types do not cause the rest of Moodle to break.
}
}
return $qtypes;
class question_finder {
/**
* Get the ids of all the questions in a list of categoryies.
- * @param int|string|array $categoryids either a categoryid, or a comma-separated list
+ * @param array $categoryids either a categoryid, or a comma-separated list
* category ids, or an array of them.
- * @param string $extraconditions extra conditions to AND with the rest of the where clause.
+ * @param string $extraconditions extra conditions to AND with the rest of
+ * the where clause. Must use named parameters.
+ * @param array $extraparams any parameters used by $extraconditions.
* @return array questionid => questionid.
*/
- public function get_questions_from_categories($categoryids, $extraconditions) {
+ public function get_questions_from_categories($categoryids, $extraconditions, $extraparams = array()) {
global $DB;
- if (is_array($categoryids)) {
- $categoryids = implode(',', $categoryids);
- }
+ list($qcsql, $qcparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'qc0000');
if ($extraconditions) {
$extraconditions = ' AND (' . $extraconditions . ')';
}
- // TODO switch to using $DB->in_or_equal.
- $questionids = $DB->get_records_select_menu('question',
- "category IN ($categoryids)
+
+ return $DB->get_records_select_menu('question',
+ "category $qcsql
AND parent = 0
AND hidden = 0
- $extraconditions", array(), '', 'id,id AS id2');
- return $questionids;
+ $extraconditions", $qcparams + $extraparams, '', 'id,id AS id2');
}
}
* this method.
*
* @param string $qubaid SQL fragment that controls which usage is summed.
- * This might be the name of a column in the outer query.
+ * This will normally be the name of a column in the outer query. Not that this
+ * SQL fragment must not contain any placeholders.
* @return string SQL code for the subquery.
*/
public function sum_usage_marks_subquery($qubaid) {
JOIN {question_attempt_steps} qas ON qas.id = lateststepid.latestid
WHERE qa.questionusageid = $qubaid
HAVING COUNT(CASE WHEN qas.state = 'needsgrading' AND qa.maxmark > 0 THEN 1 ELSE NULL END) = 0";
- // TODO handle $qubaid with placeholders.
}
public function question_attempt_latest_state_view($alias) {
}
/**
- * Delete a {@link question_usage_by_activity} from the database, based on its id.
- * @param int $qubaid the id of the usage to delete.
+ * Delete {@link question_usage_by_activity}s from the database that match
+ * an arbitrary SQL where clause.
+ * @param string $where a where clause. Becuase of MySQL limitations, you
+ * must refer to {question_usages}.id in full like that.
+ * @param array $params values to substitute for placeholders in $where.
*/
public static function delete_questions_usage_by_activities($where, $params) {
$dm = new question_engine_data_mapper();
* @return int the number used to identify this question within this usage.
*/
public function add_question(question_definition $question, $maxmark = null) {
- $qa = new question_attempt($question, $this->get_id(),
- $this->context->id, $this->observer, $maxmark);
+ $qa = new question_attempt($question, $this->get_id(), $this->observer, $maxmark);
if (count($this->questionattempts) == 0) {
$this->questionattempts[1] = $qa;
} else {
$this->observer->notify_delete_attempt_steps($oldqa);
$newqa = new question_attempt($oldqa->get_question(), $oldqa->get_usage_id(),
- $this->context->id, $this->observer, $newmaxmark);
+ $this->observer, $newmaxmark);
$newqa->set_database_id($oldqa->get_database_id());
$newqa->regrade($oldqa, $finished);
/** @var integer|string the id of the question_usage_by_activity we belong to. */
protected $usageid;
- /** @var integer the id of the context this question_attempt belongs to. */
- // TODO I don't think this is actually needed.
- protected $owningcontextid = null;
-
/** @var integer the number used to identify this question_attempt within the usage. */
protected $slot = null;
* @param number $maxmark the maximum grade for this question_attempt. If not
* passed, $question->defaultmark is used.
*/
- public function __construct(question_definition $question, $usageid, $owningcontextid,
+ public function __construct(question_definition $question, $usageid,
question_usage_observer $observer = null, $maxmark = null) {
$this->question = $question;
$this->usageid = $usageid;
- $this->owningcontextid = $owningcontextid;
if (is_null($observer)) {
$observer = new question_usage_null_observer();
}
* @param string|null $number The question number to display.
* @return string HTML fragment representing the question.
*/
- public function render($options, $number) {
- global $PAGE;
- $qoutput = $PAGE->get_renderer('core', 'question');
- $qtoutput = $this->question->get_renderer();
+ public function render($options, $number, $page = null) {
+ if (is_null($page)) {
+ global $PAGE;
+ $page = $PAGE;
+ }
+ $qoutput = $page->get_renderer('core', 'question');
+ $qtoutput = $this->question->get_renderer($page);
return $this->behaviour->render($options, $number, $qoutput, $qtoutput);
}
* attempt is displayed in the body.
* @return string HTML fragment.
*/
- public function render_head_html() {
- return $this->question->get_renderer()->head_code($this) .
- $this->behaviour->get_renderer()->head_code($this);
+ public function render_head_html($page = null) {
+ if (is_null($page)) {
+ global $PAGE;
+ $page = $PAGE;
+ }
+ // TODO go via behaviour.
+ return $this->question->get_renderer($page)->head_code($this) .
+ $this->behaviour->get_renderer($page)->head_code($this);
}
/**
}
$qa = new question_attempt($question, $record->questionusageid,
- $record->contextid, null, $record->maxmark + 0);
+ null, $record->maxmark + 0);
$qa->set_database_id($record->questionattemptid);
$qa->set_number_in_usage($record->slot);
$qa->minfraction = $record->minfraction + 0;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_question_renderer extends plugin_renderer_base {
+ public function get_page() {
+ return $this->page;
+ }
/**
* Generate the display of a question in a particular state, and with certain
* @return question_attempt the question attempt.
*/
public function get_a_qa($question, $maxmark = 3) {
- return new question_attempt($question, 13, 0, null, $maxmark);
+ return new question_attempt($question, 13, null, $maxmark);
}
/**
$this->question = test_question_maker::make_a_description_question();
$this->question->defaultmark = 3;
$this->usageid = 13;
- $this->qa = new question_attempt($this->question, $this->usageid, 0);
+ $this->qa = new question_attempt($this->question, $this->usageid);
}
public function tearDown() {
}
public function test_constructor_sets_maxmark() {
- $qa = new question_attempt($this->question, $this->usageid, 0);
+ $qa = new question_attempt($this->question, $this->usageid);
$this->assertIdentical($this->question, $qa->get_question());
$this->assertEqual(3, $qa->get_max_mark());
}
public function test_maxmark_beats_default_mark() {
- $qa = new question_attempt($this->question, $this->usageid, 0, null, 2);
+ $qa = new question_attempt($this->question, $this->usageid, null, 2);
$this->assertEqual(2, $qa->get_max_mark());
}
public function setUp() {
$this->question = test_question_maker::make_a_description_question();
- $this->qa = new testable_question_attempt($this->question, 0, 0, null, 2);
+ $this->qa = new testable_question_attempt($this->question, 0, null, 2);
for ($i = 0; $i < 3; $i++) {
$step = new question_attempt_step(array('i' => $i));
$this->qa->add_step($step);
}
public function test_cannot_get_min_fraction_before_start() {
- $qa = new question_attempt($this->question, null, 0);
+ $qa = new question_attempt($this->question, 0);
$this->expectException();
$qa->get_min_fraction();
}
public function setUp() {
$question = test_question_maker::make_a_description_question();
- $this->qa = new testable_question_attempt($question, 0, 0);
+ $this->qa = new testable_question_attempt($question, 0);
for ($i = 0; $i < 3; $i++) {
$step = new question_attempt_step(array('i' => $i));
$this->qa->add_step($step);
return NULL;
}
- /**
- * get directory into which export is going
- * @return string file path
- */
- function question_get_export_dir() {
- // TODO is this still used.
- global $USER;
- if ($this->canaccessbackupdata) {
- $dirname = get_string('exportfilename', 'question');
- $path = $this->course->id . '/backupdata/' . $dirname; // backupdata is protected directory
- } else {
- $path = 'temp/questionexport/' . $USER->id;
- }
- return $path;
- }
-
/**
* Convert the question text to plain text, so it can safely be displayed
* during import to let the user see roughly what is going on.
*/
function exportprocess() {
- global $CFG, $OUTPUT;
+ global $CFG, $OUTPUT, $USER;
$courseid = $this->course->id;
+ $path = 'temp/qformat_qti_two/' . $USER->id . '/' . $this->filename;
// create a directory for the exports (if not already existing)
- if (!$export_dir = make_upload_directory($this->question_get_export_dir().'/'.$this->filename)) {
- print_error('cannotcreatepath', 'question', '', $export_dir);
+ if (!make_upload_directory($path)) {
+ throw new moodle_exception('cannotcreatepath', 'question', '', $path);
}
- $path = $CFG->dataroot.'/'.$this->question_get_export_dir().'/'.$this->filename;
+ $path = $CFG->dataroot . '/' . $path;
// get the questions (from database) in this category
$questions = get_questions_category( $this->category );
// Do anything before that we need to
if (!$qformat->importpreprocess()) {
- //TODO: need more detailed error info
print_error('cannotimport', '', $thispageurl->out());
}
// Process the uploaded file
if (!$qformat->importprocess($category)) {
- //TODO: need more detailed error info
print_error('cannotimport', '', $thispageurl->out());
}
// In case anything needs to be done after
if (!$qformat->importpostprocess()) {
- //TODO: need more detailed error info
print_error('cannotimport', '', $thispageurl->out());
}
* files to be processed both in backup and restore.
*/
public static function get_qtype_fileareas() {
- // TODO: Discuss. Commented below are the "in theory" correct
- // mappings for those fileareas. Instead we are using question for
- // them, that will cause problems in the future if we want to change
- // any of them to be 1..n (i.e. we should be always pointing to own id)
return array(
- //'instruction' => 'question_numerical_option',
- //'correctfeedback' => 'question_calculated_option',
- //'partiallycorrectfeedback' => 'question_calculated_option',
- //'incorrectfeedback' => 'question_calculated_option');
'instruction' => 'question_created',
'correctfeedback' => 'question_created',
'partiallycorrectfeedback' => 'question_created',
}
return true;
} else if ($filearea == 'instruction') {
- // TODO: should it be display all the time like questiontext?
- // check if question id exists
+ // Displayed all the time like the question text. Check if question id exists
if ($itemid != $question->id) {
return false;
} else {
return true;
}
} else if (in_array($filearea, array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) {
- // TODO: calculated type doesn't display question feedback yet
+ // Note: calculated type doesn't display question feedback yet
return false;
} else {
return parent::check_file_access($question, $state, $options, $contextid, $component,
// Save the units.
$virtualqtype = $this->get_virtual_qtype($question);
- // TODO: What is this?
- // $result = $virtualqtype->save_numerical_units($question);
if (isset($result->error)) {
return $result;
} else {
$itemid = reset($args);
if ($component == 'question' && $filearea == 'answerfeedback') {
- // check if answer id exists
+ // Check if answer id exists
$result = $options->feedback && array_key_exists($itemid, $question->options->answers);
if (!$result) {
return false;
}
return true;
} else if ($filearea == 'instruction') {
- // TODO: should it be display all the time like questiontext?
- // check if question id exists
+ // Displayed all the time like the question text. Check if question id exists
if ($itemid != $question->id) {
return false;
} else {
public function test_render_missing() {
$questiondata = $this->get_unknown_questiondata();
$q = question_bank::make_question($questiondata);
- $qa = new testable_question_attempt($q, 0, 0);
+ $qa = new testable_question_attempt($q, 0);
$step = new question_attempt_step(array('answer' => 'frog'));
$step->set_state(question_state::$todo);
* files to be processed both in backup and restore.
*/
public static function get_qtype_fileareas() {
- // TODO: Discuss. Commented below are the "in theory" correct
- // mappings for those fileareas. Instead we are using question for
- // them, that will cause problems in the future if we want to change
- // any of them to be 1..n (i.e. we should be always pointing to own id)
return array(
- //'correctfeedback' => 'question_multichoice',
- //'partiallycorrectfeedback' => 'question_multichoice',
- //'incorrectfeedback' => 'question_multichoice');
'correctfeedback' => 'question_created',
'partiallycorrectfeedback' => 'question_created',
'incorrectfeedback' => 'question_created');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multichoice_single_question extends qtype_multichoice_base {
- public function get_renderer() {
- global $PAGE; // TODO get rid of this global.
- return $PAGE->get_renderer('qtype_multichoice', 'single');
+ public function get_renderer(moodle_page $page) {
+ return $page->get_renderer('qtype_multichoice', 'single');
}
public function get_min_fraction() {
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multichoice_multi_question extends qtype_multichoice_base {
- public function get_renderer() {
- global $PAGE; // TODO get rid of this global.
- return $PAGE->get_renderer('qtype_multichoice', 'multi');
+ public function get_renderer(moodle_page $page) {
+ return $page->get_renderer('qtype_multichoice', 'multi');
}
public function get_min_fraction() {
-/* TODO */
.que.multichoice .answer .specificfeedback {
display: inline;
padding: 0 0.7em;
* files to be processed both in backup and restore.
*/
public static function get_qtype_fileareas() {
- // TODO: Discuss. Commented below are the "in theory" correct
- // mappings for those fileareas. Instead we are using question for
- // them, that will cause problems in the future if we want to change
- // any of them to be 1..n (i.e. we should be always pointing to own id)
- return array(
- //'instruction' => 'question_numerical_option');
- 'instruction' => 'question_created');
+ return array('instruction' => 'question_created');
}
}
$mform->setType('unitpenalty', PARAM_NUMBER);
$mform->setDefault('unitpenalty', 0.1);
$mform->setDefault('unitgradingtypes', 1);
- $mform->addHelpButton('penaltygrp', 'unitpenalty', 'qtype_numerical'); // TODO help did not exist before MDL-21695
+ $mform->addHelpButton('penaltygrp', 'unitpenalty', 'qtype_numerical');
$mform->setDefault('unitsleft', 0);
$mform->setType('instructions', PARAM_RAW);
$mform->addHelpButton('instructions', 'numericalinstructions', 'qtype_numerical');
/**
* @return qtype_renderer the renderer to use for outputting this question.
*/
- public function get_renderer() {
- global $PAGE; // TODO get rid of this global.
- return $PAGE->get_renderer('qtype_' . $this->qtype->name());
+ public function get_renderer(moodle_page $page) {
+ return $page->get_renderer($this->qtype->plugin_name());
}
/**
if ($question->questiontext) {
$categorylist = question_categorylist($question->category);
} else {
- $categorylist = $question->category;
+ $categorylist = array($question->category);
}
+ list($qcsql, $qcparams) = $DB->get_in_or_equal($categorylist);
+ // TODO use in_or_equal for $otherquestionsinuse and $this->manualqtypes
return $DB->record_exists_select('question',
- "category IN ($categorylist)
+ "category $qcsql
AND parent = 0
AND hidden = 0
AND id NOT IN ($otherquestionsinuse)
- AND qtype IN ($this->manualqtypes)");
+ AND qtype IN ($this->manualqtypes)", $qcparams);
}
/**
if ($subcategories) {
$categoryids = question_categorylist($categoryid);
} else {
- $categoryids = $categoryid;
+ $categoryids = array($categoryid);
}
$questionids = question_bank::get_finder()->get_questions_from_categories(
// recurse into subcategories
$categorylist = question_categorylist($question->category);
} else {
- $categorylist = $question->category;
+ $categorylist = array($question->category);
}
$saquestions = $this->get_sa_candidates($categorylist, $cmoptions->questionsinuse);
$count = count($saquestions);
$wanted = $question->options->choose;
- $errorstr = '';
- if ($count < $wanted && has_coursecontact_role($USER->id)) { //TODO: this teacher test is far from optimal
- if ($count >= 2) {
- $errorstr = "Error: could not get enough Short-Answer questions!
- Got $count Short-Answer questions, but wanted $wanted.
- Reducing number to choose from to $count!";
- $wanted = $question->options->choose = $count;
- } else {
- $errorstr = "Error: could not get enough Short-Answer questions!
- This can happen if all available Short-Answer questions are already
- taken up by other Random questions or Random Short-Answer question.
- Another possible cause for this error is that Short-Answer
- questions were deleted after this Random Short-Answer question was
- created.";
- }
- echo $OUTPUT->notification($errorstr);
- $errorstr = '<span class="notifyproblem">' . $errorstr . '</span>';
- }
if ($count < $wanted) {
- $question->questiontext = "$errorstr<br /><br />Insufficient selection options are
- available for this question, therefore it is not available in this
- quiz. Please inform your teacher.";
+ $question->questiontext = "Insufficient selection options are
+ available for this question, therefore it is not available in this
+ quiz. Please inform your teacher.";
// Treat this as a description from this point on
$question->qtype = DESCRIPTION;
return true;
return $response;
}
- function get_sa_candidates($categorylist, $questionsinuse=0) {
+ function get_sa_candidates($categorylist, $questionsinuse = 0) {
global $DB;
- list ($usql, $params) = $DB->get_in_or_equal(explode(',', $categorylist));
+ list ($usql, $params) = $DB->get_in_or_equal($categorylist);
list ($ques_usql, $ques_params) = $DB->get_in_or_equal(explode(',', $questionsinuse), SQL_PARAMS_QM, null, false);
$params = array_merge($params, $ques_params);
return $DB->get_records_select('question',
// recurse into subcategories
$categorylist = question_categorylist($question->category);
} else {
- $categorylist = $question->category;
+ $categorylist = array($question->category);
}
$question->options->subquestions = $this->get_sa_candidates($categorylist);
* @return string HTML fragment.
*/
public function head_code(question_attempt $qa) {
- // TODO I think we can get rid of this, but what about Opaque?
+ // This method is used by the Opaque question type. The remote question
+ // engine can send back arbitrary CSS that we have to link to in the
+ // page header. If it was not for that, we might be able to eliminate
+ // this method and load the required CSS and JS some other way.
$qa->get_question()->qtype->find_standard_scripts();
}