Merge commit 'catalyst/MOODLE_19_STABLE' into mdl19-linuxchix
[moodle-linuxchix.git] / question / backuplib.php
blob1216b6cdc2d3b17a423eb2f408c1119fe4f90d5d
1 <?php // $Id$
2 /**
3 * Question bank backup code.
5 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
6 * @package questionbank
7 *//** */
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
15 // (CL,pk->id)
16 // |
17 // |
18 // |.......................................
19 // | .
20 // | .
21 // | -------question_datasets------ .
22 // | | (CL,pk->id,fk->question, | .
23 // | | fk->dataset_definition) | .
24 // | | | .
25 // | | | .
26 // | | | .
27 // | | question_dataset_definitions
28 // | | (CL,pk->id,fk->category)
29 // question |
30 // (CL,pk->id,fk->category,files) |
31 // | question_dataset_items
32 // | (CL,pk->id,fk->definition)
33 // |
34 // |
35 // |
36 // --------------------------------------------------------------------------------------------------------------
37 // | | | | | | |
38 // | | | | | | |
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)
47 // . . . . . . | .
48 // . . . . . . | .
49 // . . . . . . | .
50 // . . . . . . | question_match_sub
51 // ........................................................................................ |--(CL,pk->id,fk->question)
52 // . |
53 // . |
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
61 // question_sessions
62 // (UL,pk->id,fk->attempt,question)
63 // .
64 // .
65 // question_states
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) {
81 $status = true;
82 $context = get_context_instance_by_id($contextid);
83 $status = $status && fwrite($bf,start_tag("CONTEXT",4,true));
84 switch ($context->contextlevel){
85 case CONTEXT_MODULE:
86 $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'module'));
87 $status = $status && fwrite($bf,full_tag("INSTANCE",5,false, $context->instanceid));
88 break;
89 case CONTEXT_COURSE:
90 $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'course'));
91 break;
92 case CONTEXT_COURSECAT:
93 $thiscourse = get_record('course', 'id', $course);
94 $cat = $thiscourse->category;
95 $catno = 1;
96 while($context->instanceid != $cat){
97 $catno ++;
98 if ($cat ==0) {
99 return false;
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));
105 break;
106 case CONTEXT_SYSTEM:
107 $status = $status && fwrite($bf,full_tag("LEVEL",5,false, 'system'));
108 break;
109 default :
110 return false;
112 $status = $status && fwrite($bf,end_tag("CONTEXT",4,true));
113 return $status;
116 function backup_question_categories($bf,$preferences) {
118 global $CFG;
120 $status = true;
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
126 if ($categories) {
127 //Write start tag
128 $status = $status && fwrite($bf,start_tag("QUESTION_CATEGORIES",2,true));
129 //Iterate over each category
130 foreach ($categories as $cat) {
131 //Start category
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);
145 //End category
146 $status = $status && fwrite ($bf,end_tag("QUESTION_CATEGORY",3,true));
148 //Write end tag
149 $status = $status && fwrite ($bf,end_tag("QUESTION_CATEGORIES",2,true));
152 return $status;
155 //This function backups all the questions in selected category and their
156 //asociated data
157 function backup_question($bf,$preferences,$category, $level = 4) {
159 global $CFG, $QTYPES;
161 $status = true;
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
173 if ($questions) {
174 //Write start tag
175 $status = $status && fwrite ($bf,start_tag("QUESTIONS",$level,true));
176 $counter = 0;
177 //Iterate over each question
178 foreach ($questions as $question) {
179 // Deal with missing question types - they need to be included becuase
180 // user data or quizzes may refer to them.
181 if (!array_key_exists($question->qtype, $QTYPES)) {
182 $question->qtype = 'missingtype';
183 $question->questiontext = '<p>' . get_string('warningmissingtype', 'quiz') . '</p>' . $question->questiontext;
185 //Start question
186 $status = $status && fwrite ($bf,start_tag("QUESTION",$level + 1,true));
187 //Print question contents
188 fwrite ($bf,full_tag("ID",$level + 2,false,$question->id));
189 fwrite ($bf,full_tag("PARENT",$level + 2,false,$question->parent));
190 fwrite ($bf,full_tag("NAME",$level + 2,false,$question->name));
191 fwrite ($bf,full_tag("QUESTIONTEXT",$level + 2,false,$question->questiontext));
192 fwrite ($bf,full_tag("QUESTIONTEXTFORMAT",$level + 2,false,$question->questiontextformat));
193 fwrite ($bf,full_tag("IMAGE",$level + 2,false,$question->image));
194 fwrite ($bf,full_tag("GENERALFEEDBACK",$level + 2,false,$question->generalfeedback));
195 fwrite ($bf,full_tag("DEFAULTGRADE",$level + 2,false,$question->defaultgrade));
196 fwrite ($bf,full_tag("PENALTY",$level + 2,false,$question->penalty));
197 fwrite ($bf,full_tag("QTYPE",$level + 2,false,$question->qtype));
198 fwrite ($bf,full_tag("LENGTH",$level + 2,false,$question->length));
199 fwrite ($bf,full_tag("STAMP",$level + 2,false,$question->stamp));
200 fwrite ($bf,full_tag("VERSION",$level + 2,false,$question->version));
201 fwrite ($bf,full_tag("HIDDEN",$level + 2,false,$question->hidden));
202 fwrite ($bf,full_tag("TIMECREATED",$level + 2,false,$question->timecreated));
203 fwrite ($bf,full_tag("TIMEMODIFIED",$level + 2,false,$question->timemodified));
204 fwrite ($bf,full_tag("CREATEDBY",$level + 2,false,$question->createdby));
205 fwrite ($bf,full_tag("MODIFIEDBY",$level + 2,false,$question->modifiedby));
206 // Backup question type specific data
207 $status = $status && $QTYPES[$question->qtype]->backup($bf,$preferences,$question->id, $level + 2);
208 //End question
209 $status = $status && fwrite ($bf,end_tag("QUESTION",$level + 1,true));
210 //Do some output
211 $counter++;
212 if ($counter % 10 == 0) {
213 echo ".";
214 if ($counter % 200 == 0) {
215 echo "<br />";
217 backup_flush(300);
220 //Write end tag
221 $status = $status && fwrite ($bf,end_tag("QUESTIONS",$level,true));
223 return $status;
226 //This function backups the answers data in some question types
227 //(truefalse, shortanswer,multichoice,numerical,calculated)
228 function question_backup_answers($bf,$preferences,$question, $level = 6) {
230 global $CFG;
232 $status = true;
234 $answers = get_records("question_answers","question",$question,"id");
235 //If there are answers
236 if ($answers) {
237 $status = $status && fwrite ($bf,start_tag("ANSWERS",$level,true));
238 //Iterate over each answer
239 foreach ($answers as $answer) {
240 $status = $status && fwrite ($bf,start_tag("ANSWER",$level + 1,true));
241 //Print answer contents
242 fwrite ($bf,full_tag("ID",$level + 2,false,$answer->id));
243 fwrite ($bf,full_tag("ANSWER_TEXT",$level + 2,false,$answer->answer));
244 fwrite ($bf,full_tag("FRACTION",$level + 2,false,$answer->fraction));
245 fwrite ($bf,full_tag("FEEDBACK",$level + 2,false,$answer->feedback));
246 $status = $status && fwrite ($bf,end_tag("ANSWER",$level + 1,true));
248 $status = $status && fwrite ($bf,end_tag("ANSWERS",$level,true));
250 return $status;
253 //This function backups question_numerical_units from different question types
254 function question_backup_numerical_units($bf,$preferences,$question,$level=7) {
256 global $CFG;
258 $status = true;
260 $numerical_units = get_records("question_numerical_units","question",$question,"id");
261 //If there are numericals_units
262 if ($numerical_units) {
263 $status = $status && fwrite ($bf,start_tag("NUMERICAL_UNITS",$level,true));
264 //Iterate over each numerical_unit
265 foreach ($numerical_units as $numerical_unit) {
266 $status = $status && fwrite ($bf,start_tag("NUMERICAL_UNIT",$level+1,true));
267 //Print numerical_unit contents
268 fwrite ($bf,full_tag("MULTIPLIER",$level+2,false,$numerical_unit->multiplier));
269 fwrite ($bf,full_tag("UNIT",$level+2,false,$numerical_unit->unit));
270 //Now backup numerical_units
271 $status = $status && fwrite ($bf,end_tag("NUMERICAL_UNIT",$level+1,true));
273 $status = $status && fwrite ($bf,end_tag("NUMERICAL_UNITS",$level,true));
276 return $status;
280 //This function backups dataset_definitions (via question_datasets) from different question types
281 function question_backup_datasets($bf,$preferences,$question,$level=7) {
283 global $CFG;
285 $status = true;
287 //First, we get the used datasets for this question
288 $question_datasets = get_records("question_datasets","question",$question,"id");
289 //If there are question_datasets
290 if ($question_datasets) {
291 $status = $status &&fwrite ($bf,start_tag("DATASET_DEFINITIONS",$level,true));
292 //Iterate over each question_dataset
293 foreach ($question_datasets as $question_dataset) {
294 $def = NULL;
295 //Get dataset_definition
296 if ($def = get_record("question_dataset_definitions","id",$question_dataset->datasetdefinition)) {;
297 $status = $status &&fwrite ($bf,start_tag("DATASET_DEFINITION",$level+1,true));
298 //Print question_dataset contents
299 fwrite ($bf,full_tag("CATEGORY",$level+2,false,$def->category));
300 fwrite ($bf,full_tag("NAME",$level+2,false,$def->name));
301 fwrite ($bf,full_tag("TYPE",$level+2,false,$def->type));
302 fwrite ($bf,full_tag("OPTIONS",$level+2,false,$def->options));
303 fwrite ($bf,full_tag("ITEMCOUNT",$level+2,false,$def->itemcount));
304 //Now backup dataset_entries
305 $status = $status && question_backup_dataset_items($bf,$preferences,$def->id,$level+2);
306 //End dataset definition
307 $status = $status &&fwrite ($bf,end_tag("DATASET_DEFINITION",$level+1,true));
310 $status = $status &&fwrite ($bf,end_tag("DATASET_DEFINITIONS",$level,true));
313 return $status;
317 //This function backups datases_items from dataset_definitions
318 function question_backup_dataset_items($bf,$preferences,$datasetdefinition,$level=9) {
320 global $CFG;
322 $status = true;
324 //First, we get the datasets_items for this dataset_definition
325 $dataset_items = get_records("question_dataset_items","definition",$datasetdefinition,"id");
326 //If there are dataset_items
327 if ($dataset_items) {
328 $status = $status &&fwrite ($bf,start_tag("DATASET_ITEMS",$level,true));
329 //Iterate over each dataset_item
330 foreach ($dataset_items as $dataset_item) {
331 $status = $status &&fwrite ($bf,start_tag("DATASET_ITEM",$level+1,true));
332 //Print question_dataset contents
333 fwrite ($bf,full_tag("NUMBER",$level+2,false,$dataset_item->itemnumber));
334 fwrite ($bf,full_tag("VALUE",$level+2,false,$dataset_item->value));
335 //End dataset definition
336 $status = $status &&fwrite ($bf,end_tag("DATASET_ITEM",$level+1,true));
338 $status = $status &&fwrite ($bf,end_tag("DATASET_ITEMS",$level,true));
341 return $status;
346 //Backup question_states contents (executed from backup_quiz_attempts)
347 function backup_question_states ($bf,$preferences,$attempt, $level = 6) {
349 global $CFG;
351 $status = true;
353 $question_states = get_records("question_states","attempt",$attempt,"id");
354 //If there are states
355 if ($question_states) {
356 //Write start tag
357 $status = $status && fwrite ($bf,start_tag("STATES",$level,true));
358 //Iterate over each state
359 foreach ($question_states as $state) {
360 //Start state
361 $status = $status && fwrite ($bf,start_tag("STATE",$level + 1,true));
362 //Print state contents
363 fwrite ($bf,full_tag("ID",$level + 2,false,$state->id));
364 fwrite ($bf,full_tag("QUESTION",$level + 2,false,$state->question));
365 fwrite ($bf,full_tag("ORIGINALQUESTION",$level + 2,false,$state->originalquestion));
366 fwrite ($bf,full_tag("SEQ_NUMBER",$level + 2,false,$state->seq_number));
367 fwrite ($bf,full_tag("ANSWER",$level + 2,false,$state->answer));
368 fwrite ($bf,full_tag("TIMESTAMP",$level + 2,false,$state->timestamp));
369 fwrite ($bf,full_tag("EVENT",$level + 2,false,$state->event));
370 fwrite ($bf,full_tag("GRADE",$level + 2,false,$state->grade));
371 fwrite ($bf,full_tag("RAW_GRADE",$level + 2,false,$state->raw_grade));
372 fwrite ($bf,full_tag("PENALTY",$level + 2,false,$state->penalty));
373 //End state
374 $status = $status && fwrite ($bf,end_tag("STATE",$level + 1,true));
376 //Write end tag
377 $status = $status && fwrite ($bf,end_tag("STATES",$level,true));
381 //Backup question_sessions contents (executed from backup_quiz_attempts)
382 function backup_question_sessions ($bf,$preferences,$attempt, $level = 6) {
383 global $CFG;
385 $status = true;
387 $question_sessions = get_records("question_sessions","attemptid",$attempt,"id");
388 //If there are sessions
389 if ($question_sessions) {
390 //Write start tag (the funny name 'newest states' has historical reasons)
391 $status = $status && fwrite ($bf,start_tag("NEWEST_STATES",$level,true));
392 //Iterate over each newest_state
393 foreach ($question_sessions as $newest_state) {
394 //Start newest_state
395 $status = $status && fwrite ($bf,start_tag("NEWEST_STATE",$level + 1,true));
396 //Print newest_state contents
397 fwrite ($bf,full_tag("ID",$level + 2,false,$newest_state->id));
398 fwrite ($bf,full_tag("QUESTIONID",$level + 2,false,$newest_state->questionid));
399 fwrite ($bf,full_tag("NEWEST",$level + 2,false,$newest_state->newest));
400 fwrite ($bf,full_tag("NEWGRADED",$level + 2,false,$newest_state->newgraded));
401 fwrite ($bf,full_tag("SUMPENALTY",$level + 2,false,$newest_state->sumpenalty));
402 fwrite ($bf,full_tag("MANUALCOMMENT",$level + 2,false,$newest_state->manualcomment));
403 //End newest_state
404 $status = $status && fwrite ($bf,end_tag("NEWEST_STATE",$level + 1,true));
406 //Write end tag
407 $status = $status && fwrite ($bf,end_tag("NEWEST_STATES",$level,true));
409 return $status;
412 //Returns an array of categories id
413 function question_category_ids_by_backup ($backup_unique_code) {
415 global $CFG;
417 return get_records_sql ("SELECT a.old_id, a.backup_code
418 FROM {$CFG->prefix}backup_ids a
419 WHERE a.backup_code = '$backup_unique_code' AND
420 a.table_name = 'question_categories'");
423 function question_ids_by_backup ($backup_unique_code) {
425 global $CFG;
427 return get_records_sql ("SELECT old_id, backup_code
428 FROM {$CFG->prefix}backup_ids
429 WHERE backup_code = '$backup_unique_code' AND
430 table_name = 'question'");
433 //Function for inserting question and category ids into db that are all called from
434 // quiz_check_backup_mods during execution of backup_check.html
437 function question_insert_c_and_q_ids_for_course($coursecontext, $backup_unique_code){
438 global $CFG;
439 // First, all categories from this course's context.
440 $status = execute_sql("INSERT INTO {$CFG->prefix}backup_ids
441 (backup_code, table_name, old_id, info)
442 SELECT '$backup_unique_code', 'question_categories', qc.id, 'course'
443 FROM {$CFG->prefix}question_categories qc
444 WHERE qc.contextid = {$coursecontext->id}", false);
445 $status = $status && question_insert_q_ids($backup_unique_code, 'course');
446 return $status;
449 * Insert all question ids for categories whose ids have already been inserted in the backup_ids table
450 * Insert code to identify categories to later insert all question ids later eg. course, quiz or other module name.
452 function question_insert_q_ids($backup_unique_code, $info){
453 global $CFG;
454 //put the ids of the questions from all these categories into the db.
455 $status = execute_sql("INSERT INTO {$CFG->prefix}backup_ids
456 (backup_code, table_name, old_id, info)
457 SELECT '$backup_unique_code', 'question', q.id, ''
458 FROM {$CFG->prefix}question q, {$CFG->prefix}backup_ids bk
459 WHERE q.category = bk.old_id AND bk.table_name = 'question_categories'
460 AND bk.info = '$info'
461 AND bk.backup_code = '$backup_unique_code'", false);
462 return $status;
465 function question_insert_c_and_q_ids_for_module($backup_unique_code, $course, $modulename, $instances){
466 global $CFG;
467 $status = true;
468 // using 'dummykeyname' in sql because otherwise get_records_sql_menu returns an error
469 // if two key names are the same.
470 $cmcontexts = array();
471 if(!empty($instances)) {
472 $cmcontexts = get_records_sql_menu("SELECT c.id, c.id AS dummykeyname FROM {$CFG->prefix}modules m,
473 {$CFG->prefix}course_modules cm,
474 {$CFG->prefix}context c
475 WHERE m.name = '$modulename' AND m.id = cm.module AND cm.id = c.instanceid
476 AND c.contextlevel = ".CONTEXT_MODULE." AND cm.course = $course
477 AND cm.instance IN (".implode(',',array_keys($instances)).")");
480 if ($cmcontexts){
481 $status = $status && execute_sql("INSERT INTO {$CFG->prefix}backup_ids
482 (backup_code, table_name, old_id, info)
483 SELECT '$backup_unique_code', 'question_categories', qc.id, '$modulename'
484 FROM {$CFG->prefix}question_categories qc
485 WHERE qc.contextid IN (".join(array_keys($cmcontexts), ', ').")", false);
487 $status = $status && question_insert_q_ids($backup_unique_code, $modulename);
488 return $status;
491 function question_insert_site_file_names($course, $backup_unique_code){
492 global $QTYPES, $CFG;
493 $status = true;
494 $questionids = question_ids_by_backup ($backup_unique_code);
495 $urls = array();
496 if ($questionids){
497 foreach ($questionids as $question_bk){
498 $question = get_record('question', 'id', $question_bk->old_id);
499 $QTYPES[$question->qtype]->get_question_options($question);
500 $urls = array_merge_recursive($urls, $QTYPES[$question->qtype]->find_file_links($question, SITEID));
503 ksort($urls);
504 foreach (array_keys($urls) as $url){
505 if (file_exists($CFG->dataroot.'/'.SITEID.'/'.$url)){
506 $inserturl = new object();
507 $inserturl->backup_code = $backup_unique_code;
508 $inserturl->file_type = 'site';
509 $url = clean_param($url, PARAM_PATH);
510 $inserturl->path = addslashes($url);
511 $status = $status && insert_record('backup_files', $inserturl);
512 } else {
513 notify(get_string('linkedfiledoesntexist', 'question', $url));
516 return $status;