Merge commit 'catalyst/MOODLE_19_STABLE' into mdl19-linuxchix
[moodle-linuxchix.git] / mod / quiz / backuplib.php
blob1d846bbd9c920b1c89cd0709da82de414e0847ba
1 <?php // $Id$
2 //This php script contains all the stuff to backup quizzes
4 //This is the "graphical" structure of the quiz mod:
5 //To see, put your terminal to 160cc
7 //
8 // quiz
9 // (CL,pk->id)
10 // |
11 // -------------------------------------------------------------------
12 // | | | | |
13 // | quiz_grades | quiz_question_versions |
14 // | (UL,pk->id,fk->quiz) | (CL,pk->id,fk->quiz) |
15 // | | |
16 // quiz_attempts quiz_question_instances quiz_feedback
17 // (UL,pk->id,fk->quiz) (CL,pk->id,fk->quiz,question) (CL,pk->id,fk->quiz)
19 // Meaning: pk->primary key field of the table
20 // fk->foreign key to link with parent
21 // nt->nested field (recursive data)
22 // SL->site level info
23 // CL->course level info
24 // UL->user level info
25 // files->table may have files
27 //-----------------------------------------------------------
29 // When we backup a quiz we also need to backup the questions and possibly
30 // the data about student interaction with the questions. The functions to do
31 // that are included with the following library
32 require_once("$CFG->dirroot/question/backuplib.php");
35 * Insert necessary category ids to backup_ids table. Called during backup_check.html
36 * This backs up ids for quiz module. It backs up :
37 * all categories and questions in course
38 * all categories and questions in contexts of quiz module instances which have been selected for backup
39 * all categories and questions in contexts above course level that are used by quizzes that have been selected for backup
41 function quiz_insert_category_and_question_ids($course, $backup_unique_code, $instances = null) {
42 global $CFG;
43 $status = true;
45 // Create missing categories and reasign orphaned questions.
46 quiz_fix_orphaned_questions($course);
48 $coursecontext = get_context_instance(CONTEXT_COURSE, $course);
49 $status = $status && question_insert_c_and_q_ids_for_course($coursecontext, $backup_unique_code);
51 // then, all categories and questions from this course's modules' contexts.
52 $status = $status && question_insert_c_and_q_ids_for_module($backup_unique_code, $course, 'quiz', $instances);
54 // Then categories from parent contexts used by the quizzes we are backing up.
55 //TODO this will need generalising when we have modules other than quiz using shared questions above course level.
56 $parentcontexts = get_parent_contexts($coursecontext);
57 $from = "{$CFG->prefix}quiz quiz,";
58 $where = "AND quiz.course = '$course'
59 AND qqi.quiz = quiz.id";
60 if (!empty($instances) && is_array($instances) && count($instances)) {
61 $questionselectsqlfrom = '';
62 $questionselectsqlwhere = 'AND qqi.quiz IN ('.implode(',',array_keys($instances)).')';
63 } else {
64 $questionselectsqlfrom = "{$CFG->prefix}quiz quiz,";
65 $questionselectsqlwhere = "AND quiz.course = '$course'
66 AND qqi.quiz = quiz.id";
68 $categories = get_records_sql("
69 SELECT id, parent, 0 AS childrendone
70 FROM {$CFG->prefix}question_categories
71 WHERE contextid IN (".join($parentcontexts, ', ').")
72 AND id IN (
73 SELECT DISTINCT question.category
74 FROM {$CFG->prefix}question question,
75 $questionselectsqlfrom
76 {$CFG->prefix}quiz_question_instances qqi
77 WHERE qqi.question = question.id
78 $questionselectsqlwhere
79 )", false);
80 if (!$categories) {
81 $categories = array();
82 } else {
83 //put the ids of the used questions from all these categories into the db.
84 $status = $status && execute_sql("INSERT INTO {$CFG->prefix}backup_ids
85 (backup_code, table_name, old_id, info)
86 SELECT DISTINCT $backup_unique_code, 'question', q.id, ''
87 FROM {$CFG->prefix}question q,
88 $from
89 {$CFG->prefix}question_categories qc,
90 {$CFG->prefix}quiz_question_instances qqi
91 WHERE (qqi.question = q.id
92 OR qqi.question = q.parent)
93 AND q.category = qc.id
94 AND qc.contextid IN (".join($parentcontexts, ', ').")
95 $where", false);
97 // Add the parent categories, of these categories up to the top of the category tree.
98 // not backing up the questions in these categories.
99 foreach ($categories as $category) {
100 while ($category->parent != 0) {
101 if (array_key_exists($category->parent, $categories)) {
102 // Parent category already on the list.
103 break;
105 $currentid = $category->id;
106 $category = get_record('question_categories', 'id', $category->parent, '', '', '', '', 'id, parent, 0 AS childrendone');
107 if ($category) {
108 $categories[$category->id] = $category;
109 } else {
110 // Parent not found: this indicates an error, but just fix it.
111 set_field('question_categories', 'parent', 0, 'id', $currentid);
112 break;
117 // Now we look for categories from other courses containing random questions
118 // in our quizzes that select from the category and its subcategories. That implies
119 // those subcategories also need to be backed up. (The categories themselves
120 // and their parents will already have been included.)
121 $categorieswithrandom = get_records_sql("
122 SELECT question.category AS id, SUM(" .
123 sql_cast_char2int('questiontext', true) . ") AS numqsusingsubcategories
124 FROM {$CFG->prefix}quiz_question_instances qqi,
125 $from
126 {$CFG->prefix}question question
127 WHERE question.id = qqi.question
128 AND question.qtype = '" . RANDOM . "'
129 $where
130 GROUP BY question.category
132 $randomselectedquestions = array();
133 if ($categorieswithrandom) {
134 foreach ($categorieswithrandom as $category) {
135 if ($category->numqsusingsubcategories > 0) {
136 $status = $status && quiz_backup_add_sub_categories($categories, $randomselectedquestions, $category->id);
139 $returnval = get_records_sql("
140 SELECT question.id
141 FROM {$CFG->prefix}question question
142 WHERE question.category IN (".join(array_keys($categorieswithrandom), ', ').")");
143 if ($returnval) {
144 $randomselectedquestions += $returnval;
148 // Finally, add all these extra categories to the backup_ids table.
149 foreach ($categories as $category) {
150 $status = $status && backup_putid($backup_unique_code, 'question_categories', $category->id, 0);
152 // Finally, add all these extra categories to the backup_ids table.
153 foreach ($randomselectedquestions as $question) {
154 $status = $status && backup_putid($backup_unique_code, 'question', $question->id, 0);
157 return $status;
161 * Helper function adding the id of all the subcategories of a category to an array.
163 function quiz_backup_add_sub_categories(&$categories, &$questions, $categoryid) {
164 global $CFG;
165 $status = true;
166 if ($categories[$categoryid]->childrendone) {
167 return $status;
169 if ($subcategories = get_records('question_categories', 'parent', $categoryid, '', 'id, 0 AS childrendone')) {
170 foreach ($subcategories as $subcategory) {
171 if (!array_key_exists($subcategory->id, $categories)) {
172 $categories[$subcategory->id] = $subcategory;
174 $status = $status && quiz_backup_add_sub_categories($categories, $questions, $subcategory->id);
176 $subcatlist = join(array_keys($subcategories), ',');
177 $returnval = get_records_sql("
178 SELECT question.id
179 FROM {$CFG->prefix}question question
180 WHERE question.category IN ($subcatlist)
182 if ($returnval) {
183 $questions += $returnval;
186 $categories[$categoryid]->childrendone = 1;
187 return $status;
191 //This function is used to detect orphaned questions (pointing to a
192 //non existing category) and to recreate such category. This function
193 //is used by the backup process, to ensure consistency and should be
194 //executed in the upgrade process and, perhaps in the health center.
195 function quiz_fix_orphaned_questions ($course) {
197 global $CFG;
199 $categories = get_records_sql("SELECT DISTINCT t.category, t.category
200 FROM {$CFG->prefix}question t,
201 {$CFG->prefix}quiz_question_instances g,
202 {$CFG->prefix}quiz q
203 WHERE q.course = '$course' AND
204 g.quiz = q.id AND
205 g.question = t.id",false);
206 if ($categories) {
207 foreach ($categories as $key => $category) {
208 $exist = get_record('question_categories','id', $key);
209 //If the category doesn't exist
210 if (!$exist) {
211 //Build a new category
212 $db_cat = new stdClass;
213 // always create missing categories in course context
214 $db_cat->contextid = get_context_instance(CONTEXT_COURSE, $course);
215 $db_cat->name = get_string('recreatedcategory','',$key);
216 $db_cat->info = get_string('recreatedcategory','',$key);
217 $db_cat->stamp = make_unique_id_code();
218 //Insert the new category
219 $catid = insert_record('question_categories',$db_cat);
220 unset ($db_cat);
221 if ($catid) {
222 //Reasign orphaned questions to their new category
223 set_field ('question','category',$catid,'category',$key);
231 //STEP 2. Backup quizzes and associated structures
232 // (course dependent)
234 function quiz_backup_one_mod($bf,$preferences,$quiz) {
235 $status = true;
237 if (is_numeric($quiz)) {
238 $quiz = get_record('quiz','id',$quiz);
241 //Start mod
242 fwrite ($bf,start_tag("MOD",3,true));
243 //Print quiz data
244 fwrite ($bf,full_tag("ID",4,false,$quiz->id));
245 fwrite ($bf,full_tag("MODTYPE",4,false,"quiz"));
246 fwrite ($bf,full_tag("NAME",4,false,$quiz->name));
247 fwrite ($bf,full_tag("INTRO",4,false,$quiz->intro));
248 fwrite ($bf,full_tag("TIMEOPEN",4,false,$quiz->timeopen));
249 fwrite ($bf,full_tag("TIMECLOSE",4,false,$quiz->timeclose));
250 fwrite ($bf,full_tag("OPTIONFLAGS",4,false,$quiz->optionflags));
251 fwrite ($bf,full_tag("PENALTYSCHEME",4,false,$quiz->penaltyscheme));
252 fwrite ($bf,full_tag("ATTEMPTS_NUMBER",4,false,$quiz->attempts));
253 fwrite ($bf,full_tag("ATTEMPTONLAST",4,false,$quiz->attemptonlast));
254 fwrite ($bf,full_tag("GRADEMETHOD",4,false,$quiz->grademethod));
255 fwrite ($bf,full_tag("DECIMALPOINTS",4,false,$quiz->decimalpoints));
256 fwrite ($bf,full_tag("REVIEW",4,false,$quiz->review));
257 fwrite ($bf,full_tag("QUESTIONSPERPAGE",4,false,$quiz->questionsperpage));
258 fwrite ($bf,full_tag("SHUFFLEQUESTIONS",4,false,$quiz->shufflequestions));
259 fwrite ($bf,full_tag("SHUFFLEANSWERS",4,false,$quiz->shuffleanswers));
260 fwrite ($bf,full_tag("QUESTIONS",4,false,$quiz->questions));
261 fwrite ($bf,full_tag("SUMGRADES",4,false,$quiz->sumgrades));
262 fwrite ($bf,full_tag("GRADE",4,false,$quiz->grade));
263 fwrite ($bf,full_tag("TIMECREATED",4,false,$quiz->timecreated));
264 fwrite ($bf,full_tag("TIMEMODIFIED",4,false,$quiz->timemodified));
265 fwrite ($bf,full_tag("TIMELIMIT",4,false,$quiz->timelimit));
266 fwrite ($bf,full_tag("PASSWORD",4,false,$quiz->password));
267 fwrite ($bf,full_tag("SUBNET",4,false,$quiz->subnet));
268 fwrite ($bf,full_tag("POPUP",4,false,$quiz->popup));
269 fwrite ($bf,full_tag("DELAY1",4,false,$quiz->delay1));
270 fwrite ($bf,full_tag("DELAY2",4,false,$quiz->delay2));
271 //Now we print to xml question_instances (Course Level)
272 $status = backup_quiz_question_instances($bf,$preferences,$quiz->id);
273 //Now we print to xml quiz_feedback (Course Level)
274 $status = backup_quiz_feedback($bf,$preferences,$quiz->id);
275 //Now we print to xml question_versions (Course Level)
276 $status = backup_quiz_question_versions($bf,$preferences,$quiz->id);
277 //if we've selected to backup users info, then execute:
278 // - backup_quiz_grades
279 // - backup_quiz_attempts
280 if (backup_userdata_selected($preferences,'quiz',$quiz->id) && $status) {
281 $status = backup_quiz_grades($bf,$preferences,$quiz->id);
282 if ($status) {
283 $status = backup_quiz_attempts($bf,$preferences,$quiz->id);
286 //End mod
287 $status = fwrite ($bf,end_tag("MOD",3,true));
289 return $status;
293 function quiz_backup_mods($bf,$preferences) {
295 global $CFG;
297 $status = true;
299 //Iterate over quiz table
300 $quizzes = get_records ("quiz","course",$preferences->backup_course,"id");
301 if ($quizzes) {
302 foreach ($quizzes as $quiz) {
303 if (backup_mod_selected($preferences,'quiz',$quiz->id)) {
304 $status = quiz_backup_one_mod($bf,$preferences,$quiz);
308 return $status;
311 //Backup quiz_question_instances contents (executed from quiz_backup_mods)
312 function backup_quiz_question_instances ($bf,$preferences,$quiz) {
313 $status = true;
315 $quiz_question_instances = get_records("quiz_question_instances","quiz",$quiz,"id");
316 //If there are question_instances
317 if ($quiz_question_instances) {
318 //Write start tag
319 $status = fwrite ($bf,start_tag("QUESTION_INSTANCES",4,true));
320 //Iterate over each question_instance
321 foreach ($quiz_question_instances as $que_ins) {
322 //Start question instance
323 $status = fwrite ($bf,start_tag("QUESTION_INSTANCE",5,true));
324 //Print question_instance contents
325 fwrite ($bf,full_tag("ID",6,false,$que_ins->id));
326 fwrite ($bf,full_tag("QUESTION",6,false,$que_ins->question));
327 fwrite ($bf,full_tag("GRADE",6,false,$que_ins->grade));
328 //End question instance
329 $status = fwrite ($bf,end_tag("QUESTION_INSTANCE",5,true));
331 //Write end tag
332 $status = fwrite ($bf,end_tag("QUESTION_INSTANCES",4,true));
334 return $status;
337 //Backup quiz_question_instances contents (executed from quiz_backup_mods)
338 function backup_quiz_feedback ($bf,$preferences,$quiz) {
339 $status = true;
341 $quiz_feedback = get_records('quiz_feedback', 'quizid', $quiz, 'id');
342 // If there are question_instances ...
343 if ($quiz_feedback) {
344 // Write start tag.
345 $status = $status & fwrite($bf,start_tag('FEEDBACKS', 4, true));
347 // Iterate over each question_instance.
348 foreach ($quiz_feedback as $feedback) {
350 //Start feedback instance
351 $status = $status & fwrite($bf, start_tag('FEEDBACK',5,true));
353 //Print question_instance contents.
354 $status = $status & fwrite($bf, full_tag('ID', 6, false, $feedback->id));
355 $status = $status & fwrite($bf, full_tag('QUIZID', 6, false, $feedback->quizid));
356 $status = $status & fwrite($bf, full_tag('FEEDBACKTEXT', 6, false, $feedback->feedbacktext));
357 $status = $status & fwrite($bf, full_tag('MINGRADE', 6, false, $feedback->mingrade));
358 $status = $status & fwrite($bf, full_tag('MAXGRADE', 6, false, $feedback->maxgrade));
360 // End feedback instance.
361 $status = $status & fwrite($bf, end_tag('FEEDBACK', 5, true));
364 // Write end tag.
365 $status = $status & fwrite($bf, end_tag('FEEDBACKS', 4, true));
367 return $status;
370 //Backup quiz_question_versions contents (executed from quiz_backup_mods)
371 function backup_quiz_question_versions ($bf,$preferences,$quiz) {
372 $status = true;
374 $quiz_question_versions = get_records("quiz_question_versions","quiz",$quiz,"id");
375 //If there are question_versions
376 if ($quiz_question_versions) {
377 //Write start tag
378 $status = fwrite ($bf,start_tag("QUESTION_VERSIONS",4,true));
379 //Iterate over each question_version
380 foreach ($quiz_question_versions as $que_ver) {
381 //Start question version
382 $status = fwrite ($bf,start_tag("QUESTION_VERSION",5,true));
383 //Print question_version contents
384 fwrite ($bf,full_tag("ID",6,false,$que_ver->id));
385 fwrite ($bf,full_tag("OLDQUESTION",6,false,$que_ver->oldquestion));
386 fwrite ($bf,full_tag("NEWQUESTION",6,false,$que_ver->newquestion));
387 fwrite ($bf,full_tag("ORIGINALQUESTION",6,false,$que_ver->originalquestion));
388 fwrite ($bf,full_tag("USERID",6,false,$que_ver->userid));
389 fwrite ($bf,full_tag("TIMESTAMP",6,false,$que_ver->timestamp));
390 //End question version
391 $status = fwrite ($bf,end_tag("QUESTION_VERSION",5,true));
393 //Write end tag
394 $status = fwrite ($bf,end_tag("QUESTION_VERSIONS",4,true));
396 return $status;
400 //Backup quiz_grades contents (executed from quiz_backup_mods)
401 function backup_quiz_grades ($bf,$preferences,$quiz) {
402 $status = true;
404 $quiz_grades = get_records("quiz_grades","quiz",$quiz,"id");
405 //If there are grades
406 if ($quiz_grades) {
407 //Write start tag
408 $status = fwrite ($bf,start_tag("GRADES",4,true));
409 //Iterate over each grade
410 foreach ($quiz_grades as $gra) {
411 //Start grade
412 $status = fwrite ($bf,start_tag("GRADE",5,true));
413 //Print grade contents
414 fwrite ($bf,full_tag("ID",6,false,$gra->id));
415 fwrite ($bf,full_tag("USERID",6,false,$gra->userid));
416 fwrite ($bf,full_tag("GRADEVAL",6,false,$gra->grade));
417 fwrite ($bf,full_tag("TIMEMODIFIED",6,false,$gra->timemodified));
418 //End question grade
419 $status = fwrite ($bf,end_tag("GRADE",5,true));
421 //Write end tag
422 $status = fwrite ($bf,end_tag("GRADES",4,true));
424 return $status;
427 //Backup quiz_attempts contents (executed from quiz_backup_mods)
428 function backup_quiz_attempts ($bf,$preferences,$quiz) {
429 $status = true;
431 $quiz_attempts = get_records("quiz_attempts","quiz",$quiz,"id");
432 //If there are attempts
433 if ($quiz_attempts) {
434 //Write start tag
435 $status = fwrite ($bf,start_tag("ATTEMPTS",4,true));
436 //Iterate over each attempt
437 foreach ($quiz_attempts as $attempt) {
438 //Start attempt
439 $status = fwrite ($bf,start_tag("ATTEMPT",5,true));
440 //Print attempt contents
441 fwrite ($bf,full_tag("ID",6,false,$attempt->id));
442 fwrite ($bf,full_tag("UNIQUEID",6,false,$attempt->uniqueid));
443 fwrite ($bf,full_tag("USERID",6,false,$attempt->userid));
444 fwrite ($bf,full_tag("ATTEMPTNUM",6,false,$attempt->attempt));
445 fwrite ($bf,full_tag("SUMGRADES",6,false,$attempt->sumgrades));
446 fwrite ($bf,full_tag("TIMESTART",6,false,$attempt->timestart));
447 fwrite ($bf,full_tag("TIMEFINISH",6,false,$attempt->timefinish));
448 fwrite ($bf,full_tag("TIMEMODIFIED",6,false,$attempt->timemodified));
449 fwrite ($bf,full_tag("LAYOUT",6,false,$attempt->layout));
450 fwrite ($bf,full_tag("PREVIEW",6,false,$attempt->preview));
451 //Now write to xml the states (in this attempt)
452 $status = backup_question_states ($bf,$preferences,$attempt->uniqueid);
453 //Now write to xml the sessions (in this attempt)
454 $status = backup_question_sessions ($bf,$preferences,$attempt->uniqueid);
455 //End attempt
456 $status = fwrite ($bf,end_tag("ATTEMPT",5,true));
458 //Write end tag
459 $status = fwrite ($bf,end_tag("ATTEMPTS",4,true));
461 return $status;
464 function quiz_check_backup_mods_instances($instance,$backup_unique_code) {
465 // the keys in this array need to be unique as they get merged...
466 $info[$instance->id.'0'][0] = '<b>'.$instance->name.'</b>';
467 $info[$instance->id.'0'][1] = '';
469 //Categories
470 $info[$instance->id.'1'][0] = get_string("categories","quiz");
471 if ($ids = question_category_ids_by_backup ($backup_unique_code)) {
472 $info[$instance->id.'1'][1] = count($ids);
473 } else {
474 $info[$instance->id.'1'][1] = 0;
476 //Questions
477 $info[$instance->id.'2'][0] = get_string("questionsinclhidden","quiz");
478 if ($ids = question_ids_by_backup ($backup_unique_code)) {
479 $info[$instance->id.'2'][1] = count($ids);
480 } else {
481 $info[$instance->id.'2'][1] = 0;
484 //Now, if requested, the user_data
485 if (!empty($instance->userdata)) {
486 //Grades
487 $info[$instance->id.'3'][0] = get_string("grades");
488 if ($ids = quiz_grade_ids_by_instance ($instance->id)) {
489 $info[$instance->id.'3'][1] = count($ids);
490 } else {
491 $info[$instance->id.'3'][1] = 0;
494 return $info;
497 ////Return an array of info (name,value)
498 /// $instances is an array with key = instanceid, value = object (name,id,userdata)
499 function quiz_check_backup_mods($course,$user_data= false,$backup_unique_code,$instances=null) {
500 //this function selects all the questions / categories to be backed up.
501 quiz_insert_category_and_question_ids($course, $backup_unique_code, $instances);
502 if ($course != SITEID){
503 question_insert_site_file_names($course, $backup_unique_code);
505 if (!empty($instances) && is_array($instances) && count($instances)) {
506 $info = array();
507 foreach ($instances as $id => $instance) {
508 $info += quiz_check_backup_mods_instances($instance,$backup_unique_code);
510 return $info;
512 //First the course data
513 $info[0][0] = get_string("modulenameplural","quiz");
514 if ($ids = quiz_ids ($course)) {
515 $info[0][1] = count($ids);
516 } else {
517 $info[0][1] = 0;
519 //Categories
520 $info[1][0] = get_string("categories","quiz");
521 if ($ids = question_category_ids_by_backup ($backup_unique_code)) {
522 $info[1][1] = count($ids);
523 } else {
524 $info[1][1] = 0;
526 //Questions
527 $info[2][0] = get_string("questions","quiz");
528 if ($ids = question_ids_by_backup ($backup_unique_code)) {
529 $info[2][1] = count($ids);
530 } else {
531 $info[2][1] = 0;
534 //Now, if requested, the user_data
535 if ($user_data) {
536 //Grades
537 $info[3][0] = get_string("grades");
538 if ($ids = quiz_grade_ids_by_course ($course)) {
539 $info[3][1] = count($ids);
540 } else {
541 $info[3][1] = 0;
545 return $info;
548 //Return a content encoded to support interactivities linking. Every module
549 //should have its own. They are called automatically from the backup procedure.
550 function quiz_encode_content_links ($content,$preferences) {
552 global $CFG;
554 $base = preg_quote($CFG->wwwroot,"/");
556 //Link to the list of quizs
557 $buscar="/(".$base."\/mod\/quiz\/index.php\?id\=)([0-9]+)/";
558 $result= preg_replace($buscar,'$@QUIZINDEX*$2@$',$content);
560 //Link to quiz view by moduleid
561 $buscar="/(".$base."\/mod\/quiz\/view.php\?id\=)([0-9]+)/";
562 $result= preg_replace($buscar,'$@QUIZVIEWBYID*$2@$',$result);
564 //Link to quiz view by quizid
565 $buscar="/(".$base."\/mod\/quiz\/view.php\?q\=)([0-9]+)/";
566 $result= preg_replace($buscar,'$@QUIZVIEWBYQ*$2@$',$result);
568 return $result;
571 // INTERNAL FUNCTIONS. BASED IN THE MOD STRUCTURE
573 //Returns an array of quiz id
574 function quiz_ids ($course) {
576 global $CFG;
578 return get_records_sql ("SELECT a.id, a.course
579 FROM {$CFG->prefix}quiz a
580 WHERE a.course = '$course'");
583 function quiz_grade_ids_by_course ($course) {
585 global $CFG;
587 return get_records_sql ("SELECT g.id, g.quiz
588 FROM {$CFG->prefix}quiz a,
589 {$CFG->prefix}quiz_grades g
590 WHERE a.course = '$course' and
591 g.quiz = a.id");
594 function quiz_grade_ids_by_instance($instanceid) {
596 global $CFG;
598 return get_records_sql ("SELECT g.id, g.quiz
599 FROM {$CFG->prefix}quiz_grades g
600 WHERE g.quiz = $instanceid");