3 * Question bank backup code.
5 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
6 * @package questionbank
9 //This is the "graphical" structure of the question database:
10 //To see, put your terminal to 160cc
12 // The following holds student-independent information about the questions
14 // question_categories
18 // |.......................................
21 // | -------question_datasets------ .
22 // | | (CL,pk->id,fk->question, | .
23 // | | fk->dataset_definition) | .
27 // | | question_dataset_definitions
28 // | | (CL,pk->id,fk->category)
30 // (CL,pk->id,fk->category,files) |
31 // | question_dataset_items
32 // | (CL,pk->id,fk->definition)
36 // --------------------------------------------------------------------------------------------------------------
39 // | | | | question_calculated | |
40 // question_truefalse | question_multichoice | (CL,pl->id,fk->question) | |
41 // (CL,pk->id,fk->question) | (CL,pk->id,fk->question) | . | | question_randomsamatch
42 // . | . | . | |--(CL,pk->id,fk->question)
43 // . question_shortanswer . question_numerical . question_multianswer. |
44 // . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) |
45 // . . . . . . | question_match
46 // . . . . . . |--(CL,pk->id,fk->question)
50 // . . . . . . | question_match_sub
51 // ........................................................................................ |--(CL,pk->id,fk->question)
54 // . | question_numerical_units
55 // question_answers |--(CL,pk->id,fk->question)
56 // (CL,pk->id,fk->question)----------------------------------------------------------
59 // The following holds the information about student interaction with the questions
62 // (UL,pk->id,fk->attempt,question)
66 // (UL,pk->id,fk->attempt,question)
68 // Meaning: pk->primary key field of the table
69 // fk->foreign key to link with parent
70 // nt->nested field (recursive data)
71 // SL->site level info
72 // CL->course level info
73 // UL->user level info
74 // files->table may have files
76 //-----------------------------------------------------------
78 require_once("$CFG->libdir/questionlib.php");
80 function backup_question_category_context($bf, $contextid, $course) {
82 $context = get_context_instance_by_id($contextid);
83 $status = $status && fwrite($bf,start_tag("CONTEXT",4,true));
84 switch ($context->contextlevel
){
86 $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'module'));
87 $status = $status && fwrite($bf,full_tag("INSTANCE",5,false, $context->instanceid
));
90 $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'course'));
92 case CONTEXT_COURSECAT
:
93 $thiscourse = get_record('course', 'id', $course);
94 $cat = $thiscourse->category
;
96 while($context->instanceid
!= $cat){
101 $cat = get_field('course_categories', 'parent', 'id', $cat);
103 $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'coursecategory'));
104 $status = $status && fwrite($bf,full_tag("COURSECATEGORYLEVEL",5,false, $catno));
107 $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'system'));
112 $status = $status && fwrite($bf,end_tag("CONTEXT",4,true));
116 function backup_question_categories($bf,$preferences) {
122 //First, we get the used categories from backup_ids
123 $categories = question_category_ids_by_backup ($preferences->backup_unique_code
);
125 //If we've categories
128 $status = $status && fwrite($bf,start_tag("QUESTION_CATEGORIES",2,true));
129 //Iterate over each category
130 foreach ($categories as $cat) {
132 $status = $status && fwrite ($bf,start_tag("QUESTION_CATEGORY",3,true));
133 //Get category data from question_categories
134 $category = get_record ("question_categories","id",$cat->old_id
);
135 //Print category contents
136 $status = $status && fwrite($bf,full_tag("ID",4,false,$category->id
));
137 $status = $status && fwrite($bf,full_tag("NAME",4,false,$category->name
));
138 $status = $status && fwrite($bf,full_tag("INFO",4,false,$category->info
));
139 $status = $status && backup_question_category_context($bf, $category->contextid
, $preferences->backup_course
);
140 $status = $status && fwrite($bf,full_tag("STAMP",4,false,$category->stamp
));
141 $status = $status && fwrite($bf,full_tag("PARENT",4,false,$category->parent
));
142 $status = $status && fwrite($bf,full_tag("SORTORDER",4,false,$category->sortorder
));
143 //Now, backup their questions
144 $status = $status && backup_question($bf,$preferences,$category->id
);
146 $status = $status && fwrite ($bf,end_tag("QUESTION_CATEGORY",3,true));
149 $status = $status && fwrite ($bf,end_tag("QUESTION_CATEGORIES",2,true));
155 //This function backups all the questions in selected category and their
157 function backup_question($bf,$preferences,$category, $level = 4) {
159 global $CFG, $QTYPES;
163 // We'll fetch the questions sorted by parent so that questions with no parents
164 // (these are the ones which could be parents themselves) are backed up first. This
165 // is important for the recoding of the parent field during the restore process
166 // Only select questions with ids in backup_ids table
167 $questions = get_records_sql("SELECT q.* FROM {$CFG->prefix}backup_ids bk, {$CFG->prefix}question q ".
168 "WHERE q.category= $category AND ".
169 "bk.old_id=q.id AND ".
170 "bk.backup_code = {$preferences->backup_unique_code} ".
171 "ORDER BY parent ASC, id");
172 //If there are questions
175 $status = $status && fwrite ($bf,start_tag("QUESTIONS",$level,true));
177 //Iterate over each question
178 foreach ($questions as $question) {
180 $status = $status && fwrite ($bf,start_tag("QUESTION",$level +
1,true));
181 //Print question contents
182 fwrite ($bf,full_tag("ID",$level +
2,false,$question->id
));
183 fwrite ($bf,full_tag("PARENT",$level +
2,false,$question->parent
));
184 fwrite ($bf,full_tag("NAME",$level +
2,false,$question->name
));
185 fwrite ($bf,full_tag("QUESTIONTEXT",$level +
2,false,$question->questiontext
));
186 fwrite ($bf,full_tag("QUESTIONTEXTFORMAT",$level +
2,false,$question->questiontextformat
));
187 fwrite ($bf,full_tag("IMAGE",$level +
2,false,$question->image
));
188 fwrite ($bf,full_tag("GENERALFEEDBACK",$level +
2,false,$question->generalfeedback
));
189 fwrite ($bf,full_tag("DEFAULTGRADE",$level +
2,false,$question->defaultgrade
));
190 fwrite ($bf,full_tag("PENALTY",$level +
2,false,$question->penalty
));
191 fwrite ($bf,full_tag("QTYPE",$level +
2,false,$question->qtype
));
192 fwrite ($bf,full_tag("LENGTH",$level +
2,false,$question->length
));
193 fwrite ($bf,full_tag("STAMP",$level +
2,false,$question->stamp
));
194 fwrite ($bf,full_tag("VERSION",$level +
2,false,$question->version
));
195 fwrite ($bf,full_tag("HIDDEN",$level +
2,false,$question->hidden
));
196 fwrite ($bf,full_tag("TIMECREATED",$level +
2,false,$question->timecreated
));
197 fwrite ($bf,full_tag("TIMEMODIFIED",$level +
2,false,$question->timemodified
));
198 fwrite ($bf,full_tag("CREATEDBY",$level +
2,false,$question->createdby
));
199 fwrite ($bf,full_tag("MODIFIEDBY",$level +
2,false,$question->modifiedby
));
200 // Backup question type specific data
201 $status = $status && $QTYPES[$question->qtype
]->backup($bf,$preferences,$question->id
, $level +
2);
203 $status = $status && fwrite ($bf,end_tag("QUESTION",$level +
1,true));
206 if ($counter %
10 == 0) {
208 if ($counter %
200 == 0) {
215 $status = $status && fwrite ($bf,end_tag("QUESTIONS",$level,true));
220 //This function backups the answers data in some question types
221 //(truefalse, shortanswer,multichoice,numerical,calculated)
222 function question_backup_answers($bf,$preferences,$question, $level = 6) {
228 $answers = get_records("question_answers","question",$question,"id");
229 //If there are answers
231 $status = $status && fwrite ($bf,start_tag("ANSWERS",$level,true));
232 //Iterate over each answer
233 foreach ($answers as $answer) {
234 $status = $status && fwrite ($bf,start_tag("ANSWER",$level +
1,true));
235 //Print answer contents
236 fwrite ($bf,full_tag("ID",$level +
2,false,$answer->id
));
237 fwrite ($bf,full_tag("ANSWER_TEXT",$level +
2,false,$answer->answer
));
238 fwrite ($bf,full_tag("FRACTION",$level +
2,false,$answer->fraction
));
239 fwrite ($bf,full_tag("FEEDBACK",$level +
2,false,$answer->feedback
));
240 $status = $status && fwrite ($bf,end_tag("ANSWER",$level +
1,true));
242 $status = $status && fwrite ($bf,end_tag("ANSWERS",$level,true));
247 //This function backups question_numerical_units from different question types
248 function question_backup_numerical_units($bf,$preferences,$question,$level=7) {
254 $numerical_units = get_records("question_numerical_units","question",$question,"id");
255 //If there are numericals_units
256 if ($numerical_units) {
257 $status = $status && fwrite ($bf,start_tag("NUMERICAL_UNITS",$level,true));
258 //Iterate over each numerical_unit
259 foreach ($numerical_units as $numerical_unit) {
260 $status = $status && fwrite ($bf,start_tag("NUMERICAL_UNIT",$level+
1,true));
261 //Print numerical_unit contents
262 fwrite ($bf,full_tag("MULTIPLIER",$level+
2,false,$numerical_unit->multiplier
));
263 fwrite ($bf,full_tag("UNIT",$level+
2,false,$numerical_unit->unit
));
264 //Now backup numerical_units
265 $status = $status && fwrite ($bf,end_tag("NUMERICAL_UNIT",$level+
1,true));
267 $status = $status && fwrite ($bf,end_tag("NUMERICAL_UNITS",$level,true));
274 //This function backups dataset_definitions (via question_datasets) from different question types
275 function question_backup_datasets($bf,$preferences,$question,$level=7) {
281 //First, we get the used datasets for this question
282 $question_datasets = get_records("question_datasets","question",$question,"id");
283 //If there are question_datasets
284 if ($question_datasets) {
285 $status = $status &&fwrite ($bf,start_tag("DATASET_DEFINITIONS",$level,true));
286 //Iterate over each question_dataset
287 foreach ($question_datasets as $question_dataset) {
289 //Get dataset_definition
290 if ($def = get_record("question_dataset_definitions","id",$question_dataset->datasetdefinition
)) {;
291 $status = $status &&fwrite ($bf,start_tag("DATASET_DEFINITION",$level+
1,true));
292 //Print question_dataset contents
293 fwrite ($bf,full_tag("CATEGORY",$level+
2,false,$def->category
));
294 fwrite ($bf,full_tag("NAME",$level+
2,false,$def->name
));
295 fwrite ($bf,full_tag("TYPE",$level+
2,false,$def->type
));
296 fwrite ($bf,full_tag("OPTIONS",$level+
2,false,$def->options
));
297 fwrite ($bf,full_tag("ITEMCOUNT",$level+
2,false,$def->itemcount
));
298 //Now backup dataset_entries
299 $status = $status && question_backup_dataset_items($bf,$preferences,$def->id
,$level+
2);
300 //End dataset definition
301 $status = $status &&fwrite ($bf,end_tag("DATASET_DEFINITION",$level+
1,true));
304 $status = $status &&fwrite ($bf,end_tag("DATASET_DEFINITIONS",$level,true));
311 //This function backups datases_items from dataset_definitions
312 function question_backup_dataset_items($bf,$preferences,$datasetdefinition,$level=9) {
318 //First, we get the datasets_items for this dataset_definition
319 $dataset_items = get_records("question_dataset_items","definition",$datasetdefinition,"id");
320 //If there are dataset_items
321 if ($dataset_items) {
322 $status = $status &&fwrite ($bf,start_tag("DATASET_ITEMS",$level,true));
323 //Iterate over each dataset_item
324 foreach ($dataset_items as $dataset_item) {
325 $status = $status &&fwrite ($bf,start_tag("DATASET_ITEM",$level+
1,true));
326 //Print question_dataset contents
327 fwrite ($bf,full_tag("NUMBER",$level+
2,false,$dataset_item->itemnumber
));
328 fwrite ($bf,full_tag("VALUE",$level+
2,false,$dataset_item->value
));
329 //End dataset definition
330 $status = $status &&fwrite ($bf,end_tag("DATASET_ITEM",$level+
1,true));
332 $status = $status &&fwrite ($bf,end_tag("DATASET_ITEMS",$level,true));
340 //Backup question_states contents (executed from backup_quiz_attempts)
341 function backup_question_states ($bf,$preferences,$attempt, $level = 6) {
347 $question_states = get_records("question_states","attempt",$attempt,"id");
348 //If there are states
349 if ($question_states) {
351 $status = $status && fwrite ($bf,start_tag("STATES",$level,true));
352 //Iterate over each state
353 foreach ($question_states as $state) {
355 $status = $status && fwrite ($bf,start_tag("STATE",$level +
1,true));
356 //Print state contents
357 fwrite ($bf,full_tag("ID",$level +
2,false,$state->id
));
358 fwrite ($bf,full_tag("QUESTION",$level +
2,false,$state->question
));
359 fwrite ($bf,full_tag("ORIGINALQUESTION",$level +
2,false,$state->originalquestion
));
360 fwrite ($bf,full_tag("SEQ_NUMBER",$level +
2,false,$state->seq_number
));
361 fwrite ($bf,full_tag("ANSWER",$level +
2,false,$state->answer
));
362 fwrite ($bf,full_tag("TIMESTAMP",$level +
2,false,$state->timestamp
));
363 fwrite ($bf,full_tag("EVENT",$level +
2,false,$state->event
));
364 fwrite ($bf,full_tag("GRADE",$level +
2,false,$state->grade
));
365 fwrite ($bf,full_tag("RAW_GRADE",$level +
2,false,$state->raw_grade
));
366 fwrite ($bf,full_tag("PENALTY",$level +
2,false,$state->penalty
));
368 $status = $status && fwrite ($bf,end_tag("STATE",$level +
1,true));
371 $status = $status && fwrite ($bf,end_tag("STATES",$level,true));
375 //Backup question_sessions contents (executed from backup_quiz_attempts)
376 function backup_question_sessions ($bf,$preferences,$attempt, $level = 6) {
381 $question_sessions = get_records("question_sessions","attemptid",$attempt,"id");
382 //If there are sessions
383 if ($question_sessions) {
384 //Write start tag (the funny name 'newest states' has historical reasons)
385 $status = $status && fwrite ($bf,start_tag("NEWEST_STATES",$level,true));
386 //Iterate over each newest_state
387 foreach ($question_sessions as $newest_state) {
389 $status = $status && fwrite ($bf,start_tag("NEWEST_STATE",$level +
1,true));
390 //Print newest_state contents
391 fwrite ($bf,full_tag("ID",$level +
2,false,$newest_state->id
));
392 fwrite ($bf,full_tag("QUESTIONID",$level +
2,false,$newest_state->questionid
));
393 fwrite ($bf,full_tag("NEWEST",$level +
2,false,$newest_state->newest
));
394 fwrite ($bf,full_tag("NEWGRADED",$level +
2,false,$newest_state->newgraded
));
395 fwrite ($bf,full_tag("SUMPENALTY",$level +
2,false,$newest_state->sumpenalty
));
396 fwrite ($bf,full_tag("MANUALCOMMENT",$level +
2,false,$newest_state->manualcomment
));
398 $status = $status && fwrite ($bf,end_tag("NEWEST_STATE",$level +
1,true));
401 $status = $status && fwrite ($bf,end_tag("NEWEST_STATES",$level,true));
406 //Returns an array of categories id
407 function question_category_ids_by_backup ($backup_unique_code) {
411 return get_records_sql ("SELECT a.old_id, a.backup_code
412 FROM {$CFG->prefix}backup_ids a
413 WHERE a.backup_code = '$backup_unique_code' AND
414 a.table_name = 'question_categories'");
417 function question_ids_by_backup ($backup_unique_code) {
421 return get_records_sql ("SELECT old_id, backup_code
422 FROM {$CFG->prefix}backup_ids
423 WHERE backup_code = '$backup_unique_code' AND
424 table_name = 'question'");
427 //Delete category ids from backup_ids table
428 function delete_ids ($backup_unique_code, $tablename) {
430 $status = execute_sql("DELETE FROM {$CFG->prefix}backup_ids
431 WHERE backup_code = '$backup_unique_code' AND table_name = '$tablename'",false);
434 function question_insert_site_file_names($course, $backup_unique_code){
435 global $QTYPES, $CFG;
437 $questionids = question_ids_by_backup ($backup_unique_code);
440 foreach ($questionids as $question_bk){
441 $question = get_record('question', 'id', $question_bk->old_id
);
442 $QTYPES[$question->qtype
]->get_question_options(&$question);
443 $urls = array_merge_recursive($urls, $QTYPES[$question->qtype
]->find_file_links($question, SITEID
));
447 foreach (array_keys($urls) as $url){
448 if (file_exists($CFG->dataroot
.'/'.SITEID
.'/'.$url)){
449 $inserturl = new object();
450 $inserturl->backup_code
= $backup_unique_code;
451 $inserturl->file_type
= 'site';
452 $url = clean_param($url, PARAM_PATH
);
453 $inserturl->path
= addslashes($url);
454 $status = $status && insert_record('backup_files', $inserturl);
456 notify(get_string('linkedfiledoesntexist', 'question', $url));