diff --git a/backup/moodle2/restore_block_task.class.php b/backup/moodle2/restore_block_task.class.php index de51e6e9244..b00f5d94fbe 100644 --- a/backup/moodle2/restore_block_task.class.php +++ b/backup/moodle2/restore_block_task.class.php @@ -31,12 +31,22 @@ abstract class restore_block_task extends restore_task { protected $taskbasepath; // To store the basepath of this block + protected $blockname; // Name of the block + protected $contextid; // new (target) context of the block + protected $oldcontextid;// old (original) context of the block + protected $blockid; // new (target) id of the block + protected $oldblockid; // old (original) id of the block /** * Constructor - instantiates one object of this class */ public function __construct($name, $taskbasepath, $plan = null) { $this->taskbasepath = $taskbasepath; + $this->blockname = ''; + $this->contextid = 0; + $this->oldcontextid = 0; + $this->blockid = 0; + $this->oldblockid = 0; parent::__construct($name, $plan); } @@ -58,15 +68,75 @@ abstract class restore_block_task extends restore_task { return; } - // TODO: See backup. If "child" of activity task and it has been excluded, nothing to do + // If "child" of activity task and it has been excluded, nothing to do + $parent = basename(dirname(dirname($this->taskbasepath))); + if ($parent != 'course') { + $includedsetting = $parent . '_included'; + if (!$this->get_setting_value($includedsetting)) { + $this->built = true; + return; + } + } + // Process the block.xml common file (instance + positions) + $this->add_step(new restore_block_instance_structure_step('block_commons', 'block.xml')); - // TODO: Link all the activity steps here + // Here we add all the common steps for any block and, in the point of interest + // we call to define_my_steps() in order to get the particular ones inserted in place. + $this->define_my_steps(); + + // Restore block role assignments and overrides (internally will observe the role_assignments setting) + $this->add_step(new restore_ras_and_caps_structure_step('block_ras_and_caps', 'roles.xml')); + + // Restore block comments (conditionally) + if ($this->get_setting_value('comments')) { + $this->add_step(new restore_comments_structure_step('block_comments', 'comments.xml')); + } // At the end, mark it as built $this->built = true; } + public function set_blockname($blockname) { + $this->blockname = $blockname; + } + + public function get_blockname() { + return $this->blockname; + } + + public function set_blockid($blockid) { + $this->blockid = $blockid; + } + + public function get_blockid() { + return $this->blockid; + } + + public function set_old_blockid($blockid) { + $this->oldblockid = $blockid; + } + + public function get_old_blockid() { + return $this->oldblockid; + } + + public function set_contextid($contextid) { + $this->contextid = $contextid; + } + + public function get_contextid() { + return $this->contextid; + } + + public function set_old_contextid($contextid) { + $this->oldcontextid = $contextid; + } + + public function get_old_contextid() { + return $this->oldcontextid; + } + // Protected API starts here /** @@ -90,6 +160,11 @@ abstract class restore_block_task extends restore_task { */ abstract protected function define_my_steps(); + /** + * Define one array() of fileareas that each block controls + */ + abstract public function get_fileareas(); + /** * Define one array() of configdata attributes * that need to be decoded diff --git a/backup/moodle2/restore_default_block_task.class.php b/backup/moodle2/restore_default_block_task.class.php index 0c1d8d2bfcc..1d2d1c64291 100644 --- a/backup/moodle2/restore_default_block_task.class.php +++ b/backup/moodle2/restore_default_block_task.class.php @@ -37,6 +37,10 @@ class restore_default_block_task extends restore_block_task { protected function define_my_steps() { } + public function get_fileareas() { + return array(); + } + public function get_configdata_encoded_attributes() { return array(); } diff --git a/backup/moodle2/restore_final_task.class.php b/backup/moodle2/restore_final_task.class.php index 71fa38a682d..24e7d9a5d37 100644 --- a/backup/moodle2/restore_final_task.class.php +++ b/backup/moodle2/restore_final_task.class.php @@ -35,9 +35,15 @@ class restore_final_task extends restore_task { */ public function build() { + // Review all the block_position records in backup_ids in order + // match them now that all the contexts are created populating DB + // as needed. Only if we are restoring blocks. + if ($this->get_setting_value('blocks')) { + $this->add_step(new restore_review_pending_block_positions('review_block_positions')); + } + // TODO: Gradebook // TODO: interlinks - // TODO: Apply all the block_positions accumulated along the process // Clean the temp dir (conditionally) and drop temp table $this->add_step(new restore_drop_and_clean_temp_stuff('drop_and_clean_temp_stuff')); diff --git a/backup/moodle2/restore_plan_builder.class.php b/backup/moodle2/restore_plan_builder.class.php index 9e3f528c80c..cdc192ed90e 100644 --- a/backup/moodle2/restore_plan_builder.class.php +++ b/backup/moodle2/restore_plan_builder.class.php @@ -117,7 +117,7 @@ abstract class restore_plan_builder { $blocks = backup_general_helper::get_blocks_from_path($task->get_taskbasepath()); foreach ($blocks as $basepath => $name) { if ($task = restore_factory::get_restore_block_task($name, $basepath)) { - $plan->add_task($task); + //$plan->add_task($task); } else { // TODO: Debug information about block not supported } diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index de16dcba63f..0d6c926f358 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -66,6 +66,37 @@ class restore_drop_and_clean_temp_stuff extends restore_execution_step { } } + +/** + * Review all the (pending) block positions in backup_ids, matching by + * contextid, creating positions as needed. This is executed by the + * final task, once all the contexts have been created + */ +class restore_review_pending_block_positions extends restore_execution_step { + + protected function define_execution() { + global $DB; + + // Get all the block_position objects pending to match + $params = array('backupid' => $this->get_restoreid(), 'itemname' => 'block_position'); + $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'itemid'); + // Process block positions, creating them or accumulating for final step + foreach($rs as $posrec) { + // Get the complete position object (stored as info) + $position = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'block_position', $posrec->itemid)->info; + // If position is for one already mapped (known) contextid + // process it now, creating the position, else nothing to + // do, position finally discarded + if ($newctx = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $position->contextid)) { + $position->contextid = $newctx->newitemid; + // Create the block position + $DB->insert_record('block_positions', $position); + } + } + $rs->close(); + } +} + /* * Execution step that, *conditionally* (if there isn't preloaded information) * will load the inforef files for all the included course/section/activity tasks @@ -646,8 +677,8 @@ class restore_ras_and_caps_structure_step extends restore_structure_step { // Check roleid, userid are one of the mapped ones $newroleid = $this->get_mappingid('role', $data->roleid); $newuserid = $this->get_mappingid('user', $data->userid); - // If newroleid and newuserid and component is empty assign via API (handles dupes and friends) - if ($newroleid && $newuserid && empty($data->component)) { + // If newroleid and newuserid and component is empty and context valid assign via API (handles dupes and friends) + if ($newroleid && $newuserid && empty($data->component) && $this->task->get_contextid()) { // TODO: role_assign() needs one userid param to be able to specify our restore userid role_assign($newroleid, $newuserid, $this->task->get_contextid()); } @@ -658,9 +689,10 @@ class restore_ras_and_caps_structure_step extends restore_structure_step { // Check roleid is one of the mapped ones $newroleid = $this->get_mappingid('role', $data->roleid); - // If newroleid is valid assign it via API (it handles dupes and so on) - if ($newroleid) { + // If newroleid and context are valid assign it via API (it handles dupes and so on) + if ($newroleid && $this->task->get_contextid()) { // TODO: assign_capability() needs one userid param to be able to specify our restore userid + // TODO: it seems that assign_capability() doesn't check for valid capabilities at all ??? assign_capability($data->capability, $data->permission, $newroleid, $this->task->get_contextid()); } } @@ -820,9 +852,128 @@ class restore_comments_structure_step extends restore_structure_step { } // Only restore the comment if has no mapping OR we have found the matching mapping if (!$mapping || $newitemid) { - if ($data->userid = $this->get_mappingid('user', $data->userid)) { + // Only if user mapping and context + $data->userid = $this->get_mappingid('user', $data->userid); + if ($data->userid && $this->task->get_contextid()) { $data->contextid = $this->task->get_contextid(); - $DB->insert_record('comments', $data); + // Only if there is another comment with same context/user/timecreated + $params = array('contextid' => $data->contextid, 'userid' => $data->userid, 'timecreated' => $data->timecreated); + if (!$DB->record_exists('comments', $params)) { + $DB->insert_record('comments', $data); + } + } + } + } +} + + +/** + * This structure steps restores one instance + positions of one block + * Note: Positions corresponding to one existing context are restored + * here, but all the ones having unknown contexts are sent to backup_ids + * for a later chance to be restored at the end (final task) + */ +class restore_block_instance_structure_step extends restore_structure_step { + + protected function define_structure() { + + $paths = array(); + + $paths[] = new restore_path_element('block', '/block', true); // Get the whole XML together + $paths[] = new restore_path_element('block_position', '/block/block_positions/block_position'); + + return $paths; + } + + public function process_block($data) { + global $DB; + + $data = (object)$data; // Handy + $oldcontextid = $data->contextid; + $oldid = $data->id; + $positions = isset($data->block_positions['block_position']) ? $data->block_positions['block_position'] : array(); + + // Look for the parent contextid + if (!$data->parentcontextid = $this->get_mappingid('context', $data->parentcontextid)) { + throw new restore_step_exception('restore_block_missing_parent_ctx', $data->parentcontextid); + } + + // If there is already one block of that type in the parent context + // and the block is not multiple, stop processing + if ($DB->record_exists_sql("SELECT bi.id + FROM {block_instances} bi + JOIN {block} b ON b.name = bi.blockname + WHERE bi.parentcontextid = ? + AND bi.blockname = ? + AND b.multiple = 0", array($data->parentcontextid, $data->blockname))) { + return false; + } + + // If there is already one block of that type in the parent context + // with the same showincontexts, pagetypepattern, subpagepattern, defaultregion and configdata + // stop processing + $params = array( + 'blockname' => $data->blockname, 'parentcontextid' => $data->parentcontextid, + 'showinsubcontexts' => $data->showinsubcontexts, 'pagetypepattern' => $data->pagetypepattern, + 'subpagepattern' => $data->subpagepattern, 'defaultregion' => $data->defaultregion); + if ($birecs = $DB->get_records('block_instances', $params)) { + foreach($birecs as $birec) { + if ($birec->configdata == $data->configdata) { + return false; + } + } + } + + // Set task old contextid, blockid and blockname once we know them + $this->task->set_old_contextid($oldcontextid); + $this->task->set_old_blockid($oldid); + $this->task->set_blockname($data->blockname); + + // Let's look for anything within configdata neededing processing + // (nulls and uses of legacy file.php) + if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) { + $configdata = (array)unserialize(base64_decode($data->configdata)); + foreach ($configdata as $attribute => $value) { + if (in_array($attribute, $attrstotransform)) { + $configdata[$attribute] = $this->contentprocessor->process_cdata($value); + } + } + $data->configdata = base64_encode(serialize((object)$configdata)); + } + + // Create the block instance + $newitemid = $DB->insert_record('block_instances', $data); + // Save the mapping (with restorefiles support) + $this->set_mapping('block_instance', $oldid, $newitemid, true); + // Create the block context + $newcontextid = get_context_instance(CONTEXT_BLOCK, $newitemid)->id; + // Save the block contexts mapping and sent it to task + $this->set_mapping('context', $oldcontextid, $newcontextid); + $this->task->set_contextid($newcontextid); + $this->task->set_blockid($newitemid); + + // Restore block fileareas if declared + $component = 'block_' . $this->task->get_blockname(); + foreach ($this->task->get_fileareas() as $filearea) { // Simple match by contextid. No itemname needed + $this->add_related_files($component, $filearea, null); + } + + // Process block positions, creating them or accumulating for final step + foreach($positions as $position) { + $position = (object)$position; + $position->blockinstanceid = $newitemid; // The instance is always the restored one + // If position is for one already mapped (known) contextid + // process it now, creating the position + if ($newpositionctxid = $this->get_mappingid('context', $position->contextid)) { + $position->contextid = $newpositionctxid; + // Create the block position + $DB->insert_record('block_positions', $position); + + // The position belongs to an unknown context, send it to backup_ids + // to process them as part of the final steps of restore. We send the + // whole $position object there, hence use the low level method. + } else { + restore_dbops::set_backup_ids_record($this->get_restoreid(), 'block_position', $position->id, 0, null, $position); } } }