Source for file linker.php

Documentation is available at linker.php

  1. <?php
  2.     /**
  3.     * Sloodle quiz linker (for Sloodle 0.3).
  4.     * Allows in-world objects to interact with Moodle quizzes.
  5.     * Part of the Sloodle project (www.sloodle.org).
  6.     *
  7.     * @package sloodlequiz
  8.     * @copyright Copyright (c) 2006-8 Sloodle (various contributors)
  9.     * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
  10.     *
  11.     * @contributor (various Moodle authors)
  12.     * @contributor Edmund Edgar
  13.     * @contributor Peter R. Bloomfield
  14.     */
  15.  
  16.     // This script is expected to be requested by an in-world object.
  17.     // The following parameters are required:
  18.     //
  19.     //   sloodlecontrollerid = ID of the controller through which to access Moodle
  20.     //   sloodlepwd = password to authenticate the request
  21.     //   sloodlemoduleid = the course module ID of the quiz to access
  22.     //   sloodleuuid = UUID of the avatar making the request (optional if 'sloodleavname' is specified)
  23.     //   sloodleavname = avatar name of the user making the request (optional if 'sloodleuuid' is specified)
  24.     //
  25.     //
  26.     //
  27.     // The following are the original quiz parameters:
  28.     //
  29.     //   q = ID of the quiz course module instance
  30.     //   id = ID of the course which this request relates to
  31.     //   page = ?
  32.     //   questionids = ?
  33.     //   finishattempt = ?
  34.     //   timeup = true if submission was by timer
  35.     //    forcenew = teacher has requested a new preview
  36.     //    action = ??
  37.     
  38.  
  39.     /** Lets Sloodle know we are in a linker script. */
  40.     define('SLOODLE_LINKER_SCRIPT'true);
  41.     
  42.     /** Grab the Sloodle/Moodle configuration. */
  43.     require_once('../../sl_config.php');
  44.     /** Include the Sloodle PHP API. */
  45.     require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
  46.  
  47.     // We're using a copy of the locallib.php file from the quiz module itself.
  48.     // Linking directly to it seems to break - it would be better to find out why and see if there's a way we can reference it directly.
  49.     //2007101509 is the 1.9 release, which has a new locallib file
  50.     if $CFG->version >= 2007101500 {
  51.        require_once("locallib.1.9.php");
  52.     else {
  53.        require_once("locallib.php");
  54.     }
  55.  
  56.     // Authenticate the request and login the user
  57.     $sloodle new SloodleSession();
  58.     $sloodle->authenticate_request();
  59.     $sloodle->validate_user();
  60.     $sloodle->user->login();
  61.     
  62.     // Grab our additional parameters
  63.     $id $sloodle->request->get_module_id();
  64.     $limittoquestion optional_param('ltq'0PARAM_INT);
  65.  
  66.     $output array();
  67.  
  68.     $courseid optional_param('courseid'0PARAM_INT);               // Course Module ID
  69.     //$id = optional_param('id', 0, PARAM_INT);               // Course Module ID
  70.     $q optional_param('q'0PARAM_INT);                 // or quiz ID
  71.     $page optional_param('page'0PARAM_INT);
  72.     $questionids optional_param('questionids''');
  73.     $finishattempt optional_param('finishattempt'0PARAM_BOOL);
  74.     $timeup optional_param('timeup'0PARAM_BOOL)// True if form was submitted by timer.
  75.     $forcenew optional_param('forcenew'falsePARAM_BOOL)// Teacher has requested new preview
  76.  
  77.     $isnotify optional_param'action'falsePARAM_RAW == 'notify' ;
  78.  
  79.     // remember the current time as the time any responses were submitted
  80.     // (so as to make sure students don't get penalized for slow processing on this page)
  81.     $timestamp time();
  82.  
  83.     // We treat automatically closed attempts just like normally closed attempts
  84.     if ($timeup{
  85.         $finishattempt 1;
  86.     }
  87.  
  88.     if ( ($courseid != 0&& ($id == 0&& ($q == 0) ) {
  89.         // fetch a quiz with the id for the course
  90.          if ($mod get_record("modules""name""quiz") ) {
  91.              $sloodle->response->quick_output(-712'MODULE_INSTANCE''Could not find quiz module'FALSE);
  92.              exit;
  93.          }
  94.  
  95.          //if (! $coursemod = get_record("course_modules", "courseid", $courseid, "module", $mod->id)) {
  96.          if ($coursemod get_record("course_modules""course"$courseid"module"$mod->id)) {
  97.              $sloodle->response->quick_output(-712'MODULE_INSTANCE''Could not find course module instance'FALSE);
  98.              exit;
  99.          }
  100.  
  101.          $id $coursemod->id;
  102.  
  103.     }
  104.     if ($id{
  105.         if ($cm get_coursemodule_from_id('quiz'$id)) {
  106.             $sloodle->response->quick_output(-701'MODULE_INSTANCE''Identified module instance is not a quiz'FALSE);
  107.             exit();
  108.         }
  109.  
  110.         if ($course get_record("course""id"$cm->course)) {
  111.             $sloodle->response->quick_output(-701'MODULE_INSTANCE''Quiz module is misconfigured'FALSE);
  112.             exit();
  113.         }
  114.  
  115.         if ($quiz get_record("quiz""id"$cm->instance)) {
  116.             $sloodle->response->quick_output(-712'MODULE_INSTANCE''Part of quiz module instance is missing'FALSE);
  117.             exit();
  118.         }
  119.  
  120.     else {
  121.         if ($quiz get_record("quiz""id"$q)) {
  122.             $sloodle->response->quick_output(-712'MODULE_INSTANCE''Could not find quiz module'FALSE);
  123.             exit();
  124.         }
  125.         if ($course get_record("course""id"$quiz->course)) {
  126.             $sloodle->response->quick_output(-712'MODULE_INSTANCE''Part of quiz module instance is missing'FALSE);
  127.             exit();
  128.         }
  129.         if ($cm get_coursemodule_from_instance("quiz"$quiz->id$course->id)) {
  130.             $sloodle->response->quick_output(-701'MODULE_INSTANCE''Unable to resolve course module instance'FALSE);
  131.             exit();
  132.         }
  133.     }
  134.  
  135.  
  136. // Get number for the next or unfinished attempt
  137.     if(!$attemptnumber = (int)get_field_sql('SELECT MAX(attempt)+1 FROM ' .
  138.      "{$CFG->prefix}quiz_attempts WHERE quiz = '{$quiz->id}' AND .
  139.      "userid = '{$USER->id}' AND timefinish > 0 AND preview != 1")) {
  140.         $attemptnumber 1;
  141.     }
  142.  
  143.     $strattemptnum get_string('attempt''quiz'$attemptnumber);
  144.     $strquizzes get_string("modulenameplural""quiz");
  145.  
  146.     // course id: $course->id
  147.     // course id: $course->id
  148.     // course name: $quiz->id
  149.     // attempts: $quiz->attempts
  150.     if ($limittoquestion == 0$output[array('course',$course->id,$course->fullname);
  151.  
  152.     $numberofpreviousattempts count_records_select('quiz_attempts'"quiz = '{$quiz->id}' AND .
  153.         "userid = '{$USER->id}' AND timefinish > 0 AND preview != 1");
  154.     if ($quiz->attempts and $numberofpreviousattempts >= $quiz->attempts{
  155.         $sloodle->response->quick_output(-10301'QUIZ''You do not have any attempts left'FALSE);
  156.         exit();
  157.     }
  158.  
  159. /// Check subnet access
  160.     if ($quiz->subnet and !address_in_subnet(getremoteaddr()$quiz->subnet)) {
  161.         $sloodle->response->quick_output(-1'MISC''A subnet error occurred'FALSE);
  162.         exit();
  163.     }
  164.  
  165. /// Check password access
  166.     if ($quiz->password{
  167.         $sloodle->response->quick_output(-10302'QUIZ''Quiz requires password - not supported by Sloodle'FALSE);
  168.         exit();
  169.     }
  170.  
  171.     if ($quiz->delay1 or $quiz->delay2{
  172.         //quiz enforced time delay
  173.         if ($attempts quiz_get_user_attempts($quiz->id$USER->id)) {
  174.             $numattempts count($attempts);
  175.         else {
  176.             $numattempts 0;
  177.         }
  178.         $timenow time();
  179.         $lastattempt_obj get_record_select('quiz_attempts'"quiz = $quiz->id AND attempt = $numattempts AND userid = $USER->id"'timefinish');
  180.         if ($lastattempt_obj{
  181.             $lastattempt $lastattempt_obj->timefinish;
  182.         }
  183.         if ($numattempts == && $quiz->delay1{
  184.             if ($timenow $quiz->delay1 $lastattempt{
  185.                 $sloodle->response->quick_output(-701'QUIZ''You need to wait until the time delay has expired.'FALSE);
  186.                 exit();
  187.             }
  188.         else if($numattempts && $quiz->delay2{
  189.             if ($timenow $quiz->delay2 $lastattempt{
  190.                 $sloodle->response->quick_output(-701'QUIZ''You need to wait until the time delay has expired.'FALSE);
  191.                 exit();
  192.             }
  193.         }
  194.     }
  195.  
  196. //    sloodle_prim_render_output($output);
  197. //    exit;
  198.  
  199. /// Load attempt or create a new attempt if there is no unfinished one
  200.  
  201.     $attempt get_record('quiz_attempts''quiz'$quiz->id,
  202.      'userid'$USER->id'timefinish'0);
  203.  
  204.     $newattempt false;
  205.     if (!$attempt{
  206.         $newattempt true;
  207.         // Start a new attempt and initialize the question sessions
  208.         $attempt quiz_create_attempt($quiz$attemptnumber);
  209.         // If this is an attempt by a teacher mark it as a preview
  210.         // Save the attempt
  211.         if (!$attempt->id insert_record('quiz_attempts'$attempt)) {
  212.             $sloodle->response->quick_output(-701'QUIZ''Could not create new attempt.'FALSE);
  213.             exit();
  214.         }
  215.         // make log entries
  216.         add_to_log($course->id'quiz''attempt',
  217.                        "review.php?attempt=$attempt->id",
  218.                        "$quiz->id"$cm->id);
  219.     else {
  220.         // log continuation of attempt only if some time has lapsed
  221.         if (($timestamp $attempt->timemodified600// 10 minutes have elapsed
  222.              add_to_log($course->id'quiz''continue attemp'// this action used to be called 'continue attempt' but the database field has only 15 characters
  223.                            "review.php?attempt=$attempt->id",
  224.                            "$quiz->id"$cm->id);
  225.         }
  226.     }
  227.     if (!$attempt->timestart// shouldn't really happen, just for robustness
  228.         $attempt->timestart time();
  229.     }
  230.  
  231. /// Load all the questions and states needed by this script
  232.  
  233.     // list of questions needed by page
  234.     $pagelist quiz_questions_on_page($attempt->layout$page);
  235.  
  236.     if ($newattempt{
  237.         $questionlist quiz_questions_in_quiz($attempt->layout);
  238.     else {
  239.         $questionlist $pagelist;
  240.     }
  241.  
  242.     // add all questions that are on the submitted form
  243.     if ($questionids{
  244.         $questionlist .= ','.$questionids;
  245.     }
  246.  
  247.     if (!$questionlist{
  248.         $sloodle->response->quick_output(-10303'QUIZ''No questions found.'FALSE);
  249.         exit();
  250.     }
  251.  
  252.     $sql "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
  253.            "  FROM {$CFG->prefix}question q,".
  254.            "       {$CFG->prefix}quiz_question_instances i".
  255.            " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
  256.            "   AND q.id IN ($questionlist)";
  257.  
  258.     // Load the questions
  259.     if (!$questions get_records_sql($sql)) {
  260.         $sloodle->response->quick_output(-10303'QUIZ''No questions found.'FALSE);
  261.         exit();
  262.     }
  263.  
  264.     // Load the question type specific information
  265.     if (!get_question_options($questions)) {
  266.         $sloodle->response->quick_output(-10303'QUIZ''Could not load question options.'FALSE);
  267.         exit();
  268.     }
  269.  
  270.     // Restore the question sessions to their most recent states
  271.     // creating new sessions where required
  272.     if (!$states get_question_states($questions$quiz$attempt)) {
  273.         $sloodle->response->quick_output(-701'QUIZ''Could not restore questions sessions.'FALSE);
  274.         exit();
  275.     }
  276.  
  277.     // Save all the newly created states
  278.     if ($newattempt{
  279.         foreach ($questions as $i => $question{
  280.             save_question_session($questions[$i]$states[$i]);
  281.         }
  282.     }
  283.  
  284.     // If the new attempt is to be based on a previous attempt copy responses over
  285.     if ($newattempt and $attempt->attempt and $quiz->attemptonlast and !$attempt->preview{
  286.         // Find the previous attempt
  287.         if (!$lastattemptid get_field('quiz_attempts''uniqueid''quiz'$attempt->quiz'userid'$attempt->userid'attempt'$attempt->attempt-1)) {
  288.             $sloodle->response->quick_output(-701'QUIZ''Could not find previous attempt to build on.'FALSE);
  289.             exit();
  290.         }
  291.         // For each question find the responses from the previous attempt and save them to the new session
  292.         foreach ($questions as $i => $question{
  293.             // Load the last graded state for the question
  294.             $statefields 'n.questionid as question, s.*, n.sumpenalty';
  295.             $sql "SELECT $statefields".
  296.                    "  FROM {$CFG->prefix}question_states s,".
  297.                    "       {$CFG->prefix}question_sessions n".
  298.                    " WHERE s.id = n.newgraded".
  299.                    "   AND n.attemptid = '$lastattemptid'".
  300.                    "   AND n.questionid = '$i'";
  301.             if (!$laststate get_record_sql($sql)) {
  302.                 // Only restore previous responses that have been graded
  303.                 continue;
  304.             }
  305.             // Restore the state so that the responses will be restored
  306.             restore_question_state($questions[$i]$laststate);
  307.             // prepare the previous responses for new processing
  308.             $action new stdClass;
  309.             $action->responses $laststate->responses;
  310.             $action->timestamp $laststate->timestamp;
  311.             $action->event QUESTION_EVENTOPEN;
  312.  
  313.             // Process these responses ...
  314.             question_process_responses($questions[$i]$states[$i]$action$quiz$attempt);
  315.  
  316.             // Fix for Bug #5506: When each attempt is built on the last one,
  317.             // preserve the options from any previous attempt. 
  318.             if isset($laststate->options) ) {
  319.                 $states[$i]->options $laststate->options;
  320.             }
  321.  
  322.             // ... and save the new states
  323.             save_question_session($questions[$i]$states[$i]);
  324.         }
  325.     }
  326.  
  327. /// Process form data /////////////////////////////////////////////////
  328.  
  329.     if ($isnotify{
  330.  
  331.         $responses = (object)$_GET// GET version of data_submitted (see lib/weblib) used in original web version
  332.  
  333.         // set the default event. This can be overruled by individual buttons.
  334.         $event (array_key_exists('markall'$responses)) QUESTION_EVENTSUBMIT :
  335.          ($finishattempt QUESTION_EVENTCLOSE QUESTION_EVENTSAVE);
  336.  
  337.         // Unset any variables we know are not responses
  338.         unset($responses->id);
  339.         unset($responses->q);
  340.         unset($responses->oldpage);
  341.         unset($responses->newpage);
  342.         unset($responses->review);
  343.         unset($responses->questionids);
  344.         unset($responses->saveattempt)// responses get saved anway
  345.         unset($responses->finishattempt)// same as $finishattempt
  346.         unset($responses->markall);
  347.         unset($responses->forcenewattempt);
  348.  
  349.         // extract responses
  350.         // $actions is an array indexed by the questions ids
  351.         $actions question_extract_responses($questions$responses$event);
  352.  
  353.         // Process each question in turn
  354.  
  355.         $questionidarray explode(','$questionids);
  356.         foreach($questionidarray as $i{
  357.             if (!isset($actions[$i])) {
  358.                 $actions[$i]->responses array('' => '');
  359.             }
  360.             $actions[$i]->timestamp $timestamp;
  361.             question_process_responses($questions[$i]$states[$i]$actions[$i]$quiz$attempt);
  362.             save_question_session($questions[$i]$states[$i]);
  363.         }
  364.  
  365.         $attempt->timemodified $timestamp;
  366.  
  367.     // We have now finished processing form data
  368.     }
  369.  
  370.  
  371. /// Finish attempt if requested
  372.     if ($finishattempt{
  373.  
  374.         // Set the attempt to be finished
  375.         $attempt->timefinish $timestamp;
  376.  
  377.         // Find all the questions for this attempt for which the newest
  378.         // state is not also the newest graded state
  379.         if ($closequestions get_records_select('question_sessions',
  380.          "attemptid = $attempt->uniqueid AND newest != newgraded"'''questionid, questionid')) {
  381.  
  382.             // load all the questions
  383.             $closequestionlist implode(','array_keys($closequestions));
  384.             $sql "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
  385.                    "  FROM {$CFG->prefix}question q,".
  386.                    "       {$CFG->prefix}quiz_question_instances i".
  387.                    " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
  388.                    "   AND q.id IN ($closequestionlist)";
  389.             if (!$closequestions get_records_sql($sql)) {
  390.                 $sloodle->response->quick_output(-10303'QUIZ''Questions missing.'FALSE);
  391.                 exit();
  392.             }
  393.  
  394.             // Load the question type specific information
  395.             if (!get_question_options($closequestions)) {
  396.                 $sloodle->response->quick_output(-10303'QUIZ''Could not load question options.'FALSE);
  397.                 exit();
  398.             }
  399.  
  400.             // Restore the question sessions
  401.             if (!$closestates get_question_states($closequestions$quiz$attempt)) {
  402.                 $sloodle->response->quick_output(-701'QUIZ''Could not restore question sessions.'FALSE);
  403.                 exit();
  404.             }
  405.  
  406.             foreach($closequestions as $key => $question{
  407.                 $action->event QUESTION_EVENTCLOSE;
  408.                 $action->responses $closestates[$key]->responses;
  409.                 $action->timestamp $closestates[$key]->timestamp;
  410.                 question_process_responses($question$closestates[$key]$action$quiz$attempt);
  411.                             save_question_session($question$closestates[$key]);
  412.             }
  413.         }
  414.         add_to_log($course->id'quiz''close attempt',
  415.                            "review.php?attempt=$attempt->id",
  416.                            "$quiz->id"$cm->id);
  417.     }
  418.  
  419. /// Update the quiz attempt and the overall grade for the quiz
  420.     if ((isset($responses&& $responses|| $finishattempt{
  421.         if (!update_record('quiz_attempts'$attempt)) {
  422.             $sloodle->response->quick_output(-701'QUIZ''Failed to save current quiz attempt.'FALSE);
  423.             exit();
  424.         }
  425.         if (($attempt->attempt || $attempt->timefinish 0and !$attempt->preview{
  426.             quiz_save_best_grade($quiz);
  427.         }
  428.     }
  429.  
  430. /// Check access to quiz page
  431.  
  432.     // check the quiz times
  433.     //TODO: Figure out what this does...
  434.     if ($timestamp $quiz->timeopen || ($quiz->timeclose and $timestamp $quiz->timeclose)) {
  435.         $sloodle->response->quick_output(-701'QUIZ''Quiz not available.'FALSE);
  436.         exit();
  437.     }
  438.  
  439.     if ($finishattempt{
  440.         // redirect('review.php?attempt='.$attempt->id);
  441.         //$sloodle->response->quick_output(-701, 'QUIZ', 'Got to finishattempt - but do not yet have Sloodle code to handle it.', FALSE);
  442.         //exit();
  443.     }
  444.  
  445. /// Print the quiz page ////////////////////////////////////////////////////////
  446.  
  447.     if (!$isnotify{
  448.  
  449.         $pagequestions explode(','$pagelist);
  450.         $lastquestion count($pagequestions);
  451.  
  452.         // Only output quiz data if a question has not been requetsed
  453.         if ($limittoquestion == 0{
  454.             $output[array('quiz',$quiz->attempts,$quiz->name,$quiz->timelimit,$quiz->id,$lastquestion);
  455.             $output[array('quizpages',quiz_number_of_pages($attempt->layout),$page,$pagelist);
  456.         }
  457.  
  458.         // We will keep track of question numbers local to this quiz
  459.         $localqnum 0;
  460.         /// Print all the questions
  461.         $number quiz_first_questionnumber($attempt->layout$pagelist);
  462.         foreach ($pagequestions as $i{
  463.             $options quiz_get_renderoptions($quiz->review$states[$i]);
  464.             // Print the question
  465.             // var_dump($questions[$i]);
  466.             $q $questions[$i];
  467.             $localqnum++;
  468.             
  469.             if ($limittoquestion == $q->id{
  470.             
  471.                 // Make sure the variables exist (avoids warnings!)
  472.                 if (!isset($q->single)) $q->single 0;
  473.                 if (!isset($q->shuffleanswers)) $q->shuffleanswers 0;
  474.             
  475.                 $output[array(
  476.                     'question',
  477.                     $localqnum//$i, // The value in $i is equal to $q->id, rather than being sequential in the quiz
  478.                     $q->id,
  479.                     $q->parent,
  480.                     strip_tags(stripslashes($q->questiontext)),
  481.                     $q->defaultgrade,
  482.                     $q->penalty,
  483.                     $q->qtype,
  484.                     $q->hidden,
  485.                     $q->maxgrade,
  486.                     $q->single,
  487.                     $q->shuffleanswers,
  488.                     //$deferred    // This variable doesn't seem to be mentioned anywhere else in the file
  489.                 );
  490.  
  491.                 $ops $q->options;
  492.                 foreach($ops as $opkey=>$op{
  493.                    
  494.                    if (!is_array($op)) continue// Ignore this if there are no options (Prevents nasty PHP notices!)
  495.                 
  496.                    foreach($op as $ok=>$ov{
  497.                       $output[array(
  498.                         'questionoption',
  499.                         $i,
  500.                         $ov->id,
  501.                         $ov->question,
  502.                         strip_tags(stripslashes($ov->answer)),
  503.                         $ov->fraction,
  504.                         $ov->feedback
  505.                       );
  506.                    }
  507.                 }
  508.             }
  509.             //print_question($questions[$i], $states[$i], $number, $quiz, $options);
  510.             save_question_session($questions[$i]$states[$i]);
  511.             $number += $questions[$i]->length;
  512.         }
  513.  
  514.         $secondsleft ($quiz->timeclose $quiz->timeclose 999999999999time();
  515.         //if ($isteacher) {
  516.         //    // For teachers ignore the quiz closing time
  517.         //    $secondsleft = 999999999999;
  518.         //}
  519.         // If time limit is set include floating timer.
  520.         if ($quiz->timelimit 0{
  521.             $output[array('seconds left',$secondsleft);
  522.         }
  523.     }
  524.  
  525.     $sloodle->response->set_status_code(1);
  526.     $sloodle->response->set_status_descriptor('QUIZ');
  527.     $sloodle->response->set_data($output);
  528.     
  529.     sloodle_debug('<pre>');
  530.     $sloodle->response->render_to_output();
  531.     sloodle_debug('</pre>');
  532.     
  533.     exit;
  534.  
  535. ?>

Documentation generated on Mon, 07 Jul 2008 12:32:43 +0100 by phpDocumentor 1.4.0