MDL-21432 backup - role load/mappings and integration with UI via controller (MDL...
authorEloy Lafuente <stronk7@moodle.org>
Tue, 20 Jul 2010 22:51:04 +0000 (22:51 +0000)
committerEloy Lafuente <stronk7@moodle.org>
Tue, 20 Jul 2010 22:51:04 +0000 (22:51 +0000)
backup/controller/restore_controller.class.php
backup/moodle2/restore_stepslib.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_general_helper.class.php
backup/util/helper/restore_prechecks_helper.class.php

index b817582..aaecc85 100644 (file)
@@ -265,14 +265,27 @@ class restore_controller extends backup implements loggable {
         return $this->plan->execute();
     }
 
-    public function execute_precheck() {
+    /**
+     * Execute the restore prechecks to detect any problem before proceed with restore
+     *
+     * This function checks various parts of the restore (versions, users, roles...)
+     * returning true if everything was ok or false if any warning/error was detected.
+     * Any warning/error is returned by the get_precheck_results() method.
+     * Note: if any problem is found it will, automatically, drop all the restore temp
+     * tables as far as the next step is to inform about the warning/errors. If no problem
+     * is found, then default behaviour is to keep the temp tables so, in the same request
+     * restore will be executed, saving a lot of checks to be executed again.
+     * Note: If for any reason (UI to show after prechecks...) you want to force temp tables
+     * to be dropped always, you can pass true to the $droptemptablesafter parameter
+     */
+    public function execute_precheck($droptemptablesafter = false) {
         if (is_array($this->precheck)) {
             throw new restore_controller_exception('precheck_alredy_executed', $this->status);
         }
         if ($this->status != backup::STATUS_NEED_PRECHECK) {
             throw new restore_controller_exception('cannot_precheck_wrong_status', $this->status);
         }
-        $this->precheck = restore_prechecks_helper::execute_prechecks($this);
+        $this->precheck = restore_prechecks_helper::execute_prechecks($this, $droptemptablesafter);
         if (!array_key_exists('errors', $this->precheck)) { // No errors, can be executed
             $this->set_status(backup::STATUS_AWAITING);
         }
index 2494a64..59e012f 100644 (file)
@@ -131,15 +131,18 @@ class restore_load_included_files extends restore_structure_step {
 class restore_load_and_map_roles extends restore_execution_step {
 
     protected function define_execution() {
-        if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do
+        if ($this->task->get_preloaded_information()) { // if info is already preloaded
             return;
         }
 
         $file = $this->get_basepath() . '/roles.xml';
         // Load needed toles to temp_ids
         restore_dbops::load_roles_to_tempids($this->get_restoreid(), $file);
+
         // Process roles, mapping/skipping. Any error throws exception
-        restore_dbops::process_included_roles($this->get_restoreid(), $this->task->get_courseid(), $this->task->get_userid(), $this->task->is_samesite());
+        // Note we pass controller's info because it can contain role mapping information
+        // about manual mappings performed by UI
+        restore_dbops::process_included_roles($this->get_restoreid(), $this->task->get_courseid(), $this->task->get_userid(), $this->task->is_samesite(), $this->task->get_info()->role_mappings);
     }
 }
 
@@ -416,6 +419,8 @@ class restore_outcomes_structure_step extends restore_structure_step {
             // Remap the user
             $userid = $this->get_mappingid('user', $data->usermodified);
             $data->usermodified = $userid ? $userid : $this->get_userid();
+            // Remap the scale
+            $data->scaleid = $this->get_mappingid('scale', $data->scaleid);
             // Remap the course if course outcome
             $data->courseid = $data->courseid ? $this->get_courseid() : null;
             // If global outcome (course=null), check the user has perms to create it
index 80f478b..e7d1e7d 100644 (file)
@@ -113,24 +113,103 @@ abstract class restore_dbops {
     /**
      * Precheck the loaded roles, return empty array if everything is ok, and
      * array with 'errors', 'warnings' elements (suitable to be used by restore_prechecks)
-     * with any problem found
+     * with any problem found. At the same time, store all the mapping into backup_ids_temp
+     * and also put the information into $rolemappings (controller->info), so it can be reworked later by
+     * post-precheck stages while at the same time accept modified info in the same object coming from UI
      */
-    public static function precheck_included_roles($restoreid, $courseid, $userid, $samesite) {
-        debugging('implement the roles mapping/skip here. returns errors/warnings array', DEBUG_DEVELOPER);
-        return array();
+    public static function precheck_included_roles($restoreid, $courseid, $userid, $samesite, $rolemappings) {
+        global $DB;
+
+        $problems = array(); // To store warnings/errors
+
+        // Get loaded roles from backup_ids
+        $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'role'), '', 'itemid');
+        foreach ($rs as $recrole) {
+            // If the rolemappings->modified flag is set, that means that we are coming from
+            // manually modified mappings (by UI), so accept those mappings an put them to backup_ids
+            if ($rolemappings->modified) {
+                $target = $rolemappings->mappings[$recrole->itemid]->targetroleid;
+                self::set_backup_ids_record($restoreid, 'role', $recrole->itemid, $target);
+
+            // Else, we haven't any info coming from UI, let's calculate the mappings, matching
+            // in multiple ways and checking permissions. Note mapping to 0 means "skip"
+            } else {
+                $role = (object)self::get_backup_ids_record($restoreid, 'role', $recrole->itemid)->info;
+                $match = self::get_best_assignable_role($role, $courseid, $userid, $samesite);
+                // Send match to backup_ids
+                self::set_backup_ids_record($restoreid, 'role', $recrole->itemid, $match);
+                // Build the rolemappings element for controller
+                unset($role->id);
+                unset($role->nameincourse);
+                unset($role->nameincourse);
+                $role->targetroleid = $match;
+                $rolemappings->mappings[$recrole->itemid] = $role;
+                // Prepare warning if no match found
+                if (!$match) {
+                    $problems['warnings'][] = get_string('cannotfindassignablerole', 'backup', $role->name);
+                }
+            }
+        }
+        $rs->close();
+        return $problems;
     }
 
+    /**
+     * Given one role, as loaded from XML, perform the best possible matching against the assignable
+     * roles, using different fallback alternatives (shortname, archetype, editingteacher => teacher, defaultcourseroleid)
+     * returning the id of the best matching role or 0 if no match is found
+     */
+    protected static function get_best_assignable_role($role, $courseid, $userid, $samesite) {
+        global $CFG, $DB;
+
+        // Gather various information about roles
+        $coursectx = get_context_instance(CONTEXT_COURSE, $courseid);
+        $allroles = $DB->get_records('role');
+        $assignablerolesshortname = get_assignable_roles($coursectx, ROLENAME_SHORT, false, $userid);
+
+        // Note: under 1.9 we had one function restore_samerole() that performed one complete
+        // matching of roles (all caps) and if match was found the mapping was availabe bypassing
+        // any assignable_roles() security. IMO that was wrong and we must not allow such
+        // mappings anymore. So we have left that matching strategy out in 2.0
+
+        // Empty assignable roles, mean no match possible
+        if (empty($assignablerolesshortname)) {
+            return 0;
+        }
+
+        // Match by shortname
+        if ($match = array_search($role->shortname, $assignablerolesshortname)) {
+            return $match;
+        }
+
+        // Match by archetype
+        list($in_sql, $in_params) = $DB->get_in_or_equal(array_keys($assignablerolesshortname));
+        $params = array_merge(array($role->archetype), $in_params);
+        if ($rec = $DB->get_record_select('role', "archetype = ? AND id $in_sql", $params, 'id', IGNORE_MULTIPLE)) {
+            return $rec->id;
+        }
+
+        // Match editingteacher to teacher (happens a lot, from 1.9)
+        if ($role->shortname == 'editingteacher' && in_array('teacher', $assignablerolesshortname)) {
+            return array_search('teacher', $assignablerolesshortname);
+        }
+
+        // No match, return 0
+        return 0;
+    }
+
+
     /**
      * Process the loaded roles, looking for their best mapping or skipping
      * Any error will cause exception. Note this is one wrapper over
      * precheck_included_roles, that contains all the logic, but returns
      * errors/warnings instead and is executed as part of the restore prechecks
      */
-     public static function process_included_roles($restoreid, $courseid, $userid, $samesite) {
+     public static function process_included_roles($restoreid, $courseid, $userid, $samesite, $rolemappings) {
         global $DB;
 
         // Just let precheck_included_roles() to do all the hard work
-        $problems = self::precheck_included_roles($restoreid, $courseid, $userid, $samesite);
+        $problems = self::precheck_included_roles($restoreid, $courseid, $userid, $samesite, $rolemappings);
 
         // With problems of type error, throw exception, shouldn't happen if prechecks executed
         if (array_key_exists('errors', $problems)) {
index aa369dc..5cab2fe 100644 (file)
@@ -145,6 +145,12 @@ abstract class backup_general_helper extends backup_helper {
         $info->type   =  $infoarr['details']['detail'][0]['type'];
         $info->format =  $infoarr['details']['detail'][0]['format'];
         $info->mode   =  $infoarr['details']['detail'][0]['mode'];
+        // Build the role mappings custom object
+        $rolemappings = new stdclass();
+        $rolemappings->modified = false;
+        $rolemappings->mappings = array();
+        $info->role_mappings = $rolemappings;
+
 
         // Now the contents
         $contentsarr = $infoarr['contents'];
index bf90cc1..81689b0 100644 (file)
@@ -43,7 +43,7 @@ abstract class restore_prechecks_helper {
      *
      * Returns empty array or warnings/errors array
      */
-    public static function execute_prechecks($controller) {
+    public static function execute_prechecks($controller, $droptemptablesafter = false) {
         global $CFG;
 
         $errors = array();
@@ -57,6 +57,7 @@ abstract class restore_prechecks_helper {
         $courseid = $controller->get_courseid();
         $userid = $controller->get_userid();
         $inforeffiles = restore_dbops::get_needed_inforef_files($restoreid); // Get all inforef files
+        $rolemappings = $controller->get_info()->role_mappings;
 
         // Create temp tables
         restore_controller_dbops::create_restore_temp_tables($controller->get_restoreid());
@@ -111,11 +112,10 @@ abstract class restore_prechecks_helper {
             }
         }
 
-        // TODO: Perform role_mappings, warning about non-mappable ones being ignored (see 1.9)
         // Note: restore won't create roles at all. Only mapping/skip!
         $file = $controller->get_plan()->get_basepath() . '/roles.xml';
-        restore_dbops::load_roles_to_tempids($restoreid, $file); // Load needed users to temp_ids
-        if ($problems = restore_dbops::precheck_included_roles($restoreid, $courseid, $userid, $samesite)) {
+        restore_dbops::load_roles_to_tempids($restoreid, $file); // Load needed roles to temp_ids
+        if ($problems = restore_dbops::precheck_included_roles($restoreid, $courseid, $userid, $samesite, $rolemappings)) {
             $errors = array_key_exists('errors', $problems) ? array_merge($errors, $problems['errors']) : $errors;
             $warnings = array_key_exists('warnings', $problems) ? array_merge($warnings, $problems['warnings']) : $warnings;
         }
@@ -128,8 +128,8 @@ abstract class restore_prechecks_helper {
         if (!empty($warnings)) {
             $results['warnings'] = $warnings;
         }
-        // Warnings/errors detected, drop temp tables
-        if (!empty($results)) {
+        // Warnings/errors detected or want to do so explicitly, drop temp tables
+        if (!empty($results) || $droptemptablesafter) {
             restore_controller_dbops::drop_restore_temp_tables($controller->get_restoreid());
         }
         return $results;