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
11 // -------------------------------------------------------------------
13 // | quiz_grades | quiz_question_versions |
14 // | (UL,pk->id,fk->quiz) | (CL,pk->id,fk->quiz) |
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->libdir/questionlib.php");
33 require_once("$CFG->dirroot/question/backuplib.php");
35 //STEP 1. Backup categories/questions and associated structures
36 // (course independent)
38 //Insert necessary category ids to backup_ids table
39 function insert_category_ids($course, $backup_unique_code, $instances = null) {
41 include_once("$CFG->dirroot/mod/quiz/lib.php");
43 // Create missing categories and reasign orphaned questions.
44 fix_orphaned_questions($course);
46 // First, all categories from this course.
47 $status = execute_sql("INSERT INTO {$CFG->prefix}backup_ids
48 (backup_code, table_name, old_id, info)
49 SELECT '$backup_unique_code', 'question_categories', qc.id, ''
50 FROM {$CFG->prefix}question_categories qc
51 WHERE qc.course = $course", false);
53 // Then published categories from other courses used by the quizzes we are backing up.
54 $from = "{$CFG->prefix}quiz quiz,";
55 $where = "AND quiz.course = '$course'
56 AND qqi.quiz = quiz.id";
57 if (!empty($instances) && is_array($instances) && count($instances)) {
59 $where = 'AND qqi.quiz IN ('.implode(',',array_keys($instances)).')';
61 $categories = get_records_sql("
62 SELECT id, parent, 0 AS childrendone
63 FROM {$CFG->prefix}question_categories
64 WHERE course <> $course
66 SELECT DISTINCT question.category
67 FROM {$CFG->prefix}question question,
69 {$CFG->prefix}quiz_question_instances qqi
70 WHERE qqi.question = question.id
74 $categories = array();
77 // Add the parent categories, of these categories up to the top of the category tree.
78 foreach ($categories as $category) {
79 while ($category->parent
!= 0) {
80 if (array_key_exists($category->parent
, $categories)) {
81 // Parent category already on the list.
84 $currentid = $category->id
;
85 $category = get_record('question_categories', 'id', $category->parent
, '', '', '', '', 'id, parent, 0 AS childrendone');
87 $categories[$category->id
] = $category;
89 // Parent not found: this indicates an error, but just fix it.
90 set_field('question_categories', 'parent', 0, 'id', $currentid);
96 // Now we look for categories from other courses containing random questions
97 // in our quiz that select from the category and its subcategories. That implies
98 // those subcategories also need to be backed up. (The categories themselves
99 // and their parents will already have been included.)
100 $categorieswithrandom = get_records_sql("
101 SELECT DISTINCT question.category AS id
102 FROM {$CFG->prefix}quiz_question_instances qqi,
104 {$CFG->prefix}question question
105 WHERE question.id = qqi.question
106 AND question.qtype = '" . RANDOM
. "'
107 AND question.questiontext = '1'
110 if ($categorieswithrandom) {
111 foreach ($categorieswithrandom as $category) {
112 $status = quiz_backup_add_sub_categories($categories, $category->id
);
116 // Finally, add all these extra categories to the backup_ids table.
117 foreach ($categories as $category) {
118 $status = $status && backup_putid($backup_unique_code, 'question_categories', $category->id
, 0);
125 * Helper function adding the id of all the subcategories of a category to an array.
127 function quiz_backup_add_sub_categories(&$categories, $categoryid) {
129 if ($categories[$categoryid]->childrendone
) {
132 if ($subcategories = get_records('question_categories', 'parent', $categoryid, '', 'id, 0 AS childrendone')) {
133 foreach ($subcategories as $subcategory) {
134 if (!array_key_exists($subcategory->id
, $categories)) {
135 $categories[$subcategory->id
] = $subcategory;
137 $status = $status && quiz_backup_add_sub_categories($categories, $subcategory->id
);
140 $categories[$categoryid]->childrendone
= 1;
145 //This function is used to detect orphaned questions (pointing to a
146 //non existing category) and to recreate such category. This function
147 //is used by the backup process, to ensure consistency and should be
148 //executed in the upgrade process and, perhaps in the health center.
149 function fix_orphaned_questions ($course) {
153 $categories = get_records_sql("SELECT DISTINCT t.category, t.category
154 FROM {$CFG->prefix}question t,
155 {$CFG->prefix}quiz_question_instances g,
157 WHERE q.course = '$course' AND
159 g.question = t.id",false);
161 foreach ($categories as $key => $category) {
162 $exist = get_record('question_categories','id', $key);
163 //If the category doesn't exist
165 //Build a new category
166 $db_cat = new stdClass
;
167 $db_cat->course
= $course;
168 $db_cat->name
= get_string('recreatedcategory','',$key);
169 $db_cat->info
= get_string('recreatedcategory','',$key);
170 $db_cat->publish
= 1;
171 $db_cat->stamp
= make_unique_id_code();
172 //Insert the new category
173 $catid = insert_record('question_categories',$db_cat);
176 //Reasign orphaned questions to their new category
177 set_field ('question','category',$catid,'category',$key);
185 //STEP 2. Backup quizzes and associated structures
186 // (course dependent)
188 function quiz_backup_one_mod($bf,$preferences,$quiz) {
191 if (is_numeric($quiz)) {
192 $quiz = get_record('quiz','id',$quiz);
196 fwrite ($bf,start_tag("MOD",3,true));
198 fwrite ($bf,full_tag("ID",4,false,$quiz->id
));
199 fwrite ($bf,full_tag("MODTYPE",4,false,"quiz"));
200 fwrite ($bf,full_tag("NAME",4,false,$quiz->name
));
201 fwrite ($bf,full_tag("INTRO",4,false,$quiz->intro
));
202 fwrite ($bf,full_tag("TIMEOPEN",4,false,$quiz->timeopen
));
203 fwrite ($bf,full_tag("TIMECLOSE",4,false,$quiz->timeclose
));
204 fwrite ($bf,full_tag("OPTIONFLAGS",4,false,$quiz->optionflags
));
205 fwrite ($bf,full_tag("PENALTYSCHEME",4,false,$quiz->penaltyscheme
));
206 fwrite ($bf,full_tag("ATTEMPTS_NUMBER",4,false,$quiz->attempts
));
207 fwrite ($bf,full_tag("ATTEMPTONLAST",4,false,$quiz->attemptonlast
));
208 fwrite ($bf,full_tag("GRADEMETHOD",4,false,$quiz->grademethod
));
209 fwrite ($bf,full_tag("DECIMALPOINTS",4,false,$quiz->decimalpoints
));
210 fwrite ($bf,full_tag("REVIEW",4,false,$quiz->review
));
211 fwrite ($bf,full_tag("QUESTIONSPERPAGE",4,false,$quiz->questionsperpage
));
212 fwrite ($bf,full_tag("SHUFFLEQUESTIONS",4,false,$quiz->shufflequestions
));
213 fwrite ($bf,full_tag("SHUFFLEANSWERS",4,false,$quiz->shuffleanswers
));
214 fwrite ($bf,full_tag("QUESTIONS",4,false,$quiz->questions
));
215 fwrite ($bf,full_tag("SUMGRADES",4,false,$quiz->sumgrades
));
216 fwrite ($bf,full_tag("GRADE",4,false,$quiz->grade
));
217 fwrite ($bf,full_tag("TIMECREATED",4,false,$quiz->timecreated
));
218 fwrite ($bf,full_tag("TIMEMODIFIED",4,false,$quiz->timemodified
));
219 fwrite ($bf,full_tag("TIMELIMIT",4,false,$quiz->timelimit
));
220 fwrite ($bf,full_tag("PASSWORD",4,false,$quiz->password
));
221 fwrite ($bf,full_tag("SUBNET",4,false,$quiz->subnet
));
222 fwrite ($bf,full_tag("POPUP",4,false,$quiz->popup
));
223 fwrite ($bf,full_tag("DELAY1",4,false,$quiz->delay1
));
224 fwrite ($bf,full_tag("DELAY2",4,false,$quiz->delay2
));
225 //Now we print to xml question_instances (Course Level)
226 $status = backup_quiz_question_instances($bf,$preferences,$quiz->id
);
227 //Now we print to xml quiz_feedback (Course Level)
228 $status = backup_quiz_feedback($bf,$preferences,$quiz->id
);
229 //Now we print to xml question_versions (Course Level)
230 $status = backup_quiz_question_versions($bf,$preferences,$quiz->id
);
231 //if we've selected to backup users info, then execute:
232 // - backup_quiz_grades
233 // - backup_quiz_attempts
234 if (backup_userdata_selected($preferences,'quiz',$quiz->id
) && $status) {
235 $status = backup_quiz_grades($bf,$preferences,$quiz->id
);
237 $status = backup_quiz_attempts($bf,$preferences,$quiz->id
);
241 $status = fwrite ($bf,end_tag("MOD",3,true));
247 function quiz_backup_mods($bf,$preferences) {
253 //Iterate over quiz table
254 $quizzes = get_records ("quiz","course",$preferences->backup_course
,"id");
256 foreach ($quizzes as $quiz) {
257 if (backup_mod_selected($preferences,'quiz',$quiz->id
)) {
258 $status = quiz_backup_one_mod($bf,$preferences,$quiz);
265 //Backup quiz_question_instances contents (executed from quiz_backup_mods)
266 function backup_quiz_question_instances ($bf,$preferences,$quiz) {
269 $quiz_question_instances = get_records("quiz_question_instances","quiz",$quiz,"id");
270 //If there are question_instances
271 if ($quiz_question_instances) {
273 $status = fwrite ($bf,start_tag("QUESTION_INSTANCES",4,true));
274 //Iterate over each question_instance
275 foreach ($quiz_question_instances as $que_ins) {
276 //Start question instance
277 $status = fwrite ($bf,start_tag("QUESTION_INSTANCE",5,true));
278 //Print question_instance contents
279 fwrite ($bf,full_tag("ID",6,false,$que_ins->id
));
280 fwrite ($bf,full_tag("QUESTION",6,false,$que_ins->question
));
281 fwrite ($bf,full_tag("GRADE",6,false,$que_ins->grade
));
282 //End question instance
283 $status = fwrite ($bf,end_tag("QUESTION_INSTANCE",5,true));
286 $status = fwrite ($bf,end_tag("QUESTION_INSTANCES",4,true));
291 //Backup quiz_question_instances contents (executed from quiz_backup_mods)
292 function backup_quiz_feedback ($bf,$preferences,$quiz) {
295 $quiz_feedback = get_records('quiz_feedback', 'quizid', $quiz, 'id');
296 // If there are question_instances ...
297 if ($quiz_feedback) {
299 $status = $status & fwrite($bf,start_tag('FEEDBACKS', 4, true));
301 // Iterate over each question_instance.
302 foreach ($quiz_feedback as $feedback) {
304 //Start feedback instance
305 $status = $status & fwrite($bf, start_tag('FEEDBACK',5,true));
307 //Print question_instance contents.
308 $status = $status & fwrite($bf, full_tag('ID', 6, false, $feedback->id
));
309 $status = $status & fwrite($bf, full_tag('QUIZID', 6, false, $feedback->quizid
));
310 $status = $status & fwrite($bf, full_tag('FEEDBACKTEXT', 6, false, $feedback->feedbacktext
));
311 $status = $status & fwrite($bf, full_tag('MINGRADE', 6, false, $feedback->mingrade
));
312 $status = $status & fwrite($bf, full_tag('MAXGRADE', 6, false, $feedback->maxgrade
));
314 // End feedback instance.
315 $status = $status & fwrite($bf, end_tag('FEEDBACK', 5, true));
319 $status = $status & fwrite($bf, end_tag('FEEDBACKS', 4, true));
324 //Backup quiz_question_versions contents (executed from quiz_backup_mods)
325 function backup_quiz_question_versions ($bf,$preferences,$quiz) {
328 $quiz_question_versions = get_records("quiz_question_versions","quiz",$quiz,"id");
329 //If there are question_versions
330 if ($quiz_question_versions) {
332 $status = fwrite ($bf,start_tag("QUESTION_VERSIONS",4,true));
333 //Iterate over each question_version
334 foreach ($quiz_question_versions as $que_ver) {
335 //Start question version
336 $status = fwrite ($bf,start_tag("QUESTION_VERSION",5,true));
337 //Print question_version contents
338 fwrite ($bf,full_tag("ID",6,false,$que_ver->id
));
339 fwrite ($bf,full_tag("OLDQUESTION",6,false,$que_ver->oldquestion
));
340 fwrite ($bf,full_tag("NEWQUESTION",6,false,$que_ver->newquestion
));
341 fwrite ($bf,full_tag("ORIGINALQUESTION",6,false,$que_ver->originalquestion
));
342 fwrite ($bf,full_tag("USERID",6,false,$que_ver->userid
));
343 fwrite ($bf,full_tag("TIMESTAMP",6,false,$que_ver->timestamp
));
344 //End question version
345 $status = fwrite ($bf,end_tag("QUESTION_VERSION",5,true));
348 $status = fwrite ($bf,end_tag("QUESTION_VERSIONS",4,true));
354 //Backup quiz_grades contents (executed from quiz_backup_mods)
355 function backup_quiz_grades ($bf,$preferences,$quiz) {
358 $quiz_grades = get_records("quiz_grades","quiz",$quiz,"id");
359 //If there are grades
362 $status = fwrite ($bf,start_tag("GRADES",4,true));
363 //Iterate over each grade
364 foreach ($quiz_grades as $gra) {
366 $status = fwrite ($bf,start_tag("GRADE",5,true));
367 //Print grade contents
368 fwrite ($bf,full_tag("ID",6,false,$gra->id
));
369 fwrite ($bf,full_tag("USERID",6,false,$gra->userid
));
370 fwrite ($bf,full_tag("GRADEVAL",6,false,$gra->grade
));
371 fwrite ($bf,full_tag("TIMEMODIFIED",6,false,$gra->timemodified
));
373 $status = fwrite ($bf,end_tag("GRADE",5,true));
376 $status = fwrite ($bf,end_tag("GRADES",4,true));
381 //Backup quiz_attempts contents (executed from quiz_backup_mods)
382 function backup_quiz_attempts ($bf,$preferences,$quiz) {
385 $quiz_attempts = get_records("quiz_attempts","quiz",$quiz,"id");
386 //If there are attempts
387 if ($quiz_attempts) {
389 $status = fwrite ($bf,start_tag("ATTEMPTS",4,true));
390 //Iterate over each attempt
391 foreach ($quiz_attempts as $attempt) {
393 $status = fwrite ($bf,start_tag("ATTEMPT",5,true));
394 //Print attempt contents
395 fwrite ($bf,full_tag("ID",6,false,$attempt->id
));
396 fwrite ($bf,full_tag("UNIQUEID",6,false,$attempt->uniqueid
));
397 fwrite ($bf,full_tag("USERID",6,false,$attempt->userid
));
398 fwrite ($bf,full_tag("ATTEMPTNUM",6,false,$attempt->attempt
));
399 fwrite ($bf,full_tag("SUMGRADES",6,false,$attempt->sumgrades
));
400 fwrite ($bf,full_tag("TIMESTART",6,false,$attempt->timestart
));
401 fwrite ($bf,full_tag("TIMEFINISH",6,false,$attempt->timefinish
));
402 fwrite ($bf,full_tag("TIMEMODIFIED",6,false,$attempt->timemodified
));
403 fwrite ($bf,full_tag("LAYOUT",6,false,$attempt->layout
));
404 fwrite ($bf,full_tag("PREVIEW",6,false,$attempt->preview
));
405 //Now write to xml the states (in this attempt)
406 $status = backup_question_states ($bf,$preferences,$attempt->uniqueid
);
407 //Now write to xml the sessions (in this attempt)
408 $status = backup_question_sessions ($bf,$preferences,$attempt->uniqueid
);
410 $status = fwrite ($bf,end_tag("ATTEMPT",5,true));
413 $status = fwrite ($bf,end_tag("ATTEMPTS",4,true));
418 function quiz_check_backup_mods_instances($instance,$backup_unique_code) {
419 // the keys in this array need to be unique as they get merged...
420 $info[$instance->id
.'0'][0] = '<b>'.$instance->name
.'</b>';
421 $info[$instance->id
.'0'][1] = '';
424 $info[$instance->id
.'1'][0] = get_string("categories","quiz");
425 if ($ids = question_category_ids_by_backup ($backup_unique_code)) {
426 $info[$instance->id
.'1'][1] = count($ids);
428 $info[$instance->id
.'1'][1] = 0;
431 $info[$instance->id
.'2'][0] = get_string("questionsinclhidden","quiz");
432 if ($ids = question_ids_by_backup ($backup_unique_code)) {
433 $info[$instance->id
.'2'][1] = count($ids);
435 $info[$instance->id
.'2'][1] = 0;
438 //Now, if requested, the user_data
439 if (!empty($instance->userdata
)) {
441 $info[$instance->id
.'3'][0] = get_string("grades");
442 if ($ids = quiz_grade_ids_by_instance ($instance->id
)) {
443 $info[$instance->id
.'3'][1] = count($ids);
445 $info[$instance->id
.'3'][1] = 0;
451 ////Return an array of info (name,value)
452 /// $instances is an array with key = instanceid, value = object (name,id,userdata)
453 function quiz_check_backup_mods($course,$user_data= false,$backup_unique_code,$instances=null) {
455 //Deletes data from mdl_backup_ids (categories section)
456 delete_category_ids ($backup_unique_code);
457 //Create date into mdl_backup_ids (categories section)
458 insert_category_ids ($course,$backup_unique_code,$instances);
459 if (!empty($instances) && is_array($instances) && count($instances)) {
461 foreach ($instances as $id => $instance) {
462 $info +
= quiz_check_backup_mods_instances($instance,$backup_unique_code);
466 //First the course data
467 $info[0][0] = get_string("modulenameplural","quiz");
468 if ($ids = quiz_ids ($course)) {
469 $info[0][1] = count($ids);
474 $info[1][0] = get_string("categories","quiz");
475 if ($ids = question_category_ids_by_backup ($backup_unique_code)) {
476 $info[1][1] = count($ids);
481 $info[2][0] = get_string("questions","quiz");
482 if ($ids = question_ids_by_backup ($backup_unique_code)) {
483 $info[2][1] = count($ids);
488 //Now, if requested, the user_data
491 $info[3][0] = get_string("grades");
492 if ($ids = quiz_grade_ids_by_course ($course)) {
493 $info[3][1] = count($ids);
502 //Return a content encoded to support interactivities linking. Every module
503 //should have its own. They are called automatically from the backup procedure.
504 function quiz_encode_content_links ($content,$preferences) {
508 $base = preg_quote($CFG->wwwroot
,"/");
510 //Link to the list of quizs
511 $buscar="/(".$base."\/mod\/quiz\/index.php\?id\=)([0-9]+)/";
512 $result= preg_replace($buscar,'$@QUIZINDEX*$2@$',$content);
514 //Link to quiz view by moduleid
515 $buscar="/(".$base."\/mod\/quiz\/view.php\?id\=)([0-9]+)/";
516 $result= preg_replace($buscar,'$@QUIZVIEWBYID*$2@$',$result);
521 // INTERNAL FUNCTIONS. BASED IN THE MOD STRUCTURE
523 //Returns an array of quiz id
524 function quiz_ids ($course) {
528 return get_records_sql ("SELECT a.id, a.course
529 FROM {$CFG->prefix}quiz a
530 WHERE a.course = '$course'");
533 function quiz_grade_ids_by_course ($course) {
537 return get_records_sql ("SELECT g.id, g.quiz
538 FROM {$CFG->prefix}quiz a,
539 {$CFG->prefix}quiz_grades g
540 WHERE a.course = '$course' and
544 function quiz_grade_ids_by_instance($instanceid) {
548 return get_records_sql ("SELECT g.id, g.quiz
549 FROM {$CFG->prefix}quiz_grades g
550 WHERE g.quiz = $instanceid");