/** * Upgrade states for an attempt to Moodle 1.5 model * * Any state that does not yet have its timestamp set to nonzero has not yet been * upgraded from Moodle 1.4. The reason these are still around is that for large * sites it would have taken too long to upgrade all states at once. This function * sets the timestamp field and creates an entry in the question_sessions table. * @param object $attempt The attempt whose states need upgrading */ function quiz_upgrade_very_old_question_sessions($attempt) { global $DB; // The old quiz model only allowed a single response per quiz attempt so that there will be // only one state record per question for this attempt. // We set the timestamp of all states to the timemodified field of the attempt. $DB->execute("UPDATE {question_states} SET timestamp = ? WHERE attempt = ?", array($attempt->timemodified, $attempt->uniqueid)); // For each state we create an entry in the question_sessions table, with both newest and // newgraded pointing to this state. // Actually we only do this for states whose question is actually listed in $attempt->layout. // We do not do it for states associated to wrapped questions like for example the questions // used by a random question $session = new stdClass(); $session->attemptid = $attempt->uniqueid; $session->sumpenalty = 0; $session->manualcomment = ''; $session->manualcommentformat = FORMAT_HTML; $session->flagged = 0; $questionlist = str_replace(',0', '', quiz_clean_layout($attempt->layout, true)); if (!$questionlist) { return; } list($usql, $question_params) = $DB->get_in_or_equal(explode(',', $questionlist)); $params = array_merge(array($attempt->uniqueid), $question_params); if ($states = $DB->get_records_select('question_states', "attempt = ? AND question {$usql}", $params)) { foreach ($states as $state) { if ($DB->record_exists('question_sessions', array('attemptid' => $attempt->uniqueid, 'questionid' => $state->question))) { // It was possible for the code to get here when some of the necessary // question_sessions were already in the database. That lead to a // unique key violation, so we manually detect and avoid that. continue; } $session->newgraded = $state->id; $session->newest = $state->id; $session->questionid = $state->question; $DB->insert_record('question_sessions', $session, false); } } // It was possible to have old question_states records for this attempt but // pointing to questionids that were no longer in quiz_attempt->layout. // That makes no sense, and will break things later in the upgrade, so delete // those now. list($qidsql, $params) = $DB->get_in_or_equal(explode(',', $questionlist), SQL_PARAMS_QM, 'param', false); $params[] = $attempt->uniqueid; $DB->delete_records_select('question_states', "question {$usql} AND attempt = ?", $params); }
function test_quiz_clean_layout() { // Without stripping empty pages. $this->assertEqual(quiz_clean_layout(',,1,,,2,,'), '1,2,0'); $this->assertEqual(quiz_clean_layout(''), '0'); $this->assertEqual(quiz_clean_layout('0'), '0'); $this->assertEqual(quiz_clean_layout('0,0'), '0,0'); $this->assertEqual(quiz_clean_layout('0,0,0'), '0,0,0'); $this->assertEqual(quiz_clean_layout('1'), '1,0'); $this->assertEqual(quiz_clean_layout('1,2'), '1,2,0'); $this->assertEqual(quiz_clean_layout('1,0,2'), '1,0,2,0'); $this->assertEqual(quiz_clean_layout('0,1,0,0,2,0'), '0,1,0,0,2,0'); // With stripping empty pages. $this->assertEqual(quiz_clean_layout('', true), '0'); $this->assertEqual(quiz_clean_layout('0', true), '0'); $this->assertEqual(quiz_clean_layout('0,0', true), '0'); $this->assertEqual(quiz_clean_layout('0,0,0', true), '0'); $this->assertEqual(quiz_clean_layout('1', true), '1,0'); $this->assertEqual(quiz_clean_layout('1,2', true), '1,2,0'); $this->assertEqual(quiz_clean_layout('1,0,2', true), '1,0,2,0'); $this->assertEqual(quiz_clean_layout('0,1,0,0,2,0', true), '1,0,2,0'); }
/** * Upgrade states for an attempt to Moodle 1.5 model * * Any state that does not yet have its timestamp set to nonzero has not yet been * upgraded from Moodle 1.4. The reason these are still around is that for large * sites it would have taken too long to upgrade all states at once. This function * sets the timestamp field and creates an entry in the question_sessions table. * @param object $attempt The attempt whose states need upgrading */ function quiz_upgrade_very_old_question_sessions($attempt) { global $DB; // The old quiz model only allowed a single response per quiz attempt so that there will be // only one state record per question for this attempt. // We set the timestamp of all states to the timemodified field of the attempt. $DB->execute("UPDATE {question_states} SET timestamp = ? WHERE attempt = ?", array($attempt->timemodified, $attempt->uniqueid)); // For each state we create an entry in the question_sessions table, with both newest and // newgraded pointing to this state. // Actually we only do this for states whose question is actually listed in $attempt->layout. // We do not do it for states associated to wrapped questions like for example the questions // used by a random question $session = new stdClass(); $session->attemptid = $attempt->uniqueid; $session->sumpenalty = 0; $session->manualcomment = ''; $session->manualcommentformat = FORMAT_HTML; $session->flagged = 0; $questionlist = str_replace(',0', '', quiz_clean_layout($layout, true)); if (!$questionlist) { return; } list($usql, $question_params) = $DB->get_in_or_equal(explode(',', $questionlist)); $params = array_merge(array($attempt->uniqueid), $question_params); if ($states = $DB->get_records_select('question_states', "attempt = ? AND question $usql", $params)) { foreach ($states as $state) { $session->newgraded = $state->id; $session->newest = $state->id; $session->questionid = $state->question; $DB->insert_record('question_sessions', $session, false); } } }
$select = html_writer::select($randomcount, 'randomcount', '1', null, $attributes); $out .= get_string('addrandom', 'quiz', $select); $out .= '<input type="hidden" name="recurse" value="' . $recurse . '" />'; $out .= '<input type="hidden" name="categoryid" value="' . $category->id . '" />'; $out .= ' <input type="submit" name="addrandom" value="' . get_string('addtoquiz', 'quiz') . '"' . $disabled . ' />'; $out .= $OUTPUT->help_icon('addarandomquestion', 'quiz'); } return $out; } // These params are only passed from page request to request while we stay on // this page otherwise they would go in question_edit_setup. $quiz_reordertool = optional_param('reordertool', -1, PARAM_BOOL); $quiz_qbanktool = optional_param('qbanktool', -1, PARAM_BOOL); $scrollpos = optional_param('scrollpos', '', PARAM_INT); list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) = question_edit_setup('editq', '/mod/quiz/edit.php', true); $quiz->questions = quiz_clean_layout($quiz->questions); $defaultcategoryobj = question_make_default_categories($contexts->all()); $defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid; if ($quiz_qbanktool > -1) { $thispageurl->param('qbanktool', $quiz_qbanktool); set_user_preference('quiz_qbanktool_open', $quiz_qbanktool); } else { $quiz_qbanktool = get_user_preferences('quiz_qbanktool_open', 0); } if ($quiz_reordertool > -1) { $thispageurl->param('reordertool', $quiz_reordertool); set_user_preference('quiz_reordertab', $quiz_reordertool); } else { $quiz_reordertool = get_user_preferences('quiz_reordertab', 0); } $canaddrandom = $contexts->have_cap('moodle/question:useall');
/** * Prints a list of quiz questions for the edit.php main view for edit * ($reordertool = false) and order and paging ($reordertool = true) tabs * * @param object $quiz This is not the standard quiz object used elsewhere but * it contains the quiz layout in $quiz->questions and the grades in * $quiz->grades * @param moodle_url $pageurl The url of the current page with the parameters required * for links returning to the current page, as a moodle_url object * @param bool $allowdelete Indicates whether the delete icons should be displayed * @param bool $reordertool Indicates whether the reorder tool should be displayed * @param bool $quiz_qbanktool Indicates whether the question bank should be displayed * @param bool $hasattempts Indicates whether the quiz has attempts * @param object $defaultcategoryobj * @param bool $canaddquestion is the user able to add and use questions anywere? * @param bool $canaddrandom is the user able to add random questions anywere? */ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool, $quiz_qbanktool, $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom) { global $CFG, $DB, $OUTPUT; $strorder = get_string('order'); $strquestionname = get_string('questionname', 'quiz'); $strmaxmark = get_string('markedoutof', 'question'); $strremove = get_string('remove', 'quiz'); $stredit = get_string('edit'); $strview = get_string('view'); $straction = get_string('action'); $strmove = get_string('move'); $strmoveup = get_string('moveup'); $strmovedown = get_string('movedown'); $strsave = get_string('save', 'quiz'); $strreorderquestions = get_string('reorderquestions', 'quiz'); $strselectall = get_string('selectall', 'quiz'); $strselectnone = get_string('selectnone', 'quiz'); $strtype = get_string('type', 'quiz'); $strpreview = get_string('preview', 'quiz'); if ($quiz->questions) { list($usql, $params) = $DB->get_in_or_equal(explode(',', $quiz->questions)); $params[] = $quiz->id; $questions = $DB->get_records_sql("SELECT q.*, qc.contextid, qqi.grade as maxmark FROM {question} q JOIN {question_categories} qc ON qc.id = q.category JOIN {quiz_question_instances} qqi ON qqi.question = q.id WHERE q.id $usql AND qqi.quiz = ?", $params); } else { $questions = array(); } $layout = quiz_clean_layout($quiz->questions); $order = explode(',', $layout); $lastindex = count($order) - 1; $disabled = ''; $pagingdisabled = ''; if ($hasattempts) { $disabled = 'disabled="disabled"'; } if ($hasattempts || $quiz->shufflequestions) { $pagingdisabled = 'disabled="disabled"'; } $reordercontrolssetdefaultsubmit = '<div style="display:none;">' . '<input type="submit" name="savechanges" value="' . $strreorderquestions . '" ' . $pagingdisabled . ' /></div>'; $reordercontrols1 = '<div class="addnewpagesafterselected">' . '<input type="submit" name="addnewpagesafterselected" value="' . get_string('addnewpagesafterselected', 'quiz') . '" ' . $pagingdisabled . ' /></div>'; $reordercontrols1 .= '<div class="quizdeleteselected">' . '<input type="submit" name="quizdeleteselected" ' . 'onclick="return confirm(\'' . get_string('areyousureremoveselected', 'quiz') . '\');" value="' . get_string('removeselected', 'quiz') . '" ' . $disabled . ' /></div>'; $a = '<input name="moveselectedonpagetop" type="text" size="2" ' . $pagingdisabled . ' />'; $b = '<input name="moveselectedonpagebottom" type="text" size="2" ' . $pagingdisabled . ' />'; $reordercontrols2top = '<div class="moveselectedonpage">' . get_string('moveselectedonpage', 'quiz', $a) . '<input type="submit" name="savechanges" value="' . $strmove . '" ' . $pagingdisabled . ' />' . ' <br /><input type="submit" name="savechanges" value="' . $strreorderquestions . '" /></div>'; $reordercontrols2bottom = '<div class="moveselectedonpage">' . '<input type="submit" name="savechanges" value="' . $strreorderquestions . '" /><br />' . get_string('moveselectedonpage', 'quiz', $b) . '<input type="submit" name="savechanges" value="' . $strmove . '" ' . $pagingdisabled . ' /> ' . '</div>'; $reordercontrols3 = '<a href="javascript:select_all_in(\'FORM\', null, ' . '\'quizquestions\');">' . $strselectall . '</a> /'; $reordercontrols3.= ' <a href="javascript:deselect_all_in(\'FORM\', ' . 'null, \'quizquestions\');">' . $strselectnone . '</a>'; $reordercontrolstop = '<div class="reordercontrols">' . $reordercontrolssetdefaultsubmit . $reordercontrols1 . $reordercontrols2top . $reordercontrols3 . "</div>"; $reordercontrolsbottom = '<div class="reordercontrols">' . $reordercontrolssetdefaultsubmit . $reordercontrols2bottom . $reordercontrols1 . $reordercontrols3 . "</div>"; if ($reordertool) { echo '<form method="post" action="edit.php" id="quizquestions"><div>'; echo html_writer::input_hidden_params($pageurl); echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />'; echo $reordercontrolstop; } // The current question ordinal (no descriptions). $qno = 1; // The current question (includes questions and descriptions). $questioncount = 0; // The current page number in iteration. $pagecount = 0; $pageopen = false; $returnurl = $pageurl->out_as_local_url(false); $questiontotalcount = count($order); foreach ($order as $count => $qnum) { $reordercheckbox = ''; $reordercheckboxlabel = ''; $reordercheckboxlabelclose = ''; // If the questiontype is missing change the question type. if ($qnum && !array_key_exists($qnum, $questions)) { $fakequestion = new stdClass(); $fakequestion->id = $qnum; $fakequestion->category = 0; $fakequestion->qtype = 'missingtype'; $fakequestion->name = get_string('missingquestion', 'quiz'); $fakequestion->questiontext = ' '; $fakequestion->questiontextformat = FORMAT_HTML; $fakequestion->length = 1; $questions[$qnum] = $fakequestion; $quiz->grades[$qnum] = 0; } else if ($qnum && !question_bank::qtype_exists($questions[$qnum]->qtype)) { $questions[$qnum]->qtype = 'missingtype'; } if ($qnum != 0 || ($qnum == 0 && !$pageopen)) { // This is either a question or a page break after another (no page is currently open). if (!$pageopen) { // If no page is open, start display of a page. $pagecount++; echo '<div class="quizpage"><span class="pagetitle">' . get_string('page') . ' ' . $pagecount . '</span><div class="pagecontent">'; $pageopen = true; } if ($qnum == 0 && $count < $questiontotalcount) { // This is the second successive page break. Tell the user the page is empty. echo '<div class="pagestatus">'; print_string('noquestionsonpage', 'quiz'); echo '</div>'; if ($allowdelete) { echo '<div class="quizpagedelete">'; echo $OUTPUT->action_icon($pageurl->out(true, array('deleteemptypage' => $count - 1, 'sesskey'=>sesskey())), new pix_icon('t/delete', $strremove), new component_action('click', 'M.core_scroll_manager.save_scroll_action'), array('title' => $strremove)); echo '</div>'; } } if ($qnum != 0) { $question = $questions[$qnum]; $questionparams = array( 'returnurl' => $returnurl, 'cmid' => $quiz->cmid, 'id' => $question->id); $questionurl = new moodle_url('/question/question.php', $questionparams); $questioncount++; // This is an actual question. ?> <div class="question"> <div class="questioncontainer <?php echo $question->qtype; ?>"> <div class="qnum"> <?php $reordercheckbox = ''; $reordercheckboxlabel = ''; $reordercheckboxlabelclose = ''; if ($reordertool) { $reordercheckbox = '<input type="checkbox" name="s' . $question->id . '" id="s' . $question->id . '" />'; $reordercheckboxlabel = '<label for="s' . $question->id . '">'; $reordercheckboxlabelclose = '</label>'; } if ($question->length == 0) { $qnodisplay = get_string('infoshort', 'quiz'); } else if ($quiz->shufflequestions) { $qnodisplay = '?'; } else { if ($qno > 999 || ($reordertool && $qno > 99)) { $qnodisplay = html_writer::tag('small', $qno); } else { $qnodisplay = $qno; } $qno += $question->length; } echo $reordercheckboxlabel . $qnodisplay . $reordercheckboxlabelclose . $reordercheckbox; ?> </div> <div class="content"> <div class="questioncontrols"> <?php if ($count != 0) { if (!$hasattempts) { $upbuttonclass = ''; if ($count >= $lastindex - 1) { $upbuttonclass = 'upwithoutdown'; } echo $OUTPUT->action_icon($pageurl->out(true, array('up' => $question->id, 'sesskey'=>sesskey())), new pix_icon('t/up', $strmoveup), new component_action('click', 'M.core_scroll_manager.save_scroll_action'), array('title' => $strmoveup)); } } if ($count < $lastindex - 1) { if (!$hasattempts) { echo $OUTPUT->action_icon($pageurl->out(true, array('down' => $question->id, 'sesskey'=>sesskey())), new pix_icon('t/down', $strmovedown), new component_action('click', 'M.core_scroll_manager.save_scroll_action'), array('title' => $strmovedown)); } } if ($allowdelete && ($question->qtype == 'missingtype' || question_has_capability_on($question, 'use', $question->category))) { // Remove from quiz, not question delete. if (!$hasattempts) { echo $OUTPUT->action_icon($pageurl->out(true, array('remove' => $question->id, 'sesskey'=>sesskey())), new pix_icon('t/delete', $strremove), new component_action('click', 'M.core_scroll_manager.save_scroll_action'), array('title' => $strremove)); } } ?> </div><?php if (!in_array($question->qtype, array('description', 'missingtype')) && !$reordertool) { ?> <div class="points"> <form method="post" action="edit.php" class="quizsavegradesform"><div> <fieldset class="invisiblefieldset" style="display: block;"> <label for="<?php echo "inputq$question->id" ?>"><?php echo $strmaxmark; ?></label>:<br /> <input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" /> <?php echo html_writer::input_hidden_params($pageurl); ?> <input type="hidden" name="savechanges" value="save" /> <?php echo '<input type="text" name="g' . $question->id . '" id="inputq' . $question->id . '" size="' . ($quiz->decimalpoints + 2) . '" value="' . (0 + $quiz->grades[$qnum]) . '" tabindex="' . ($lastindex + $qno) . '" />'; ?> <input type="submit" class="pointssubmitbutton" value="<?php echo $strsave; ?>" /> </fieldset> <?php if ($question->qtype == 'random') { echo '<a href="' . $questionurl->out() . '" class="configurerandomquestion">' . get_string("configurerandomquestion", "quiz") . '</a>'; } ?> </div> </form> </div> <?php } else if ($reordertool) { if ($qnum) { ?> <div class="qorder"> <?php echo '<input type="text" name="o' . $question->id . '" size="2" value="' . (10*$count + 10) . '" tabindex="' . ($lastindex + $qno) . '" />'; ?> </div> <?php } } ?> <div class="questioncontentcontainer"> <?php if ($question->qtype == 'random') { // It is a random question. if (!$reordertool) { quiz_print_randomquestion($question, $pageurl, $quiz, $quiz_qbanktool); } else { quiz_print_randomquestion_reordertool($question, $pageurl, $quiz); } } else { // It is a single question. if (!$reordertool) { quiz_print_singlequestion($question, $returnurl, $quiz); } else { quiz_print_singlequestion_reordertool($question, $returnurl, $quiz); } } ?> </div> </div> </div> </div> <?php } } // A page break: end the existing page. if ($qnum == 0) { if ($pageopen) { if (!$reordertool && !($quiz->shufflequestions && $count < $questiontotalcount - 1)) { quiz_print_pagecontrols($quiz, $pageurl, $pagecount, $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom); } else if ($count < $questiontotalcount - 1) { // Do not include the last page break for reordering // to avoid creating a new extra page in the end. echo '<input type="hidden" name="opg' . $pagecount . '" size="2" value="' . (10*$count + 10) . '" />'; } echo "</div></div>"; if (!$reordertool && !$quiz->shufflequestions) { echo $OUTPUT->container_start('addpage'); $url = new moodle_url($pageurl->out_omit_querystring(), array('cmid' => $quiz->cmid, 'courseid' => $quiz->course, 'addpage' => $count, 'sesskey' => sesskey())); echo $OUTPUT->single_button($url, get_string('addpagehere', 'quiz'), 'post', array('disabled' => $hasattempts, 'actions' => array(new component_action('click', 'M.core_scroll_manager.save_scroll_action')))); echo $OUTPUT->container_end(); } $pageopen = false; $count++; } } } if ($reordertool) { echo $reordercontrolsbottom; echo '</div></form>'; } }
$viewobj->editurl = new moodle_url('/mod/quiz/edit.php', array('cmid' => $cm->id)); $viewobj->backtocourseurl = new moodle_url('/course/view.php', array('id' => $course->id)); $viewobj->startattempturl = $quizobj->start_attempt_url(); $viewobj->startattemptwarning = $quizobj->confirm_start_attempt_message($unfinished); $viewobj->popuprequired = $accessmanager->attempt_must_be_in_popup(); $viewobj->popupoptions = $accessmanager->get_popup_options(); // Display information about this quiz. $viewobj->infomessages = $viewobj->accessmanager->describe_rules(); if ($quiz->attempts != 1) { $viewobj->infomessages[] = get_string('gradingmethod', 'quiz', quiz_get_grading_option_name($quiz->grademethod)); } // Determine wheter a start attempt button should be displayed. $viewobj->quizhasquestions = (bool) quiz_clean_layout($quiz->questions, true); $viewobj->preventmessages = array(); if (!$viewobj->quizhasquestions) { $viewobj->buttontext = ''; } else { if ($unfinished) { if ($canattempt) { $viewobj->buttontext = get_string('continueattemptquiz', 'quiz'); } else if ($canpreview) { $viewobj->buttontext = get_string('continuepreview', 'quiz'); } } else { if ($canattempt) { $viewobj->preventmessages = $viewobj->accessmanager->prevent_new_attempt(
/** * Prints a list of quiz questions for the edit.php main view for edit * ($reordertool = false) and order and paging ($reordertool = true) tabs * * @return int sum of maximum grades * @param object $quiz This is not the standard quiz object used elsewhere but * it contains the quiz layout in $quiz->questions and the grades in * $quiz->grades * @param object $pageurl The url of the current page with the parameters required * for links returning to the current page, as a moodle_url object * @param boolean $allowdelete Indicates whether the delete icons should be displayed * @param boolean $reordertool Indicates whether the reorder tool should be displayed * @param boolean $quiz_qbanktool Indicates whether the question bank should be displayed * @param boolean $hasattempts Indicates whether the quiz has attempts */ function quiz_print_question_list($quiz, $pageurl, $allowdelete = true, $reordertool = false, $quiz_qbanktool = false, $hasattempts = false) { global $USER, $CFG, $QTYPES, $DB; $strorder = get_string('order'); $strquestionname = get_string('questionname', 'quiz'); $strgrade = get_string('grade'); $strremove = get_string('remove', 'quiz'); $stredit = get_string('edit'); $strview = get_string('view'); $straction = get_string('action'); $strmove = get_string('move'); $strmoveup = get_string('moveup'); $strmovedown = get_string('movedown'); $strsave = get_string('save', 'quiz'); $strreorderquestions = get_string('reorderquestions', 'quiz'); $strselectall = get_string('selectall', 'quiz'); $strselectnone = get_string('selectnone', 'quiz'); $strtype = get_string('type', 'quiz'); $strpreview = get_string('preview', 'quiz'); if ($quiz->questions) { list($usql, $params) = $DB->get_in_or_equal(explode(',', $quiz->questions)); $questions = $DB->get_records_sql("SELECT q.*,c.contextid\n FROM {question} q,\n {question_categories} c\n WHERE q.id {$usql}\n AND q.category = c.id", $params); } else { $questions = array(); } $layout = quiz_clean_layout($quiz->questions); $order = explode(',', $layout); $lastindex = count($order) - 1; $disabled = ''; $pagingdisabled = ''; if ($hasattempts) { $disabled = 'disabled="disabled"'; } if ($hasattempts || $quiz->shufflequestions) { $pagingdisabled = 'disabled="disabled"'; } $reordercontrolssetdefaultsubmit = '<div style="display:none;">' . '<input type="submit" name="savechanges" value="' . $strreorderquestions . '" ' . $pagingdisabled . ' /></div>'; $reordercontrols1 = '<div class="addnewpagesafterselected">' . '<input type="submit" name="addnewpagesafterselected" value="' . get_string('addnewpagesafterselected', 'quiz') . '" ' . $pagingdisabled . ' /></div>'; $reordercontrols1 .= '<div class="quizdeleteselected">' . '<input type="submit" name="quizdeleteselected" ' . 'onclick="return confirm(\'' . get_string('areyousureremoveselected', 'quiz') . '\');" value="' . get_string('removeselected', 'quiz') . '" ' . $disabled . ' /></div>'; $a = '<input name="moveselectedonpagetop" type="text" size="2" ' . $pagingdisabled . ' />'; $reordercontrols2top = '<div class="moveselectedonpage">' . get_string('moveselectedonpage', 'quiz', $a) . '<input type="submit" name="savechanges" value="' . $strmove . '" ' . $pagingdisabled . ' />' . ' <br /><input type="submit" name="savechanges" value="' . $strreorderquestions . '" /></div>'; $reordercontrols2bottom = '<div class="moveselectedonpage">' . '<input type="submit" name="savechanges" value="' . $strreorderquestions . '" /><br />' . get_string('moveselectedonpage', 'quiz', $a) . '<input type="submit" name="savechanges" value="' . $strmove . '" ' . $pagingdisabled . ' /> ' . '</div>'; $reordercontrols3 = '<a href="javascript:select_all_in(\'FORM\', null, ' . '\'quizquestions\');">' . $strselectall . '</a> /'; $reordercontrols3 .= ' <a href="javascript:deselect_all_in(\'FORM\', ' . 'null, \'quizquestions\');">' . $strselectnone . '</a>'; $reordercontrolstop = '<div class="reordercontrols">' . $reordercontrolssetdefaultsubmit . $reordercontrols1 . $reordercontrols2top . $reordercontrols3 . "</div>"; $reordercontrolsbottom = '<div class="reordercontrols">' . $reordercontrolssetdefaultsubmit . $reordercontrols2bottom . $reordercontrols1 . $reordercontrols3 . "</div>"; if ($reordertool) { echo '<form method="post" action="edit.php" id="quizquestions"><div>'; echo $pageurl->hidden_params_out(); echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />'; echo $reordercontrolstop; } //the current question ordinal (no descriptions) $qno = 1; //the current question (includes questions and descriptions) $questioncount = 0; //the ordinal of current element in the layout //(includes page breaks, questions and descriptions) $count = 0; //the current page number in iteration $pagecount = 0; $sumgrade = 0; $pageopen = false; $returnurl = $pageurl->out(); $questiontotalcount = count($order); foreach ($order as $i => $qnum) { $reordercheckbox = ''; $reordercheckboxlabel = ''; $reordercheckboxlabelclose = ''; if ($qnum && empty($questions[$qnum])) { continue; } // If the questiontype is missing change the question type if ($qnum && !array_key_exists($questions[$qnum]->qtype, $QTYPES)) { $questions[$qnum]->qtype = 'missingtype'; } $deletex = "delete.gif"; if ($qnum != 0 || $qnum == 0 && !$pageopen) { //this is either a question or a page break after another // (no page is currently open) if (!$pageopen) { //if no page is open, start display of a page $pagecount++; echo '<div class="quizpage"><span class="pagetitle">' . get_string('page') . ' ' . $pagecount . '</span><div class="pagecontent">'; $pageopen = true; } if ($qnum == 0 && $i < $questiontotalcount) { // This is the second successive page break. Tell the user the page is empty. echo '<div class="pagestatus">'; print_string('noquestionsonpage', 'quiz'); echo '</div>'; if ($allowdelete && !$quiz->questionsperpage) { echo '<div class="quizpagedelete">'; echo '<a title="' . get_string('removeemptypage', 'quiz') . '" href="' . $pageurl->out_action(array('deleteemptypage' => $i - 1)) . '"><img src="' . $CFG->pixpath . '/t/delete.gif" ' . 'class="iconsmall" alt="' . $strremove . '" /></a>'; echo '</div>'; } } if ($qnum != 0) { $question = $questions[$qnum]; $questionparams = array('returnurl' => $returnurl, 'cmid' => $quiz->cmid, 'id' => $question->id); $questionurl = new moodle_url("{$CFG->wwwroot}/question/question.php", $questionparams); $questioncount++; //this is an actual question /* Display question start */ ?> <div class="question"> <div class="questioncontainer <?php echo $question->qtype; ?> "> <div class="qnum"> <?php $reordercheckbox = ''; $reordercheckboxlabel = ''; $reordercheckboxlabelclose = ''; if ($reordertool) { $reordercheckbox = '<input type="checkbox" name="s' . $question->id . '" id="s' . $question->id . '" />'; $reordercheckboxlabel = '<label for="s' . $question->id . '">'; $reordercheckboxlabelclose = '</label>'; } if (!$quiz->shufflequestions) { // Print and increment question number $questioncountstring = ''; if ($questioncount > 999 || $reordertool && $questioncount > 99) { $questioncountstring = "{$reordercheckboxlabel}<small>{$questioncount}</small>" . $reordercheckboxlabelclose . $reordercheckbox; } else { $questioncountstring = $reordercheckboxlabel . $questioncount . $reordercheckboxlabelclose . $reordercheckbox; } echo $questioncountstring; $qno += $question->length; } else { echo "{$reordercheckboxlabel} ? {$reordercheckboxlabelclose}" . " {$reordercheckbox}"; } ?> </div> <div class="content"> <div class="questioncontrols"> <?php if ($count != 0) { if (!$hasattempts) { $upbuttonclass = ''; if ($count >= $lastindex - 1) { $upbuttonclass = 'upwithoutdown'; } echo "<a title=\"{$strmoveup}\" href=\"" . $pageurl->out_action(array('up' => $question->id)) . "\"><img\n src=\"{$CFG->pixpath}/t/up.gif\" class=\"iconsmall\n {$upbuttonclass}\" alt=\"{$strmoveup}\" /></a>"; } } if ($count < $lastindex - 1) { if (!$hasattempts) { echo "<a title=\"{$strmovedown}\" href=\"" . $pageurl->out_action(array('down' => $question->id)) . "\"><img\n src=\"{$CFG->pixpath}/t/down.gif\" class=\"iconsmall\"" . " alt=\"{$strmovedown}\" /></a>"; } } if ($allowdelete && question_has_capability_on($question, 'use', $question->category)) { // remove from quiz, not question delete. if (!$hasattempts) { echo "<a title=\"{$strremove}\" href=\"" . $pageurl->out_action(array('remove' => $question->id)) . "\">\n <img src=\"{$CFG->pixpath}/t/delete.gif\" " . "class=\"iconsmall\" alt=\"{$strremove}\" /></a>"; } } ?> </div><?php if ($question->qtype != 'description' && !$reordertool) { ?> <div class="points"> <form method="post" action="edit.php"><div> <fieldset class="invisiblefieldset" style="display: block;"> <label for="<?php echo "inputq{$question->id}"; ?> "><?php echo $strgrade; ?> </label>:<br /> <input type="hidden" name="sesskey" value="<?php echo sesskey(); ?> " /> <?php echo $pageurl->hidden_params_out(); ?> <input type="hidden" name="savechanges" value="save" /> <?php echo '<input type="text" name="g' . $question->id . '" id="inputq' . $question->id . '" size="' . ($quiz->decimalpoints + 2) . '" value="' . (0 + $quiz->grades[$qnum]) . '" tabindex="' . ($lastindex + $qno) . '" />'; ?> <input type="submit" class="pointssubmitbutton" value="<?php echo $strsave; ?> " /> </fieldset> <?php if ($question->qtype == 'random') { echo '<a href="' . $questionurl->out() . '" class="configurerandomquestion">' . get_string("configurerandomquestion", "quiz") . '</a>'; } ?> </div> </form> </div> <?php } else { if ($reordertool) { if ($qnum) { ?> <div class="qorder"> <?php echo '<input type="text" name="o' . $question->id . '" size="2" value="' . (10 * $count + 10) . '" tabindex="' . ($lastindex + $qno) . '" />'; ?> <!-- <input type="submit" class="pointssubmitbutton" value="<?php echo $strsave; ?> " /> --> </div> <?php } } } ?> <div class="questioncontentcontainer"> <?php if ($question->qtype == 'random') { // it is a random question if (!$reordertool) { quiz_print_randomquestion($question, $pageurl, $quiz, $quiz_qbanktool); } else { quiz_print_randomquestion_reordertool($question, $pageurl, $quiz); } } else { // it is a single question if (!$reordertool) { quiz_print_singlequestion($question, $returnurl, $quiz); } else { quiz_print_singlequestion_reordertool($question, $returnurl, $quiz); } } ?> </div> </div> </div> </div> <?php /* Display question end */ $count++; $sumgrade += $quiz->grades[$qnum]; } } //a page break: end the existing page. if ($qnum == 0) { if ($pageopen) { if (!$reordertool && !($quiz->shufflequestions && $i < $questiontotalcount - 1)) { quiz_print_pagecontrols($quiz, $pageurl, $pagecount, $hasattempts); } else { if ($i < $questiontotalcount - 1) { //do not include the last page break for reordering //to avoid creating a new extra page in the end echo '<input type="hidden" name="opg' . $pagecount . '" size="2" value="' . (10 * $count + 10) . '" />'; } } echo "</div></div>"; if (!$reordertool && !$quiz->shufflequestions) { echo "<div class=\"addpage\">"; print_single_button($pageurl->out(true), array('cmid' => $quiz->cmid, 'courseid' => $quiz->course, 'addpage' => $count, 'sesskey' => sesskey()), get_string('addpagehere', 'quiz'), 'get', '_self', false, '', $hasattempts); echo "</div>"; } $pageopen = false; $count++; } } } if ($reordertool) { echo $reordercontrolsbottom; echo '</div></form>'; } return $sumgrade; }
private function determine_layout() { $this->pagelayout = array(); // Break up the layout string into pages. $pagelayouts = explode(',0', quiz_clean_layout($this->attempt->layout, true)); // Strip off any empty last page (normally there is one). if (end($pagelayouts) == '') { array_pop($pagelayouts); } // File the ids into the arrays. $this->pagelayout = array(); foreach ($pagelayouts as $page => $pagelayout) { $pagelayout = trim($pagelayout, ','); if ($pagelayout == '') { continue; } $this->pagelayout[$page] = explode(',', $pagelayout); } }
/** * Returns the number of questions in the quiz layout * * @param string $layout the string representing the quiz layout. * @return int The number of questions in the quiz. */ function quiz_number_of_questions_in_quiz($layout) { $layout = quiz_questions_in_quiz(quiz_clean_layout($layout)); $count = substr_count($layout, ','); if ($layout !== '') { $count++; } return $count; }
$resultinfo .= '<p class="quizteacherfeedback">' . $gradebookfeedback . "</p>\n"; } if ($feedbackcolumn) { $resultinfo .= $OUTPUT->heading(get_string('overallfeedback', 'quiz'), 3, 'main'); $resultinfo .= '<p class="quizgradefeedback">' . quiz_feedback_for_grade($mygrade, $quiz, $context, $cm) . "</p>\n"; } if ($resultinfo) { echo $OUTPUT->box($resultinfo, 'generalbox', 'feedback'); } } /// Determine if we should be showing a start/continue attempt button, /// or a button to go back to the course page. echo $OUTPUT->box_start('quizattempt'); $buttontext = ''; // This will be set something if as start/continue attempt button should appear. if (!quiz_clean_layout($quiz->questions, true)) { echo $OUTPUT->heading(get_string("noquestions", "quiz")); } else { if ($unfinished) { if ($canpreview) { $buttontext = get_string('continuepreview', 'quiz'); } else { if ($canattempt) { $buttontext = get_string('continueattemptquiz', 'quiz'); } } } else { if ($canpreview) { $buttontext = get_string('previewquiznow', 'quiz'); } else { if ($canattempt) {
/** * Given an object containing all the necessary data, * (defined by the form in mod_form.php) this function * will update an existing instance with new data. * * @param object $quiz the data that came from the form. * @return mixed true on success, false or a string error message on failure. */ function quiz_update_instance($quiz, $mform) { global $CFG, $DB; require_once $CFG->dirroot . '/mod/quiz/locallib.php'; // Process the options from the form. $result = quiz_process_options($quiz); if ($result && is_string($result)) { return $result; } // Get the current value, so we can see what changed. $oldquiz = $DB->get_record('quiz', array('id' => $quiz->instance)); // We need two values from the existing DB record that are not in the form, // in some of the function calls below. $quiz->sumgrades = $oldquiz->sumgrades; $quiz->grade = $oldquiz->grade; // Repaginate, if asked to. if (!$quiz->shufflequestions && !empty($quiz->repaginatenow)) { $quiz->questions = quiz_repaginate(quiz_clean_layout($oldquiz->questions, true), $quiz->questionsperpage); } unset($quiz->repaginatenow); // Update the database. $quiz->id = $quiz->instance; $DB->update_record('quiz', $quiz); // Do the processing required after an add or an update. quiz_after_add_or_update($quiz); if ($oldquiz->grademethod != $quiz->grademethod) { quiz_update_all_final_grades($quiz); quiz_update_grades($quiz); } $quizdateschanged = $oldquiz->timelimit != $quiz->timelimit || $oldquiz->timeclose != $quiz->timeclose || $oldquiz->graceperiod != $quiz->graceperiod; if ($quizdateschanged) { quiz_update_open_attempts(array('quizid' => $quiz->id)); } // Delete any previous preview attempts. quiz_delete_previews($quiz); return true; }
/** * Update the sumgrades field of the quiz. This needs to be called whenever * the grading structure of the quiz is changed. For example if a question is * added or removed, or a question weight is changed. * * @param object $quiz a quiz. */ function quiz_update_sumgrades($quiz) { global $DB; $sql = 'UPDATE {quiz} SET sumgrades = COALESCE(( SELECT SUM(grade) FROM {quiz_question_instances} WHERE quiz = {quiz}.id ), 0) WHERE id = ?'; $DB->execute($sql, array($quiz->id)); $quiz->sumgrades = $DB->get_field('quiz', 'sumgrades', array('id' => $quiz->id)); if ($quiz->sumgrades < 0.000005 && quiz_clean_layout($quiz->questions, true)) { // If there is at least one question in the quiz, and the sumgrades has been // set to 0, then also set the maximum possible grade to 0. quiz_set_grade(0, $quiz); } }
/** * Creates an object to represent a new attempt at a quiz * * Creates an attempt object to represent an attempt at the quiz by the current * user starting at the current time. The ->id field is not set. The object is * NOT written to the database. * * @param object $quiz the quiz to create an attempt for. * @param integer $attemptnumber the sequence number for the attempt. * @param object $lastattempt the previous attempt by this user, if any. Only needed * if $attemptnumber > 1 and $quiz->attemptonlast is true. * @param integer $timenow the time the attempt was started at. * @param boolean $ispreview whether this new attempt is a preview. * * @return object the newly created attempt object. */ function quiz_create_attempt($quiz, $attemptnumber, $lastattempt, $timenow, $ispreview = false) { global $USER; if ($attemptnumber == 1 || !$quiz->attemptonlast) { /// We are not building on last attempt so create a new attempt. $attempt = new stdClass(); $attempt->quiz = $quiz->id; $attempt->userid = $USER->id; $attempt->preview = 0; if ($quiz->shufflequestions) { $attempt->layout = quiz_clean_layout(quiz_repaginate($quiz->questions, $quiz->questionsperpage, true), true); } else { $attempt->layout = quiz_clean_layout($quiz->questions, true); } } else { /// Build on last attempt. if (empty($lastattempt)) { print_error('cannotfindprevattempt', 'quiz'); } $attempt = $lastattempt; } $attempt->attempt = $attemptnumber; $attempt->sumgrades = 0.0; $attempt->timestart = $timenow; $attempt->timefinish = 0; $attempt->timemodified = $timenow; $attempt->uniqueid = question_new_attempt_uniqueid(); /// If this is a preview, mark it as such. if ($ispreview) { $attempt->preview = 1; } return $attempt; }
/** * Populate {@link $questionids} and {@link $pagequestionids} from the layout. */ protected function determine_layout() { $this->questionids = array(); $this->pagequestionids = array(); // Get the appropriate layout string (from quiz or attempt). $layout = $this->get_layout_string(); if (empty($layout)) { // Nothing to do. return; } // Break up the layout string into pages. $pagelayouts = explode(',0', quiz_clean_layout($layout, true)); // Strip off any empty last page (normally there is one). if (end($pagelayouts) == '') { array_pop($pagelayouts); } // File the ids into the arrays. $this->questionids = array(); $this->pagequestionids = array(); foreach ($pagelayouts as $page => $pagelayout) { $pagelayout = trim($pagelayout, ','); if ($pagelayout == '') { continue; } $this->pagequestionids[$page] = explode(',', $pagelayout); foreach ($this->pagequestionids[$page] as $id) { $this->questionids[] = $id; } } }
/** * Generates the view attempt button * * @param int $course The course ID * @param array $quiz Array containging quiz date * @param int $cm The Course Module ID * @param int $context The page Context ID * @param mod_quiz_view_object $viewobj * @param string $buttontext */ public function view_attempt_button($course, $quiz, $cm, $context, $viewobj, $buttontext, $preventmessages) { $output = ''; // Determine if we should be showing a start/continue attempt button, // or a button to go back to the course page. $output .= $this->box_start('quizattempt'); // Now actually print the appropriate button. if (!quiz_clean_layout($quiz->questions, true)) { $output .= quiz_no_questions_message($quiz, $cm, $context); } if ($preventmessages) { $output .= $this->access_messages($preventmessages); } if ($buttontext) { $output .= $viewobj->accessmanager->print_start_attempt_button($viewobj->canpreview, $buttontext, $viewobj->unfinished); } else if ($buttontext === '') { $output .= $this->single_button(new moodle_url('/course/view.php', array('id' => $course->id)), get_string('backtocourse', 'quiz'), 'get', array('class' => 'continuebutton')); } $output .= $this->box_end(); return $output; }
/** * Given an object containing all the necessary data, * (defined by the form in mod_form.php) this function * will update an existing instance with new data. * * @param object $quiz the data that came from the form. * @return mixed true on success, false or a string error message on failure. */ function quiz_update_instance($quiz, $mform) { global $CFG, $DB; // Process the options from the form. $result = quiz_process_options($quiz); if ($result && is_string($result)) { return $result; } $oldquiz = $DB->get_record('quiz', array('id' => $quiz->instance)); // Repaginate, if asked to. if (!$quiz->shufflequestions && !empty($quiz->repaginatenow)) { require_once($CFG->dirroot . '/mod/quiz/locallib.php'); $quiz->questions = quiz_repaginate(quiz_clean_layout($oldquiz->questions, true), $quiz->questionsperpage); } unset($quiz->repaginatenow); // Update the database. $quiz->id = $quiz->instance; $DB->update_record('quiz', $quiz); // Do the processing required after an add or an update. quiz_after_add_or_update($quiz); if ($oldquiz->grademethod != $quiz->grademethod) { require_once($CFG->dirroot . '/mod/quiz/locallib.php'); $quiz->sumgrades = $oldquiz->sumgrades; $quiz->grade = $oldquiz->grade; quiz_update_all_final_grades($quiz); quiz_update_grades($quiz); } // Delete any previous preview attempts. quiz_delete_previews($quiz); return true; }