Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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')); 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', 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 // Build the tree. 943 944 $badges->add_child($badge); 945 $badge->add_child($criteria); 946 $criteria->add_child($criterion); 947 $criterion->add_child($parameters); 948 $parameters->add_child($parameter); 949 $badge->add_child($endorsement); 950 $badge->add_child($alignments); 951 $alignments->add_child($alignment); 952 $badge->add_child($relatedbadges); 953 $relatedbadges->add_child($relatedbadge); 954 $badge->add_child($manual_awards); 955 $manual_awards->add_child($manual_award); 956 957 // Define sources. 958 959 $parametersql = ' 960 SELECT * 961 FROM {badge} 962 WHERE courseid = :courseid 963 AND status != ' . BADGE_STATUS_ARCHIVED; 964 $parameterparams = [ 965 'courseid' => backup::VAR_COURSEID 966 ]; 967 $badge->set_source_sql($parametersql, $parameterparams); 968 $criterion->set_source_table('badge_criteria', array('badgeid' => backup::VAR_PARENTID)); 969 $endorsement->set_source_table('badge_endorsement', array('badgeid' => backup::VAR_PARENTID)); 970 971 $alignment->set_source_table('badge_alignment', array('badgeid' => backup::VAR_PARENTID)); 972 $relatedbadge->set_source_table('badge_related', array('badgeid' => backup::VAR_PARENTID)); 973 974 $parametersql = 'SELECT cp.*, c.criteriatype 975 FROM {badge_criteria_param} cp JOIN {badge_criteria} c 976 ON cp.critid = c.id 977 WHERE critid = :critid'; 978 $parameterparams = array('critid' => backup::VAR_PARENTID); 979 $parameter->set_source_sql($parametersql, $parameterparams); 980 981 $manual_award->set_source_table('badge_manual_award', array('badgeid' => backup::VAR_PARENTID)); 982 983 // Define id annotations. 984 985 $badge->annotate_ids('user', 'usercreated'); 986 $badge->annotate_ids('user', 'usermodified'); 987 $criterion->annotate_ids('badge', 'badgeid'); 988 $parameter->annotate_ids('criterion', 'critid'); 989 $endorsement->annotate_ids('badge', 'badgeid'); 990 $alignment->annotate_ids('badge', 'badgeid'); 991 $relatedbadge->annotate_ids('badge', 'badgeid'); 992 $relatedbadge->annotate_ids('badge', 'relatedbadgeid'); 993 $badge->annotate_files('badges', 'badgeimage', 'id'); 994 $manual_award->annotate_ids('badge', 'badgeid'); 995 $manual_award->annotate_ids('user', 'recipientid'); 996 $manual_award->annotate_ids('user', 'issuerid'); 997 $manual_award->annotate_ids('role', 'issuerrole'); 998 999 // Return the root element ($badges). 1000 return $badges; 1001 } 1002 } 1003 1004 /** 1005 * structure step in charge of constructing the calender.xml file for all the events found 1006 * in a given context 1007 */ 1008 class backup_calendarevents_structure_step extends backup_structure_step { 1009 1010 protected function define_structure() { 1011 1012 // Define each element separated 1013 1014 $events = new backup_nested_element('events'); 1015 1016 $event = new backup_nested_element('event', array('id'), array( 1017 'name', 'description', 'format', 'courseid', 'groupid', 'userid', 1018 'repeatid', 'modulename', 'instance', 'type', 'eventtype', 'timestart', 1019 'timeduration', 'timesort', 'visible', 'uuid', 'sequence', 'timemodified', 1020 'priority', 'location')); 1021 1022 // Build the tree 1023 $events->add_child($event); 1024 1025 // Define sources 1026 if ($this->name == 'course_calendar') { 1027 $calendar_items_sql ="SELECT * FROM {event} 1028 WHERE courseid = :courseid 1029 AND (eventtype = 'course' OR eventtype = 'group')"; 1030 $calendar_items_params = array('courseid'=>backup::VAR_COURSEID); 1031 $event->set_source_sql($calendar_items_sql, $calendar_items_params); 1032 } else if ($this->name == 'activity_calendar') { 1033 // We don't backup action events. 1034 $params = array('instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME, 1035 'type' => array('sqlparam' => CALENDAR_EVENT_TYPE_ACTION)); 1036 // If we don't want to include the userinfo in the backup then setting the courseid 1037 // will filter out all of the user override events (which have a course id of zero). 1038 $coursewhere = ""; 1039 if (!$this->get_setting_value('userinfo')) { 1040 $params['courseid'] = backup::VAR_COURSEID; 1041 $coursewhere = " AND courseid = :courseid"; 1042 } 1043 $calendarsql = "SELECT * FROM {event} 1044 WHERE instance = :instance 1045 AND type <> :type 1046 AND modulename = :modulename"; 1047 $calendarsql = $calendarsql . $coursewhere; 1048 $event->set_source_sql($calendarsql, $params); 1049 } else { 1050 $event->set_source_table('event', array('courseid' => backup::VAR_COURSEID, 'instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME)); 1051 } 1052 1053 // Define id annotations 1054 1055 $event->annotate_ids('user', 'userid'); 1056 $event->annotate_ids('group', 'groupid'); 1057 $event->annotate_files('calendar', 'event_description', 'id'); 1058 1059 // Return the root element (events) 1060 return $events; 1061 } 1062 } 1063 1064 /** 1065 * structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course 1066 * NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step 1067 */ 1068 class backup_gradebook_structure_step extends backup_structure_step { 1069 1070 /** 1071 * We need to decide conditionally, based on dynamic information 1072 * about the execution of this step. Only will be executed if all 1073 * the module gradeitems have been already included in backup 1074 */ 1075 protected function execute_condition() { 1076 $courseid = $this->get_courseid(); 1077 if ($courseid == SITEID) { 1078 return false; 1079 } 1080 1081 return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid()); 1082 } 1083 1084 protected function define_structure() { 1085 global $CFG, $DB; 1086 1087 // are we including user info? 1088 $userinfo = $this->get_setting_value('users'); 1089 1090 $gradebook = new backup_nested_element('gradebook'); 1091 1092 //grade_letters are done in backup_activity_grades_structure_step() 1093 1094 //calculated grade items 1095 $grade_items = new backup_nested_element('grade_items'); 1096 $grade_item = new backup_nested_element('grade_item', array('id'), array( 1097 'categoryid', 'itemname', 'itemtype', 'itemmodule', 1098 'iteminstance', 'itemnumber', 'iteminfo', 'idnumber', 1099 'calculation', 'gradetype', 'grademax', 'grademin', 1100 'scaleid', 'outcomeid', 'gradepass', 'multfactor', 1101 'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride', 1102 'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime', 1103 'needsupdate', 'timecreated', 'timemodified')); 1104 1105 $this->add_plugin_structure('local', $grade_item, true); 1106 1107 $grade_grades = new backup_nested_element('grade_grades'); 1108 $grade_grade = new backup_nested_element('grade_grade', array('id'), array( 1109 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin', 1110 'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 1111 'locked', 'locktime', 'exported', 'overridden', 1112 'excluded', 'feedback', 'feedbackformat', 'information', 1113 'informationformat', 'timecreated', 'timemodified', 1114 'aggregationstatus', 'aggregationweight')); 1115 1116 //grade_categories 1117 $grade_categories = new backup_nested_element('grade_categories'); 1118 $grade_category = new backup_nested_element('grade_category', array('id'), array( 1119 //'courseid', 1120 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh', 1121 'droplow', 'aggregateonlygraded', 'aggregateoutcomes', 1122 'timecreated', 'timemodified', 'hidden')); 1123 1124 $letters = new backup_nested_element('grade_letters'); 1125 $letter = new backup_nested_element('grade_letter', 'id', array( 1126 'lowerboundary', 'letter')); 1127 1128 $grade_settings = new backup_nested_element('grade_settings'); 1129 $grade_setting = new backup_nested_element('grade_setting', 'id', array( 1130 'name', 'value')); 1131 1132 $gradebook_attributes = new backup_nested_element('attributes', null, array('calculations_freeze')); 1133 1134 // Build the tree 1135 $gradebook->add_child($gradebook_attributes); 1136 1137 $gradebook->add_child($grade_categories); 1138 $grade_categories->add_child($grade_category); 1139 1140 $gradebook->add_child($grade_items); 1141 $grade_items->add_child($grade_item); 1142 $grade_item->add_child($grade_grades); 1143 $grade_grades->add_child($grade_grade); 1144 1145 $gradebook->add_child($letters); 1146 $letters->add_child($letter); 1147 1148 $gradebook->add_child($grade_settings); 1149 $grade_settings->add_child($grade_setting); 1150 1151 // Define sources 1152 1153 // Add attribute with gradebook calculation freeze date if needed. 1154 $attributes = new stdClass(); 1155 $gradebookcalculationfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid()); 1156 if ($gradebookcalculationfreeze) { 1157 $attributes->calculations_freeze = $gradebookcalculationfreeze; 1158 } 1159 $gradebook_attributes->set_source_array([$attributes]); 1160 1161 //Include manual, category and the course grade item 1162 $grade_items_sql ="SELECT * FROM {grade_items} 1163 WHERE courseid = :courseid 1164 AND (itemtype='manual' OR itemtype='course' OR itemtype='category')"; 1165 $grade_items_params = array('courseid'=>backup::VAR_COURSEID); 1166 $grade_item->set_source_sql($grade_items_sql, $grade_items_params); 1167 1168 if ($userinfo) { 1169 $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID)); 1170 } 1171 1172 $grade_category_sql = "SELECT gc.*, gi.sortorder 1173 FROM {grade_categories} gc 1174 JOIN {grade_items} gi ON (gi.iteminstance = gc.id) 1175 WHERE gc.courseid = :courseid 1176 AND (gi.itemtype='course' OR gi.itemtype='category') 1177 ORDER BY gc.parent ASC";//need parent categories before their children 1178 $grade_category_params = array('courseid'=>backup::VAR_COURSEID); 1179 $grade_category->set_source_sql($grade_category_sql, $grade_category_params); 1180 1181 $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID)); 1182 1183 // Set the grade settings source, forcing the inclusion of minmaxtouse if not present. 1184 $settings = array(); 1185 $rs = $DB->get_recordset('grade_settings', array('courseid' => $this->get_courseid())); 1186 foreach ($rs as $record) { 1187 $settings[$record->name] = $record; 1188 } 1189 $rs->close(); 1190 if (!isset($settings['minmaxtouse'])) { 1191 $settings['minmaxtouse'] = (object) array('name' => 'minmaxtouse', 'value' => $CFG->grade_minmaxtouse); 1192 } 1193 $grade_setting->set_source_array($settings); 1194 1195 1196 // Annotations (both as final as far as they are going to be exported in next steps) 1197 $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0 1198 $grade_item->annotate_ids('outcomefinal', 'outcomeid'); 1199 1200 //just in case there are any users not already annotated by the activities 1201 $grade_grade->annotate_ids('userfinal', 'userid'); 1202 1203 // Return the root element 1204 return $gradebook; 1205 } 1206 } 1207 1208 /** 1209 * Step in charge of constructing the grade_history.xml file containing the grade histories. 1210 */ 1211 class backup_grade_history_structure_step extends backup_structure_step { 1212 1213 /** 1214 * Limit the execution. 1215 * 1216 * This applies the same logic than the one applied to {@link backup_gradebook_structure_step}, 1217 * because we do not want to save the history of items which are not backed up. At least for now. 1218 */ 1219 protected function execute_condition() { 1220 $courseid = $this->get_courseid(); 1221 if ($courseid == SITEID) { 1222 return false; 1223 } 1224 1225 return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid()); 1226 } 1227 1228 protected function define_structure() { 1229 1230 // Settings to use. 1231 $userinfo = $this->get_setting_value('users'); 1232 $history = $this->get_setting_value('grade_histories'); 1233 1234 // Create the nested elements. 1235 $bookhistory = new backup_nested_element('grade_history'); 1236 $grades = new backup_nested_element('grade_grades'); 1237 $grade = new backup_nested_element('grade_grade', array('id'), array( 1238 'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid', 1239 'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid', 1240 'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden', 1241 'excluded', 'feedback', 'feedbackformat', 'information', 1242 'informationformat', 'timemodified')); 1243 1244 // Build the tree. 1245 $bookhistory->add_child($grades); 1246 $grades->add_child($grade); 1247 1248 // This only happens if we are including user info and history. 1249 if ($userinfo && $history) { 1250 // Only keep the history of grades related to items which have been backed up, The query is 1251 // similar (but not identical) to the one used in backup_gradebook_structure_step::define_structure(). 1252 $gradesql = "SELECT ggh.* 1253 FROM {grade_grades_history} ggh 1254 JOIN {grade_items} gi ON ggh.itemid = gi.id 1255 WHERE gi.courseid = :courseid 1256 AND (gi.itemtype = 'manual' OR gi.itemtype = 'course' OR gi.itemtype = 'category')"; 1257 $grade->set_source_sql($gradesql, array('courseid' => backup::VAR_COURSEID)); 1258 } 1259 1260 // Annotations. (Final annotations as this step is part of the final task). 1261 $grade->annotate_ids('scalefinal', 'rawscaleid'); 1262 $grade->annotate_ids('userfinal', 'loggeduser'); 1263 $grade->annotate_ids('userfinal', 'userid'); 1264 $grade->annotate_ids('userfinal', 'usermodified'); 1265 1266 // Return the root element. 1267 return $bookhistory; 1268 } 1269 1270 } 1271 1272 /** 1273 * structure step in charge if constructing the completion.xml file for all the users completion 1274 * information in a given activity 1275 */ 1276 class backup_userscompletion_structure_step extends backup_structure_step { 1277 1278 /** 1279 * Skip completion on the front page. 1280 * @return bool 1281 */ 1282 protected function execute_condition() { 1283 return ($this->get_courseid() != SITEID); 1284 } 1285 1286 protected function define_structure() { 1287 1288 // Define each element separated 1289 1290 $completions = new backup_nested_element('completions'); 1291 1292 $completion = new backup_nested_element('completion', array('id'), array( 1293 'userid', 'completionstate', 'viewed', 'timemodified')); 1294 1295 // Build the tree 1296 1297 $completions->add_child($completion); 1298 1299 // Define sources 1300 1301 $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID)); 1302 1303 // Define id annotations 1304 1305 $completion->annotate_ids('user', 'userid'); 1306 1307 // Return the root element (completions) 1308 return $completions; 1309 } 1310 } 1311 1312 /** 1313 * structure step in charge of constructing the main groups.xml file for all the groups and 1314 * groupings information already annotated 1315 */ 1316 class backup_groups_structure_step extends backup_structure_step { 1317 1318 protected function define_structure() { 1319 1320 // To know if we are including users. 1321 $userinfo = $this->get_setting_value('users'); 1322 // To know if we are including groups and groupings. 1323 $groupinfo = $this->get_setting_value('groups'); 1324 1325 // Define each element separated 1326 1327 $groups = new backup_nested_element('groups'); 1328 1329 $group = new backup_nested_element('group', array('id'), array( 1330 'name', 'idnumber', 'description', 'descriptionformat', 'enrolmentkey', 1331 'picture', 'timecreated', 'timemodified')); 1332 1333 $members = new backup_nested_element('group_members'); 1334 1335 $member = new backup_nested_element('group_member', array('id'), array( 1336 'userid', 'timeadded', 'component', 'itemid')); 1337 1338 $groupings = new backup_nested_element('groupings'); 1339 1340 $grouping = new backup_nested_element('grouping', 'id', array( 1341 'name', 'idnumber', 'description', 'descriptionformat', 'configdata', 1342 'timecreated', 'timemodified')); 1343 1344 $groupinggroups = new backup_nested_element('grouping_groups'); 1345 1346 $groupinggroup = new backup_nested_element('grouping_group', array('id'), array( 1347 'groupid', 'timeadded')); 1348 1349 // Build the tree 1350 1351 $groups->add_child($group); 1352 $groups->add_child($groupings); 1353 1354 $group->add_child($members); 1355 $members->add_child($member); 1356 1357 $groupings->add_child($grouping); 1358 $grouping->add_child($groupinggroups); 1359 $groupinggroups->add_child($groupinggroup); 1360 1361 // Define sources 1362 1363 // This only happens if we are including groups/groupings. 1364 if ($groupinfo) { 1365 $group->set_source_sql(" 1366 SELECT g.* 1367 FROM {groups} g 1368 JOIN {backup_ids_temp} bi ON g.id = bi.itemid 1369 WHERE bi.backupid = ? 1370 AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID)); 1371 1372 $grouping->set_source_sql(" 1373 SELECT g.* 1374 FROM {groupings} g 1375 JOIN {backup_ids_temp} bi ON g.id = bi.itemid 1376 WHERE bi.backupid = ? 1377 AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID)); 1378 $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID)); 1379 1380 // This only happens if we are including users. 1381 if ($userinfo) { 1382 $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID)); 1383 } 1384 } 1385 1386 // Define id annotations (as final) 1387 1388 $member->annotate_ids('userfinal', 'userid'); 1389 1390 // Define file annotations 1391 1392 $group->annotate_files('group', 'description', 'id'); 1393 $group->annotate_files('group', 'icon', 'id'); 1394 $grouping->annotate_files('grouping', 'description', 'id'); 1395 1396 // Return the root element (groups) 1397 return $groups; 1398 } 1399 } 1400 1401 /** 1402 * structure step in charge of constructing the main users.xml file for all the users already 1403 * annotated (final). Includes custom profile fields, preferences, tags, role assignments and 1404 * overrides. 1405 */ 1406 class backup_users_structure_step extends backup_structure_step { 1407 1408 protected function define_structure() { 1409 global $CFG; 1410 1411 // To know if we are anonymizing users 1412 $anonymize = $this->get_setting_value('anonymize'); 1413 // To know if we are including role assignments 1414 $roleassignments = $this->get_setting_value('role_assignments'); 1415 1416 // Define each element separate. 1417 1418 $users = new backup_nested_element('users'); 1419 1420 // Create the array of user fields by hand, as far as we have various bits to control 1421 // anonymize option, password backup, mnethostid... 1422 1423 // First, the fields not needing anonymization nor special handling 1424 $normalfields = array( 1425 'confirmed', 'policyagreed', 'deleted', 1426 'lang', 'theme', 'timezone', 'firstaccess', 1427 'lastaccess', 'lastlogin', 'currentlogin', 1428 'mailformat', 'maildigest', 'maildisplay', 1429 'autosubscribe', 'trackforums', 'timecreated', 1430 'timemodified', 'trustbitmask'); 1431 1432 // Then, the fields potentially needing anonymization 1433 $anonfields = array( 1434 'username', 'idnumber', 'email', 'phone1', 1435 'phone2', 'institution', 'department', 'address', 1436 'city', 'country', 'lastip', 'picture', 1437 'description', 'descriptionformat', 'imagealt', 'auth'); 1438 $anonfields = array_merge($anonfields, \core_user\fields::get_name_fields()); 1439 1440 // Add anonymized fields to $userfields with custom final element 1441 foreach ($anonfields as $field) { 1442 if ($anonymize) { 1443 $userfields[] = new anonymizer_final_element($field); 1444 } else { 1445 $userfields[] = $field; // No anonymization, normally added 1446 } 1447 } 1448 1449 // mnethosturl requires special handling (custom final element) 1450 $userfields[] = new mnethosturl_final_element('mnethosturl'); 1451 1452 // password added conditionally 1453 if (!empty($CFG->includeuserpasswordsinbackup)) { 1454 $userfields[] = 'password'; 1455 } 1456 1457 // Merge all the fields 1458 $userfields = array_merge($userfields, $normalfields); 1459 1460 $user = new backup_nested_element('user', array('id', 'contextid'), $userfields); 1461 1462 $customfields = new backup_nested_element('custom_fields'); 1463 1464 $customfield = new backup_nested_element('custom_field', array('id'), array( 1465 'field_name', 'field_type', 'field_data')); 1466 1467 $tags = new backup_nested_element('tags'); 1468 1469 $tag = new backup_nested_element('tag', array('id'), array( 1470 'name', 'rawname')); 1471 1472 $preferences = new backup_nested_element('preferences'); 1473 1474 $preference = new backup_nested_element('preference', array('id'), array( 1475 'name', 'value')); 1476 1477 $roles = new backup_nested_element('roles'); 1478 1479 $overrides = new backup_nested_element('role_overrides'); 1480 1481 $override = new backup_nested_element('override', array('id'), array( 1482 'roleid', 'capability', 'permission', 'timemodified', 1483 'modifierid')); 1484 1485 $assignments = new backup_nested_element('role_assignments'); 1486 1487 $assignment = new backup_nested_element('assignment', array('id'), array( 1488 'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here 1489 'sortorder')); 1490 1491 // Build the tree 1492 1493 $users->add_child($user); 1494 1495 $user->add_child($customfields); 1496 $customfields->add_child($customfield); 1497 1498 $user->add_child($tags); 1499 $tags->add_child($tag); 1500 1501 $user->add_child($preferences); 1502 $preferences->add_child($preference); 1503 1504 $user->add_child($roles); 1505 1506 $roles->add_child($overrides); 1507 $roles->add_child($assignments); 1508 1509 $overrides->add_child($override); 1510 $assignments->add_child($assignment); 1511 1512 // Define sources 1513 1514 $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl 1515 FROM {user} u 1516 JOIN {backup_ids_temp} bi ON bi.itemid = u.id 1517 LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . ' 1518 LEFT JOIN {mnet_host} m ON m.id = u.mnethostid 1519 WHERE bi.backupid = ? 1520 AND bi.itemname = ?', array( 1521 backup_helper::is_sqlparam($this->get_backupid()), 1522 backup_helper::is_sqlparam('userfinal'))); 1523 1524 // All the rest on information is only added if we arent 1525 // in an anonymized backup 1526 if (!$anonymize) { 1527 $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data 1528 FROM {user_info_field} f 1529 JOIN {user_info_data} d ON d.fieldid = f.id 1530 WHERE d.userid = ?', array(backup::VAR_PARENTID)); 1531 1532 $customfield->set_source_alias('shortname', 'field_name'); 1533 $customfield->set_source_alias('datatype', 'field_type'); 1534 $customfield->set_source_alias('data', 'field_data'); 1535 1536 $tag->set_source_sql('SELECT t.id, t.name, t.rawname 1537 FROM {tag} t 1538 JOIN {tag_instance} ti ON ti.tagid = t.id 1539 WHERE ti.itemtype = ? 1540 AND ti.itemid = ?', array( 1541 backup_helper::is_sqlparam('user'), 1542 backup::VAR_PARENTID)); 1543 1544 $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID)); 1545 1546 $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid')); 1547 1548 // Assignments only added if specified 1549 if ($roleassignments) { 1550 $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid')); 1551 } 1552 1553 // Define id annotations (as final) 1554 $override->annotate_ids('rolefinal', 'roleid'); 1555 } 1556 // Return root element (users) 1557 return $users; 1558 } 1559 } 1560 1561 /** 1562 * structure step in charge of constructing the block.xml file for one 1563 * given block (instance and positions). If the block has custom DB structure 1564 * that will go to a separate file (different step defined in block class) 1565 */ 1566 class backup_block_instance_structure_step extends backup_structure_step { 1567 1568 protected function define_structure() { 1569 global $DB; 1570 1571 // Define each element separated 1572 1573 $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array( 1574 'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern', 1575 'subpagepattern', 'defaultregion', 'defaultweight', 'configdata', 1576 'timecreated', 'timemodified')); 1577 1578 $positions = new backup_nested_element('block_positions'); 1579 1580 $position = new backup_nested_element('block_position', array('id'), array( 1581 'contextid', 'pagetype', 'subpage', 'visible', 1582 'region', 'weight')); 1583 1584 // Build the tree 1585 1586 $block->add_child($positions); 1587 $positions->add_child($position); 1588 1589 // Transform configdata information if needed (process links and friends) 1590 $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid())); 1591 if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) { 1592 $configdata = array_filter( 1593 (array) unserialize_object(base64_decode($blockrec->configdata)), 1594 static function($value): bool { 1595 return !($value instanceof __PHP_Incomplete_Class); 1596 } 1597 ); 1598 1599 foreach ($configdata as $attribute => $value) { 1600 if (in_array($attribute, $attrstotransform)) { 1601 $configdata[$attribute] = $this->contenttransformer->process($value); 1602 } 1603 } 1604 $blockrec->configdata = base64_encode(serialize((object)$configdata)); 1605 } 1606 $blockrec->contextid = $this->task->get_contextid(); 1607 // Get the version of the block 1608 $blockrec->version = get_config('block_'.$this->task->get_blockname(), 'version'); 1609 1610 // Define sources 1611 1612 $block->set_source_array(array($blockrec)); 1613 1614 $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID)); 1615 1616 // File anotations (for fileareas specified on each block) 1617 foreach ($this->task->get_fileareas() as $filearea) { 1618 $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null); 1619 } 1620 1621 // Return the root element (block) 1622 return $block; 1623 } 1624 } 1625 1626 /** 1627 * structure step in charge of constructing the logs.xml file for all the log records found 1628 * in course. Note that we are sending to backup ALL the log records having cmid = 0. That 1629 * includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do 1630 * that just in case they become restored some day in the future 1631 */ 1632 class backup_course_logs_structure_step extends backup_structure_step { 1633 1634 protected function define_structure() { 1635 1636 // Define each element separated 1637 1638 $logs = new backup_nested_element('logs'); 1639 1640 $log = new backup_nested_element('log', array('id'), array( 1641 'time', 'userid', 'ip', 'module', 1642 'action', 'url', 'info')); 1643 1644 // Build the tree 1645 1646 $logs->add_child($log); 1647 1648 // Define sources (all the records belonging to the course, having cmid = 0) 1649 1650 $log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0))); 1651 1652 // Annotations 1653 // NOTE: We don't annotate users from logs as far as they MUST be 1654 // always annotated by the course (enrol, ras... whatever) 1655 1656 // Return the root element (logs) 1657 1658 return $logs; 1659 } 1660 } 1661 1662 /** 1663 * structure step in charge of constructing the logs.xml file for all the log records found 1664 * in activity 1665 */ 1666 class backup_activity_logs_structure_step extends backup_structure_step { 1667 1668 protected function define_structure() { 1669 1670 // Define each element separated 1671 1672 $logs = new backup_nested_element('logs'); 1673 1674 $log = new backup_nested_element('log', array('id'), array( 1675 'time', 'userid', 'ip', 'module', 1676 'action', 'url', 'info')); 1677 1678 // Build the tree 1679 1680 $logs->add_child($log); 1681 1682 // Define sources 1683 1684 $log->set_source_table('log', array('cmid' => backup::VAR_MODID)); 1685 1686 // Annotations 1687 // NOTE: We don't annotate users from logs as far as they MUST be 1688 // always annotated by the activity (true participants). 1689 1690 // Return the root element (logs) 1691 1692 return $logs; 1693 } 1694 } 1695 1696 /** 1697 * Structure step in charge of constructing the logstores.xml file for the course logs. 1698 * 1699 * This backup step will backup the logs for all the enabled logstore subplugins supporting 1700 * it, for logs belonging to the course level. 1701 */ 1702 class backup_course_logstores_structure_step extends backup_structure_step { 1703 1704 protected function define_structure() { 1705 1706 // Define the structure of logstores container. 1707 $logstores = new backup_nested_element('logstores'); 1708 $logstore = new backup_nested_element('logstore'); 1709 $logstores->add_child($logstore); 1710 1711 // Add the tool_log logstore subplugins information to the logstore element. 1712 $this->add_subplugin_structure('logstore', $logstore, true, 'tool', 'log'); 1713 1714 return $logstores; 1715 } 1716 } 1717 1718 /** 1719 * Structure step in charge of constructing the loglastaccess.xml file for the course logs. 1720 * 1721 * This backup step will backup the logs of the user_lastaccess table. 1722 */ 1723 class backup_course_loglastaccess_structure_step extends backup_structure_step { 1724 1725 /** 1726 * This function creates the structures for the loglastaccess.xml file. 1727 * Expected structure would look like this. 1728 * <loglastaccesses> 1729 * <loglastaccess id=2> 1730 * <userid>5</userid> 1731 * <timeaccess>1616887341</timeaccess> 1732 * </loglastaccess> 1733 * </loglastaccesses> 1734 * 1735 * @return backup_nested_element 1736 */ 1737 protected function define_structure() { 1738 1739 // To know if we are including userinfo. 1740 $userinfo = $this->get_setting_value('users'); 1741 1742 // Define the structure of logstores container. 1743 $lastaccesses = new backup_nested_element('lastaccesses'); 1744 $lastaccess = new backup_nested_element('lastaccess', array('id'), array('userid', 'timeaccess')); 1745 1746 // Define build tree. 1747 $lastaccesses->add_child($lastaccess); 1748 1749 // This element should only happen if we are including user info. 1750 if ($userinfo) { 1751 // Define sources. 1752 $lastaccess->set_source_sql(' 1753 SELECT id, userid, timeaccess 1754 FROM {user_lastaccess} 1755 WHERE courseid = ?', 1756 array(backup::VAR_COURSEID)); 1757 1758 // Define userid annotation to user. 1759 $lastaccess->annotate_ids('user', 'userid'); 1760 } 1761 1762 // Return the root element (lastaccessess). 1763 return $lastaccesses; 1764 } 1765 } 1766 1767 /** 1768 * Structure step in charge of constructing the logstores.xml file for the activity logs. 1769 * 1770 * Note: Activity structure is completely equivalent to the course one, so just extend it. 1771 */ 1772 class backup_activity_logstores_structure_step extends backup_course_logstores_structure_step { 1773 } 1774 1775 /** 1776 * Course competencies backup structure step. 1777 */ 1778 class backup_course_competencies_structure_step extends backup_structure_step { 1779 1780 protected function define_structure() { 1781 $userinfo = $this->get_setting_value('users'); 1782 1783 $wrapper = new backup_nested_element('course_competencies'); 1784 1785 $settings = new backup_nested_element('settings', array('id'), array('pushratingstouserplans')); 1786 $wrapper->add_child($settings); 1787 1788 $sql = 'SELECT s.pushratingstouserplans 1789 FROM {' . \core_competency\course_competency_settings::TABLE . '} s 1790 WHERE s.courseid = :courseid'; 1791 $settings->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID)); 1792 1793 $competencies = new backup_nested_element('competencies'); 1794 $wrapper->add_child($competencies); 1795 1796 $competency = new backup_nested_element('competency', null, array('id', 'idnumber', 'ruleoutcome', 1797 'sortorder', 'frameworkid', 'frameworkidnumber')); 1798 $competencies->add_child($competency); 1799 1800 $sql = 'SELECT c.id, c.idnumber, cc.ruleoutcome, cc.sortorder, f.id AS frameworkid, f.idnumber AS frameworkidnumber 1801 FROM {' . \core_competency\course_competency::TABLE . '} cc 1802 JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cc.competencyid 1803 JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid 1804 WHERE cc.courseid = :courseid 1805 ORDER BY cc.sortorder'; 1806 $competency->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID)); 1807 1808 $usercomps = new backup_nested_element('user_competencies'); 1809 $wrapper->add_child($usercomps); 1810 if ($userinfo) { 1811 $usercomp = new backup_nested_element('user_competency', null, array('userid', 'competencyid', 1812 'proficiency', 'grade')); 1813 $usercomps->add_child($usercomp); 1814 1815 $sql = 'SELECT ucc.userid, ucc.competencyid, ucc.proficiency, ucc.grade 1816 FROM {' . \core_competency\user_competency_course::TABLE . '} ucc 1817 WHERE ucc.courseid = :courseid 1818 AND ucc.grade IS NOT NULL'; 1819 $usercomp->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID)); 1820 $usercomp->annotate_ids('user', 'userid'); 1821 } 1822 1823 return $wrapper; 1824 } 1825 1826 /** 1827 * Execute conditions. 1828 * 1829 * @return bool 1830 */ 1831 protected function execute_condition() { 1832 1833 // Do not execute if competencies are not included. 1834 if (!$this->get_setting_value('competencies')) { 1835 return false; 1836 } 1837 1838 return true; 1839 } 1840 } 1841 1842 /** 1843 * Activity competencies backup structure step. 1844 */ 1845 class backup_activity_competencies_structure_step extends backup_structure_step { 1846 1847 protected function define_structure() { 1848 $wrapper = new backup_nested_element('course_module_competencies'); 1849 1850 $competencies = new backup_nested_element('competencies'); 1851 $wrapper->add_child($competencies); 1852 1853 $competency = new backup_nested_element('competency', null, array('idnumber', 'ruleoutcome', 1854 'sortorder', 'frameworkidnumber')); 1855 $competencies->add_child($competency); 1856 1857 $sql = 'SELECT c.idnumber, cmc.ruleoutcome, cmc.sortorder, f.idnumber AS frameworkidnumber 1858 FROM {' . \core_competency\course_module_competency::TABLE . '} cmc 1859 JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cmc.competencyid 1860 JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid 1861 WHERE cmc.cmid = :coursemoduleid 1862 ORDER BY cmc.sortorder'; 1863 $competency->set_source_sql($sql, array('coursemoduleid' => backup::VAR_MODID)); 1864 1865 return $wrapper; 1866 } 1867 1868 /** 1869 * Execute conditions. 1870 * 1871 * @return bool 1872 */ 1873 protected function execute_condition() { 1874 1875 // Do not execute if competencies are not included. 1876 if (!$this->get_setting_value('competencies')) { 1877 return false; 1878 } 1879 1880 return true; 1881 } 1882 } 1883 1884 /** 1885 * structure in charge of constructing the inforef.xml file for all the items we want 1886 * to have referenced there (users, roles, files...) 1887 */ 1888 class backup_inforef_structure_step extends backup_structure_step { 1889 1890 protected function define_structure() { 1891 1892 // Items we want to include in the inforef file. 1893 $items = backup_helper::get_inforef_itemnames(); 1894 1895 // Build the tree 1896 1897 $inforef = new backup_nested_element('inforef'); 1898 1899 // For each item, conditionally, if there are already records, build element 1900 foreach ($items as $itemname) { 1901 if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) { 1902 $elementroot = new backup_nested_element($itemname . 'ref'); 1903 $element = new backup_nested_element($itemname, array(), array('id')); 1904 $inforef->add_child($elementroot); 1905 $elementroot->add_child($element); 1906 $element->set_source_sql(" 1907 SELECT itemid AS id 1908 FROM {backup_ids_temp} 1909 WHERE backupid = ? 1910 AND itemname = ?", 1911 array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname))); 1912 } 1913 } 1914 1915 // We don't annotate anything there, but rely in the next step 1916 // (move_inforef_annotations_to_final) that will change all the 1917 // already saved 'inforref' entries to their 'final' annotations. 1918 return $inforef; 1919 } 1920 } 1921 1922 /** 1923 * This step will get all the annotations already processed to inforef.xml file and 1924 * transform them into 'final' annotations. 1925 */ 1926 class move_inforef_annotations_to_final extends backup_execution_step { 1927 1928 protected function define_execution() { 1929 1930 // Items we want to include in the inforef file 1931 $items = backup_helper::get_inforef_itemnames(); 1932 $progress = $this->task->get_progress(); 1933 $progress->start_progress($this->get_name(), count($items)); 1934 $done = 1; 1935 foreach ($items as $itemname) { 1936 // Delegate to dbops 1937 backup_structure_dbops::move_annotations_to_final($this->get_backupid(), 1938 $itemname, $progress); 1939 $progress->progress($done++); 1940 } 1941 $progress->end_progress(); 1942 } 1943 } 1944 1945 /** 1946 * structure in charge of constructing the files.xml file with all the 1947 * annotated (final) files along the process. At, the same time, and 1948 * using one specialised nested_element, will copy them form moodle storage 1949 * to backup storage 1950 */ 1951 class backup_final_files_structure_step extends backup_structure_step { 1952 1953 protected function define_structure() { 1954 1955 // Define elements 1956 1957 $files = new backup_nested_element('files'); 1958 1959 $file = new file_nested_element('file', array('id'), array( 1960 'contenthash', 'contextid', 'component', 'filearea', 'itemid', 1961 'filepath', 'filename', 'userid', 'filesize', 1962 'mimetype', 'status', 'timecreated', 'timemodified', 1963 'source', 'author', 'license', 'sortorder', 1964 'repositorytype', 'repositoryid', 'reference')); 1965 1966 // Build the tree 1967 1968 $files->add_child($file); 1969 1970 // Define sources 1971 1972 $file->set_source_sql("SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.reference 1973 FROM {files} f 1974 LEFT JOIN {files_reference} fr ON fr.id = f.referencefileid 1975 LEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryid 1976 LEFT JOIN {repository} r ON r.id = ri.typeid 1977 JOIN {backup_ids_temp} bi ON f.id = bi.itemid 1978 WHERE bi.backupid = ? 1979 AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID)); 1980 1981 return $files; 1982 } 1983 } 1984 1985 /** 1986 * Structure step in charge of creating the main moodle_backup.xml file 1987 * where all the information related to the backup, settings, license and 1988 * other information needed on restore is added*/ 1989 class backup_main_structure_step extends backup_structure_step { 1990 1991 protected function define_structure() { 1992 1993 global $CFG; 1994 1995 $info = array(); 1996 1997 $info['name'] = $this->get_setting_value('filename'); 1998 $info['moodle_version'] = $CFG->version; 1999 $info['moodle_release'] = $CFG->release; 2000 $info['backup_version'] = $CFG->backup_version; 2001 $info['backup_release'] = $CFG->backup_release; 2002 $info['backup_date'] = time(); 2003 $info['backup_uniqueid']= $this->get_backupid(); 2004 $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid()); 2005 $info['include_files'] = backup_controller_dbops::backup_includes_files($this->get_backupid()); 2006 $info['include_file_references_to_external_content'] = 2007 backup_controller_dbops::backup_includes_file_references($this->get_backupid()); 2008 $info['original_wwwroot']=$CFG->wwwroot; 2009 $info['original_site_identifier_hash'] = md5(get_site_identifier()); 2010 $info['original_course_id'] = $this->get_courseid(); 2011 $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid()); 2012 $info['original_course_format'] = $originalcourseinfo->format; 2013 $info['original_course_fullname'] = $originalcourseinfo->fullname; 2014 $info['original_course_shortname'] = $originalcourseinfo->shortname; 2015 $info['original_course_startdate'] = $originalcourseinfo->startdate; 2016 $info['original_course_enddate'] = $originalcourseinfo->enddate; 2017 $info['original_course_contextid'] = context_course::instance($this->get_courseid())->id; 2018 $info['original_system_contextid'] = context_system::instance()->id; 2019 2020 // Get more information from controller 2021 list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information( 2022 $this->get_backupid(), $this->get_task()->get_progress()); 2023 2024 // Define elements 2025 2026 $moodle_backup = new backup_nested_element('moodle_backup'); 2027 2028 $information = new backup_nested_element('information', null, array( 2029 'name', 'moodle_version', 'moodle_release', 'backup_version', 2030 'backup_release', 'backup_date', 'mnet_remoteusers', 'include_files', 'include_file_references_to_external_content', 'original_wwwroot', 2031 'original_site_identifier_hash', 'original_course_id', 'original_course_format', 2032 'original_course_fullname', 'original_course_shortname', 'original_course_startdate', 'original_course_enddate', 2033 'original_course_contextid', 'original_system_contextid')); 2034 2035 $details = new backup_nested_element('details'); 2036 2037 $detail = new backup_nested_element('detail', array('backup_id'), array( 2038 'type', 'format', 'interactive', 'mode', 2039 'execution', 'executiontime')); 2040 2041 $contents = new backup_nested_element('contents'); 2042 2043 $activities = new backup_nested_element('activities'); 2044 2045 $activity = new backup_nested_element('activity', null, array( 2046 'moduleid', 'sectionid', 'modulename', 'title', 2047 'directory')); 2048 2049 $sections = new backup_nested_element('sections'); 2050 2051 $section = new backup_nested_element('section', null, array( 2052 'sectionid', 'title', 'directory')); 2053 2054 $course = new backup_nested_element('course', null, array( 2055 'courseid', 'title', 'directory')); 2056 2057 $settings = new backup_nested_element('settings'); 2058 2059 $setting = new backup_nested_element('setting', null, array( 2060 'level', 'section', 'activity', 'name', 'value')); 2061 2062 // Build the tree 2063 2064 $moodle_backup->add_child($information); 2065 2066 $information->add_child($details); 2067 $details->add_child($detail); 2068 2069 $information->add_child($contents); 2070 if (!empty($cinfo['activities'])) { 2071 $contents->add_child($activities); 2072 $activities->add_child($activity); 2073 } 2074 if (!empty($cinfo['sections'])) { 2075 $contents->add_child($sections); 2076 $sections->add_child($section); 2077 } 2078 if (!empty($cinfo['course'])) { 2079 $contents->add_child($course); 2080 } 2081 2082 $information->add_child($settings); 2083 $settings->add_child($setting); 2084 2085 2086 // Set the sources 2087 2088 $information->set_source_array(array((object)$info)); 2089 2090 $detail->set_source_array($dinfo); 2091 2092 $activity->set_source_array($cinfo['activities']); 2093 2094 $section->set_source_array($cinfo['sections']); 2095 2096 $course->set_source_array($cinfo['course']); 2097 2098 $setting->set_source_array($sinfo); 2099 2100 // Prepare some information to be sent to main moodle_backup.xml file 2101 return $moodle_backup; 2102 } 2103 2104 } 2105 2106 /** 2107 * Execution step that will generate the final zip (.mbz) file with all the contents 2108 */ 2109 class backup_zip_contents extends backup_execution_step implements file_progress { 2110 /** 2111 * @var bool True if we have started tracking progress 2112 */ 2113 protected $startedprogress; 2114 2115 protected function define_execution() { 2116 2117 // Get basepath 2118 $basepath = $this->get_basepath(); 2119 2120 // Get the list of files in directory 2121 $filestemp = get_directory_list($basepath, '', false, true, true); 2122 $files = array(); 2123 foreach ($filestemp as $file) { // Add zip paths and fs paths to all them 2124 $files[$file] = $basepath . '/' . $file; 2125 } 2126 2127 // Add the log file if exists 2128 $logfilepath = $basepath . '.log'; 2129 if (file_exists($logfilepath)) { 2130 $files['moodle_backup.log'] = $logfilepath; 2131 } 2132 2133 // Calculate the zip fullpath (in OS temp area it's always backup.mbz) 2134 $zipfile = $basepath . '/backup.mbz'; 2135 2136 // Get the zip packer 2137 $zippacker = get_file_packer('application/vnd.moodle.backup'); 2138 2139 // Track overall progress for the 2 long-running steps (archive to 2140 // pathname, get backup information). 2141 $reporter = $this->task->get_progress(); 2142 $reporter->start_progress('backup_zip_contents', 2); 2143 2144 // Zip files 2145 $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this); 2146 2147 // If any sub-progress happened, end it. 2148 if ($this->startedprogress) { 2149 $this->task->get_progress()->end_progress(); 2150 $this->startedprogress = false; 2151 } else { 2152 // No progress was reported, manually move it on to the next overall task. 2153 $reporter->progress(1); 2154 } 2155 2156 // Something went wrong. 2157 if ($result === false) { 2158 @unlink($zipfile); 2159 throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip'); 2160 } 2161 // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid. 2162 try { 2163 backup_general_helper::get_backup_information_from_mbz($zipfile, $this); 2164 } catch (backup_helper_exception $e) { 2165 @unlink($zipfile); 2166 throw new backup_step_exception('error_zip_packing', '', $e->debuginfo); 2167 } 2168 2169 // If any sub-progress happened, end it. 2170 if ($this->startedprogress) { 2171 $this->task->get_progress()->end_progress(); 2172 $this->startedprogress = false; 2173 } else { 2174 $reporter->progress(2); 2175 } 2176 $reporter->end_progress(); 2177 } 2178 2179 /** 2180 * Implementation for file_progress interface to display unzip progress. 2181 * 2182 * @param int $progress Current progress 2183 * @param int $max Max value 2184 */ 2185 public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) { 2186 $reporter = $this->task->get_progress(); 2187 2188 // Start tracking progress if necessary. 2189 if (!$this->startedprogress) { 2190 $reporter->start_progress('extract_file_to_dir', ($max == file_progress::INDETERMINATE) 2191 ? \core\progress\base::INDETERMINATE : $max); 2192 $this->startedprogress = true; 2193 } 2194 2195 // Pass progress through to whatever handles it. 2196 $reporter->progress(($progress == file_progress::INDETERMINATE) 2197 ? \core\progress\base::INDETERMINATE : $progress); 2198 } 2199 } 2200 2201 /** 2202 * This step will send the generated backup file to its final destination 2203 */ 2204 class backup_store_backup_file extends backup_execution_step { 2205 2206 protected function define_execution() { 2207 2208 // Get basepath 2209 $basepath = $this->get_basepath(); 2210 2211 // Calculate the zip fullpath (in OS temp area it's always backup.mbz) 2212 $zipfile = $basepath . '/backup.mbz'; 2213 2214 $has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid()); 2215 // Perform storage and return it (TODO: shouldn't be array but proper result object) 2216 return array( 2217 'backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile, 2218 $this->task->get_progress()), 2219 'include_file_references_to_external_content' => $has_file_references 2220 ); 2221 } 2222 } 2223 2224 2225 /** 2226 * This step will search for all the activity (not calculations, categories nor aggregations) grade items 2227 * and put them to the backup_ids tables, to be used later as base to backup them 2228 */ 2229 class backup_activity_grade_items_to_ids extends backup_execution_step { 2230 2231 protected function define_execution() { 2232 2233 // Fetch all activity grade items 2234 if ($items = grade_item::fetch_all(array( 2235 'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(), 2236 'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) { 2237 // Annotate them in backup_ids 2238 foreach ($items as $item) { 2239 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id); 2240 } 2241 } 2242 } 2243 } 2244 2245 2246 /** 2247 * This step allows enrol plugins to annotate custom fields. 2248 * 2249 * @package core_backup 2250 * @copyright 2014 University of Wisconsin 2251 * @author Matt Petro 2252 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2253 */ 2254 class backup_enrolments_execution_step extends backup_execution_step { 2255 2256 /** 2257 * Function that will contain all the code to be executed. 2258 */ 2259 protected function define_execution() { 2260 global $DB; 2261 2262 $plugins = enrol_get_plugins(true); 2263 $enrols = $DB->get_records('enrol', array( 2264 'courseid' => $this->task->get_courseid())); 2265 2266 // Allow each enrol plugin to add annotations. 2267 foreach ($enrols as $enrol) { 2268 if (isset($plugins[$enrol->enrol])) { 2269 $plugins[$enrol->enrol]->backup_annotate_custom_fields($this, $enrol); 2270 } 2271 } 2272 } 2273 2274 /** 2275 * Annotate a single name/id pair. 2276 * This can be called from {@link enrol_plugin::backup_annotate_custom_fields()}. 2277 * 2278 * @param string $itemname 2279 * @param int $itemid 2280 */ 2281 public function annotate_id($itemname, $itemid) { 2282 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), $itemname, $itemid); 2283 } 2284 } 2285 2286 /** 2287 * This step will annotate all the groups and groupings belonging to the course 2288 */ 2289 class backup_annotate_course_groups_and_groupings extends backup_execution_step { 2290 2291 protected function define_execution() { 2292 global $DB; 2293 2294 // Get all the course groups 2295 if ($groups = $DB->get_records('groups', array( 2296 'courseid' => $this->task->get_courseid()))) { 2297 foreach ($groups as $group) { 2298 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id); 2299 } 2300 } 2301 2302 // Get all the course groupings 2303 if ($groupings = $DB->get_records('groupings', array( 2304 'courseid' => $this->task->get_courseid()))) { 2305 foreach ($groupings as $grouping) { 2306 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id); 2307 } 2308 } 2309 } 2310 } 2311 2312 /** 2313 * This step will annotate all the groups belonging to already annotated groupings 2314 */ 2315 class backup_annotate_groups_from_groupings extends backup_execution_step { 2316 2317 protected function define_execution() { 2318 global $DB; 2319 2320 // Fetch all the annotated groupings 2321 if ($groupings = $DB->get_records('backup_ids_temp', array( 2322 'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) { 2323 foreach ($groupings as $grouping) { 2324 if ($groups = $DB->get_records('groupings_groups', array( 2325 'groupingid' => $grouping->itemid))) { 2326 foreach ($groups as $group) { 2327 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid); 2328 } 2329 } 2330 } 2331 } 2332 } 2333 } 2334 2335 /** 2336 * This step will annotate all the scales belonging to already annotated outcomes 2337 */ 2338 class backup_annotate_scales_from_outcomes extends backup_execution_step { 2339 2340 protected function define_execution() { 2341 global $DB; 2342 2343 // Fetch all the annotated outcomes 2344 if ($outcomes = $DB->get_records('backup_ids_temp', array( 2345 'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) { 2346 foreach ($outcomes as $outcome) { 2347 if ($scale = $DB->get_record('grade_outcomes', array( 2348 'id' => $outcome->itemid))) { 2349 // Annotate as scalefinal because it's > 0 2350 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid); 2351 } 2352 } 2353 } 2354 } 2355 } 2356 2357 /** 2358 * This step will generate all the file annotations for the already 2359 * annotated (final) question_categories. It calculates the different 2360 * contexts that are being backup and, annotates all the files 2361 * on every context belonging to the "question" component. As far as 2362 * we are always including *complete* question banks it is safe and 2363 * optimal to do that in this (one pass) way 2364 */ 2365 class backup_annotate_all_question_files extends backup_execution_step { 2366 2367 protected function define_execution() { 2368 global $DB; 2369 2370 // Get all the different contexts for the final question_categories 2371 // annotated along the whole backup 2372 $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid 2373 FROM {question_categories} qc 2374 JOIN {backup_ids_temp} bi ON bi.itemid = qc.id 2375 WHERE bi.backupid = ? 2376 AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid())); 2377 // To know about qtype specific components/fileareas 2378 $components = backup_qtype_plugin::get_components_and_fileareas(); 2379 $progress = $this->task->get_progress(); 2380 $progress->start_progress($this->get_name()); 2381 // Let's loop 2382 foreach($rs as $record) { 2383 // Backup all the file areas the are managed by the core question component. 2384 // That is, by the question_type base class. In particular, we don't want 2385 // to include files belonging to responses here. 2386 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'questiontext', null, 2387 $progress); 2388 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'generalfeedback', null, 2389 $progress); 2390 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answer', null, 2391 $progress); 2392 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answerfeedback', null, 2393 $progress); 2394 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'hint', null, 2395 $progress); 2396 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'correctfeedback', null, 2397 $progress); 2398 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 2399 'partiallycorrectfeedback', null, $progress); 2400 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'incorrectfeedback', null, 2401 $progress); 2402 2403 // For files belonging to question types, we make the leap of faith that 2404 // all the files belonging to the question type are part of the question definition, 2405 // so we can just backup all the files in bulk, without specifying each 2406 // file area name separately. 2407 foreach ($components as $component => $fileareas) { 2408 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null, 2409 $progress); 2410 } 2411 } 2412 $progress->end_progress(); 2413 $rs->close(); 2414 } 2415 } 2416 2417 /** 2418 * structure step in charge of constructing the questions.xml file for all the 2419 * question categories and questions required by the backup 2420 * and letters related to one activity. 2421 */ 2422 class backup_questions_structure_step extends backup_structure_step { 2423 2424 protected function define_structure() { 2425 2426 // Define each element separately. 2427 $qcategories = new backup_nested_element('question_categories'); 2428 2429 $qcategory = new backup_nested_element('question_category', ['id'], 2430 [ 2431 'name', 2432 'contextid', 2433 'contextlevel', 2434 'contextinstanceid', 2435 'info', 2436 'infoformat', 2437 'stamp', 2438 'parent', 2439 'sortorder', 2440 'idnumber', 2441 ]); 2442 2443 $questionbankentries = new backup_nested_element('question_bank_entries'); 2444 2445 $questionbankentry = new backup_nested_element('question_bank_entry', ['id'], 2446 [ 2447 'questioncategoryid', 2448 'idnumber', 2449 'ownerid', 2450 ]); 2451 2452 $questionversions = new backup_nested_element('question_version'); 2453 2454 $questionverion = new backup_nested_element('question_versions', ['id'], ['version', 'status']); 2455 2456 $questions = new backup_nested_element('questions'); 2457 2458 $question = new backup_nested_element('question', ['id'], 2459 [ 2460 'parent', 2461 'name', 2462 'questiontext', 2463 'questiontextformat', 2464 'generalfeedback', 2465 'generalfeedbackformat', 2466 'defaultmark', 2467 'penalty', 2468 'qtype', 2469 'length', 2470 'stamp', 2471 'timecreated', 2472 'timemodified', 2473 'createdby', 2474 'modifiedby', 2475 ]); 2476 2477 // Attach qtype plugin structure to $question element, only one allowed. 2478 $this->add_plugin_structure('qtype', $question, false); 2479 2480 // Attach qbank plugin stucture to $question element, multiple allowed. 2481 $this->add_plugin_structure('qbank', $question, true); 2482 2483 // attach local plugin stucture to $question element, multiple allowed 2484 $this->add_plugin_structure('local', $question, true); 2485 2486 $qhints = new backup_nested_element('question_hints'); 2487 2488 $qhint = new backup_nested_element('question_hint', ['id'], 2489 [ 2490 'hint', 2491 'hintformat', 2492 'shownumcorrect', 2493 'clearwrong', 2494 'options', 2495 ]); 2496 2497 $tags = new backup_nested_element('tags'); 2498 2499 $tag = new backup_nested_element('tag', ['id', 'contextid'], ['name', 'rawname']); 2500 2501 // Build the initial tree. 2502 $qcategories->add_child($qcategory); 2503 $qcategory->add_child($questionbankentries); 2504 $questionbankentries->add_child($questionbankentry); 2505 $questionbankentry->add_child($questionversions); 2506 $questionversions->add_child($questionverion); 2507 $questionverion->add_child($questions); 2508 $questions->add_child($question); 2509 $question->add_child($qhints); 2510 $qhints->add_child($qhint); 2511 2512 // Add question tags. 2513 $question->add_child($tags); 2514 $tags->add_child($tag); 2515 2516 $qcategory->set_source_sql(" 2517 SELECT gc.*, 2518 contextlevel, 2519 instanceid AS contextinstanceid 2520 FROM {question_categories} gc 2521 JOIN {backup_ids_temp} bi ON bi.itemid = gc.id 2522 JOIN {context} co ON co.id = gc.contextid 2523 WHERE bi.backupid = ? 2524 AND bi.itemname = 'question_categoryfinal'", [backup::VAR_BACKUPID]); 2525 2526 $questionbankentry->set_source_table('question_bank_entries', ['questioncategoryid' => backup::VAR_PARENTID]); 2527 2528 $questionverion->set_source_table('question_versions', ['questionbankentryid' => backup::VAR_PARENTID]); 2529 2530 $question->set_source_sql(' 2531 SELECT q.* 2532 FROM {question} q 2533 JOIN {question_versions} qv ON qv.questionid = q.id 2534 JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid 2535 WHERE qv.id = ?', [backup::VAR_PARENTID]); 2536 2537 $qhint->set_source_sql(' 2538 SELECT * 2539 FROM {question_hints} 2540 WHERE questionid = :questionid 2541 ORDER BY id', ['questionid' => backup::VAR_PARENTID]); 2542 2543 $tag->set_source_sql("SELECT t.id, ti.contextid, t.name, t.rawname 2544 FROM {tag} t 2545 JOIN {tag_instance} ti ON ti.tagid = t.id 2546 WHERE ti.itemid = ? 2547 AND ti.itemtype = 'question' 2548 AND ti.component = 'core_question'", [backup::VAR_PARENTID]); 2549 2550 // Don't need to annotate ids nor files. 2551 // ...(already done by {@see backup_annotate_all_question_files()}. 2552 2553 return $qcategories; 2554 } 2555 } 2556 2557 2558 2559 /** 2560 * This step will generate all the file annotations for the already 2561 * annotated (final) users. Need to do this here because each user 2562 * has its own context and structure tasks only are able to handle 2563 * one context. Also, this step will guarantee that every user has 2564 * its context created (req for other steps) 2565 */ 2566 class backup_annotate_all_user_files extends backup_execution_step { 2567 2568 protected function define_execution() { 2569 global $DB; 2570 2571 // List of fileareas we are going to annotate 2572 $fileareas = array('profile', 'icon'); 2573 2574 // Fetch all annotated (final) users 2575 $rs = $DB->get_recordset('backup_ids_temp', array( 2576 'backupid' => $this->get_backupid(), 'itemname' => 'userfinal')); 2577 $progress = $this->task->get_progress(); 2578 $progress->start_progress($this->get_name()); 2579 foreach ($rs as $record) { 2580 $userid = $record->itemid; 2581 $userctx = context_user::instance($userid, IGNORE_MISSING); 2582 if (!$userctx) { 2583 continue; // User has not context, sure it's a deleted user, so cannot have files 2584 } 2585 // Proceed with every user filearea 2586 foreach ($fileareas as $filearea) { 2587 // We don't need to specify itemid ($userid - 5th param) as far as by 2588 // context we can get all the associated files. See MDL-22092 2589 backup_structure_dbops::annotate_files($this->get_backupid(), $userctx->id, 'user', $filearea, null); 2590 $progress->progress(); 2591 } 2592 } 2593 $progress->end_progress(); 2594 $rs->close(); 2595 } 2596 } 2597 2598 2599 /** 2600 * Defines the backup step for advanced grading methods attached to the activity module 2601 */ 2602 class backup_activity_grading_structure_step extends backup_structure_step { 2603 2604 /** 2605 * Include the grading.xml only if the module supports advanced grading 2606 */ 2607 protected function execute_condition() { 2608 2609 // No grades on the front page. 2610 if ($this->get_courseid() == SITEID) { 2611 return false; 2612 } 2613 2614 return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false); 2615 } 2616 2617 /** 2618 * Declares the gradable areas structures and data sources 2619 */ 2620 protected function define_structure() { 2621 2622 // To know if we are including userinfo 2623 $userinfo = $this->get_setting_value('userinfo'); 2624 2625 // Define the elements 2626 2627 $areas = new backup_nested_element('areas'); 2628 2629 $area = new backup_nested_element('area', array('id'), array( 2630 'areaname', 'activemethod')); 2631 2632 $definitions = new backup_nested_element('definitions'); 2633 2634 $definition = new backup_nested_element('definition', array('id'), array( 2635 'method', 'name', 'description', 'descriptionformat', 'status', 2636 'timecreated', 'timemodified', 'options')); 2637 2638 $instances = new backup_nested_element('instances'); 2639 2640 $instance = new backup_nested_element('instance', array('id'), array( 2641 'raterid', 'itemid', 'rawgrade', 'status', 'feedback', 2642 'feedbackformat', 'timemodified')); 2643 2644 // Build the tree including the method specific structures 2645 // (beware - the order of how gradingform plugins structures are attached is important) 2646 $areas->add_child($area); 2647 // attach local plugin stucture to $area element, multiple allowed 2648 $this->add_plugin_structure('local', $area, true); 2649 $area->add_child($definitions); 2650 $definitions->add_child($definition); 2651 $this->add_plugin_structure('gradingform', $definition, true); 2652 // attach local plugin stucture to $definition element, multiple allowed 2653 $this->add_plugin_structure('local', $definition, true); 2654 $definition->add_child($instances); 2655 $instances->add_child($instance); 2656 $this->add_plugin_structure('gradingform', $instance, false); 2657 // attach local plugin stucture to $instance element, multiple allowed 2658 $this->add_plugin_structure('local', $instance, true); 2659 2660 // Define data sources 2661 2662 $area->set_source_table('grading_areas', array('contextid' => backup::VAR_CONTEXTID, 2663 'component' => array('sqlparam' => 'mod_'.$this->get_task()->get_modulename()))); 2664 2665 $definition->set_source_table('grading_definitions', array('areaid' => backup::VAR_PARENTID)); 2666 2667 if ($userinfo) { 2668 $instance->set_source_table('grading_instances', array('definitionid' => backup::VAR_PARENTID)); 2669 } 2670 2671 // Annotate references 2672 $definition->annotate_files('grading', 'description', 'id'); 2673 $instance->annotate_ids('user', 'raterid'); 2674 2675 // Return the root element 2676 return $areas; 2677 } 2678 } 2679 2680 2681 /** 2682 * structure step in charge of constructing the grades.xml file for all the grade items 2683 * and letters related to one activity 2684 */ 2685 class backup_activity_grades_structure_step extends backup_structure_step { 2686 2687 /** 2688 * No grades on the front page. 2689 * @return bool 2690 */ 2691 protected function execute_condition() { 2692 return ($this->get_courseid() != SITEID); 2693 } 2694 2695 protected function define_structure() { 2696 global $CFG; 2697 2698 require_once($CFG->libdir . '/grade/constants.php'); 2699 2700 // To know if we are including userinfo 2701 $userinfo = $this->get_setting_value('userinfo'); 2702 2703 // Define each element separated 2704 2705 $book = new backup_nested_element('activity_gradebook'); 2706 2707 $items = new backup_nested_element('grade_items'); 2708 2709 $item = new backup_nested_element('grade_item', array('id'), array( 2710 'categoryid', 'itemname', 'itemtype', 'itemmodule', 2711 'iteminstance', 'itemnumber', 'iteminfo', 'idnumber', 2712 'calculation', 'gradetype', 'grademax', 'grademin', 2713 'scaleid', 'outcomeid', 'gradepass', 'multfactor', 2714 'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride', 2715 'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime', 2716 'needsupdate', 'timecreated', 'timemodified')); 2717 2718 $grades = new backup_nested_element('grade_grades'); 2719 2720 $grade = new backup_nested_element('grade_grade', array('id'), array( 2721 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin', 2722 'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 2723 'locked', 'locktime', 'exported', 'overridden', 2724 'excluded', 'feedback', 'feedbackformat', 'information', 2725 'informationformat', 'timecreated', 'timemodified', 2726 'aggregationstatus', 'aggregationweight')); 2727 2728 $letters = new backup_nested_element('grade_letters'); 2729 2730 $letter = new backup_nested_element('grade_letter', 'id', array( 2731 'lowerboundary', 'letter')); 2732 2733 // Build the tree 2734 2735 $book->add_child($items); 2736 $items->add_child($item); 2737 2738 $item->add_child($grades); 2739 $grades->add_child($grade); 2740 2741 $book->add_child($letters); 2742 $letters->add_child($letter); 2743 2744 // Define sources 2745 2746 $item->set_source_sql("SELECT gi.* 2747 FROM {grade_items} gi 2748 JOIN {backup_ids_temp} bi ON gi.id = bi.itemid 2749 WHERE bi.backupid = ? 2750 AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID)); 2751 2752 // This only happens if we are including user info 2753 if ($userinfo) { 2754 $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID)); 2755 $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA, 'id'); 2756 } 2757 2758 $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID)); 2759 2760 // Annotations 2761 2762 $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0 2763 $item->annotate_ids('outcome', 'outcomeid'); 2764 2765 $grade->annotate_ids('user', 'userid'); 2766 $grade->annotate_ids('user', 'usermodified'); 2767 2768 // Return the root element (book) 2769 2770 return $book; 2771 } 2772 } 2773 2774 /** 2775 * Structure step in charge of constructing the grade history of an activity. 2776 * 2777 * This step is added to the task regardless of the setting 'grade_histories'. 2778 * The reason is to allow for a more flexible step in case the logic needs to be 2779 * split accross different settings to control the history of items and/or grades. 2780 */ 2781 class backup_activity_grade_history_structure_step extends backup_structure_step { 2782 2783 /** 2784 * No grades on the front page. 2785 * @return bool 2786 */ 2787 protected function execute_condition() { 2788 return ($this->get_courseid() != SITEID); 2789 } 2790 2791 protected function define_structure() { 2792 global $CFG; 2793 2794 require_once($CFG->libdir . '/grade/constants.php'); 2795 2796 // Settings to use. 2797 $userinfo = $this->get_setting_value('userinfo'); 2798 $history = $this->get_setting_value('grade_histories'); 2799 2800 // Create the nested elements. 2801 $bookhistory = new backup_nested_element('grade_history'); 2802 $grades = new backup_nested_element('grade_grades'); 2803 $grade = new backup_nested_element('grade_grade', array('id'), array( 2804 'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid', 2805 'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid', 2806 'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden', 2807 'excluded', 'feedback', 'feedbackformat', 'information', 2808 'informationformat', 'timemodified')); 2809 2810 // Build the tree. 2811 $bookhistory->add_child($grades); 2812 $grades->add_child($grade); 2813 2814 // This only happens if we are including user info and history. 2815 if ($userinfo && $history) { 2816 // Define sources. Only select the history related to existing activity items. 2817 $grade->set_source_sql("SELECT ggh.* 2818 FROM {grade_grades_history} ggh 2819 JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid 2820 WHERE bi.backupid = ? 2821 AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID)); 2822 $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA, 'id'); 2823 } 2824 2825 // Annotations. 2826 $grade->annotate_ids('scalefinal', 'rawscaleid'); // Straight as scalefinal because it's > 0. 2827 $grade->annotate_ids('user', 'loggeduser'); 2828 $grade->annotate_ids('user', 'userid'); 2829 $grade->annotate_ids('user', 'usermodified'); 2830 2831 // Return the root element. 2832 return $bookhistory; 2833 } 2834 } 2835 2836 /** 2837 * Backups up the course completion information for the course. 2838 */ 2839 class backup_course_completion_structure_step extends backup_structure_step { 2840 2841 protected function execute_condition() { 2842 2843 // No completion on front page. 2844 if ($this->get_courseid() == SITEID) { 2845 return false; 2846 } 2847 2848 // Check that all activities have been included 2849 if ($this->task->is_excluding_activities()) { 2850 return false; 2851 } 2852 return true; 2853 } 2854 2855 /** 2856 * The structure of the course completion backup 2857 * 2858 * @return backup_nested_element 2859 */ 2860 protected function define_structure() { 2861 2862 // To know if we are including user completion info 2863 $userinfo = $this->get_setting_value('userscompletion'); 2864 2865 $cc = new backup_nested_element('course_completion'); 2866 2867 $criteria = new backup_nested_element('course_completion_criteria', array('id'), array( 2868 'course', 'criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod', 2869 'timeend', 'gradepass', 'role', 'roleshortname' 2870 )); 2871 2872 $criteriacompletions = new backup_nested_element('course_completion_crit_completions'); 2873 2874 $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array( 2875 'criteriaid', 'userid', 'gradefinal', 'unenrolled', 'timecompleted' 2876 )); 2877 2878 $coursecompletions = new backup_nested_element('course_completions', array('id'), array( 2879 'userid', 'course', 'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate' 2880 )); 2881 2882 $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array( 2883 'course','criteriatype','method','value' 2884 )); 2885 2886 $cc->add_child($criteria); 2887 $criteria->add_child($criteriacompletions); 2888 $criteriacompletions->add_child($criteriacomplete); 2889 $cc->add_child($coursecompletions); 2890 $cc->add_child($aggregatemethod); 2891 2892 // We need some extra data for the restore. 2893 // - courseinstances shortname rather than an ID. 2894 // - roleshortname in case restoring on a different site. 2895 $sourcesql = "SELECT ccc.*, c.shortname AS courseinstanceshortname, r.shortname AS roleshortname 2896 FROM {course_completion_criteria} ccc 2897 LEFT JOIN {course} c ON c.id = ccc.courseinstance 2898 LEFT JOIN {role} r ON r.id = ccc.role 2899 WHERE ccc.course = ?"; 2900 $criteria->set_source_sql($sourcesql, array(backup::VAR_COURSEID)); 2901 2902 $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID)); 2903 2904 if ($userinfo) { 2905 $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID)); 2906 $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID)); 2907 } 2908 2909 $criteria->annotate_ids('role', 'role'); 2910 $criteriacomplete->annotate_ids('user', 'userid'); 2911 $coursecompletions->annotate_ids('user', 'userid'); 2912 2913 return $cc; 2914 2915 } 2916 } 2917 2918 /** 2919 * Backup completion defaults for each module type. 2920 * 2921 * @package core_backup 2922 * @copyright 2017 Marina Glancy 2923 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2924 */ 2925 class backup_completion_defaults_structure_step extends backup_structure_step { 2926 2927 /** 2928 * To conditionally decide if one step will be executed or no 2929 */ 2930 protected function execute_condition() { 2931 // No completion on front page. 2932 if ($this->get_courseid() == SITEID) { 2933 return false; 2934 } 2935 return true; 2936 } 2937 2938 /** 2939 * The structure of the course completion backup 2940 * 2941 * @return backup_nested_element 2942 */ 2943 protected function define_structure() { 2944 2945 $cc = new backup_nested_element('course_completion_defaults'); 2946 2947 $defaults = new backup_nested_element('course_completion_default', array('id'), array( 2948 'modulename', 'completion', 'completionview', 'completionusegrade', 'completionpassgrade', 2949 'completionexpected', 'customrules' 2950 )); 2951 2952 // Use module name instead of module id so we can insert into another site later. 2953 $sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade, 2954 d.completionpassgrade, d.completionexpected, d.customrules 2955 FROM {course_completion_defaults} d join {modules} m on d.module = m.id 2956 WHERE d.course = ?"; 2957 $defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID)); 2958 2959 $cc->add_child($defaults); 2960 return $cc; 2961 2962 } 2963 } 2964 2965 /** 2966 * Structure step in charge of constructing the contentbank.xml file for all the contents found in a given context 2967 */ 2968 class backup_contentbankcontent_structure_step extends backup_structure_step { 2969 2970 /** 2971 * Define structure for content bank step 2972 */ 2973 protected function define_structure() { 2974 2975 // Define each element separated. 2976 $contents = new backup_nested_element('contents'); 2977 $content = new backup_nested_element('content', ['id'], [ 2978 'name', 'contenttype', 'instanceid', 'configdata', 'usercreated', 'usermodified', 'timecreated', 'timemodified']); 2979 2980 // Build the tree. 2981 $contents->add_child($content); 2982 2983 // Define sources. 2984 $content->set_source_table('contentbank_content', ['contextid' => backup::VAR_CONTEXTID]); 2985 2986 // Define annotations. 2987 $content->annotate_ids('user', 'usercreated'); 2988 $content->annotate_ids('user', 'usermodified'); 2989 $content->annotate_files('contentbank', 'public', 'id'); 2990 2991 // Return the root element (contents). 2992 return $contents; 2993 } 2994 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body