Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * Defines various backup steps that will be used by common tasks in backup 20 * 21 * @package core_backup 22 * @subpackage moodle2 23 * @category backup 24 * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} 25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 26 */ 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 /** 31 * Create the temp dir where backup/restore will happen and create temp ids table. 32 */ 33 class create_and_clean_temp_stuff extends backup_execution_step { 34 35 protected function define_execution() { 36 $progress = $this->task->get_progress(); 37 $progress->start_progress('Deleting backup directories'); 38 backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir 39 backup_helper::clear_backup_dir($this->get_backupid(), $progress); // Empty temp dir, just in case 40 backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table 41 backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table 42 $progress->end_progress(); 43 } 44 } 45 46 /** 47 * Delete the temp dir used by backup/restore (conditionally) and drop temp ids table. 48 * Note we delete the directory but not the corresponding log file that will be 49 * there until cron cleans it up. 50 */ 51 class drop_and_clean_temp_stuff extends backup_execution_step { 52 53 protected $skipcleaningtempdir = false; 54 55 protected function define_execution() { 56 global $CFG; 57 58 backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table 59 // Delete temp dir conditionally: 60 // 1) If $CFG->keeptempdirectoriesonbackup is not enabled 61 // 2) If backup temp dir deletion has been marked to be avoided 62 if (empty($CFG->keeptempdirectoriesonbackup) && !$this->skipcleaningtempdir) { 63 $progress = $this->task->get_progress(); 64 $progress->start_progress('Deleting backup dir'); 65 backup_helper::delete_backup_dir($this->get_backupid(), $progress); // Empty backup dir 66 $progress->end_progress(); 67 } 68 } 69 70 public function skip_cleaning_temp_dir($skip) { 71 $this->skipcleaningtempdir = $skip; 72 } 73 } 74 75 /** 76 * Create the directory where all the task (activity/block...) information will be stored 77 */ 78 class create_taskbasepath_directory extends backup_execution_step { 79 80 protected function define_execution() { 81 global $CFG; 82 $basepath = $this->task->get_taskbasepath(); 83 if (!check_dir_exists($basepath, true, true)) { 84 throw new backup_step_exception('cannot_create_taskbasepath_directory', $basepath); 85 } 86 } 87 } 88 89 /** 90 * Abstract structure step, parent of all the activity structure steps. Used to wrap the 91 * activity structure definition within the main <activity ...> tag. 92 */ 93 abstract class backup_activity_structure_step extends backup_structure_step { 94 95 /** 96 * Wraps any activity backup structure within the common 'activity' element 97 * that will include common to all activities information like id, context... 98 * 99 * @param backup_nested_element $activitystructure the element to wrap 100 * @return backup_nested_element the $activitystructure wrapped by the common 'activity' element 101 */ 102 protected function prepare_activity_structure($activitystructure) { 103 104 // Create the wrap element 105 $activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null); 106 107 // Build the tree 108 $activity->add_child($activitystructure); 109 110 // Set the source 111 $activityarr = array((object)array( 112 'id' => $this->task->get_activityid(), 113 'moduleid' => $this->task->get_moduleid(), 114 'modulename' => $this->task->get_modulename(), 115 'contextid' => $this->task->get_contextid())); 116 117 $activity->set_source_array($activityarr); 118 119 // Return the root element (activity) 120 return $activity; 121 } 122 } 123 124 /** 125 * Helper code for use by any plugin that stores question attempt data that it needs to back up. 126 */ 127 trait backup_questions_attempt_data_trait { 128 129 /** 130 * Attach to $element (usually attempts) the needed backup structures 131 * for question_usages and all the associated data. 132 * 133 * @param backup_nested_element $element the element that will contain all the question_usages data. 134 * @param string $usageidname the name of the element that holds the usageid. 135 * This must be child of $element, and must be a final element. 136 * @param string $nameprefix this prefix is added to all the element names we create. 137 * Element names in the XML must be unique, so if you are using usages in 138 * two different ways, you must give a prefix to at least one of them. If 139 * you only use one sort of usage, then you can just use the default empty prefix. 140 * This should include a trailing underscore. For example "myprefix_" 141 */ 142 protected function add_question_usages($element, $usageidname, $nameprefix = '') { 143 global $CFG; 144 require_once($CFG->dirroot . '/question/engine/lib.php'); 145 146 // Check $element is one nested_backup_element 147 if (! $element instanceof backup_nested_element) { 148 throw new backup_step_exception('question_states_bad_parent_element', $element); 149 } 150 if (! $element->get_final_element($usageidname)) { 151 throw new backup_step_exception('question_states_bad_question_attempt_element', $usageidname); 152 } 153 154 $quba = new backup_nested_element($nameprefix . 'question_usage', array('id'), 155 array('component', 'preferredbehaviour')); 156 157 $qas = new backup_nested_element($nameprefix . 'question_attempts'); 158 $qa = new backup_nested_element($nameprefix . 'question_attempt', array('id'), array( 159 'slot', 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 160 'flagged', 'questionsummary', 'rightanswer', 'responsesummary', 161 'timemodified')); 162 163 $steps = new backup_nested_element($nameprefix . 'steps'); 164 $step = new backup_nested_element($nameprefix . 'step', array('id'), array( 165 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid')); 166 167 $response = new backup_nested_element($nameprefix . 'response'); 168 $variable = new backup_nested_element($nameprefix . 'variable', null, array('name', 'value')); 169 170 // Build the tree 171 $element->add_child($quba); 172 $quba->add_child($qas); 173 $qas->add_child($qa); 174 $qa->add_child($steps); 175 $steps->add_child($step); 176 $step->add_child($response); 177 $response->add_child($variable); 178 179 // Set the sources 180 $quba->set_source_table('question_usages', 181 array('id' => '../' . $usageidname)); 182 $qa->set_source_table('question_attempts', array('questionusageid' => backup::VAR_PARENTID), 'slot ASC'); 183 $step->set_source_table('question_attempt_steps', array('questionattemptid' => backup::VAR_PARENTID), 'sequencenumber ASC'); 184 $variable->set_source_table('question_attempt_step_data', array('attemptstepid' => backup::VAR_PARENTID)); 185 186 // Annotate ids 187 $qa->annotate_ids('question', 'questionid'); 188 $step->annotate_ids('user', 'userid'); 189 190 // Annotate files 191 $fileareas = question_engine::get_all_response_file_areas(); 192 foreach ($fileareas as $filearea) { 193 $step->annotate_files('question', $filearea, 'id'); 194 } 195 } 196 } 197 198 /** 199 * Helper to backup question reference data for an instance. 200 */ 201 trait backup_question_reference_data_trait { 202 203 /** 204 * Backup the related data from reference table for the instance. 205 * 206 * @param backup_nested_element $element 207 * @param string $component 208 * @param string $questionarea 209 */ 210 protected function add_question_references($element, $component, $questionarea) { 211 // Check $element is one nested_backup_element. 212 if (! $element instanceof backup_nested_element) { 213 throw new backup_step_exception('question_states_bad_parent_element', $element); 214 } 215 216 $reference = new backup_nested_element('question_reference', ['id'], 217 ['usingcontextid', 'component', 'questionarea', 'questionbankentryid', 'version']); 218 219 $element->add_child($reference); 220 221 $reference->set_source_table('question_references', [ 222 'usingcontextid' => backup::VAR_CONTEXTID, 223 'component' => backup_helper::is_sqlparam($component), 224 'questionarea' => backup_helper::is_sqlparam($questionarea), 225 'itemid' => backup::VAR_PARENTID 226 ]); 227 } 228 } 229 230 /** 231 * Helper to backup question set reference data for an instance. 232 */ 233 trait backup_question_set_reference_trait { 234 235 /** 236 * Backup the related data from set_reference table for the instance. 237 * 238 * @param backup_nested_element $element 239 * @param string $component 240 * @param string $questionarea 241 */ 242 protected function add_question_set_references($element, $component, $questionarea) { 243 // Check $element is one nested_backup_element. 244 if (! $element instanceof backup_nested_element) { 245 throw new backup_step_exception('question_states_bad_parent_element', $element); 246 } 247 248 $setreference = new backup_nested_element('question_set_reference', ['id'], 249 ['usingcontextid', 'component', 'questionarea', 'questionscontextid', 'filtercondition']); 250 251 $element->add_child($setreference); 252 253 $setreference->set_source_table('question_set_references', [ 254 'usingcontextid' => backup::VAR_CONTEXTID, 255 'component' => backup_helper::is_sqlparam($component), 256 'questionarea' => backup_helper::is_sqlparam($questionarea), 257 'itemid' => backup::VAR_PARENTID 258 ]); 259 } 260 } 261 262 263 /** 264 * Abstract structure step to help activities that store question attempt data, reference data and set reference data. 265 * 266 * @copyright 2011 The Open University 267 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 268 */ 269 abstract class backup_questions_activity_structure_step extends backup_activity_structure_step { 270 use backup_questions_attempt_data_trait; 271 use backup_question_reference_data_trait; 272 use backup_question_set_reference_trait; 273 } 274 275 276 /** 277 * backup structure step in charge of calculating the categories to be 278 * included in backup, based in the context being backuped (module/course) 279 * and the already annotated questions present in backup_ids_temp 280 */ 281 class backup_calculate_question_categories extends backup_execution_step { 282 283 protected function define_execution() { 284 backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid()); 285 } 286 } 287 288 /** 289 * backup structure step in charge of deleting all the questions annotated 290 * in the backup_ids_temp table 291 */ 292 class backup_delete_temp_questions extends backup_execution_step { 293 294 protected function define_execution() { 295 backup_question_dbops::delete_temp_questions($this->get_backupid()); 296 } 297 } 298 299 /** 300 * Abstract structure step, parent of all the block structure steps. Used to wrap the 301 * block structure definition within the main <block ...> tag 302 */ 303 abstract class backup_block_structure_step extends backup_structure_step { 304 305 protected function prepare_block_structure($blockstructure) { 306 307 // Create the wrap element 308 $block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null); 309 310 // Build the tree 311 $block->add_child($blockstructure); 312 313 // Set the source 314 $blockarr = array((object)array( 315 'id' => $this->task->get_blockid(), 316 'blockname' => $this->task->get_blockname(), 317 'contextid' => $this->task->get_contextid())); 318 319 $block->set_source_array($blockarr); 320 321 // Return the root element (block) 322 return $block; 323 } 324 } 325 326 /** 327 * structure step that will generate the module.xml file for the activity, 328 * accumulating various information about the activity, annotating groupings 329 * and completion/avail conf 330 */ 331 class backup_module_structure_step extends backup_structure_step { 332 333 protected function define_structure() { 334 global $DB; 335 336 // Define each element separated 337 338 $module = new backup_nested_element('module', array('id', 'version'), array( 339 'modulename', 'sectionid', 'sectionnumber', 'idnumber', 340 'added', 'score', 'indent', 'visible', 'visibleoncoursepage', 341 'visibleold', 'groupmode', 'groupingid', 342 'completion', 'completiongradeitemnumber', 'completionpassgrade', 343 'completionview', 'completionexpected', 344 'availability', 'showdescription', 'downloadcontent', 'lang')); 345 346 $tags = new backup_nested_element('tags'); 347 $tag = new backup_nested_element('tag', array('id'), array('name', 'rawname')); 348 349 // attach format plugin structure to $module element, only one allowed 350 $this->add_plugin_structure('format', $module, false); 351 352 // Attach report plugin structure to $module element, multiple allowed. 353 $this->add_plugin_structure('report', $module, true); 354 355 // attach plagiarism plugin structure to $module element, there can be potentially 356 // many plagiarism plugins storing information about this course 357 $this->add_plugin_structure('plagiarism', $module, true); 358 359 // attach local plugin structure to $module, multiple allowed 360 $this->add_plugin_structure('local', $module, true); 361 362 // Attach admin tools plugin structure to $module. 363 $this->add_plugin_structure('tool', $module, true); 364 365 $module->add_child($tags); 366 $tags->add_child($tag); 367 368 // Set the sources 369 $concat = $DB->sql_concat("'mod_'", 'm.name'); 370 $module->set_source_sql(" 371 SELECT cm.*, cp.value AS version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber 372 FROM {course_modules} cm 373 JOIN {modules} m ON m.id = cm.module 374 JOIN {config_plugins} cp ON cp.plugin = $concat AND cp.name = 'version' 375 JOIN {course_sections} s ON s.id = cm.section 376 WHERE cm.id = ?", array(backup::VAR_MODID)); 377 378 $tag->set_source_sql("SELECT t.id, t.name, t.rawname 379 FROM {tag} t 380 JOIN {tag_instance} ti ON ti.tagid = t.id 381 WHERE ti.itemtype = 'course_modules' 382 AND ti.component = 'core' 383 AND ti.itemid = ?", array(backup::VAR_MODID)); 384 385 // Define annotations 386 $module->annotate_ids('grouping', 'groupingid'); 387 388 // Return the root element ($module) 389 return $module; 390 } 391 } 392 393 /** 394 * structure step that will generate the section.xml file for the section 395 * annotating files 396 */ 397 class backup_section_structure_step extends backup_structure_step { 398 399 protected function define_structure() { 400 401 // Define each element separated 402 403 $section = new backup_nested_element('section', array('id'), array( 404 'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible', 405 'availabilityjson', 'timemodified')); 406 407 // attach format plugin structure to $section element, only one allowed 408 $this->add_plugin_structure('format', $section, false); 409 410 // attach local plugin structure to $section element, multiple allowed 411 $this->add_plugin_structure('local', $section, true); 412 413 // Add nested elements for course_format_options table 414 $formatoptions = new backup_nested_element('course_format_options', array('id'), array( 415 'format', 'name', 'value')); 416 $section->add_child($formatoptions); 417 418 // Define sources. 419 $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID)); 420 $formatoptions->set_source_sql('SELECT cfo.id, cfo.format, cfo.name, cfo.value 421 FROM {course} c 422 JOIN {course_format_options} cfo 423 ON cfo.courseid = c.id AND cfo.format = c.format 424 WHERE c.id = ? AND cfo.sectionid = ?', 425 array(backup::VAR_COURSEID, backup::VAR_SECTIONID)); 426 427 // Aliases 428 $section->set_source_alias('section', 'number'); 429 // The 'availability' field needs to be renamed because it clashes with 430 // the old nested element structure for availability data. 431 $section->set_source_alias('availability', 'availabilityjson'); 432 433 // Set annotations 434 $section->annotate_files('course', 'section', 'id'); 435 436 return $section; 437 } 438 } 439 440 /** 441 * structure step that will generate the course.xml file for the course, including 442 * course category reference, tags, modules restriction information 443 * and some annotations (files & groupings) 444 */ 445 class backup_course_structure_step extends backup_structure_step { 446 447 protected function define_structure() { 448 global $DB; 449 450 // Define each element separated 451 452 $course = new backup_nested_element('course', array('id', 'contextid'), array( 453 'shortname', 'fullname', 'idnumber', 454 'summary', 'summaryformat', 'format', 'showgrades', 455 'newsitems', 'startdate', 'enddate', 456 'marker', 'maxbytes', 'legacyfiles', 'showreports', 457 'visible', 'groupmode', 'groupmodeforce', 458 'defaultgroupingid', 'lang', 'theme', 459 'timecreated', 'timemodified', 460 'requested', 461 'showactivitydates', 462 'showcompletionconditions', 'pdfexportfont', 463 'enablecompletion', 'completionstartonenrol', 'completionnotify')); 464 465 $category = new backup_nested_element('category', array('id'), array( 466 'name', 'description')); 467 468 $tags = new backup_nested_element('tags'); 469 470 $tag = new backup_nested_element('tag', array('id'), array( 471 'name', 'rawname')); 472 473 $customfields = new backup_nested_element('customfields'); 474 $customfield = new backup_nested_element('customfield', array('id'), array( 475 'shortname', 'type', 'value', 'valueformat' 476 )); 477 478 $courseformatoptions = new backup_nested_element('courseformatoptions'); 479 $courseformatoption = new backup_nested_element('courseformatoption', [], [ 480 'courseid', 'format', 'sectionid', 'name', 'value' 481 ]); 482 483 // attach format plugin structure to $course element, only one allowed 484 $this->add_plugin_structure('format', $course, false); 485 486 // attach theme plugin structure to $course element; multiple themes can 487 // save course data (in case of user theme, legacy theme, etc) 488 $this->add_plugin_structure('theme', $course, true); 489 490 // attach general report plugin structure to $course element; multiple 491 // reports can save course data if required 492 $this->add_plugin_structure('report', $course, true); 493 494 // attach course report plugin structure to $course element; multiple 495 // course reports can save course data if required 496 $this->add_plugin_structure('coursereport', $course, true); 497 498 // attach plagiarism plugin structure to $course element, there can be potentially 499 // many plagiarism plugins storing information about this course 500 $this->add_plugin_structure('plagiarism', $course, true); 501 502 // attach local plugin structure to $course element; multiple local plugins 503 // can save course data if required 504 $this->add_plugin_structure('local', $course, true); 505 506 // Attach admin tools plugin structure to $course element; multiple plugins 507 // can save course data if required. 508 $this->add_plugin_structure('tool', $course, true); 509 510 // Build the tree 511 512 $course->add_child($category); 513 514 $course->add_child($tags); 515 $tags->add_child($tag); 516 517 $course->add_child($customfields); 518 $customfields->add_child($customfield); 519 520 $course->add_child($courseformatoptions); 521 $courseformatoptions->add_child($courseformatoption); 522 523 // Set the sources 524 525 $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid())); 526 $courserec->contextid = $this->task->get_contextid(); 527 528 // Add 'numsections' in order to be able to restore in previous versions of Moodle. 529 // Even though Moodle does not officially support restore into older verions of Moodle from the 530 // version where backup was made, without 'numsections' restoring will go very wrong. 531 if (!property_exists($courserec, 'numsections') && course_get_format($courserec)->uses_sections()) { 532 $courserec->numsections = course_get_format($courserec)->get_last_section_number(); 533 } 534 535 $course->set_source_array(array($courserec)); 536 537 $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category)); 538 539 $category->set_source_array(array($categoryrec)); 540 541 $tag->set_source_sql('SELECT t.id, t.name, t.rawname 542 FROM {tag} t 543 JOIN {tag_instance} ti ON ti.tagid = t.id 544 WHERE ti.itemtype = ? 545 AND ti.itemid = ?', array( 546 backup_helper::is_sqlparam('course'), 547 backup::VAR_PARENTID)); 548 549 // Section level settings are dealt with in backup_section_structure_step. 550 // We only need to deal with course level (sectionid = 0) here. 551 $courseformatoption->set_source_sql('SELECT id, format, sectionid, name, value 552 FROM {course_format_options} 553 WHERE courseid = ? AND sectionid = 0', [ backup::VAR_PARENTID ]); 554 555 $handler = core_course\customfield\course_handler::create(); 556 $fieldsforbackup = $handler->get_instance_data_for_backup($this->task->get_courseid()); 557 $customfield->set_source_array($fieldsforbackup); 558 559 // Some annotations 560 561 $course->annotate_ids('grouping', 'defaultgroupingid'); 562 563 $course->annotate_files('course', 'summary', null); 564 $course->annotate_files('course', 'overviewfiles', null); 565 566 if ($this->get_setting_value('legacyfiles')) { 567 $course->annotate_files('course', 'legacy', null); 568 } 569 570 // Return root element ($course) 571 572 return $course; 573 } 574 } 575 576 /** 577 * structure step that will generate the enrolments.xml file for the given course 578 */ 579 class backup_enrolments_structure_step extends backup_structure_step { 580 581 /** 582 * Skip enrolments on the front page. 583 * @return bool 584 */ 585 protected function execute_condition() { 586 return ($this->get_courseid() != SITEID); 587 } 588 589 protected function define_structure() { 590 global $DB; 591 592 // To know if we are including users 593 $users = $this->get_setting_value('users'); 594 $keptroles = $this->task->get_kept_roles(); 595 596 // Define each element separated 597 598 $enrolments = new backup_nested_element('enrolments'); 599 600 $enrols = new backup_nested_element('enrols'); 601 602 $enrol = new backup_nested_element('enrol', array('id'), array( 603 'enrol', 'status', 'name', 'enrolperiod', 'enrolstartdate', 604 'enrolenddate', 'expirynotify', 'expirythreshold', 'notifyall', 605 'password', 'cost', 'currency', 'roleid', 606 'customint1', 'customint2', 'customint3', 'customint4', 'customint5', 'customint6', 'customint7', 'customint8', 607 'customchar1', 'customchar2', 'customchar3', 608 'customdec1', 'customdec2', 609 'customtext1', 'customtext2', 'customtext3', 'customtext4', 610 'timecreated', 'timemodified')); 611 612 $userenrolments = new backup_nested_element('user_enrolments'); 613 614 $enrolment = new backup_nested_element('enrolment', array('id'), array( 615 'status', 'userid', 'timestart', 'timeend', 'modifierid', 616 'timemodified')); 617 618 // Build the tree 619 $enrolments->add_child($enrols); 620 $enrols->add_child($enrol); 621 $enrol->add_child($userenrolments); 622 $userenrolments->add_child($enrolment); 623 624 // Define sources - the instances are restored using the same sortorder, we do not need to store it in xml and deal with it afterwards. 625 $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID), 'sortorder ASC'); 626 627 // User enrolments only added only if users included. 628 if (empty($keptroles) && $users) { 629 $enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID)); 630 $enrolment->annotate_ids('user', 'userid'); 631 } else if (!empty($keptroles)) { 632 list($insql, $inparams) = $DB->get_in_or_equal($keptroles); 633 $params = array( 634 backup::VAR_CONTEXTID, 635 backup::VAR_PARENTID 636 ); 637 foreach ($inparams as $inparam) { 638 $params[] = backup_helper::is_sqlparam($inparam); 639 } 640 $enrolment->set_source_sql( 641 "SELECT ue.* 642 FROM {user_enrolments} ue 643 INNER JOIN {role_assignments} ra ON ue.userid = ra.userid 644 WHERE ra.contextid = ? 645 AND ue.enrolid = ? 646 AND ra.roleid $insql", 647 $params); 648 $enrolment->annotate_ids('user', 'userid'); 649 } 650 651 $enrol->annotate_ids('role', 'roleid'); 652 653 // Add enrol plugin structure. 654 $this->add_plugin_structure('enrol', $enrol, true); 655 656 return $enrolments; 657 } 658 } 659 660 /** 661 * structure step that will generate the roles.xml file for the given context, observing 662 * the role_assignments setting to know if that part needs to be included 663 */ 664 class backup_roles_structure_step extends backup_structure_step { 665 666 protected function define_structure() { 667 668 // To know if we are including role assignments 669 $roleassignments = $this->get_setting_value('role_assignments'); 670 671 // Define each element separated 672 673 $roles = new backup_nested_element('roles'); 674 675 $overrides = new backup_nested_element('role_overrides'); 676 677 $override = new backup_nested_element('override', array('id'), array( 678 'roleid', 'capability', 'permission', 'timemodified', 679 'modifierid')); 680 681 $assignments = new backup_nested_element('role_assignments'); 682 683 $assignment = new backup_nested_element('assignment', array('id'), array( 684 'roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid', 685 'sortorder')); 686 687 // Build the tree 688 $roles->add_child($overrides); 689 $roles->add_child($assignments); 690 691 $overrides->add_child($override); 692 $assignments->add_child($assignment); 693 694 // Define sources 695 696 $override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID)); 697 698 // Assignments only added if specified 699 if ($roleassignments) { 700 $assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID)); 701 } 702 703 // Define id annotations 704 $override->annotate_ids('role', 'roleid'); 705 706 $assignment->annotate_ids('role', 'roleid'); 707 708 $assignment->annotate_ids('user', 'userid'); 709 710 //TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak) 711 712 return $roles; 713 } 714 } 715 716 /** 717 * structure step that will generate the roles.xml containing the 718 * list of roles used along the whole backup process. Just raw 719 * list of used roles from role table 720 */ 721 class backup_final_roles_structure_step extends backup_structure_step { 722 723 protected function define_structure() { 724 725 // Define elements 726 727 $rolesdef = new backup_nested_element('roles_definition'); 728 729 $role = new backup_nested_element('role', array('id'), array( 730 'name', 'shortname', 'nameincourse', 'description', 731 'sortorder', 'archetype')); 732 733 // Build the tree 734 735 $rolesdef->add_child($role); 736 737 // Define sources 738 739 $role->set_source_sql("SELECT r.*, rn.name AS nameincourse 740 FROM {role} r 741 JOIN {backup_ids_temp} bi ON r.id = bi.itemid 742 LEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ? 743 WHERE bi.backupid = ? 744 AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID)); 745 746 // Return main element (rolesdef) 747 return $rolesdef; 748 } 749 } 750 751 /** 752 * structure step that will generate the scales.xml containing the 753 * list of scales used along the whole backup process. 754 */ 755 class backup_final_scales_structure_step extends backup_structure_step { 756 757 protected function define_structure() { 758 759 // Define elements 760 761 $scalesdef = new backup_nested_element('scales_definition'); 762 763 $scale = new backup_nested_element('scale', array('id'), array( 764 'courseid', 'userid', 'name', 'scale', 765 'description', 'descriptionformat', 'timemodified')); 766 767 // Build the tree 768 769 $scalesdef->add_child($scale); 770 771 // Define sources 772 773 $scale->set_source_sql("SELECT s.* 774 FROM {scale} s 775 JOIN {backup_ids_temp} bi ON s.id = bi.itemid 776 WHERE bi.backupid = ? 777 AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID)); 778 779 // Annotate scale files (they store files in system context, so pass it instead of default one) 780 $scale->annotate_files('grade', 'scale', 'id', context_system::instance()->id); 781 782 // Return main element (scalesdef) 783 return $scalesdef; 784 } 785 } 786 787 /** 788 * structure step that will generate the outcomes.xml containing the 789 * list of outcomes used along the whole backup process. 790 */ 791 class backup_final_outcomes_structure_step extends backup_structure_step { 792 793 protected function define_structure() { 794 795 // Define elements 796 797 $outcomesdef = new backup_nested_element('outcomes_definition'); 798 799 $outcome = new backup_nested_element('outcome', array('id'), array( 800 'courseid', 'userid', 'shortname', 'fullname', 801 'scaleid', 'description', 'descriptionformat', 'timecreated', 802 'timemodified','usermodified')); 803 804 // Build the tree 805 806 $outcomesdef->add_child($outcome); 807 808 // Define sources 809 810 $outcome->set_source_sql("SELECT o.* 811 FROM {grade_outcomes} o 812 JOIN {backup_ids_temp} bi ON o.id = bi.itemid 813 WHERE bi.backupid = ? 814 AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID)); 815 816 // Annotate outcome files (they store files in system context, so pass it instead of default one) 817 $outcome->annotate_files('grade', 'outcome', 'id', context_system::instance()->id); 818 819 // Return main element (outcomesdef) 820 return $outcomesdef; 821 } 822 } 823 824 /** 825 * structure step in charge of constructing the filters.xml file for all the filters found 826 * in activity 827 */ 828 class backup_filters_structure_step extends backup_structure_step { 829 830 protected function define_structure() { 831 832 // Define each element separated 833 834 $filters = new backup_nested_element('filters'); 835 836 $actives = new backup_nested_element('filter_actives'); 837 838 $active = new backup_nested_element('filter_active', null, array('filter', 'active')); 839 840 $configs = new backup_nested_element('filter_configs'); 841 842 $config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value')); 843 844 // Build the tree 845 846 $filters->add_child($actives); 847 $filters->add_child($configs); 848 849 $actives->add_child($active); 850 $configs->add_child($config); 851 852 // Define sources 853 854 list($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid()); 855 856 $active->set_source_array($activearr); 857 $config->set_source_array($configarr); 858 859 // Return the root element (filters) 860 return $filters; 861 } 862 } 863 864 /** 865 * Structure step in charge of constructing the comments.xml file for all the comments found in a given context. 866 */ 867 class backup_comments_structure_step extends backup_structure_step { 868 869 protected function define_structure() { 870 // Define each element separated. 871 $comments = new backup_nested_element('comments'); 872 873 $comment = new backup_nested_element('comment', array('id'), array( 874 'component', 'commentarea', 'itemid', 'content', 'format', 875 'userid', 'timecreated')); 876 877 // Build the tree. 878 $comments->add_child($comment); 879 880 // Define sources. 881 $comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID)); 882 883 // Define id annotations. 884 $comment->annotate_ids('user', 'userid'); 885 886 // Return the root element (comments). 887 return $comments; 888 } 889 } 890 891 /** 892 * structure step in charge of constructing the badges.xml file for all the badges found 893 * in a given context 894 */ 895 class backup_badges_structure_step extends backup_structure_step { 896 897 protected function execute_condition() { 898 // Check that all activities have been included. 899 if ($this->task->is_excluding_activities()) { 900 return false; 901 } 902 return true; 903 } 904 905 protected function define_structure() { 906 global $CFG; 907 908 require_once($CFG->libdir . '/badgeslib.php'); 909 // Define each element separated. 910 911 $badges = new backup_nested_element('badges'); 912 $badge = new backup_nested_element('badge', array('id'), array('name', 'description', 913 'timecreated', 'timemodified', 'usercreated', 'usermodified', 'issuername', 914 'issuerurl', 'issuercontact', 'expiredate', 'expireperiod', 'type', 'courseid', 915 'message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron', 916 'version', 'language', 'imageauthorname', 'imageauthoremail', 'imageauthorurl', 917 'imagecaption')); 918 919 $criteria = new backup_nested_element('criteria'); 920 $criterion = new backup_nested_element('criterion', array('id'), array('badgeid', 921 'criteriatype', 'method', 'description', 'descriptionformat')); 922 923 $endorsement = new backup_nested_element('endorsement', array('id'), array('badgeid', 924 'issuername', 'issuerurl', 'issueremail', 'claimid', 'claimcomment', 'dateissued')); 925 926 $alignments = new backup_nested_element('alignments'); 927 $alignment = new backup_nested_element('alignment', array('id'), array('badgeid', 928 'targetname', 'targeturl', 'targetdescription', 'targetframework', 'targetcode')); 929 930 $relatedbadges = new backup_nested_element('relatedbadges'); 931 $relatedbadge = new backup_nested_element('relatedbadge', array('id'), array('badgeid', 932 'relatedbadgeid')); 933 934 $parameters = new backup_nested_element('parameters'); 935 $parameter = new backup_nested_element('parameter', array('id'), array('critid', 936 'name', 'value', 'criteriatype')); 937 938 $manual_awards = new backup_nested_element('manual_awards'); 939 $manual_award = new backup_nested_element('manual_award', array('id'), array('badgeid', 940 'recipientid', 'issuerid', 'issuerrole', 'datemet')); 941 942 $tags = new backup_nested_element('tags'); 943 $tag = new backup_nested_element('tag', ['id'], ['name', 'rawname']); 944 945 // Build the tree. 946 947 $badges->add_child($badge); 948 $badge->add_child($criteria); 949 $criteria->add_child($criterion); 950 $criterion->add_child($parameters); 951 $parameters->add_child($parameter); 952 $badge->add_child($endorsement); 953 $badge->add_child($alignments); 954 $alignments->add_child($alignment); 955 $badge->add_child($relatedbadges); 956 $relatedbadges->add_child($relatedbadge); 957 $badge->add_child($manual_awards); 958 $manual_awards->add_child($manual_award); 959 $badge->add_child($tags); 960 $tags->add_child($tag); 961 962 // Define sources. 963 964 $parametersql = ' 965 SELECT * 966 FROM {badge} 967 WHERE courseid = :courseid 968 AND status != ' . BADGE_STATUS_ARCHIVED; 969 $parameterparams = [ 970 'courseid' => backup::VAR_COURSEID 971 ]; 972 $badge->set_source_sql($parametersql, $parameterparams); 973 $criterion->set_source_table('badge_criteria', array('badgeid' => backup::VAR_PARENTID)); 974 $endorsement->set_source_table('badge_endorsement', array('badgeid' => backup::VAR_PARENTID)); 975 976 $alignment->set_source_table('badge_alignment', array('badgeid' => backup::VAR_PARENTID)); 977 $relatedbadge->set_source_table('badge_related', array('badgeid' => backup::VAR_PARENTID)); 978 979 $parametersql = 'SELECT cp.*, c.criteriatype 980 FROM {badge_criteria_param} cp JOIN {badge_criteria} c 981 ON cp.critid = c.id 982 WHERE critid = :critid'; 983 $parameterparams = array('critid' => backup::VAR_PARENTID); 984 $parameter->set_source_sql($parametersql, $parameterparams); 985 986 $manual_award->set_source_table('badge_manual_award', array('badgeid' => backup::VAR_PARENTID)); 987 988 $tag->set_source_sql('SELECT t.id, t.name, t.rawname 989 FROM {tag} t 990 JOIN {tag_instance} ti ON ti.tagid = t.id 991 WHERE ti.itemtype = ? 992 AND ti.itemid = ?', [backup_helper::is_sqlparam('badge'), backup::VAR_PARENTID]); 993 994 // Define id annotations. 995 996 $badge->annotate_ids('user', 'usercreated'); 997 $badge->annotate_ids('user', 'usermodified'); 998 $criterion->annotate_ids('badge', 'badgeid'); 999 $parameter->annotate_ids('criterion', 'critid'); 1000 $endorsement->annotate_ids('badge', 'badgeid'); 1001 $alignment->annotate_ids('badge', 'badgeid'); 1002 $relatedbadge->annotate_ids('badge', 'badgeid'); 1003 $relatedbadge->annotate_ids('badge', 'relatedbadgeid'); 1004 $badge->annotate_files('badges', 'badgeimage', 'id'); 1005 $manual_award->annotate_ids('badge', 'badgeid'); 1006 $manual_award->annotate_ids('user', 'recipientid'); 1007 $manual_award->annotate_ids('user', 'issuerid'); 1008 $manual_award->annotate_ids('role', 'issuerrole'); 1009 1010 // Return the root element ($badges). 1011 return $badges; 1012 } 1013 } 1014 1015 /** 1016 * structure step in charge of constructing the calender.xml file for all the events found 1017 * in a given context 1018 */ 1019 class backup_calendarevents_structure_step extends backup_structure_step { 1020 1021 protected function define_structure() { 1022 1023 // Define each element separated 1024 1025 $events = new backup_nested_element('events'); 1026 1027 $event = new backup_nested_element('event', array('id'), array( 1028 'name', 'description', 'format', 'courseid', 'groupid', 'userid', 1029 'repeatid', 'modulename', 'instance', 'type', 'eventtype', 'timestart', 1030 'timeduration', 'timesort', 'visible', 'uuid', 'sequence', 'timemodified', 1031 'priority', 'location')); 1032 1033 // Build the tree 1034 $events->add_child($event); 1035 1036 // Define sources 1037 if ($this->name == 'course_calendar') { 1038 $calendar_items_sql ="SELECT * FROM {event} 1039 WHERE courseid = :courseid 1040 AND (eventtype = 'course' OR eventtype = 'group')"; 1041 $calendar_items_params = array('courseid'=>backup::VAR_COURSEID); 1042 $event->set_source_sql($calendar_items_sql, $calendar_items_params); 1043 } else if ($this->name == 'activity_calendar') { 1044 // We don't backup action events. 1045 $params = array('instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME, 1046 'type' => array('sqlparam' => CALENDAR_EVENT_TYPE_ACTION)); 1047 // If we don't want to include the userinfo in the backup then setting the courseid 1048 // will filter out all of the user override events (which have a course id of zero). 1049 $coursewhere = ""; 1050 if (!$this->get_setting_value('userinfo')) { 1051 $params['courseid'] = backup::VAR_COURSEID; 1052 $coursewhere = " AND courseid = :courseid"; 1053 } 1054 $calendarsql = "SELECT * FROM {event} 1055 WHERE instance = :instance 1056 AND type <> :type 1057 AND modulename = :modulename"; 1058 $calendarsql = $calendarsql . $coursewhere; 1059 $event->set_source_sql($calendarsql, $params); 1060 } else { 1061 $event->set_source_table('event', array('courseid' => backup::VAR_COURSEID, 'instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME)); 1062 } 1063 1064 // Define id annotations 1065 1066 $event->annotate_ids('user', 'userid'); 1067 $event->annotate_ids('group', 'groupid'); 1068 $event->annotate_files('calendar', 'event_description', 'id'); 1069 1070 // Return the root element (events) 1071 return $events; 1072 } 1073 } 1074 1075 /** 1076 * structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course 1077 * NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step 1078 */ 1079 class backup_gradebook_structure_step extends backup_structure_step { 1080 1081 /** 1082 * We need to decide conditionally, based on dynamic information 1083 * about the execution of this step. Only will be executed if all 1084 * the module gradeitems have been already included in backup 1085 */ 1086 protected function execute_condition() { 1087 $courseid = $this->get_courseid(); 1088 if ($courseid == SITEID) { 1089 return false; 1090 } 1091 1092 return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid()); 1093 } 1094 1095 protected function define_structure() { 1096 global $CFG, $DB; 1097 1098 // are we including user info? 1099 $userinfo = $this->get_setting_value('users'); 1100 1101 $gradebook = new backup_nested_element('gradebook'); 1102 1103 //grade_letters are done in backup_activity_grades_structure_step() 1104 1105 //calculated grade items 1106 $grade_items = new backup_nested_element('grade_items'); 1107 $grade_item = new backup_nested_element('grade_item', array('id'), array( 1108 'categoryid', 'itemname', 'itemtype', 'itemmodule', 1109 'iteminstance', 'itemnumber', 'iteminfo', 'idnumber', 1110 'calculation', 'gradetype', 'grademax', 'grademin', 1111 'scaleid', 'outcomeid', 'gradepass', 'multfactor', 1112 'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride', 1113 'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime', 1114 'needsupdate', 'timecreated', 'timemodified')); 1115 1116 $this->add_plugin_structure('local', $grade_item, true); 1117 1118 $grade_grades = new backup_nested_element('grade_grades'); 1119 $grade_grade = new backup_nested_element('grade_grade', array('id'), array( 1120 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin', 1121 'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 1122 'locked', 'locktime', 'exported', 'overridden', 1123 'excluded', 'feedback', 'feedbackformat', 'information', 1124 'informationformat', 'timecreated', 'timemodified', 1125 'aggregationstatus', 'aggregationweight')); 1126 1127 //grade_categories 1128 $grade_categories = new backup_nested_element('grade_categories'); 1129 $grade_category = new backup_nested_element('grade_category', array('id'), array( 1130 //'courseid', 1131 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh', 1132 'droplow', 'aggregateonlygraded', 'aggregateoutcomes', 1133 'timecreated', 'timemodified', 'hidden')); 1134 1135 $letters = new backup_nested_element('grade_letters'); 1136 $letter = new backup_nested_element('grade_letter', 'id', array( 1137 'lowerboundary', 'letter')); 1138 1139 $grade_settings = new backup_nested_element('grade_settings'); 1140 $grade_setting = new backup_nested_element('grade_setting', 'id', array( 1141 'name', 'value')); 1142 1143 $gradebook_attributes = new backup_nested_element('attributes', null, array('calculations_freeze')); 1144 1145 // Build the tree 1146 $gradebook->add_child($gradebook_attributes); 1147 1148 $gradebook->add_child($grade_categories); 1149 $grade_categories->add_child($grade_category); 1150 1151 $gradebook->add_child($grade_items); 1152 $grade_items->add_child($grade_item); 1153 $grade_item->add_child($grade_grades); 1154 $grade_grades->add_child($grade_grade); 1155 1156 $gradebook->add_child($letters); 1157 $letters->add_child($letter); 1158 1159 $gradebook->add_child($grade_settings); 1160 $grade_settings->add_child($grade_setting); 1161 1162 // Define sources 1163 1164 // Add attribute with gradebook calculation freeze date if needed. 1165 $attributes = new stdClass(); 1166 $gradebookcalculationfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid()); 1167 if ($gradebookcalculationfreeze) { 1168 $attributes->calculations_freeze = $gradebookcalculationfreeze; 1169 } 1170 $gradebook_attributes->set_source_array([$attributes]); 1171 1172 //Include manual, category and the course grade item 1173 $grade_items_sql ="SELECT * FROM {grade_items} 1174 WHERE courseid = :courseid 1175 AND (itemtype='manual' OR itemtype='course' OR itemtype='category')"; 1176 $grade_items_params = array('courseid'=>backup::VAR_COURSEID); 1177 $grade_item->set_source_sql($grade_items_sql, $grade_items_params); 1178 1179 if ($userinfo) { 1180 $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID)); 1181 } 1182 1183 $grade_category_sql = "SELECT gc.*, gi.sortorder 1184 FROM {grade_categories} gc 1185 JOIN {grade_items} gi ON (gi.iteminstance = gc.id) 1186 WHERE gc.courseid = :courseid 1187 AND (gi.itemtype='course' OR gi.itemtype='category') 1188 ORDER BY gc.parent ASC";//need parent categories before their children 1189 $grade_category_params = array('courseid'=>backup::VAR_COURSEID); 1190 $grade_category->set_source_sql($grade_category_sql, $grade_category_params); 1191 1192 $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID)); 1193 1194 // Set the grade settings source, forcing the inclusion of minmaxtouse if not present. 1195 $settings = array(); 1196 $rs = $DB->get_recordset('grade_settings', array('courseid' => $this->get_courseid())); 1197 foreach ($rs as $record) { 1198 $settings[$record->name] = $record; 1199 } 1200 $rs->close(); 1201 if (!isset($settings['minmaxtouse'])) { 1202 $settings['minmaxtouse'] = (object) array('name' => 'minmaxtouse', 'value' => $CFG->grade_minmaxtouse); 1203 } 1204 $grade_setting->set_source_array($settings); 1205 1206 1207 // Annotations (both as final as far as they are going to be exported in next steps) 1208 $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0 1209 $grade_item->annotate_ids('outcomefinal', 'outcomeid'); 1210 1211 //just in case there are any users not already annotated by the activities 1212 $grade_grade->annotate_ids('userfinal', 'userid'); 1213 1214 // Return the root element 1215 return $gradebook; 1216 } 1217 } 1218 1219 /** 1220 * Step in charge of constructing the grade_history.xml file containing the grade histories. 1221 */ 1222 class backup_grade_history_structure_step extends backup_structure_step { 1223 1224 /** 1225 * Limit the execution. 1226 * 1227 * This applies the same logic than the one applied to {@link backup_gradebook_structure_step}, 1228 * because we do not want to save the history of items which are not backed up. At least for now. 1229 */ 1230 protected function execute_condition() { 1231 $courseid = $this->get_courseid(); 1232 if ($courseid == SITEID) { 1233 return false; 1234 } 1235 1236 return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid()); 1237 } 1238 1239 protected function define_structure() { 1240 1241 // Settings to use. 1242 $userinfo = $this->get_setting_value('users'); 1243 $history = $this->get_setting_value('grade_histories'); 1244 1245 // Create the nested elements. 1246 $bookhistory = new backup_nested_element('grade_history'); 1247 $grades = new backup_nested_element('grade_grades'); 1248 $grade = new backup_nested_element('grade_grade', array('id'), array( 1249 'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid', 1250 'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid', 1251 'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden', 1252 'excluded', 'feedback', 'feedbackformat', 'information', 1253 'informationformat', 'timemodified')); 1254 1255 // Build the tree. 1256 $bookhistory->add_child($grades); 1257 $grades->add_child($grade); 1258 1259 // This only happens if we are including user info and history. 1260 if ($userinfo && $history) { 1261 // Only keep the history of grades related to items which have been backed up, The query is 1262 // similar (but not identical) to the one used in backup_gradebook_structure_step::define_structure(). 1263 $gradesql = "SELECT ggh.* 1264 FROM {grade_grades_history} ggh 1265 JOIN {grade_items} gi ON ggh.itemid = gi.id 1266 WHERE gi.courseid = :courseid 1267 AND (gi.itemtype = 'manual' OR gi.itemtype = 'course' OR gi.itemtype = 'category')"; 1268 $grade->set_source_sql($gradesql, array('courseid' => backup::VAR_COURSEID)); 1269 } 1270 1271 // Annotations. (Final annotations as this step is part of the final task). 1272 $grade->annotate_ids('scalefinal', 'rawscaleid'); 1273 $grade->annotate_ids('userfinal', 'loggeduser'); 1274 $grade->annotate_ids('userfinal', 'userid'); 1275 $grade->annotate_ids('userfinal', 'usermodified'); 1276 1277 // Return the root element. 1278 return $bookhistory; 1279 } 1280 1281 } 1282 1283 /** 1284 * structure step in charge if constructing the completion.xml file for all the users completion 1285 * information in a given activity 1286 */ 1287 class backup_userscompletion_structure_step extends backup_structure_step { 1288 1289 /** 1290 * Skip completion on the front page. 1291 * @return bool 1292 */ 1293 protected function execute_condition() { 1294 return ($this->get_courseid() != SITEID); 1295 } 1296 1297 protected function define_structure() { 1298 1299 // Define each element separated 1300 $completions = new backup_nested_element('completions'); 1301 1302 $completion = new backup_nested_element('completion', array('id'), array( 1303 'userid', 'completionstate', 'viewed', 'timemodified')); 1304 1305 // Build the tree 1306 1307 $completions->add_child($completion); 1308 1309 // Define sources 1310 1311 $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID)); 1312 1313 // Define id annotations 1314 1315 $completion->annotate_ids('user', 'userid'); 1316 1317 $completionviews = new backup_nested_element('completionviews'); 1318 $completionview = new backup_nested_element('completionview', ['id'], ['userid', 'timecreated']); 1319 1320 // Build the tree. 1321 $completionviews->add_child($completionview); 1322 1323 // Define sources. 1324 $completionview->set_source_table('course_modules_viewed', ['coursemoduleid' => backup::VAR_MODID]); 1325 1326 // Define id annotations. 1327 $completionview->annotate_ids('user', 'userid'); 1328 1329 $completions->add_child($completionviews); 1330 // Return the root element (completions). 1331 return $completions; 1332 1333 } 1334 } 1335 1336 /** 1337 * structure step in charge of constructing the main groups.xml file for all the groups and 1338 * groupings information already annotated 1339 */ 1340 class backup_groups_structure_step extends backup_structure_step { 1341 1342 protected function define_structure() { 1343 1344 // To know if we are including users. 1345 $userinfo = $this->get_setting_value('users'); 1346 // To know if we are including groups and groupings. 1347 $groupinfo = $this->get_setting_value('groups'); 1348 1349 // Define each element separated 1350 1351 $groups = new backup_nested_element('groups'); 1352 1353 $group = new backup_nested_element('group', array('id'), array( 1354 'name', 'idnumber', 'description', 'descriptionformat', 'enrolmentkey', 1355 'picture', 'visibility', 'participation', 'timecreated', 'timemodified')); 1356 1357 $groupcustomfields = new backup_nested_element('groupcustomfields'); 1358 $groupcustomfield = new backup_nested_element('groupcustomfield', ['id'], [ 1359 'shortname', 'type', 'value', 'valueformat', 'groupid']); 1360 1361 $members = new backup_nested_element('group_members'); 1362 1363 $member = new backup_nested_element('group_member', array('id'), array( 1364 'userid', 'timeadded', 'component', 'itemid')); 1365 1366 $groupings = new backup_nested_element('groupings'); 1367 1368 $grouping = new backup_nested_element('grouping', 'id', array( 1369 'name', 'idnumber', 'description', 'descriptionformat', 'configdata', 1370 'timecreated', 'timemodified')); 1371 1372 $groupingcustomfields = new backup_nested_element('groupingcustomfields'); 1373 $groupingcustomfield = new backup_nested_element('groupingcustomfield', ['id'], [ 1374 'shortname', 'type', 'value', 'valueformat', 'groupingid']); 1375 1376 $groupinggroups = new backup_nested_element('grouping_groups'); 1377 1378 $groupinggroup = new backup_nested_element('grouping_group', array('id'), array( 1379 'groupid', 'timeadded')); 1380 1381 // Build the tree 1382 1383 $groups->add_child($group); 1384 $groups->add_child($groupcustomfields); 1385 $groupcustomfields->add_child($groupcustomfield); 1386 $groups->add_child($groupings); 1387 1388 $group->add_child($members); 1389 $members->add_child($member); 1390 1391 $groupings->add_child($grouping); 1392 $groupings->add_child($groupingcustomfields); 1393 $groupingcustomfields->add_child($groupingcustomfield); 1394 $grouping->add_child($groupinggroups); 1395 $groupinggroups->add_child($groupinggroup); 1396 1397 // Define sources 1398 1399 // This only happens if we are including groups/groupings. 1400 if ($groupinfo) { 1401 $group->set_source_sql(" 1402 SELECT g.* 1403 FROM {groups} g 1404 JOIN {backup_ids_temp} bi ON g.id = bi.itemid 1405 WHERE bi.backupid = ? 1406 AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID)); 1407 1408 $grouping->set_source_sql(" 1409 SELECT g.* 1410 FROM {groupings} g 1411 JOIN {backup_ids_temp} bi ON g.id = bi.itemid 1412 WHERE bi.backupid = ? 1413 AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID)); 1414 $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID)); 1415 1416 // This only happens if we are including users. 1417 if ($userinfo) { 1418 $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID)); 1419 } 1420 1421 $courseid = $this->task->get_courseid(); 1422 $groupcustomfield->set_source_array($this->get_group_custom_fields_for_backup($courseid)); 1423 $groupingcustomfield->set_source_array($this->get_grouping_custom_fields_for_backup($courseid)); 1424 } 1425 1426 // Define id annotations (as final) 1427 1428 $member->annotate_ids('userfinal', 'userid'); 1429 1430 // Define file annotations 1431 1432 $group->annotate_files('group', 'description', 'id'); 1433 $group->annotate_files('group', 'icon', 'id'); 1434 $grouping->annotate_files('grouping', 'description', 'id'); 1435 1436 // Return the root element (groups) 1437 return $groups; 1438 } 1439 1440 /** 1441 * Get custom fields array for group 1442 * @param int $courseid 1443 * @return array 1444 */ 1445 protected function get_group_custom_fields_for_backup(int $courseid): array { 1446 global $DB; 1447 $handler = \core_group\customfield\group_handler::create(); 1448 $fieldsforbackup = []; 1449 if ($groups = $DB->get_records('groups', ['courseid' => $courseid], '', 'id')) { 1450 foreach ($groups as $group) { 1451 $fieldsforbackup = array_merge($fieldsforbackup, $handler->get_instance_data_for_backup($group->id)); 1452 } 1453 } 1454 return $fieldsforbackup; 1455 } 1456 1457 /** 1458 * Get custom fields array for grouping 1459 * @param int $courseid 1460 * @return array 1461 */ 1462 protected function get_grouping_custom_fields_for_backup(int $courseid): array { 1463 global $DB; 1464 $handler = \core_group\customfield\grouping_handler::create(); 1465 $fieldsforbackup = []; 1466 if ($groupings = $DB->get_records('groupings', ['courseid' => $courseid], '', 'id')) { 1467 foreach ($groupings as $grouping) { 1468 $fieldsforbackup = array_merge($fieldsforbackup, $handler->get_instance_data_for_backup($grouping->id)); 1469 } 1470 } 1471 return $fieldsforbackup; 1472 } 1473 } 1474 1475 /** 1476 * structure step in charge of constructing the main users.xml file for all the users already 1477 * annotated (final). Includes custom profile fields, preferences, tags, role assignments and 1478 * overrides. 1479 */ 1480 class backup_users_structure_step extends backup_structure_step { 1481 1482 protected function define_structure() { 1483 global $CFG; 1484 1485 // To know if we are anonymizing users 1486 $anonymize = $this->get_setting_value('anonymize'); 1487 // To know if we are including role assignments 1488 $roleassignments = $this->get_setting_value('role_assignments'); 1489 1490 // Define each element separate. 1491 1492 $users = new backup_nested_element('users'); 1493 1494 // Create the array of user fields by hand, as far as we have various bits to control 1495 // anonymize option, password backup, mnethostid... 1496 1497 // First, the fields not needing anonymization nor special handling 1498 $normalfields = array( 1499 'confirmed', 'policyagreed', 'deleted', 1500 'lang', 'theme', 'timezone', 'firstaccess', 1501 'lastaccess', 'lastlogin', 'currentlogin', 1502 'mailformat', 'maildigest', 'maildisplay', 1503 'autosubscribe', 'trackforums', 'timecreated', 1504 'timemodified', 'trustbitmask'); 1505 1506 // Then, the fields potentially needing anonymization 1507 $anonfields = array( 1508 'username', 'idnumber', 'email', 'phone1', 1509 'phone2', 'institution', 'department', 'address', 1510 'city', 'country', 'lastip', 'picture', 1511 'description', 'descriptionformat', 'imagealt', 'auth'); 1512 $anonfields = array_merge($anonfields, \core_user\fields::get_name_fields()); 1513 1514 // Add anonymized fields to $userfields with custom final element 1515 foreach ($anonfields as $field) { 1516 if ($anonymize) { 1517 $userfields[] = new anonymizer_final_element($field); 1518 } else { 1519 $userfields[] = $field; // No anonymization, normally added 1520 } 1521 } 1522 1523 // mnethosturl requires special handling (custom final element) 1524 $userfields[] = new mnethosturl_final_element('mnethosturl'); 1525 1526 // password added conditionally 1527 if (!empty($CFG->includeuserpasswordsinbackup)) { 1528 $userfields[] = 'password'; 1529 } 1530 1531 // Merge all the fields 1532 $userfields = array_merge($userfields, $normalfields); 1533 1534 $user = new backup_nested_element('user', array('id', 'contextid'), $userfields); 1535 1536 $customfields = new backup_nested_element('custom_fields'); 1537 1538 $customfield = new backup_nested_element('custom_field', array('id'), array( 1539 'field_name', 'field_type', 'field_data')); 1540 1541 $tags = new backup_nested_element('tags'); 1542 1543 $tag = new backup_nested_element('tag', array('id'), array( 1544 'name', 'rawname')); 1545 1546 $preferences = new backup_nested_element('preferences'); 1547 1548 $preference = new backup_nested_element('preference', array('id'), array( 1549 'name', 'value')); 1550 1551 $roles = new backup_nested_element('roles'); 1552 1553 $overrides = new backup_nested_element('role_overrides'); 1554 1555 $override = new backup_nested_element('override', array('id'), array( 1556 'roleid', 'capability', 'permission', 'timemodified', 1557 'modifierid')); 1558 1559 $assignments = new backup_nested_element('role_assignments'); 1560 1561 $assignment = new backup_nested_element('assignment', array('id'), array( 1562 'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here 1563 'sortorder')); 1564 1565 // Build the tree 1566 1567 $users->add_child($user); 1568 1569 $user->add_child($customfields); 1570 $customfields->add_child($customfield); 1571 1572 $user->add_child($tags); 1573 $tags->add_child($tag); 1574 1575 $user->add_child($preferences); 1576 $preferences->add_child($preference); 1577 1578 $user->add_child($roles); 1579 1580 $roles->add_child($overrides); 1581 $roles->add_child($assignments); 1582 1583 $overrides->add_child($override); 1584 $assignments->add_child($assignment); 1585 1586 // Define sources 1587 1588 $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl 1589 FROM {user} u 1590 JOIN {backup_ids_temp} bi ON bi.itemid = u.id 1591 LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . ' 1592 LEFT JOIN {mnet_host} m ON m.id = u.mnethostid 1593 WHERE bi.backupid = ? 1594 AND bi.itemname = ?', array( 1595 backup_helper::is_sqlparam($this->get_backupid()), 1596 backup_helper::is_sqlparam('userfinal'))); 1597 1598 // All the rest on information is only added if we arent 1599 // in an anonymized backup 1600 if (!$anonymize) { 1601 $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data 1602 FROM {user_info_field} f 1603 JOIN {user_info_data} d ON d.fieldid = f.id 1604 WHERE d.userid = ?', array(backup::VAR_PARENTID)); 1605 1606 $customfield->set_source_alias('shortname', 'field_name'); 1607 $customfield->set_source_alias('datatype', 'field_type'); 1608 $customfield->set_source_alias('data', 'field_data'); 1609 1610 $tag->set_source_sql('SELECT t.id, t.name, t.rawname 1611 FROM {tag} t 1612 JOIN {tag_instance} ti ON ti.tagid = t.id 1613 WHERE ti.itemtype = ? 1614 AND ti.itemid = ?', array( 1615 backup_helper::is_sqlparam('user'), 1616 backup::VAR_PARENTID)); 1617 1618 $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID)); 1619 1620 $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid')); 1621 1622 // Assignments only added if specified 1623 if ($roleassignments) { 1624 $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid')); 1625 } 1626 1627 // Define id annotations (as final) 1628 $override->annotate_ids('rolefinal', 'roleid'); 1629 } 1630 // Return root element (users) 1631 return $users; 1632 } 1633 } 1634 1635 /** 1636 * structure step in charge of constructing the block.xml file for one 1637 * given block (instance and positions). If the block has custom DB structure 1638 * that will go to a separate file (different step defined in block class) 1639 */ 1640 class backup_block_instance_structure_step extends backup_structure_step { 1641 1642 protected function define_structure() { 1643 global $DB; 1644 1645 // Define each element separated 1646 1647 $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array( 1648 'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern', 1649 'subpagepattern', 'defaultregion', 'defaultweight', 'configdata', 1650 'timecreated', 'timemodified')); 1651 1652 $positions = new backup_nested_element('block_positions'); 1653 1654 $position = new backup_nested_element('block_position', array('id'), array( 1655 'contextid', 'pagetype', 'subpage', 'visible', 1656 'region', 'weight')); 1657 1658 // Build the tree 1659 1660 $block->add_child($positions); 1661 $positions->add_child($position); 1662 1663 // Transform configdata information if needed (process links and friends) 1664 $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid())); 1665 if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) { 1666 $configdata = array_filter( 1667 (array) unserialize_object(base64_decode($blockrec->configdata)), 1668 static function($value): bool { 1669 return !($value instanceof __PHP_Incomplete_Class); 1670 } 1671 ); 1672 1673 foreach ($configdata as $attribute => $value) { 1674 if (in_array($attribute, $attrstotransform)) { 1675 $configdata[$attribute] = $this->contenttransformer->process($value); 1676 } 1677 } 1678 $blockrec->configdata = base64_encode(serialize((object)$configdata)); 1679 } 1680 $blockrec->contextid = $this->task->get_contextid(); 1681 // Get the version of the block 1682 $blockrec->version = get_config('block_'.$this->task->get_blockname(), 'version'); 1683 1684 // Define sources 1685 1686 $block->set_source_array(array($blockrec)); 1687 1688 $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID)); 1689 1690 // File anotations (for fileareas specified on each block) 1691 foreach ($this->task->get_fileareas() as $filearea) { 1692 $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null); 1693 } 1694 1695 // Return the root element (block) 1696 return $block; 1697 } 1698 } 1699 1700 /** 1701 * structure step in charge of constructing the logs.xml file for all the log records found 1702 * in course. Note that we are sending to backup ALL the log records having cmid = 0. That 1703 * includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do 1704 * that just in case they become restored some day in the future 1705 */ 1706 class backup_course_logs_structure_step extends backup_structure_step { 1707 1708 protected function define_structure() { 1709 1710 // Define each element separated 1711 1712 $logs = new backup_nested_element('logs'); 1713 1714 $log = new backup_nested_element('log', array('id'), array( 1715 'time', 'userid', 'ip', 'module', 1716 'action', 'url', 'info')); 1717 1718 // Build the tree 1719 1720 $logs->add_child($log); 1721 1722 // Define sources (all the records belonging to the course, having cmid = 0) 1723 1724 $log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0))); 1725 1726 // Annotations 1727 // NOTE: We don't annotate users from logs as far as they MUST be 1728 // always annotated by the course (enrol, ras... whatever) 1729 1730 // Return the root element (logs) 1731 1732 return $logs; 1733 } 1734 } 1735 1736 /** 1737 * structure step in charge of constructing the logs.xml file for all the log records found 1738 * in activity 1739 */ 1740 class backup_activity_logs_structure_step extends backup_structure_step { 1741 1742 protected function define_structure() { 1743 1744 // Define each element separated 1745 1746 $logs = new backup_nested_element('logs'); 1747 1748 $log = new backup_nested_element('log', array('id'), array( 1749 'time', 'userid', 'ip', 'module', 1750 'action', 'url', 'info')); 1751 1752 // Build the tree 1753 1754 $logs->add_child($log); 1755 1756 // Define sources 1757 1758 $log->set_source_table('log', array('cmid' => backup::VAR_MODID)); 1759 1760 // Annotations 1761 // NOTE: We don't annotate users from logs as far as they MUST be 1762 // always annotated by the activity (true participants). 1763 1764 // Return the root element (logs) 1765 1766 return $logs; 1767 } 1768 } 1769 1770 /** 1771 * Structure step in charge of constructing the logstores.xml file for the course logs. 1772 * 1773 * This backup step will backup the logs for all the enabled logstore subplugins supporting 1774 * it, for logs belonging to the course level. 1775 */ 1776 class backup_course_logstores_structure_step extends backup_structure_step { 1777 1778 protected function define_structure() { 1779 1780 // Define the structure of logstores container. 1781 $logstores = new backup_nested_element('logstores'); 1782 $logstore = new backup_nested_element('logstore'); 1783 $logstores->add_child($logstore); 1784 1785 // Add the tool_log logstore subplugins information to the logstore element. 1786 $this->add_subplugin_structure('logstore', $logstore, true, 'tool', 'log'); 1787 1788 return $logstores; 1789 } 1790 } 1791 1792 /** 1793 * Structure step in charge of constructing the loglastaccess.xml file for the course logs. 1794 * 1795 * This backup step will backup the logs of the user_lastaccess table. 1796 */ 1797 class backup_course_loglastaccess_structure_step extends backup_structure_step { 1798 1799 /** 1800 * This function creates the structures for the loglastaccess.xml file. 1801 * Expected structure would look like this. 1802 * <loglastaccesses> 1803 * <loglastaccess id=2> 1804 * <userid>5</userid> 1805 * <timeaccess>1616887341</timeaccess> 1806 * </loglastaccess> 1807 * </loglastaccesses> 1808 * 1809 * @return backup_nested_element 1810 */ 1811 protected function define_structure() { 1812 1813 // To know if we are including userinfo. 1814 $userinfo = $this->get_setting_value('users'); 1815 1816 // Define the structure of logstores container. 1817 $lastaccesses = new backup_nested_element('lastaccesses'); 1818 $lastaccess = new backup_nested_element('lastaccess', array('id'), array('userid', 'timeaccess')); 1819 1820 // Define build tree. 1821 $lastaccesses->add_child($lastaccess); 1822 1823 // This element should only happen if we are including user info. 1824 if ($userinfo) { 1825 // Define sources. 1826 $lastaccess->set_source_sql(' 1827 SELECT id, userid, timeaccess 1828 FROM {user_lastaccess} 1829 WHERE courseid = ?', 1830 array(backup::VAR_COURSEID)); 1831 1832 // Define userid annotation to user. 1833 $lastaccess->annotate_ids('user', 'userid'); 1834 } 1835 1836 // Return the root element (lastaccessess). 1837 return $lastaccesses; 1838 } 1839 } 1840 1841 /** 1842 * Structure step in charge of constructing the logstores.xml file for the activity logs. 1843 * 1844 * Note: Activity structure is completely equivalent to the course one, so just extend it. 1845 */ 1846 class backup_activity_logstores_structure_step extends backup_course_logstores_structure_step { 1847 } 1848 1849 /** 1850 * Course competencies backup structure step. 1851 */ 1852 class backup_course_competencies_structure_step extends backup_structure_step { 1853 1854 protected function define_structure() { 1855 $userinfo = $this->get_setting_value('users'); 1856 1857 $wrapper = new backup_nested_element('course_competencies'); 1858 1859 $settings = new backup_nested_element('settings', array('id'), array('pushratingstouserplans')); 1860 $wrapper->add_child($settings); 1861 1862 $sql = 'SELECT s.pushratingstouserplans 1863 FROM {' . \core_competency\course_competency_settings::TABLE . '} s 1864 WHERE s.courseid = :courseid'; 1865 $settings->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID)); 1866 1867 $competencies = new backup_nested_element('competencies'); 1868 $wrapper->add_child($competencies); 1869 1870 $competency = new backup_nested_element('competency', null, array('id', 'idnumber', 'ruleoutcome', 1871 'sortorder', 'frameworkid', 'frameworkidnumber')); 1872 $competencies->add_child($competency); 1873 1874 $sql = 'SELECT c.id, c.idnumber, cc.ruleoutcome, cc.sortorder, f.id AS frameworkid, f.idnumber AS frameworkidnumber 1875 FROM {' . \core_competency\course_competency::TABLE . '} cc 1876 JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cc.competencyid 1877 JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid 1878 WHERE cc.courseid = :courseid 1879 ORDER BY cc.sortorder'; 1880 $competency->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID)); 1881 1882 $usercomps = new backup_nested_element('user_competencies'); 1883 $wrapper->add_child($usercomps); 1884 if ($userinfo) { 1885 $usercomp = new backup_nested_element('user_competency', null, array('userid', 'competencyid', 1886 'proficiency', 'grade')); 1887 $usercomps->add_child($usercomp); 1888 1889 $sql = 'SELECT ucc.userid, ucc.competencyid, ucc.proficiency, ucc.grade 1890 FROM {' . \core_competency\user_competency_course::TABLE . '} ucc 1891 WHERE ucc.courseid = :courseid 1892 AND ucc.grade IS NOT NULL'; 1893 $usercomp->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID)); 1894 $usercomp->annotate_ids('user', 'userid'); 1895 } 1896 1897 return $wrapper; 1898 } 1899 1900 /** 1901 * Execute conditions. 1902 * 1903 * @return bool 1904 */ 1905 protected function execute_condition() { 1906 1907 // Do not execute if competencies are not included. 1908 if (!$this->get_setting_value('competencies')) { 1909 return false; 1910 } 1911 1912 return true; 1913 } 1914 } 1915 1916 /** 1917 * Activity competencies backup structure step. 1918 */ 1919 class backup_activity_competencies_structure_step extends backup_structure_step { 1920 1921 protected function define_structure() { 1922 $wrapper = new backup_nested_element('course_module_competencies'); 1923 1924 $competencies = new backup_nested_element('competencies'); 1925 $wrapper->add_child($competencies); 1926 1927 $competency = new backup_nested_element('competency', null, array('idnumber', 'ruleoutcome', 1928 'sortorder', 'frameworkidnumber', 'overridegrade')); 1929 $competencies->add_child($competency); 1930 1931 $sql = 'SELECT c.idnumber, cmc.ruleoutcome, cmc.overridegrade, cmc.sortorder, f.idnumber AS frameworkidnumber 1932 FROM {' . \core_competency\course_module_competency::TABLE . '} cmc 1933 JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cmc.competencyid 1934 JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid 1935 WHERE cmc.cmid = :coursemoduleid 1936 ORDER BY cmc.sortorder'; 1937 $competency->set_source_sql($sql, array('coursemoduleid' => backup::VAR_MODID)); 1938 1939 return $wrapper; 1940 } 1941 1942 /** 1943 * Execute conditions. 1944 * 1945 * @return bool 1946 */ 1947 protected function execute_condition() { 1948 1949 // Do not execute if competencies are not included. 1950 if (!$this->get_setting_value('competencies')) { 1951 return false; 1952 } 1953 1954 return true; 1955 } 1956 } 1957 1958 /** 1959 * structure in charge of constructing the inforef.xml file for all the items we want 1960 * to have referenced there (users, roles, files...) 1961 */ 1962 class backup_inforef_structure_step extends backup_structure_step { 1963 1964 protected function define_structure() { 1965 1966 // Items we want to include in the inforef file. 1967 $items = backup_helper::get_inforef_itemnames(); 1968 1969 // Build the tree 1970 1971 $inforef = new backup_nested_element('inforef'); 1972 1973 // For each item, conditionally, if there are already records, build element 1974 foreach ($items as $itemname) { 1975 if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) { 1976 $elementroot = new backup_nested_element($itemname . 'ref'); 1977 $element = new backup_nested_element($itemname, array(), array('id')); 1978 $inforef->add_child($elementroot); 1979 $elementroot->add_child($element); 1980 $element->set_source_sql(" 1981 SELECT itemid AS id 1982 FROM {backup_ids_temp} 1983 WHERE backupid = ? 1984 AND itemname = ?", 1985 array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname))); 1986 } 1987 } 1988 1989 // We don't annotate anything there, but rely in the next step 1990 // (move_inforef_annotations_to_final) that will change all the 1991 // already saved 'inforref' entries to their 'final' annotations. 1992 return $inforef; 1993 } 1994 } 1995 1996 /** 1997 * This step will get all the annotations already processed to inforef.xml file and 1998 * transform them into 'final' annotations. 1999 */ 2000 class move_inforef_annotations_to_final extends backup_execution_step { 2001 2002 protected function define_execution() { 2003 2004 // Items we want to include in the inforef file 2005 $items = backup_helper::get_inforef_itemnames(); 2006 $progress = $this->task->get_progress(); 2007 $progress->start_progress($this->get_name(), count($items)); 2008 $done = 1; 2009 foreach ($items as $itemname) { 2010 // Delegate to dbops 2011 backup_structure_dbops::move_annotations_to_final($this->get_backupid(), 2012 $itemname, $progress); 2013 $progress->progress($done++); 2014 } 2015 $progress->end_progress(); 2016 } 2017 } 2018 2019 /** 2020 * structure in charge of constructing the files.xml file with all the 2021 * annotated (final) files along the process. At, the same time, and 2022 * using one specialised nested_element, will copy them form moodle storage 2023 * to backup storage 2024 */ 2025 class backup_final_files_structure_step extends backup_structure_step { 2026 2027 protected function define_structure() { 2028 2029 // Define elements 2030 2031 $files = new backup_nested_element('files'); 2032 2033 $file = new file_nested_element('file', array('id'), array( 2034 'contenthash', 'contextid', 'component', 'filearea', 'itemid', 2035 'filepath', 'filename', 'userid', 'filesize', 2036 'mimetype', 'status', 'timecreated', 'timemodified', 2037 'source', 'author', 'license', 'sortorder', 2038 'repositorytype', 'repositoryid', 'reference')); 2039 2040 // Build the tree 2041 2042 $files->add_child($file); 2043 2044 // Define sources 2045 2046 $file->set_source_sql("SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.reference 2047 FROM {files} f 2048 LEFT JOIN {files_reference} fr ON fr.id = f.referencefileid 2049 LEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryid 2050 LEFT JOIN {repository} r ON r.id = ri.typeid 2051 JOIN {backup_ids_temp} bi ON f.id = bi.itemid 2052 WHERE bi.backupid = ? 2053 AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID)); 2054 2055 return $files; 2056 } 2057 } 2058 2059 /** 2060 * Structure step in charge of creating the main moodle_backup.xml file 2061 * where all the information related to the backup, settings, license and 2062 * other information needed on restore is added*/ 2063 class backup_main_structure_step extends backup_structure_step { 2064 2065 protected function define_structure() { 2066 2067 global $CFG; 2068 2069 $info = array(); 2070 2071 $info['name'] = $this->get_setting_value('filename'); 2072 $info['moodle_version'] = $CFG->version; 2073 $info['moodle_release'] = $CFG->release; 2074 $info['backup_version'] = $CFG->backup_version; 2075 $info['backup_release'] = $CFG->backup_release; 2076 $info['backup_date'] = time(); 2077 $info['backup_uniqueid']= $this->get_backupid(); 2078 $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid()); 2079 $info['include_files'] = backup_controller_dbops::backup_includes_files($this->get_backupid()); 2080 $info['include_file_references_to_external_content'] = 2081 backup_controller_dbops::backup_includes_file_references($this->get_backupid()); 2082 $info['original_wwwroot']=$CFG->wwwroot; 2083 $info['original_site_identifier_hash'] = md5(get_site_identifier()); 2084 $info['original_course_id'] = $this->get_courseid(); 2085 $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid()); 2086 $info['original_course_format'] = $originalcourseinfo->format; 2087 $info['original_course_fullname'] = $originalcourseinfo->fullname; 2088 $info['original_course_shortname'] = $originalcourseinfo->shortname; 2089 $info['original_course_startdate'] = $originalcourseinfo->startdate; 2090 $info['original_course_enddate'] = $originalcourseinfo->enddate; 2091 $info['original_course_contextid'] = context_course::instance($this->get_courseid())->id; 2092 $info['original_system_contextid'] = context_system::instance()->id; 2093 2094 // Get more information from controller 2095 list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information( 2096 $this->get_backupid(), $this->get_task()->get_progress()); 2097 2098 // Define elements 2099 2100 $moodle_backup = new backup_nested_element('moodle_backup'); 2101 2102 $information = new backup_nested_element('information', null, array( 2103 'name', 'moodle_version', 'moodle_release', 'backup_version', 2104 'backup_release', 'backup_date', 'mnet_remoteusers', 'include_files', 'include_file_references_to_external_content', 'original_wwwroot', 2105 'original_site_identifier_hash', 'original_course_id', 'original_course_format', 2106 'original_course_fullname', 'original_course_shortname', 'original_course_startdate', 'original_course_enddate', 2107 'original_course_contextid', 'original_system_contextid')); 2108 2109 $details = new backup_nested_element('details'); 2110 2111 $detail = new backup_nested_element('detail', array('backup_id'), array( 2112 'type', 'format', 'interactive', 'mode', 2113 'execution', 'executiontime')); 2114 2115 $contents = new backup_nested_element('contents'); 2116 2117 $activities = new backup_nested_element('activities'); 2118 2119 $activity = new backup_nested_element('activity', null, array( 2120 'moduleid', 'sectionid', 'modulename', 'title', 2121 'directory')); 2122 2123 $sections = new backup_nested_element('sections'); 2124 2125 $section = new backup_nested_element('section', null, array( 2126 'sectionid', 'title', 'directory')); 2127 2128 $course = new backup_nested_element('course', null, array( 2129 'courseid', 'title', 'directory')); 2130 2131 $settings = new backup_nested_element('settings'); 2132 2133 $setting = new backup_nested_element('setting', null, array( 2134 'level', 'section', 'activity', 'name', 'value')); 2135 2136 // Build the tree 2137 2138 $moodle_backup->add_child($information); 2139 2140 $information->add_child($details); 2141 $details->add_child($detail); 2142 2143 $information->add_child($contents); 2144 if (!empty($cinfo['activities'])) { 2145 $contents->add_child($activities); 2146 $activities->add_child($activity); 2147 } 2148 if (!empty($cinfo['sections'])) { 2149 $contents->add_child($sections); 2150 $sections->add_child($section); 2151 } 2152 if (!empty($cinfo['course'])) { 2153 $contents->add_child($course); 2154 } 2155 2156 $information->add_child($settings); 2157 $settings->add_child($setting); 2158 2159 2160 // Set the sources 2161 2162 $information->set_source_array(array((object)$info)); 2163 2164 $detail->set_source_array($dinfo); 2165 2166 $activity->set_source_array($cinfo['activities']); 2167 2168 $section->set_source_array($cinfo['sections']); 2169 2170 $course->set_source_array($cinfo['course']); 2171 2172 $setting->set_source_array($sinfo); 2173 2174 // Prepare some information to be sent to main moodle_backup.xml file 2175 return $moodle_backup; 2176 } 2177 2178 } 2179 2180 /** 2181 * Execution step that will generate the final zip (.mbz) file with all the contents 2182 */ 2183 class backup_zip_contents extends backup_execution_step implements file_progress { 2184 /** 2185 * @var bool True if we have started tracking progress 2186 */ 2187 protected $startedprogress; 2188 2189 protected function define_execution() { 2190 2191 // Get basepath 2192 $basepath = $this->get_basepath(); 2193 2194 // Get the list of files in directory 2195 $filestemp = get_directory_list($basepath, '', false, true, true); 2196 $files = array(); 2197 foreach ($filestemp as $file) { // Add zip paths and fs paths to all them 2198 $files[$file] = $basepath . '/' . $file; 2199 } 2200 2201 // Add the log file if exists 2202 $logfilepath = $basepath . '.log'; 2203 if (file_exists($logfilepath)) { 2204 $files['moodle_backup.log'] = $logfilepath; 2205 } 2206 2207 // Calculate the zip fullpath (in OS temp area it's always backup.mbz) 2208 $zipfile = $basepath . '/backup.mbz'; 2209 2210 // Get the zip packer 2211 $zippacker = get_file_packer('application/vnd.moodle.backup'); 2212 2213 // Track overall progress for the 2 long-running steps (archive to 2214 // pathname, get backup information). 2215 $reporter = $this->task->get_progress(); 2216 $reporter->start_progress('backup_zip_contents', 2); 2217 2218 // Zip files 2219 $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this); 2220 2221 // If any sub-progress happened, end it. 2222 if ($this->startedprogress) { 2223 $this->task->get_progress()->end_progress(); 2224 $this->startedprogress = false; 2225 } else { 2226 // No progress was reported, manually move it on to the next overall task. 2227 $reporter->progress(1); 2228 } 2229 2230 // Something went wrong. 2231 if ($result === false) { 2232 @unlink($zipfile); 2233 throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip'); 2234 } 2235 // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid. 2236 try { 2237 backup_general_helper::get_backup_information_from_mbz($zipfile, $this); 2238 } catch (backup_helper_exception $e) { 2239 @unlink($zipfile); 2240 throw new backup_step_exception('error_zip_packing', '', $e->debuginfo); 2241 } 2242 2243 // If any sub-progress happened, end it. 2244 if ($this->startedprogress) { 2245 $this->task->get_progress()->end_progress(); 2246 $this->startedprogress = false; 2247 } else { 2248 $reporter->progress(2); 2249 } 2250 $reporter->end_progress(); 2251 } 2252 2253 /** 2254 * Implementation for file_progress interface to display unzip progress. 2255 * 2256 * @param int $progress Current progress 2257 * @param int $max Max value 2258 */ 2259 public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) { 2260 $reporter = $this->task->get_progress(); 2261 2262 // Start tracking progress if necessary. 2263 if (!$this->startedprogress) { 2264 $reporter->start_progress('extract_file_to_dir', ($max == file_progress::INDETERMINATE) 2265 ? \core\progress\base::INDETERMINATE : $max); 2266 $this->startedprogress = true; 2267 } 2268 2269 // Pass progress through to whatever handles it. 2270 $reporter->progress(($progress == file_progress::INDETERMINATE) 2271 ? \core\progress\base::INDETERMINATE : $progress); 2272 } 2273 } 2274 2275 /** 2276 * This step will send the generated backup file to its final destination 2277 */ 2278 class backup_store_backup_file extends backup_execution_step { 2279 2280 protected function define_execution() { 2281 2282 // Get basepath 2283 $basepath = $this->get_basepath(); 2284 2285 // Calculate the zip fullpath (in OS temp area it's always backup.mbz) 2286 $zipfile = $basepath . '/backup.mbz'; 2287 2288 $has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid()); 2289 // Perform storage and return it (TODO: shouldn't be array but proper result object) 2290 return array( 2291 'backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile, 2292 $this->task->get_progress()), 2293 'include_file_references_to_external_content' => $has_file_references 2294 ); 2295 } 2296 } 2297 2298 2299 /** 2300 * This step will search for all the activity (not calculations, categories nor aggregations) grade items 2301 * and put them to the backup_ids tables, to be used later as base to backup them 2302 */ 2303 class backup_activity_grade_items_to_ids extends backup_execution_step { 2304 2305 protected function define_execution() { 2306 2307 // Fetch all activity grade items 2308 if ($items = grade_item::fetch_all(array( 2309 'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(), 2310 'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) { 2311 // Annotate them in backup_ids 2312 foreach ($items as $item) { 2313 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id); 2314 } 2315 } 2316 } 2317 } 2318 2319 2320 /** 2321 * This step allows enrol plugins to annotate custom fields. 2322 * 2323 * @package core_backup 2324 * @copyright 2014 University of Wisconsin 2325 * @author Matt Petro 2326 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2327 */ 2328 class backup_enrolments_execution_step extends backup_execution_step { 2329 2330 /** 2331 * Function that will contain all the code to be executed. 2332 */ 2333 protected function define_execution() { 2334 global $DB; 2335 2336 $plugins = enrol_get_plugins(true); 2337 $enrols = $DB->get_records('enrol', array( 2338 'courseid' => $this->task->get_courseid())); 2339 2340 // Allow each enrol plugin to add annotations. 2341 foreach ($enrols as $enrol) { 2342 if (isset($plugins[$enrol->enrol])) { 2343 $plugins[$enrol->enrol]->backup_annotate_custom_fields($this, $enrol); 2344 } 2345 } 2346 } 2347 2348 /** 2349 * Annotate a single name/id pair. 2350 * This can be called from {@link enrol_plugin::backup_annotate_custom_fields()}. 2351 * 2352 * @param string $itemname 2353 * @param int $itemid 2354 */ 2355 public function annotate_id($itemname, $itemid) { 2356 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), $itemname, $itemid); 2357 } 2358 } 2359 2360 /** 2361 * This step will annotate all the groups and groupings belonging to the course 2362 */ 2363 class backup_annotate_course_groups_and_groupings extends backup_execution_step { 2364 2365 protected function define_execution() { 2366 global $DB; 2367 2368 // Get all the course groups 2369 if ($groups = $DB->get_records('groups', array( 2370 'courseid' => $this->task->get_courseid()))) { 2371 foreach ($groups as $group) { 2372 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id); 2373 } 2374 } 2375 2376 // Get all the course groupings 2377 if ($groupings = $DB->get_records('groupings', array( 2378 'courseid' => $this->task->get_courseid()))) { 2379 foreach ($groupings as $grouping) { 2380 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id); 2381 } 2382 } 2383 } 2384 } 2385 2386 /** 2387 * This step will annotate all the groups belonging to already annotated groupings 2388 */ 2389 class backup_annotate_groups_from_groupings extends backup_execution_step { 2390 2391 protected function define_execution() { 2392 global $DB; 2393 2394 // Fetch all the annotated groupings 2395 if ($groupings = $DB->get_records('backup_ids_temp', array( 2396 'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) { 2397 foreach ($groupings as $grouping) { 2398 if ($groups = $DB->get_records('groupings_groups', array( 2399 'groupingid' => $grouping->itemid))) { 2400 foreach ($groups as $group) { 2401 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid); 2402 } 2403 } 2404 } 2405 } 2406 } 2407 } 2408 2409 /** 2410 * This step will annotate all the scales belonging to already annotated outcomes 2411 */ 2412 class backup_annotate_scales_from_outcomes extends backup_execution_step { 2413 2414 protected function define_execution() { 2415 global $DB; 2416 2417 // Fetch all the annotated outcomes 2418 if ($outcomes = $DB->get_records('backup_ids_temp', array( 2419 'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) { 2420 foreach ($outcomes as $outcome) { 2421 if ($scale = $DB->get_record('grade_outcomes', array( 2422 'id' => $outcome->itemid))) { 2423 // Annotate as scalefinal because it's > 0 2424 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid); 2425 } 2426 } 2427 } 2428 } 2429 } 2430 2431 /** 2432 * This step will generate all the file annotations for the already 2433 * annotated (final) question_categories. It calculates the different 2434 * contexts that are being backup and, annotates all the files 2435 * on every context belonging to the "question" component. As far as 2436 * we are always including *complete* question banks it is safe and 2437 * optimal to do that in this (one pass) way 2438 */ 2439 class backup_annotate_all_question_files extends backup_execution_step { 2440 2441 protected function define_execution() { 2442 global $DB; 2443 2444 // Get all the different contexts for the final question_categories 2445 // annotated along the whole backup 2446 $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid 2447 FROM {question_categories} qc 2448 JOIN {backup_ids_temp} bi ON bi.itemid = qc.id 2449 WHERE bi.backupid = ? 2450 AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid())); 2451 // To know about qtype specific components/fileareas 2452 $components = backup_qtype_plugin::get_components_and_fileareas(); 2453 $progress = $this->task->get_progress(); 2454 $progress->start_progress($this->get_name()); 2455 // Let's loop 2456 foreach($rs as $record) { 2457 // Backup all the file areas the are managed by the core question component. 2458 // That is, by the question_type base class. In particular, we don't want 2459 // to include files belonging to responses here. 2460 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'questiontext', null, 2461 $progress); 2462 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'generalfeedback', null, 2463 $progress); 2464 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answer', null, 2465 $progress); 2466 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answerfeedback', null, 2467 $progress); 2468 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'hint', null, 2469 $progress); 2470 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'correctfeedback', null, 2471 $progress); 2472 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 2473 'partiallycorrectfeedback', null, $progress); 2474 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'incorrectfeedback', null, 2475 $progress); 2476 2477 // For files belonging to question types, we make the leap of faith that 2478 // all the files belonging to the question type are part of the question definition, 2479 // so we can just backup all the files in bulk, without specifying each 2480 // file area name separately. 2481 foreach ($components as $component => $fileareas) { 2482 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null, 2483 $progress); 2484 } 2485 } 2486 $progress->end_progress(); 2487 $rs->close(); 2488 } 2489 } 2490 2491 /** 2492 * structure step in charge of constructing the questions.xml file for all the 2493 * question categories and questions required by the backup 2494 * and letters related to one activity. 2495 */ 2496 class backup_questions_structure_step extends backup_structure_step { 2497 2498 protected function define_structure() { 2499 2500 // Define each element separately. 2501 $qcategories = new backup_nested_element('question_categories'); 2502 2503 $qcategory = new backup_nested_element('question_category', ['id'], 2504 [ 2505 'name', 2506 'contextid', 2507 'contextlevel', 2508 'contextinstanceid', 2509 'info', 2510 'infoformat', 2511 'stamp', 2512 'parent', 2513 'sortorder', 2514 'idnumber', 2515 ]); 2516 2517 $questionbankentries = new backup_nested_element('question_bank_entries'); 2518 2519 $questionbankentry = new backup_nested_element('question_bank_entry', ['id'], 2520 [ 2521 'questioncategoryid', 2522 'idnumber', 2523 'ownerid', 2524 ]); 2525 2526 $questionversions = new backup_nested_element('question_version'); 2527 2528 $questionverion = new backup_nested_element('question_versions', ['id'], ['version', 'status']); 2529 2530 $questions = new backup_nested_element('questions'); 2531 2532 $question = new backup_nested_element('question', ['id'], 2533 [ 2534 'parent', 2535 'name', 2536 'questiontext', 2537 'questiontextformat', 2538 'generalfeedback', 2539 'generalfeedbackformat', 2540 'defaultmark', 2541 'penalty', 2542 'qtype', 2543 'length', 2544 'stamp', 2545 'timecreated', 2546 'timemodified', 2547 'createdby', 2548 'modifiedby', 2549 ]); 2550 2551 // Attach qtype plugin structure to $question element, only one allowed. 2552 $this->add_plugin_structure('qtype', $question, false); 2553 2554 // Attach qbank plugin stucture to $question element, multiple allowed. 2555 $this->add_plugin_structure('qbank', $question, true); 2556 2557 // attach local plugin stucture to $question element, multiple allowed 2558 $this->add_plugin_structure('local', $question, true); 2559 2560 $qhints = new backup_nested_element('question_hints'); 2561 2562 $qhint = new backup_nested_element('question_hint', ['id'], 2563 [ 2564 'hint', 2565 'hintformat', 2566 'shownumcorrect', 2567 'clearwrong', 2568 'options', 2569 ]); 2570 2571 $tags = new backup_nested_element('tags'); 2572 2573 $tag = new backup_nested_element('tag', ['id', 'contextid'], ['name', 'rawname']); 2574 2575 // Build the initial tree. 2576 $qcategories->add_child($qcategory); 2577 $qcategory->add_child($questionbankentries); 2578 $questionbankentries->add_child($questionbankentry); 2579 $questionbankentry->add_child($questionversions); 2580 $questionversions->add_child($questionverion); 2581 $questionverion->add_child($questions); 2582 $questions->add_child($question); 2583 $question->add_child($qhints); 2584 $qhints->add_child($qhint); 2585 2586 // Add question tags. 2587 $question->add_child($tags); 2588 $tags->add_child($tag); 2589 2590 $qcategory->set_source_sql(" 2591 SELECT gc.*, 2592 contextlevel, 2593 instanceid AS contextinstanceid 2594 FROM {question_categories} gc 2595 JOIN {backup_ids_temp} bi ON bi.itemid = gc.id 2596 JOIN {context} co ON co.id = gc.contextid 2597 WHERE bi.backupid = ? 2598 AND bi.itemname = 'question_categoryfinal'", [backup::VAR_BACKUPID]); 2599 2600 $questionbankentry->set_source_table('question_bank_entries', ['questioncategoryid' => backup::VAR_PARENTID]); 2601 2602 $questionverion->set_source_table('question_versions', ['questionbankentryid' => backup::VAR_PARENTID]); 2603 2604 $question->set_source_sql(' 2605 SELECT q.* 2606 FROM {question} q 2607 JOIN {question_versions} qv ON qv.questionid = q.id 2608 JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid 2609 WHERE qv.id = ?', [backup::VAR_PARENTID]); 2610 2611 $qhint->set_source_sql(' 2612 SELECT * 2613 FROM {question_hints} 2614 WHERE questionid = :questionid 2615 ORDER BY id', ['questionid' => backup::VAR_PARENTID]); 2616 2617 $tag->set_source_sql("SELECT t.id, ti.contextid, t.name, t.rawname 2618 FROM {tag} t 2619 JOIN {tag_instance} ti ON ti.tagid = t.id 2620 WHERE ti.itemid = ? 2621 AND ti.itemtype = 'question' 2622 AND ti.component = 'core_question'", [backup::VAR_PARENTID]); 2623 2624 // Don't need to annotate ids nor files. 2625 // ...(already done by {@see backup_annotate_all_question_files()}. 2626 2627 return $qcategories; 2628 } 2629 } 2630 2631 2632 2633 /** 2634 * This step will generate all the file annotations for the already 2635 * annotated (final) users. Need to do this here because each user 2636 * has its own context and structure tasks only are able to handle 2637 * one context. Also, this step will guarantee that every user has 2638 * its context created (req for other steps) 2639 */ 2640 class backup_annotate_all_user_files extends backup_execution_step { 2641 2642 protected function define_execution() { 2643 global $DB; 2644 2645 // List of fileareas we are going to annotate 2646 $fileareas = array('profile', 'icon'); 2647 2648 // Fetch all annotated (final) users 2649 $rs = $DB->get_recordset('backup_ids_temp', array( 2650 'backupid' => $this->get_backupid(), 'itemname' => 'userfinal')); 2651 $progress = $this->task->get_progress(); 2652 $progress->start_progress($this->get_name()); 2653 foreach ($rs as $record) { 2654 $userid = $record->itemid; 2655 $userctx = context_user::instance($userid, IGNORE_MISSING); 2656 if (!$userctx) { 2657 continue; // User has not context, sure it's a deleted user, so cannot have files 2658 } 2659 // Proceed with every user filearea 2660 foreach ($fileareas as $filearea) { 2661 // We don't need to specify itemid ($userid - 5th param) as far as by 2662 // context we can get all the associated files. See MDL-22092 2663 backup_structure_dbops::annotate_files($this->get_backupid(), $userctx->id, 'user', $filearea, null); 2664 $progress->progress(); 2665 } 2666 } 2667 $progress->end_progress(); 2668 $rs->close(); 2669 } 2670 } 2671 2672 2673 /** 2674 * Defines the backup step for advanced grading methods attached to the activity module 2675 */ 2676 class backup_activity_grading_structure_step extends backup_structure_step { 2677 2678 /** 2679 * Include the grading.xml only if the module supports advanced grading 2680 */ 2681 protected function execute_condition() { 2682 2683 // No grades on the front page. 2684 if ($this->get_courseid() == SITEID) { 2685 return false; 2686 } 2687 2688 return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false); 2689 } 2690 2691 /** 2692 * Declares the gradable areas structures and data sources 2693 */ 2694 protected function define_structure() { 2695 2696 // To know if we are including userinfo 2697 $userinfo = $this->get_setting_value('userinfo'); 2698 2699 // Define the elements 2700 2701 $areas = new backup_nested_element('areas'); 2702 2703 $area = new backup_nested_element('area', array('id'), array( 2704 'areaname', 'activemethod')); 2705 2706 $definitions = new backup_nested_element('definitions'); 2707 2708 $definition = new backup_nested_element('definition', array('id'), array( 2709 'method', 'name', 'description', 'descriptionformat', 'status', 2710 'timecreated', 'timemodified', 'options')); 2711 2712 $instances = new backup_nested_element('instances'); 2713 2714 $instance = new backup_nested_element('instance', array('id'), array( 2715 'raterid', 'itemid', 'rawgrade', 'status', 'feedback', 2716 'feedbackformat', 'timemodified')); 2717 2718 // Build the tree including the method specific structures 2719 // (beware - the order of how gradingform plugins structures are attached is important) 2720 $areas->add_child($area); 2721 // attach local plugin stucture to $area element, multiple allowed 2722 $this->add_plugin_structure('local', $area, true); 2723 $area->add_child($definitions); 2724 $definitions->add_child($definition); 2725 $this->add_plugin_structure('gradingform', $definition, true); 2726 // attach local plugin stucture to $definition element, multiple allowed 2727 $this->add_plugin_structure('local', $definition, true); 2728 $definition->add_child($instances); 2729 $instances->add_child($instance); 2730 $this->add_plugin_structure('gradingform', $instance, false); 2731 // attach local plugin stucture to $instance element, multiple allowed 2732 $this->add_plugin_structure('local', $instance, true); 2733 2734 // Define data sources 2735 2736 $area->set_source_table('grading_areas', array('contextid' => backup::VAR_CONTEXTID, 2737 'component' => array('sqlparam' => 'mod_'.$this->get_task()->get_modulename()))); 2738 2739 $definition->set_source_table('grading_definitions', array('areaid' => backup::VAR_PARENTID)); 2740 2741 if ($userinfo) { 2742 $instance->set_source_table('grading_instances', array('definitionid' => backup::VAR_PARENTID)); 2743 } 2744 2745 // Annotate references 2746 $definition->annotate_files('grading', 'description', 'id'); 2747 $instance->annotate_ids('user', 'raterid'); 2748 2749 // Return the root element 2750 return $areas; 2751 } 2752 } 2753 2754 2755 /** 2756 * structure step in charge of constructing the grades.xml file for all the grade items 2757 * and letters related to one activity 2758 */ 2759 class backup_activity_grades_structure_step extends backup_structure_step { 2760 2761 /** 2762 * No grades on the front page. 2763 * @return bool 2764 */ 2765 protected function execute_condition() { 2766 return ($this->get_courseid() != SITEID); 2767 } 2768 2769 protected function define_structure() { 2770 global $CFG; 2771 2772 require_once($CFG->libdir . '/grade/constants.php'); 2773 2774 // To know if we are including userinfo 2775 $userinfo = $this->get_setting_value('userinfo'); 2776 2777 // Define each element separated 2778 2779 $book = new backup_nested_element('activity_gradebook'); 2780 2781 $items = new backup_nested_element('grade_items'); 2782 2783 $item = new backup_nested_element('grade_item', array('id'), array( 2784 'categoryid', 'itemname', 'itemtype', 'itemmodule', 2785 'iteminstance', 'itemnumber', 'iteminfo', 'idnumber', 2786 'calculation', 'gradetype', 'grademax', 'grademin', 2787 'scaleid', 'outcomeid', 'gradepass', 'multfactor', 2788 'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride', 2789 'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime', 2790 'needsupdate', 'timecreated', 'timemodified')); 2791 2792 $grades = new backup_nested_element('grade_grades'); 2793 2794 $grade = new backup_nested_element('grade_grade', array('id'), array( 2795 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin', 2796 'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 2797 'locked', 'locktime', 'exported', 'overridden', 2798 'excluded', 'feedback', 'feedbackformat', 'information', 2799 'informationformat', 'timecreated', 'timemodified', 2800 'aggregationstatus', 'aggregationweight')); 2801 2802 $letters = new backup_nested_element('grade_letters'); 2803 2804 $letter = new backup_nested_element('grade_letter', 'id', array( 2805 'lowerboundary', 'letter')); 2806 2807 // Build the tree 2808 2809 $book->add_child($items); 2810 $items->add_child($item); 2811 2812 $item->add_child($grades); 2813 $grades->add_child($grade); 2814 2815 $book->add_child($letters); 2816 $letters->add_child($letter); 2817 2818 // Define sources 2819 2820 $item->set_source_sql("SELECT gi.* 2821 FROM {grade_items} gi 2822 JOIN {backup_ids_temp} bi ON gi.id = bi.itemid 2823 WHERE bi.backupid = ? 2824 AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID)); 2825 2826 // This only happens if we are including user info 2827 if ($userinfo) { 2828 $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID)); 2829 $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA, 'id'); 2830 } 2831 2832 $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID)); 2833 2834 // Annotations 2835 2836 $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0 2837 $item->annotate_ids('outcome', 'outcomeid'); 2838 2839 $grade->annotate_ids('user', 'userid'); 2840 $grade->annotate_ids('user', 'usermodified'); 2841 2842 // Return the root element (book) 2843 2844 return $book; 2845 } 2846 } 2847 2848 /** 2849 * Structure step in charge of constructing the grade history of an activity. 2850 * 2851 * This step is added to the task regardless of the setting 'grade_histories'. 2852 * The reason is to allow for a more flexible step in case the logic needs to be 2853 * split accross different settings to control the history of items and/or grades. 2854 */ 2855 class backup_activity_grade_history_structure_step extends backup_structure_step { 2856 2857 /** 2858 * No grades on the front page. 2859 * @return bool 2860 */ 2861 protected function execute_condition() { 2862 return ($this->get_courseid() != SITEID); 2863 } 2864 2865 protected function define_structure() { 2866 global $CFG; 2867 2868 require_once($CFG->libdir . '/grade/constants.php'); 2869 2870 // Settings to use. 2871 $userinfo = $this->get_setting_value('userinfo'); 2872 $history = $this->get_setting_value('grade_histories'); 2873 2874 // Create the nested elements. 2875 $bookhistory = new backup_nested_element('grade_history'); 2876 $grades = new backup_nested_element('grade_grades'); 2877 $grade = new backup_nested_element('grade_grade', array('id'), array( 2878 'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid', 2879 'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid', 2880 'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden', 2881 'excluded', 'feedback', 'feedbackformat', 'information', 2882 'informationformat', 'timemodified')); 2883 2884 // Build the tree. 2885 $bookhistory->add_child($grades); 2886 $grades->add_child($grade); 2887 2888 // This only happens if we are including user info and history. 2889 if ($userinfo && $history) { 2890 // Define sources. Only select the history related to existing activity items. 2891 $grade->set_source_sql("SELECT ggh.* 2892 FROM {grade_grades_history} ggh 2893 JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid 2894 WHERE bi.backupid = ? 2895 AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID)); 2896 $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA, 'id'); 2897 } 2898 2899 // Annotations. 2900 $grade->annotate_ids('scalefinal', 'rawscaleid'); // Straight as scalefinal because it's > 0. 2901 $grade->annotate_ids('user', 'loggeduser'); 2902 $grade->annotate_ids('user', 'userid'); 2903 $grade->annotate_ids('user', 'usermodified'); 2904 2905 // Return the root element. 2906 return $bookhistory; 2907 } 2908 } 2909 2910 /** 2911 * Backups up the course completion information for the course. 2912 */ 2913 class backup_course_completion_structure_step extends backup_structure_step { 2914 2915 protected function execute_condition() { 2916 2917 // No completion on front page. 2918 if ($this->get_courseid() == SITEID) { 2919 return false; 2920 } 2921 2922 // Check that all activities have been included 2923 if ($this->task->is_excluding_activities()) { 2924 return false; 2925 } 2926 return true; 2927 } 2928 2929 /** 2930 * The structure of the course completion backup 2931 * 2932 * @return backup_nested_element 2933 */ 2934 protected function define_structure() { 2935 2936 // To know if we are including user completion info 2937 $userinfo = $this->get_setting_value('userscompletion'); 2938 2939 $cc = new backup_nested_element('course_completion'); 2940 2941 $criteria = new backup_nested_element('course_completion_criteria', array('id'), array( 2942 'course', 'criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod', 2943 'timeend', 'gradepass', 'role', 'roleshortname' 2944 )); 2945 2946 $criteriacompletions = new backup_nested_element('course_completion_crit_completions'); 2947 2948 $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array( 2949 'criteriaid', 'userid', 'gradefinal', 'unenrolled', 'timecompleted' 2950 )); 2951 2952 $coursecompletions = new backup_nested_element('course_completions', array('id'), array( 2953 'userid', 'course', 'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate' 2954 )); 2955 2956 $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array( 2957 'course','criteriatype','method','value' 2958 )); 2959 2960 $cc->add_child($criteria); 2961 $criteria->add_child($criteriacompletions); 2962 $criteriacompletions->add_child($criteriacomplete); 2963 $cc->add_child($coursecompletions); 2964 $cc->add_child($aggregatemethod); 2965 2966 // We need some extra data for the restore. 2967 // - courseinstances shortname rather than an ID. 2968 // - roleshortname in case restoring on a different site. 2969 $sourcesql = "SELECT ccc.*, c.shortname AS courseinstanceshortname, r.shortname AS roleshortname 2970 FROM {course_completion_criteria} ccc 2971 LEFT JOIN {course} c ON c.id = ccc.courseinstance 2972 LEFT JOIN {role} r ON r.id = ccc.role 2973 WHERE ccc.course = ?"; 2974 $criteria->set_source_sql($sourcesql, array(backup::VAR_COURSEID)); 2975 2976 $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID)); 2977 2978 if ($userinfo) { 2979 $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID)); 2980 $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID)); 2981 } 2982 2983 $criteria->annotate_ids('role', 'role'); 2984 $criteriacomplete->annotate_ids('user', 'userid'); 2985 $coursecompletions->annotate_ids('user', 'userid'); 2986 2987 return $cc; 2988 2989 } 2990 } 2991 2992 /** 2993 * Backup completion defaults for each module type. 2994 * 2995 * @package core_backup 2996 * @copyright 2017 Marina Glancy 2997 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2998 */ 2999 class backup_completion_defaults_structure_step extends backup_structure_step { 3000 3001 /** 3002 * To conditionally decide if one step will be executed or no 3003 */ 3004 protected function execute_condition() { 3005 // No completion on front page. 3006 if ($this->get_courseid() == SITEID) { 3007 return false; 3008 } 3009 return true; 3010 } 3011 3012 /** 3013 * The structure of the course completion backup 3014 * 3015 * @return backup_nested_element 3016 */ 3017 protected function define_structure() { 3018 3019 $cc = new backup_nested_element('course_completion_defaults'); 3020 3021 $defaults = new backup_nested_element('course_completion_default', array('id'), array( 3022 'modulename', 'completion', 'completionview', 'completionusegrade', 'completionpassgrade', 3023 'completionexpected', 'customrules' 3024 )); 3025 3026 // Use module name instead of module id so we can insert into another site later. 3027 $sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade, 3028 d.completionpassgrade, d.completionexpected, d.customrules 3029 FROM {course_completion_defaults} d join {modules} m on d.module = m.id 3030 WHERE d.course = ?"; 3031 $defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID)); 3032 3033 $cc->add_child($defaults); 3034 return $cc; 3035 3036 } 3037 } 3038 3039 /** 3040 * Structure step in charge of constructing the contentbank.xml file for all the contents found in a given context 3041 */ 3042 class backup_contentbankcontent_structure_step extends backup_structure_step { 3043 3044 /** 3045 * Define structure for content bank step 3046 */ 3047 protected function define_structure() { 3048 3049 // Define each element separated. 3050 $contents = new backup_nested_element('contents'); 3051 $content = new backup_nested_element('content', ['id'], [ 3052 'name', 'contenttype', 'instanceid', 'configdata', 'usercreated', 'usermodified', 'timecreated', 'timemodified']); 3053 3054 // Build the tree. 3055 $contents->add_child($content); 3056 3057 // Define sources. 3058 $content->set_source_table('contentbank_content', ['contextid' => backup::VAR_CONTEXTID]); 3059 3060 // Define annotations. 3061 $content->annotate_ids('user', 'usercreated'); 3062 $content->annotate_ids('user', 'usermodified'); 3063 $content->annotate_files('contentbank', 'public', 'id'); 3064 3065 // Return the root element (contents). 3066 return $contents; 3067 } 3068 } 3069 3070 /** 3071 * Structure step in charge of constructing the xapistate.xml file for all the xAPI states found in a given context. 3072 */ 3073 class backup_xapistate_structure_step extends backup_structure_step { 3074 3075 /** 3076 * Define structure for content bank step 3077 */ 3078 protected function define_structure() { 3079 3080 // Define each element separated. 3081 $states = new backup_nested_element('states'); 3082 $state = new backup_nested_element( 3083 'state', 3084 ['id'], 3085 ['component', 'userid', 'itemid', 'stateid', 'statedata', 'registration', 'timecreated', 'timemodified'] 3086 ); 3087 3088 // Build the tree. 3089 $states->add_child($state); 3090 3091 // Define sources. 3092 $state->set_source_table('xapi_states', ['itemid' => backup::VAR_CONTEXTID]); 3093 3094 // Define annotations. 3095 $state->annotate_ids('user', 'userid'); 3096 3097 // Return the root element (contents). 3098 return $states; 3099 } 3100 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body