| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- <?php
- /**
- * Quiz-engine-specific part of the SLOODLE quiz linker.
- * For Moodle 2.0 or lower
- *
- * Allows in-world objects to interact with Moodle quizzes.
- * Part of the Sloodle project (www.sloodle.org).
- *
- * @package sloodlequiz
- * @copyright Copyright (c) 2006-8 Sloodle (various contributors)
- * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
- *
- * @contributor (various Moodle authors including Tim Hunt)
- * @contributor Edmund Edgar
- * @contributor Peter R. Bloomfield
- */
- // Get number for the next or unfinished attempt
- if(!$attemptnumber = (int)sloodle_get_field_sql_params('SELECT MAX(attempt)+1 FROM ' .
- "{$CFG->prefix}quiz_attempts WHERE quiz = ? AND " .
- "userid = ? AND timefinish > 0 AND preview != 1", array($quiz->id, $USER->id))) {
- $attemptnumber = 1;
- }
- $strattemptnum = get_string('attempt', 'quiz', $attemptnumber);
- $strquizzes = get_string("modulenameplural", "quiz");
- // course id: $course->id
- // course id: $course->id
- // course name: $quiz->id
- // attempts: $quiz->attempts
- if ($limittoquestion == 0) $output[] = array('course',$course->id,$course->fullname);
- $numberofpreviousattempts = sloodle_count_records_select_params('quiz_attempts', "quiz = ? AND " .
- "userid = ? AND timefinish > 0 AND preview != 1", array($quiz->id, $USER->id));
- if ($quiz->attempts and $numberofpreviousattempts >= $quiz->attempts) {
- $sloodle->response->quick_output(-10301, 'QUIZ', 'You do not have any attempts left', FALSE);
- exit();
- }
- /// Check subnet access
- if ($quiz->subnet and !address_in_subnet(getremoteaddr(), $quiz->subnet)) {
- $sloodle->response->quick_output(-1, 'MISC', 'A subnet error occurred', FALSE);
- exit();
- }
- /// Check password access
- if ($quiz->password) {
- $sloodle->response->quick_output(-10302, 'QUIZ', 'Quiz requires password - not supported by Sloodle', FALSE);
- exit();
- }
- if ($quiz->delay1 or $quiz->delay2) {
- //quiz enforced time delay
- if ($attempts = quiz_get_user_attempts($quiz->id, $USER->id)) {
- $numattempts = count($attempts);
- } else {
- $numattempts = 0;
- }
- $timenow = time();
- $lastattempt_obj = sloodle_get_record_select_params('quiz_attempts', "quiz = ? AND attempt = ? AND userid = ?", array($quiz->id, $numattempts, $USER->id), 'timefinish');
- if ($lastattempt_obj) {
- $lastattempt = $lastattempt_obj->timefinish;
- }
- if ($numattempts == 1 && $quiz->delay1) {
- if ($timenow - $quiz->delay1 < $lastattempt) {
- $sloodle->response->quick_output(-701, 'QUIZ', 'You need to wait until the time delay has expired.', FALSE);
- exit();
- }
- } else if($numattempts > 1 && $quiz->delay2) {
- if ($timenow - $quiz->delay2 < $lastattempt) {
- $sloodle->response->quick_output(-701, 'QUIZ', 'You need to wait until the time delay has expired.', FALSE);
- exit();
- }
- }
- }
- // sloodle_prim_render_output($output);
- // exit;
- /// Load attempt or create a new attempt if there is no unfinished one
- $attempt = sloodle_get_record('quiz_attempts', 'quiz', $quiz->id,
- 'userid', $USER->id, 'timefinish', 0);
- $newattempt = false;
- if (!$attempt) {
- $newattempt = true;
- // Start a new attempt and initialize the question sessions
- $attempt = quiz_create_attempt($quiz, $attemptnumber);
- // If this is an attempt by a teacher mark it as a preview
- // Save the attempt
- if (!$attempt->timestart) { // shouldn't really happen, just for robustness
- $attempt->timestart = time();
- }
- if (!$attempt->timemodified) { // shouldn't really happen, just for robustness
- $attempt->timemodified = time();
- }
- if (!$attempt->id = sloodle_insert_record('quiz_attempts', $attempt)) {
- $sloodle->response->quick_output(-701, 'QUIZ', 'Could not create new attempt.', FALSE);
- exit();
- }
- // make log entries
- add_to_log($course->id, 'quiz', 'attempt',
- "review.php?attempt=$attempt->id",
- "$quiz->id", $cm->id);
- } else {
- // log continuation of attempt only if some time has lapsed
- if (($timestamp - $attempt->timemodified) > 600) { // 10 minutes have elapsed
- add_to_log($course->id, 'quiz', 'continue attemp', // this action used to be called 'continue attempt' but the database field has only 15 characters
- "review.php?attempt=$attempt->id",
- "$quiz->id", $cm->id);
- }
- }
- if (!$attempt->timestart) { // shouldn't really happen, just for robustness
- $attempt->timestart = time();
- }
- /// Load all the questions and states needed by this script
- ///// SLOODLE MODIFICATION /////
- // For SLOODLE to work properly, it needs the entire list of questions at all times.
- // It ignores the "page" structure of a Moodle quiz.
- $questionlist = quiz_questions_in_quiz($attempt->layout);
- $pagelist = $questionlist;
- ///// END SLOODLE MODIFICATION /////
- $questionlistids = explode(',', $questionlist);
- if ($questionids != '') {
- $questionids = explode(',', $questionids);
- }
- // add all questions that are on the submitted form
- if ($questionids && (count($questionids) > 0) ) {
- $questionlistids = array_merge($questionlistids, $questionids);
- }
-
- $params = array($quiz->id);
- $questioninstr = '';
- $delim = '';
- foreach($questionlistids as $qlid) {
- $params[] = $qlid;
- $questioninstr .= $delim.'?';
- $delim = ',';
- }
- if ( !$questionlistids || (count($questionlistids) == 0) ) {
- $sloodle->response->quick_output(-10303, 'QUIZ', 'No questions found.', FALSE);
- exit();
- }
- $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
- " FROM {$CFG->prefix}question q,".
- " {$CFG->prefix}quiz_question_instances i".
- " WHERE i.quiz = ? AND q.id = i.question".
- " AND q.id IN ($questioninstr) ;";
- // Load the questions
- $questions = sloodle_get_records_sql_params($sql, $params);
- if ( !$questions || (count($questions) == 0) ) {
- $sloodle->response->quick_output(-10303, 'QUIZ', 'No questions found.'.$sql.join(':',$params), FALSE);
- exit;
- $sloodle->response->quick_output(-10303, 'QUIZ', 'No questions found.', FALSE);
- exit();
- }
- // Load the question type specific information
- if (!get_question_options($questions)) {
- $sloodle->response->quick_output(-10303, 'QUIZ', 'Could not load question options.', FALSE);
- exit();
- }
- // Restore the question sessions to their most recent states
- // creating new sessions where required
- if (!$states = get_question_states($questions, $quiz, $attempt)) {
- $sloodle->response->quick_output(-701, 'QUIZ', 'Could not restore questions sessions.', FALSE);
- exit();
- }
- // Save all the newly created states
- if ($newattempt) {
- foreach ($questions as $i => $question) {
- save_question_session($questions[$i], $states[$i]);
- }
- }
- // If the new attempt is to be based on a previous attempt copy responses over
- if ($newattempt and $attempt->attempt > 1 and $quiz->attemptonlast and !$attempt->preview) {
- // Find the previous attempt
- if (!$lastattemptid = sloodle_get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt-1)) {
- $sloodle->response->quick_output(-701, 'QUIZ', 'Could not find previous attempt to build on.', FALSE);
- exit();
- }
- // For each question find the responses from the previous attempt and save them to the new session
- foreach ($questions as $i => $question) {
- // Load the last graded state for the question
- $statefields = 'n.questionid as question, s.*, n.sumpenalty';
- $sql = "SELECT $statefields".
- " FROM {$CFG->prefix}question_states s,".
- " {$CFG->prefix}question_sessions n".
- " WHERE s.id = n.newgraded".
- " AND n.attemptid = ?".
- " AND n.questionid = ?";
- if (!$laststate = sloodle_get_record_sql_params($sql, array($lastattemptid, $i))) {
- // Only restore previous responses that have been graded
- continue;
- }
- // Restore the state so that the responses will be restored
- restore_question_state($questions[$i], $laststate);
- // prepare the previous responses for new processing
- $action = new stdClass;
- $action->responses = $laststate->responses;
- $action->timestamp = $laststate->timestamp;
- $action->event = QUESTION_EVENTOPEN;
- // Process these responses ...
- question_process_responses($questions[$i], $states[$i], $action, $quiz, $attempt);
- // Fix for Bug #5506: When each attempt is built on the last one,
- // preserve the options from any previous attempt.
- if ( isset($laststate->options) ) {
- $states[$i]->options = $laststate->options;
- }
- // ... and save the new states
- save_question_session($questions[$i], $states[$i]);
- }
- }
- /// Process form data /////////////////////////////////////////////////
- if ($isnotify) {
- SloodleDebugLogger::log('DEBUG', "in is notify");
- $responses = (object)$_REQUEST; // GET version of data_submitted (see lib/weblib) used in original web version
- // set the default event. This can be overruled by individual buttons.
- $event = (array_key_exists('markall', $responses)) ? QUESTION_EVENTSUBMIT :
- ($finishattempt ? QUESTION_EVENTCLOSE : QUESTION_EVENTSAVE);
- SloodleDebugLogger::log('DEBUG', "checked finish attempt");
- // Unset any variables we know are not responses
- unset($responses->id);
- unset($responses->q);
- unset($responses->oldpage);
- unset($responses->newpage);
- unset($responses->review);
- unset($responses->questionids);
- unset($responses->saveattempt); // responses get saved anway
- unset($responses->finishattempt); // same as $finishattempt
- unset($responses->markall);
- unset($responses->forcenewattempt);
- // extract responses
- // $actions is an array indexed by the questions ids
- $actions = question_extract_responses($questions, $responses, $event);
- SloodleDebugLogger::log('DEBUG', "extracted");
- // Process each question in turn
- foreach($questionids as $i) {
- if (!isset($actions[$i])) {
- $actions[$i]->responses = array('' => '');
- }
- $actions[$i]->timestamp = $timestamp;
- question_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt);
- save_question_session($questions[$i], $states[$i]);
- }
- $attempt->timemodified = $timestamp;
- // We have now finished processing form data
- // With post sloodle-2.0 quiz chairs, we should be told what happened to the score.
- // In theory we should be able to get this from the data we already have, but it seems complex...
- // The process_events allows us to award points if there is an instruction to do so in the object config.
- $scorechange = floatval(optional_param( 'scorechange', 0, PARAM_TEXT));
- //SloodleDebugLogger::log('DEBUG', "active object check");
- if (!is_null($sloodle->active_object)) {
- //SloodleDebugLogger::log('DEBUG', "quiz has an active object");
- $sloodle->process_interaction('answerquestion', $scorechange);
- /*
- if ($scorechange > 0) {
- } else if ($scorechange < 0) {
- //$sloodle->active_object->process_events( 'SloodleModuleAwards', 'answerincorrect', 1, $sloodle->user->get_user_id() );
- }
- */
- // TODO: Maybe we should set a side effect code here?
- } else {
- SloodleDebugLogger::log('DEBUG', "quiz has no active object");
- }
-
- } else {
- SloodleDebugLogger::log('DEBUG', 'quiz is not notify');
- }
- /// Finish attempt if requested
- if ($finishattempt) {
- // Set the attempt to be finished
- $attempt->timefinish = $timestamp;
- // Find all the questions for this attempt for which the newest
- // state is not also the newest graded state
- if ($closequestions = sloodle_get_records_select_params('question_sessions',
- "attemptid = ? AND newest != newgraded", array($attempt->uniqueid),'', 'questionid, questionid')) {
- // load all the questions
- $closequestionlist = implode(',', array_keys($closequestions));
- $params = array($quiz->id);
- $instr = '';
- $delim = '';
- foreach(array_keys($closequestions) as $cq) {
- $params[] = $cq;
- $instr .= $delim.'?';
- $delim = ',';
- }
- $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
- " FROM {$CFG->prefix}question q,".
- " {$CFG->prefix}quiz_question_instances i".
- " WHERE i.quiz = ? AND q.id = i.question".
- " AND q.id IN ($instr)";
- if (!$closequestions = sloodle_get_records_sql_params($sql, $params)) {
- $sloodle->response->quick_output(-10303, 'QUIZ', 'Questions missing.', FALSE);
- exit();
- }
- // Load the question type specific information
- if (!get_question_options($closequestions)) {
- $sloodle->response->quick_output(-10303, 'QUIZ', 'Could not load question options.', FALSE);
- exit();
- }
- // Restore the question sessions
- if (!$closestates = get_question_states($closequestions, $quiz, $attempt)) {
- $sloodle->response->quick_output(-701, 'QUIZ', 'Could not restore question sessions.', FALSE);
- exit();
- }
- foreach($closequestions as $key => $question) {
- $action->event = QUESTION_EVENTCLOSE;
- $action->responses = $closestates[$key]->responses;
- $action->timestamp = $closestates[$key]->timestamp;
- question_process_responses($question, $closestates[$key], $action, $quiz, $attempt);
- save_question_session($question, $closestates[$key]);
- }
- }
- add_to_log($course->id, 'quiz', 'close attempt',
- "review.php?attempt=$attempt->id",
- "$quiz->id", $cm->id);
- }
- /// Update the quiz attempt and the overall grade for the quiz
- if ((isset($responses) && $responses) || $finishattempt) {
- if (!sloodle_update_record('quiz_attempts', $attempt)) {
- $sloodle->response->quick_output(-701, 'QUIZ', 'Failed to save current quiz attempt.', FALSE);
- exit();
- }
- if (($attempt->attempt > 1 || $attempt->timefinish > 0) and !$attempt->preview) {
- quiz_save_best_grade($quiz);
- }
- }
- /// Check access to quiz page
- // check the quiz times
- //TODO: Figure out what this does...
- if ($timestamp < $quiz->timeopen || ($quiz->timeclose and $timestamp > $quiz->timeclose)) {
- $sloodle->response->quick_output(-701, 'QUIZ', 'Quiz not available.', FALSE);
- exit();
- }
- if ($finishattempt) {
- // redirect('review.php?attempt='.$attempt->id);
- //$sloodle->response->quick_output(-701, 'QUIZ', 'Got to finishattempt - but do not yet have Sloodle code to handle it.', FALSE);
- //exit();
- }
- /// Print the quiz page ////////////////////////////////////////////////////////
- if (!$isnotify) {
- $pagequestions = explode(',', $pagelist);
- $lastquestion = count($pagequestions);
- // Only output quiz data if a question has not been requetsed
- if ($limittoquestion == 0) {
- $output[] = array('quiz',$quiz->attempts,$quiz->name,$quiz->timelimit,$quiz->id,$lastquestion);
- $output[] = array('quizpages',quiz_number_of_pages($attempt->layout),$page,$pagelist);
- }
- // We will keep track of question numbers local to this quiz
- $localqnum = 0;
- /// Print all the questions
- $number = quiz_first_questionnumber($attempt->layout, $pagelist);
- foreach ($pagequestions as $i) {
- $options = quiz_get_renderoptions($quiz->review, $states[$i]);
- // Print the question
- // var_dump($questions[$i]);
- $q = $questions[$i];
- $localqnum++;
-
- if ($limittoquestion == $q->id) {
- //echo "<hr>"; print_r($q); echo "<hr>";
-
- // Make sure the variables exist (avoids warnings!)
- if (!isset($q->single)) $q->single = 0;
- $shuffleanswers = 0;
- if (isset($q->options->shuffleanswers) && $q->options->shuffleanswers) $shuffleanswers = 1;
- $imgUrl = "{$CFG->wwwroot}/pluginfile.php/".$q->options->id."/question/questiontext/".$attempt->id."/".$q ->id."/".$q ->id;
- $imgUrl .=substr ( sloodle_extract_first_image_url($q->questiontext), 14);
-
-
-
- $output[] = array(
- 'question',
- $localqnum, //$i, // The value in $i is equal to $q->id, rather than being sequential in the quiz
- $q->id,
- $q->parent,
- sloodle_clean_for_output($q->questiontext),
- $q->defaultgrade,
- $q->penalty,
- $q->qtype,
- $q->hidden,
- $q->maxgrade,
- $q->single,
- $shuffleanswers,
- 0, //$deferred // This variable doesn't seem to be mentioned anywhere else in the file
- $imgUrl
-
-
-
- );
-
- // Create an output array for our options (available answers) so that we can shuffle them later if necessary
- $outputoptions = array();
- // Go through each option
- $ops = $q->options;
- foreach($ops as $opkey=>$op) {
-
- if (!is_array($op)) continue; // Ignore this if there are no options (Prevents nasty PHP notices!)
- foreach($op as $ok=>$ov) {
- $answer_imgUrl = "{$CFG->wwwroot}/pluginfile.php/".$ov->id."/question/questiontext/".$attempt->id."/".$ov->question."/".$ov->question;
- $answer_imgUrl .=substr ( sloodle_extract_first_image_url($ov->answer), 14);
- $feedback_imgUrl = "{$CFG->wwwroot}/pluginfile.php/".$ov->id."/question/questiontext/".$attempt->id."/".$ov->question."/".$ov->question;
- $feedback_imgUrl .=substr ( sloodle_extract_first_image_url($ov->feedback), 14);
- $outputoptions[] = array(
- 'questionoption',
- $i,
- $ov->id,
- $ov->question,
- sloodle_clean_for_output($ov->answer),
- $ov->fraction,
- sloodle_clean_for_output($ov->feedback),
- $answer_imgUrl,
- $feedback_imgUrl
- );
- }
- }
-
- // Shuffle the options if necessary
- if ($shuffleanswers) shuffle($outputoptions);
- // Append our options to the main output array
- $output = array_merge($output, $outputoptions);
- }
- //print_question($questions[$i], $states[$i], $number, $quiz, $options);
- save_question_session($questions[$i], $states[$i]);
- $number += $questions[$i]->length;
- }
- $secondsleft = ($quiz->timeclose ? $quiz->timeclose : 999999999999) - time();
- //if ($isteacher) {
- // // For teachers ignore the quiz closing time
- // $secondsleft = 999999999999;
- //}
- // If time limit is set include floating timer.
- if ($quiz->timelimit > 0) {
- $output[] = array('seconds left',$secondsleft);
- }
- }
- $sloodle->response->set_status_code(1);
- $sloodle->response->set_status_descriptor('QUIZ');
- $sloodle->response->set_data($output);
-
- $sloodle->response->render_to_output();
-
- exit;
- ?>
|