2 //This php script contains all the stuff to restore questions
5 // the restoration of the parent and sortorder fields in the category table needs
6 // a lot more thought. We should probably use a library function to add the category
7 // rather than just writing it to the database
9 // whereever it says "/// We have to recode the .... field" we should put in a check
10 // to see if the recoding was successful and throw an appropriate error otherwise
12 //This is the "graphical" structure of the question database:
13 //To see, put your terminal to 160cc
15 // The following holds student-independent information about the questions
17 // question_categories
21 // |.......................................
24 // | -------question_datasets------ .
25 // | | (CL,pk->id,fk->question, | .
26 // | | fk->dataset_definition) | .
30 // | | question_dataset_definitions
31 // | | (CL,pk->id,fk->category)
33 // (CL,pk->id,fk->category,files) |
34 // | question_dataset_items
35 // | (CL,pk->id,fk->definition)
36 // | question_rqp_type
39 // -------------------------------------------------------------------------------------------------------------- |
40 // | | | | | | | question_rqp
41 // | | | | | | |--(CL,pk->id,fk->question)
42 // | | | | question_calculated | |
43 // question_truefalse | question_multichoice | (CL,pl->id,fk->question) | |
44 // (CL,pk->id,fk->question) | (CL,pk->id,fk->question) | . | | question_randomsamatch
45 // . | . | . | |--(CL,pk->id,fk->question)
46 // . question_shortanswer . question_numerical . question_multianswer. |
47 // . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) |
48 // . . . . . . | question_match
49 // . . . . . . |--(CL,pk->id,fk->question)
53 // . . . . . . | question_match_sub
54 // ........................................................................................ |--(CL,pk->id,fk->question)
57 // . | question_numerical_units
58 // question_answers |--(CL,pk->id,fk->question)
59 // (CL,pk->id,fk->question)----------------------------------------------------------
62 // The following holds the information about student interaction with the questions
65 // (UL,pk->id,fk->attempt,question)
69 // (UL,pk->id,fk->attempt,question)
71 // question_rqp_states
72 // (UL,pk->id,fk->stateid)
74 // Meaning: pk->primary key field of the table
75 // fk->foreign key to link with parent
76 // nt->nested field (recursive data)
77 // SL->site level info
78 // CL->course level info
79 // UL->user level info
80 // files->table may have files
82 //-----------------------------------------------------------
84 include_once($CFG->libdir
.'/questionlib.php');
86 function restore_question_categories($category,$restore) {
92 //Hook to call Moodle < 1.5 Quiz Restore
93 if ($restore->backup_version
< 2005043000) {
94 include_once($CFG->dirroot
.'/mod/quiz/restorelibpre15.php');
95 return quiz_restore_pre15_question_categories($category,$restore);
98 //Get record from backup_ids
99 $data = backup_getid($restore->backup_unique_code
,"question_categories",$category->id
);
102 //Now get completed xmlized object
104 //traverse_xmlize($info); //Debug
105 //print_object ($GLOBALS['traverse_array']); //Debug
106 //$GLOBALS['traverse_array']=""; //Debug
108 //Now, build the question_categories record structure
109 $question_cat = new stdClass
;
110 $question_cat->course
= $restore->course_id
;
111 $question_cat->name
= backup_todb($info['QUESTION_CATEGORY']['#']['NAME']['0']['#']);
112 $question_cat->info
= backup_todb($info['QUESTION_CATEGORY']['#']['INFO']['0']['#']);
113 $question_cat->publish
= backup_todb($info['QUESTION_CATEGORY']['#']['PUBLISH']['0']['#']);
114 $question_cat->stamp
= backup_todb($info['QUESTION_CATEGORY']['#']['STAMP']['0']['#']);
115 $question_cat->parent
= backup_todb($info['QUESTION_CATEGORY']['#']['PARENT']['0']['#']);
116 $question_cat->sortorder
= backup_todb($info['QUESTION_CATEGORY']['#']['SORTORDER']['0']['#']);
118 if ($catfound = restore_get_best_question_category($question_cat, $restore->course_id
)) {
121 if (!$question_cat->stamp
) {
122 $question_cat->stamp
= make_unique_id_code();
124 $newid = insert_record ("question_categories",$question_cat);
129 if (!defined('RESTORE_SILENTLY')) {
130 echo "<li>".get_string('category', 'quiz')." \"".$question_cat->name
."\"<br />";
133 //We must never arrive here !!
134 if (!defined('RESTORE_SILENTLY')) {
135 echo "<li>".get_string('category', 'quiz')." \"".$question_cat->name
."\" Error!<br />";
141 //Here category has been created or selected, so save results in backup_ids and start with questions
142 if ($newid and $status) {
143 //We have the newid, update backup_ids
144 backup_putid($restore->backup_unique_code
,"question_categories",
145 $category->id
, $newid);
146 //Now restore question
147 $status = restore_questions ($category->id
, $newid,$info,$restore);
151 if (!defined('RESTORE_SILENTLY')) {
155 echo 'Could not get backup info for question category'. $category->id
;
161 function restore_questions ($old_category_id,$new_category_id,$info,$restore) {
163 global $CFG, $QTYPES;
166 $restored_questions = array();
168 //Get the questions array
169 if (!empty($info['QUESTION_CATEGORY']['#']['QUESTIONS'])) {
170 $questions = $info['QUESTION_CATEGORY']['#']['QUESTIONS']['0']['#']['QUESTION'];
172 $questions = array();
175 //Iterate over questions
176 for($i = 0; $i < sizeof($questions); $i++
) {
177 $que_info = $questions[$i];
178 //traverse_xmlize($que_info); //Debug
179 //print_object ($GLOBALS['traverse_array']); //Debug
180 //$GLOBALS['traverse_array']=""; //Debug
182 //We'll need this later!!
183 $oldid = backup_todb($que_info['#']['ID']['0']['#']);
185 //Now, build the question record structure
186 $question = new object;
187 $question->category
= $new_category_id;
188 $question->parent
= backup_todb($que_info['#']['PARENT']['0']['#']);
189 $question->name
= backup_todb($que_info['#']['NAME']['0']['#']);
190 $question->questiontext
= backup_todb($que_info['#']['QUESTIONTEXT']['0']['#']);
191 $question->questiontextformat
= backup_todb($que_info['#']['QUESTIONTEXTFORMAT']['0']['#']);
192 $question->image
= backup_todb($que_info['#']['IMAGE']['0']['#']);
193 if (array_key_exists('GENERALFEEDBACK', $que_info['#'])) {
194 $question->generalfeedback
= backup_todb($que_info['#']['GENERALFEEDBACK']['0']['#']);
196 $question->generalfeedback
= '';
198 $question->defaultgrade
= backup_todb($que_info['#']['DEFAULTGRADE']['0']['#']);
199 $question->penalty
= backup_todb($que_info['#']['PENALTY']['0']['#']);
200 $question->qtype
= backup_todb($que_info['#']['QTYPE']['0']['#']);
201 $question->length
= backup_todb($que_info['#']['LENGTH']['0']['#']);
202 $question->stamp
= backup_todb($que_info['#']['STAMP']['0']['#']);
203 $question->version
= backup_todb($que_info['#']['VERSION']['0']['#']);
204 $question->hidden
= backup_todb($que_info['#']['HIDDEN']['0']['#']);
206 if ($restore->backup_version
< 2006032200) {
207 // The qtype was an integer that now needs to be converted to the name
208 $qtypenames = array(1=>'shortanswer',2=>'truefalse',3=>'multichoice',4=>'random',5=>'match',
209 6=>'randomsamatch',7=>'description',8=>'numerical',9=>'multianswer',10=>'calculated',
210 11=>'rqp',12=>'essay');
211 $question->qtype
= $qtypenames[$question->qtype
];
214 //Check if the question exists
215 //by category, stamp, and version
216 $question_exists = get_record ("question","category",$question->category
,
217 "stamp",$question->stamp
,"version",$question->version
);
219 //If the question exists, only record its id
220 if ($question_exists) {
221 $newid = $question_exists->id
;
222 $creatingnewquestion = false;
223 //Else, create a new question
225 //The structure is equal to the db, so insert the question
226 $newid = insert_record ("question",$question);
227 $creatingnewquestion = true;
230 //Save newid to backup tables
232 //We have the newid, update backup_ids
233 backup_putid($restore->backup_unique_code
,"question",$oldid,
237 $restored_questions[$i] = new stdClass
;
238 $restored_questions[$i]->newid
= $newid;
239 $restored_questions[$i]->oldid
= $oldid;
240 $restored_questions[$i]->qtype
= $question->qtype
;
241 $restored_questions[$i]->parent
= $question->parent
;
242 $restored_questions[$i]->is_new
= $creatingnewquestion;
245 // Loop again, now all the question id mappings exist, so everything can
247 for($i = 0; $i < sizeof($questions); $i++
) {
248 $que_info = $questions[$i];
250 $newid = $restored_questions[$i]->newid
;
251 $oldid = $restored_questions[$i]->oldid
;
253 $question = new object;
254 $question->qtype
= $restored_questions[$i]->qtype
;
255 $question->parent
= $restored_questions[$i]->parent
;
258 //If it's a new question in the DB, restore it
259 if ($restored_questions[$i]->is_new
) {
261 ////We have to recode the parent field
262 if ($question->parent
) {
263 if ($parent = backup_getid($restore->backup_unique_code
,"question",$question->parent
)) {
264 $question->parent
= $parent->new_id
;
265 } elseif ($question->parent
= $oldid) {
266 $question->parent
= $newid;
268 echo 'Could not recode parent '.$question->parent
.' for question '.$oldid.'<br />';
272 //Now, restore every question_answers in this question
273 $status = question_restore_answers($oldid,$newid,$que_info,$restore);
274 // Restore questiontype specific data
275 if (array_key_exists($question->qtype
, $QTYPES)) {
276 $status = $QTYPES[$question->qtype
]->restore($oldid,$newid,$que_info,$restore);
278 echo 'Unknown question type '.$question->qtype
.' for question '.$oldid.'<br />';
282 //We are NOT creating the question, but we need to know every question_answers
283 //map between the XML file and the database to be able to restore the states
285 $status = question_restore_map_answers($oldid,$newid,$que_info,$restore);
286 // Do the questiontype specific mapping
287 if (array_key_exists($question->qtype
, $QTYPES)) {
288 $status = $QTYPES[$question->qtype
]->restore_map($oldid,$newid,$que_info,$restore);
290 echo 'Unknown question type '.$question->qtype
.' for question '.$oldid.'<br />';
296 if (($i+
1) %
2 == 0) {
297 if (!defined('RESTORE_SILENTLY')) {
299 if (($i+
1) %
40 == 0) {
309 function question_restore_answers ($old_question_id,$new_question_id,$info,$restore) {
314 $qtype = backup_todb($info['#']['QTYPE']['0']['#']);
316 //Get the answers array
317 if (isset($info['#']['ANSWERS']['0']['#']['ANSWER'])) {
318 $answers = $info['#']['ANSWERS']['0']['#']['ANSWER'];
320 //Iterate over answers
321 for($i = 0; $i < sizeof($answers); $i++
) {
322 $ans_info = $answers[$i];
323 //traverse_xmlize($ans_info); //Debug
324 //print_object ($GLOBALS['traverse_array']); //Debug
325 //$GLOBALS['traverse_array']=""; //Debug
327 //We'll need this later!!
328 $oldid = backup_todb($ans_info['#']['ID']['0']['#']);
330 //Now, build the question_answers record structure
331 $answer = new stdClass
;
332 $answer->question
= $new_question_id;
333 $answer->answer
= backup_todb($ans_info['#']['ANSWER_TEXT']['0']['#']);
334 $answer->fraction
= backup_todb($ans_info['#']['FRACTION']['0']['#']);
335 $answer->feedback
= backup_todb($ans_info['#']['FEEDBACK']['0']['#']);
337 // Update 'match everything' answers for numerical questions coming from old backup files.
338 if ($qtype == 'numerical' && $answer->answer
== '') {
339 $answer->answer
= '*';
342 //The structure is equal to the db, so insert the question_answers
343 $newid = insert_record ("question_answers",$answer);
346 if (($i+
1) %
50 == 0) {
347 if (!defined('RESTORE_SILENTLY')) {
349 if (($i+
1) %
1000 == 0) {
357 //We have the newid, update backup_ids
358 backup_putid($restore->backup_unique_code
,"question_answers",$oldid,
369 function question_restore_map_answers ($old_question_id,$new_question_id,$info,$restore) {
375 if (!isset($info['#']['ANSWERS'])) { // No answers in this question (eg random)
379 //Get the answers array
380 $answers = $info['#']['ANSWERS']['0']['#']['ANSWER'];
382 //Iterate over answers
383 for($i = 0; $i < sizeof($answers); $i++
) {
384 $ans_info = $answers[$i];
385 //traverse_xmlize($ans_info); //Debug
386 //print_object ($GLOBALS['traverse_array']); //Debug
387 //$GLOBALS['traverse_array']=""; //Debug
389 //We'll need this later!!
390 $oldid = backup_todb($ans_info['#']['ID']['0']['#']);
392 //Now, build the question_answers record structure
393 $answer->question
= $new_question_id;
394 $answer->answer
= backup_todb($ans_info['#']['ANSWER_TEXT']['0']['#']);
395 $answer->fraction
= backup_todb($ans_info['#']['FRACTION']['0']['#']);
396 $answer->feedback
= backup_todb($ans_info['#']['FEEDBACK']['0']['#']);
398 //If we are in this method is because the question exists in DB, so its
399 //answers must exist too.
400 //Now, we are going to look for that answer in DB and to create the
401 //mappings in backup_ids to use them later where restoring states (user level).
403 //Get the answer from DB (by question and answer)
404 $db_answer = get_record ("question_answers","question",$new_question_id,
405 "answer",$answer->answer
);
408 if (($i+
1) %
50 == 0) {
409 if (!defined('RESTORE_SILENTLY')) {
411 if (($i+
1) %
1000 == 0) {
419 //We have the database answer, update backup_ids
420 backup_putid($restore->backup_unique_code
,"question_answers",$oldid,
430 function question_restore_numerical_units ($old_question_id,$new_question_id,$info,$restore) {
436 //Get the numerical array
437 if (!empty($info['#']['NUMERICAL_UNITS'])) {
438 $numerical_units = $info['#']['NUMERICAL_UNITS']['0']['#']['NUMERICAL_UNIT'];
440 $numerical_units = array();
443 //Iterate over numerical_units
444 for($i = 0; $i < sizeof($numerical_units); $i++
) {
445 $nu_info = $numerical_units[$i];
446 //traverse_xmlize($nu_info); //Debug
447 //print_object ($GLOBALS['traverse_array']); //Debug
448 //$GLOBALS['traverse_array']=""; //Debug
450 //Now, build the question_numerical_UNITS record structure
451 $numerical_unit = new stdClass
;
452 $numerical_unit->question
= $new_question_id;
453 $numerical_unit->multiplier
= backup_todb($nu_info['#']['MULTIPLIER']['0']['#']);
454 $numerical_unit->unit
= backup_todb($nu_info['#']['UNIT']['0']['#']);
456 //The structure is equal to the db, so insert the question_numerical_units
457 $newid = insert_record ("question_numerical_units",$numerical_unit);
467 function question_restore_dataset_definitions ($old_question_id,$new_question_id,$info,$restore) {
473 //Get the dataset_definitions array
474 $dataset_definitions = $info['#']['DATASET_DEFINITIONS']['0']['#']['DATASET_DEFINITION'];
476 //Iterate over dataset_definitions
477 for($i = 0; $i < sizeof($dataset_definitions); $i++
) {
478 $dd_info = $dataset_definitions[$i];
479 //traverse_xmlize($dd_info); //Debug
480 //print_object ($GLOBALS['traverse_array']); //Debug
481 //$GLOBALS['traverse_array']=""; //Debug
483 //Now, build the question_dataset_DEFINITION record structure
484 $dataset_definition = new stdClass
;
485 $dataset_definition->category
= backup_todb($dd_info['#']['CATEGORY']['0']['#']);
486 $dataset_definition->name
= backup_todb($dd_info['#']['NAME']['0']['#']);
487 $dataset_definition->type
= backup_todb($dd_info['#']['TYPE']['0']['#']);
488 $dataset_definition->options
= backup_todb($dd_info['#']['OPTIONS']['0']['#']);
489 $dataset_definition->itemcount
= backup_todb($dd_info['#']['ITEMCOUNT']['0']['#']);
491 //We have to recode the category field (only if the category != 0)
492 if ($dataset_definition->category
!= 0) {
493 $category = backup_getid($restore->backup_unique_code
,"question_categories",$dataset_definition->category
);
495 $dataset_definition->category
= $category->new_id
;
497 echo 'Could not recode category id '.$dataset_definition->category
.' for dataset definition'.$dataset_definition->name
.'<br />';
501 //Now, we hace to decide when to create the new records or reuse an existing one
502 $create_definition = false;
504 //If the dataset_definition->category = 0, it's a individual question dataset_definition, so we'll create it
505 if ($dataset_definition->category
== 0) {
506 $create_definition = true;
508 //The category isn't 0, so it's a category question dataset_definition, we have to see if it exists
509 //Look for a definition with the same category, name and type
510 if ($definitionrec = get_record_sql("SELECT d.*
511 FROM {$CFG->prefix}question_dataset_definitions d
512 WHERE d.category = '$dataset_definition->category' AND
513 d.name = '$dataset_definition->name' AND
514 d.type = '$dataset_definition->type'")) {
515 //Such dataset_definition exist. Now we must check if it has enough itemcount
516 if ($definitionrec->itemcount
< $dataset_definition->itemcount
) {
517 //We haven't enough itemcount, so we have to create the definition as an individual question one.
518 $dataset_definition->category
= 0;
519 $create_definition = true;
521 //We have enough itemcount, so we'll reuse the existing definition
522 $create_definition = false;
523 $newid = $definitionrec->id
;
526 //Such dataset_definition doesn't exist. We'll create it.
527 $create_definition = true;
531 //If we've to create the definition, do it
532 if ($create_definition) {
533 //The structure is equal to the db, so insert the question_dataset_definitions
534 $newid = insert_record ("question_dataset_definitions",$dataset_definition);
536 //Restore question_dataset_items
537 $status = question_restore_dataset_items($newid,$dd_info,$restore);
541 //Now, we must have a definition (created o reused). Its id is in newid. Create the question_datasets record
542 //to join the question and the dataset_definition
544 $question_dataset = new stdClass
;
545 $question_dataset->question
= $new_question_id;
546 $question_dataset->datasetdefinition
= $newid;
547 $newid = insert_record ("question_datasets",$question_dataset);
558 function question_restore_dataset_items ($definitionid,$info,$restore) {
564 //Get the items array
565 $dataset_items = $info['#']['DATASET_ITEMS']['0']['#']['DATASET_ITEM'];
567 //Iterate over dataset_items
568 for($i = 0; $i < sizeof($dataset_items); $i++
) {
569 $di_info = $dataset_items[$i];
570 //traverse_xmlize($di_info); //Debug
571 //print_object ($GLOBALS['traverse_array']); //Debug
572 //$GLOBALS['traverse_array']=""; //Debug
574 //Now, build the question_dataset_ITEMS record structure
575 $dataset_item = new stdClass
;
576 $dataset_item->definition
= $definitionid;
577 $dataset_item->itemnumber
= backup_todb($di_info['#']['NUMBER']['0']['#']);
578 $dataset_item->value
= backup_todb($di_info['#']['VALUE']['0']['#']);
580 //The structure is equal to the db, so insert the question_dataset_items
581 $newid = insert_record ("question_dataset_items",$dataset_item);
592 //This function restores the question_states
593 function question_states_restore_mods($attempt_id,$info,$restore) {
595 global $CFG, $QTYPES;
599 //Get the question_states array
600 $states = $info['#']['STATES']['0']['#']['STATE'];
601 //Iterate over states
602 for($i = 0; $i < sizeof($states); $i++
) {
603 $res_info = $states[$i];
604 //traverse_xmlize($res_info); //Debug
605 //print_object ($GLOBALS['traverse_array']); //Debug
606 //$GLOBALS['traverse_array']=""; //Debug
608 //We'll need this later!!
609 $oldid = backup_todb($res_info['#']['ID']['0']['#']);
611 //Now, build the STATES record structure
612 $state = new stdClass
;
613 $state->attempt
= $attempt_id;
614 $state->question
= backup_todb($res_info['#']['QUESTION']['0']['#']);
615 $state->originalquestion
= backup_todb($res_info['#']['ORIGINALQUESTION']['0']['#']);
616 $state->seq_number
= backup_todb($res_info['#']['SEQ_NUMBER']['0']['#']);
617 $state->answer
= backup_todb($res_info['#']['ANSWER']['0']['#']);
618 $state->timestamp
= backup_todb($res_info['#']['TIMESTAMP']['0']['#']);
619 $state->event
= backup_todb($res_info['#']['EVENT']['0']['#']);
620 $state->grade
= backup_todb($res_info['#']['GRADE']['0']['#']);
621 $state->raw_grade
= backup_todb($res_info['#']['RAW_GRADE']['0']['#']);
622 $state->penalty
= backup_todb($res_info['#']['PENALTY']['0']['#']);
623 $state->oldid
= $oldid; // So it is available to restore_recode_answer.
625 //We have to recode the question field
626 $question = backup_getid($restore->backup_unique_code
,"question",$state->question
);
628 $state->question
= $question->new_id
;
630 echo 'Could not recode question id '.$state->question
.' for state '.$oldid.'<br />';
633 //We have to recode the originalquestion field if it is nonzero
634 if ($state->originalquestion
) {
635 $question = backup_getid($restore->backup_unique_code
,"question",$state->originalquestion
);
637 $state->originalquestion
= $question->new_id
;
639 echo 'Could not recode originalquestion id '.$state->question
.' for state '.$oldid.'<br />';
643 //We have to recode the answer field
644 //It depends of the question type !!
645 //We get the question first
646 if (!$question = get_record("question","id",$state->question
)) {
647 error("Can't find the record for question $state->question for which I am trying to restore a state");
649 //Depending on the qtype, we make different recodes
650 if ($state->answer
) {
651 $state->answer
= $QTYPES[$question->qtype
]->restore_recode_answer($state, $restore);
654 //The structure is equal to the db, so insert the question_states
655 $newid = insert_record ("question_states",$state);
658 if (($i+
1) %
10 == 0) {
659 if (!defined('RESTORE_SILENTLY')) {
661 if (($i+
1) %
200 == 0) {
669 //We have the newid, update backup_ids
670 backup_putid($restore->backup_unique_code
,"question_states",$oldid,
672 //Now process question type specific state information
673 $qtype = get_field('question', 'qtype', 'id', $state->question
);
674 $status = $QTYPES[$qtype]->restore_state($newid,$res_info,$restore);
680 //Get the question_sessions array
681 $sessions = $info['#']['NEWEST_STATES']['0']['#']['NEWEST_STATE'];
682 //Iterate over question_sessions
683 for($i = 0; $i < sizeof($sessions); $i++
) {
684 $res_info = $sessions[$i];
685 //traverse_xmlize($res_info); //Debug
686 //print_object ($GLOBALS['traverse_array']); //Debug
687 //$GLOBALS['traverse_array']=""; //Debug
689 //Now, build the NEWEST_STATES record structure
690 $session = new stdClass
;
691 $session->attemptid
= $attempt_id;
692 $session->questionid
= backup_todb($res_info['#']['QUESTIONID']['0']['#']);
693 $session->newest
= backup_todb($res_info['#']['NEWEST']['0']['#']);
694 $session->newgraded
= backup_todb($res_info['#']['NEWGRADED']['0']['#']);
695 $session->sumpenalty
= backup_todb($res_info['#']['SUMPENALTY']['0']['#']);
697 //We have to recode the question field
698 $question = backup_getid($restore->backup_unique_code
,"question",$session->questionid
);
700 $session->questionid
= $question->new_id
;
702 echo 'Could not recode question id '.$session->questionid
.'<br />';
705 //We have to recode the newest field
706 $state = backup_getid($restore->backup_unique_code
,"question_states",$session->newest
);
708 $session->newest
= $state->new_id
;
710 echo 'Could not recode newest state id '.$session->newest
.'<br />';
713 //If the session has been graded we have to recode the newgraded field
714 if ($session->newgraded
) {
715 $state = backup_getid($restore->backup_unique_code
,"question_states",$session->newgraded
);
717 $session->newgraded
= $state->new_id
;
719 echo 'Could not recode newest graded state id '.$session->newgraded
.'<br />';
723 //The structure is equal to the db, so insert the question_sessions
724 $newid = insert_record ("question_sessions",$session);