From ea91c36b325cefe01543a8549a86090b2ea50b29 Mon Sep 17 00:00:00 2001
From: Nigel Cunningham <nigelc@catalyst-au.net>
Date: Tue, 21 Apr 2015 16:02:11 +1000
Subject: [PATCH] Convert backup/restore to use MUC.

Convert the backup and restore code to use the Moodle Unified
Cache.

TODO:
- Update the Moodle1 converter.
- Tests still need to be modified.
- Some cleaning up around reset_backup_ids_cached
- Forward port to HEAD.

Signed-off-by: Nigel Cunningham <nigelc@catalyst-au.net>
---
 backup/converter/moodle1/lib.php                   |  28 +-
 .../moodle1/tests/moodle1_converter_test.php       |   6 -
 backup/moodle2/backup_stepslib.php                 | 134 ++++-----
 backup/moodle2/restore_plugin.class.php            |   2 +-
 backup/moodle2/restore_stepslib.php                | 275 ++++++++++-------
 backup/moodle2/restore_subplugin.class.php         |   2 +-
 backup/upgrade.txt                                 |   5 +
 .../util/dbops/backup_controller_dbops.class.php   |  88 ++----
 backup/util/dbops/backup_plan_dbops.class.php      |  29 +-
 backup/util/dbops/backup_question_dbops.class.php  |  45 +--
 backup/util/dbops/backup_structure_dbops.class.php |  59 ++--
 .../util/dbops/restore_controller_dbops.class.php  |  18 --
 backup/util/dbops/restore_dbops.class.php          | 330 ++++++++-------------
 backup/util/dbops/tests/restore_dbops_test.php     |  33 +--
 backup/util/helper/muc_helper.class.php            |  62 ++++
 .../util/helper/restore_decode_content.class.php   |  23 +-
 .../util/helper/restore_prechecks_helper.class.php |   2 +-
 backup/util/includes/backup_includes.php           |   1 +
 backup/util/includes/restore_includes.php          |   1 +
 backup/util/plan/restore_structure_step.class.php  |   2 +-
 .../util/structure/backup_nested_element.class.php |  69 +++++
 backup/util/structure/tests/structure_test.php     |  20 +-
 .../processors/grouped_parser_processor.class.php  |   9 +
 .../util/xml/parser/progressive_parser.class.php   |  14 +-
 .../moodle2/restore_html_block_task.class.php      |  35 ++-
 cache/classes/helper.php                           |   8 +-
 cache/classes/loaders.php                          |   2 +-
 cache/tests/cache_test.php                         |   5 +-
 local/automatedemails                              |   2 +-
 .../restore_qtype_multianswer_plugin.class.php     |  23 +-
 .../moodle2/restore_qtype_random_plugin.class.php  |  25 +-
 31 files changed, 733 insertions(+), 624 deletions(-)
 create mode 100644 backup/util/helper/muc_helper.class.php

diff --git a/backup/converter/moodle1/lib.php b/backup/converter/moodle1/lib.php
index 2f8232a..92e0dd3 100644
--- a/backup/converter/moodle1/lib.php
+++ b/backup/converter/moodle1/lib.php
@@ -417,25 +417,25 @@ class moodle1_converter extends base_converter {
     /**
      * Creates the temporary storage for stashed data
      *
-     * This implementation uses backup_ids_temp table.
+     * This implementation uses backup ids cache.
      */
     public function create_stash_storage() {
-        backup_controller_dbops::create_backup_ids_temp_table($this->get_id());
+        backup_controller_dbops::purge_backup_ids_temp_cache($this->get_id());
     }
 
     /**
      * Drops the temporary storage of stashed data
      *
-     * This implementation uses backup_ids_temp table.
+     * This implementation uses backup ids cache.
      */
     public function drop_stash_storage() {
-        backup_controller_dbops::drop_backup_ids_temp_table($this->get_id());
+        backup_controller_dbops::purge_backup_ids_temp_cache($this->get_id());
     }
 
     /**
      * Stores some information for later processing
      *
-     * This implementation uses backup_ids_temp table to store data. Make
+     * This implementation uses backup ids cache to store data. Make
      * sure that the $stashname + $itemid combo is unique.
      *
      * @param string $stashname name of the stash
@@ -495,13 +495,8 @@ class moodle1_converter extends base_converter {
      * @return array
      */
     public function get_stash_names() {
-        global $DB;
-
-        $search = array(
-            'backupid' => $this->get_id(),
-        );
-
-        return array_keys($DB->get_records('backup_ids_temp', $search, '', 'itemname'));
+        // @TODO
+        return $stashes;
     }
 
     /**
@@ -511,14 +506,7 @@ class moodle1_converter extends base_converter {
      * @return array
      */
     public function get_stash_itemids($stashname) {
-        global $DB;
-
-        $search = array(
-            'backupid' => $this->get_id(),
-            'itemname' => $stashname
-        );
-
-        return array_keys($DB->get_records('backup_ids_temp', $search, '', 'itemid'));
+        // @TODO
     }
 
     /**
diff --git a/backup/converter/moodle1/tests/moodle1_converter_test.php b/backup/converter/moodle1/tests/moodle1_converter_test.php
index 3209287..669b01e 100644
--- a/backup/converter/moodle1/tests/moodle1_converter_test.php
+++ b/backup/converter/moodle1/tests/moodle1_converter_test.php
@@ -88,12 +88,6 @@ class core_backup_moodle1_converter_testcase extends advanced_testcase {
         $this->assertInstanceOf('moodle1_converter', $converter);
     }
 
-    public function test_stash_storage_not_created() {
-        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
-        $this->setExpectedException('moodle1_convert_storage_exception');
-        $converter->set_stash('tempinfo', 12);
-    }
-
     public function test_stash_requiring_empty_stash() {
         $this->resetAfterTest(true);
         $converter = convert_factory::get_converter('moodle1', $this->tempdir);
diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php
index 4ba8223..49fa220 100644
--- a/backup/moodle2/backup_stepslib.php
+++ b/backup/moodle2/backup_stepslib.php
@@ -65,8 +65,6 @@ class create_and_clean_temp_stuff extends backup_execution_step {
         $progress->start_progress('Deleting backup directories');
         backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
         backup_helper::clear_backup_dir($this->get_backupid(), $progress);           // Empty temp dir, just in case
-        backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
-        backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
         $progress->end_progress();
     }
 }
@@ -85,7 +83,6 @@ class drop_and_clean_temp_stuff extends backup_execution_step {
     protected function define_execution() {
         global $CFG;
 
-        backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
         backup_helper::delete_old_backup_dirs(strtotime('-1 week'));                // Delete > 1 week old temp dirs.
         // Delete temp dir conditionally:
         // 1) If $CFG->keeptempdirectoriesonbackup is not enabled
@@ -285,7 +282,7 @@ abstract class backup_questions_activity_structure_step extends backup_activity_
 /**
  * backup structure step in charge of calculating the categories to be
  * included in backup, based in the context being backuped (module/course)
- * and the already annotated questions present in backup_ids_temp
+ * and the already annotated questions present in backup_ids_temp cache
  */
 class backup_calculate_question_categories extends backup_execution_step {
 
@@ -296,7 +293,7 @@ class backup_calculate_question_categories extends backup_execution_step {
 
 /**
  * backup structure step in charge of deleting all the questions annotated
- * in the backup_ids_temp table
+ * in the backup ids cache
  */
 class backup_delete_temp_questions extends backup_execution_step {
 
@@ -660,12 +657,12 @@ class backup_final_roles_structure_step extends backup_structure_step {
 
         // Define sources
 
-        $role->set_source_sql("SELECT r.*, rn.name AS nameincourse
+        $role->set_source_sql_using_backup_ids(
+                              "SELECT r.*, rn.name AS nameincourse
                                  FROM {role} r
-                                 JOIN {backup_ids_temp} bi ON r.id = bi.itemid
                             LEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ?
-                                WHERE bi.backupid = ?
-                                  AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID));
+                                WHERE r.id *SQL*", array(backup::VAR_CONTEXTID),
+                                backup::VAR_BACKUPID, 'rolefinal', 'itemid');
 
         // Return main element (rolesdef)
         return $rolesdef;
@@ -694,11 +691,11 @@ class backup_final_scales_structure_step extends backup_structure_step {
 
         // Define sources
 
-        $scale->set_source_sql("SELECT s.*
+        $scale->set_source_sql_using_backup_ids(
+                               "SELECT s.*
                                   FROM {scale} s
-                                  JOIN {backup_ids_temp} bi ON s.id = bi.itemid
-                                 WHERE bi.backupid = ?
-                                   AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID));
+                                 WHERE s.id *SQL*", array(), backup::VAR_BACKUPID,
+                                   'scalefinal', 'itemid');
 
         // Annotate scale files (they store files in system context, so pass it instead of default one)
         $scale->annotate_files('grade', 'scale', 'id', context_system::instance()->id);
@@ -731,11 +728,11 @@ class backup_final_outcomes_structure_step extends backup_structure_step {
 
         // Define sources
 
-        $outcome->set_source_sql("SELECT o.*
+        $outcome->set_source_sql_using_backup_ids(
+                                 "SELECT o.*
                                     FROM {grade_outcomes} o
-                                    JOIN {backup_ids_temp} bi ON o.id = bi.itemid
-                                   WHERE bi.backupid = ?
-                                     AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID));
+                                   WHERE o.id ", array(), backup::VAR_BACKUPID,
+                                     'outcomefinal', 'itemid');
 
         // Annotate outcome files (they store files in system context, so pass it instead of default one)
         $outcome->annotate_files('grade', 'outcome', 'id', context_system::instance()->id);
@@ -1131,24 +1128,22 @@ class backup_groups_structure_step extends backup_structure_step {
 
         // Define sources
 
-        $group->set_source_sql("
+        $group->set_source_sql_using_backup_ids("
             SELECT g.*
               FROM {groups} g
-              JOIN {backup_ids_temp} bi ON g.id = bi.itemid
-             WHERE bi.backupid = ?
-               AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));
+             WHERE g.id *SQL*", array(), backup::VAR_BACKUPID,
+               'groupfinal', 'itemid');
 
         // This only happens if we are including users
         if ($users) {
             $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
         }
 
-        $grouping->set_source_sql("
+        $grouping->set_source_sql_using_backup_ids("
             SELECT g.*
               FROM {groupings} g
-              JOIN {backup_ids_temp} bi ON g.id = bi.itemid
-             WHERE bi.backupid = ?
-               AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));
+             WHERE g.id *SQL*", array(), backup::VAR_BACKUPID,
+               'groupingfinal', 'itemid');
 
         $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
 
@@ -1281,15 +1276,12 @@ class backup_users_structure_step extends backup_structure_step {
 
         // Define sources
 
-        $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
+        $user->set_source_sql_using_backup_ids(
+                              'SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
                                  FROM {user} u
-                                 JOIN {backup_ids_temp} bi ON bi.itemid = u.id
                             LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . '
                             LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
-                                WHERE bi.backupid = ?
-                                  AND bi.itemname = ?', array(
-                                      backup_helper::is_sqlparam($this->get_backupid()),
-                                      backup_helper::is_sqlparam('userfinal')));
+                                WHERE u.id *SQL*', array(), $this->get_backupid(), 'userfinal', 'itemid');
 
         // All the rest on information is only added if we arent
         // in an anonymized backup
@@ -1479,12 +1471,7 @@ class backup_inforef_structure_step extends backup_structure_step {
                 $element = new backup_nested_element($itemname, array(), array('id'));
                 $inforef->add_child($elementroot);
                 $elementroot->add_child($element);
-                $element->set_source_sql("
-                    SELECT itemid AS id
-                     FROM {backup_ids_temp}
-                    WHERE backupid = ?
-                      AND itemname = ?",
-                   array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
+                $element->set_source_cache($itemname);
             }
         }
 
@@ -1545,14 +1532,14 @@ class backup_final_files_structure_step extends backup_structure_step {
 
         // Define sources
 
-        $file->set_source_sql("SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.reference
+        $file->set_source_sql_using_backup_ids(
+                              "SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.reference
                                  FROM {files} f
                                       LEFT JOIN {files_reference} fr ON fr.id = f.referencefileid
                                       LEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryid
                                       LEFT JOIN {repository} r ON r.id = ri.typeid
-                                      JOIN {backup_ids_temp} bi ON f.id = bi.itemid
-                                WHERE bi.backupid = ?
-                                  AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));
+                                WHERE f.id *SQL*", array(), backup::VAR_BACKUPID,
+                                     'filefinal', 'itemid');
 
         return $files;
     }
@@ -1851,14 +1838,13 @@ class backup_annotate_groups_from_groupings extends backup_execution_step {
         global $DB;
 
         // Fetch all the annotated groupings
-        if ($groupings = $DB->get_records('backup_ids_temp', array(
-                'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {
-            foreach ($groupings as $grouping) {
-                if ($groups = $DB->get_records('groupings_groups', array(
-                        'groupingid' => $grouping->itemid))) {
-                    foreach ($groups as $group) {
-                        backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
-                    }
+        $cache = backup_muc_manager::get('grouping');
+        $groupings = $cache->get_store()->find_all();
+        foreach ($groupings as $grouping) {
+            if ($groups = $DB->get_records('groupings_groups', array(
+                            'groupingid' => $grouping))) {
+                foreach ($groups as $group) {
+                    backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
                 }
             }
         }
@@ -1874,14 +1860,13 @@ class backup_annotate_scales_from_outcomes extends backup_execution_step {
         global $DB;
 
         // Fetch all the annotated outcomes
-        if ($outcomes = $DB->get_records('backup_ids_temp', array(
-                'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {
-            foreach ($outcomes as $outcome) {
-                if ($scale = $DB->get_record('grade_outcomes', array(
-                        'id' => $outcome->itemid))) {
-                    // Annotate as scalefinal because it's > 0
-                    backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
-                }
+        $cache = backup_muc_manager::get('outcome');
+        $outcomes = $cache->get_store()->find_all();
+        foreach ($outcomes as $outcome) {
+            if ($scale = $DB->get_record('grade_outcomes', array(
+                            'id' => $outcome))) {
+                // Annotate as scalefinal because it's > 0
+                backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
             }
         }
     }
@@ -1902,11 +1887,18 @@ class backup_annotate_all_question_files extends backup_execution_step {
 
         // Get all the different contexts for the final question_categories
         // annotated along the whole backup
+        $cache = backup_muc_manager::get('question_categoryfinal');
+        $cache_content = $cache->get_store()->find_all();
+
+        if (empty($cache_content)) {
+            return;
+        }
+
+        list($sql, $params) = $DB->get_in_or_equal($cache_content);
+
         $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
                                         FROM {question_categories} qc
-                                        JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
-                                       WHERE bi.backupid = ?
-                                         AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
+                                       WHERE qc.id $sql", $params);
         // To know about qtype specific components/fileareas
         $components = backup_qtype_plugin::get_components_and_fileareas();
         // Let's loop
@@ -1989,13 +1981,12 @@ class backup_questions_structure_step extends backup_structure_step {
 
         // Define the sources
 
-        $qcategory->set_source_sql("
+        $qcategory->set_source_sql_using_backup_ids("
             SELECT gc.*, contextlevel, instanceid AS contextinstanceid
               FROM {question_categories} gc
-              JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
               JOIN {context} co ON co.id = gc.contextid
-             WHERE bi.backupid = ?
-               AND bi.itemname = 'question_categoryfinal'", array(backup::VAR_BACKUPID));
+             WHERE gc.id *SQL*", array(), backup::VAR_BACKUPID,
+               'question_categoryfinal', 'itemid');
 
         $question->set_source_table('question', array('category' => backup::VAR_PARENTID));
 
@@ -2037,12 +2028,11 @@ class backup_annotate_all_user_files extends backup_execution_step {
         $fileareas = array('profile', 'icon');
 
         // Fetch all annotated (final) users
-        $rs = $DB->get_recordset('backup_ids_temp', array(
-            'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
+        $cache = backup_muc_manager::get('userfinal');
+        $cache_content = $cache->get_store()->find_all();
         $progress = $this->task->get_progress();
         $progress->start_progress($this->get_name());
-        foreach ($rs as $record) {
-            $userid = $record->itemid;
+        foreach ($cache_content as $userid) {
             $userctx = context_user::instance($userid, IGNORE_MISSING);
             if (!$userctx) {
                 continue; // User has not context, sure it's a deleted user, so cannot have files
@@ -2056,7 +2046,6 @@ class backup_annotate_all_user_files extends backup_execution_step {
             }
         }
         $progress->end_progress();
-        $rs->close();
     }
 }
 
@@ -2190,11 +2179,10 @@ class backup_activity_grades_structure_step extends backup_structure_step {
 
         // Define sources
 
-        $item->set_source_sql("SELECT gi.*
+        $item->set_source_sql_using_backup_ids("SELECT gi.*
                                FROM {grade_items} gi
-                               JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
-                               WHERE bi.backupid = ?
-                               AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
+                               WHERE gi.id *SQL*", array(), backup::VAR_BACKUPID,
+                               'grade_item', 'itemid');
 
         // This only happens if we are including user info
         if ($userinfo) {
diff --git a/backup/moodle2/restore_plugin.class.php b/backup/moodle2/restore_plugin.class.php
index b90bcdf..fcded87 100644
--- a/backup/moodle2/restore_plugin.class.php
+++ b/backup/moodle2/restore_plugin.class.php
@@ -170,7 +170,7 @@ abstract class restore_plugin {
      * To send ids pairs to backup_ids_table and to store them into paths
      *
      * This method will send the given itemname and old/new ids to the
-     * backup_ids_temp table, and, at the same time, will save the new id
+     * backup ids cache, and, at the same time, will save the new id
      * into the corresponding restore_path_element for easier access
      * by children. Also will inject the known old context id for the task
      * in case it's going to be used for restoring files later
diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php
index 9e310f7..4f2fd2c 100644
--- a/backup/moodle2/restore_stepslib.php
+++ b/backup/moodle2/restore_stepslib.php
@@ -56,7 +56,7 @@ class restore_drop_cron_lock extends restore_execution_step {
 }
 
 /**
- * delete old directories and conditionally create backup_temp_ids table
+ * delete old directories and conditionally create backup_temp_ids cache
  */
 class restore_create_and_clean_temp_stuff extends restore_execution_step {
 
@@ -355,21 +355,18 @@ class restore_gradebook_structure_step extends restore_structure_step {
     protected function after_execute() {
         global $DB;
 
-        $conditions = array(
-            'backupid' => $this->get_restoreid(),
-            'itemname' => 'grade_item'//,
-            //'itemid'   => $itemid
-        );
-        $rs = $DB->get_recordset('backup_ids_temp', $conditions);
+        $id_cache = backup_muc_manager::get('grade_item');
+        $cache_items = $id_cache->get_store()->find_all();
 
         // We need this for calculation magic later on.
         $mappings = array();
 
-        if (!empty($rs)) {
-            foreach($rs as $grade_item_backup) {
+        if (!empty($cache_items)) {
+            foreach($cache_items as $grade_item_id) {
+                $grade_item_backup = (object) $id_cache->get($grade_item_id);
 
                 // Store the oldid with the new id.
-                $mappings[$grade_item_backup->itemid] = $grade_item_backup->newitemid;
+                $mappings[$grade_item_id] = $grade_item_backup->newitemid;
 
                 $updateobj = new stdclass();
                 $updateobj->id = $grade_item_backup->newitemid;
@@ -388,7 +385,6 @@ class restore_gradebook_structure_step extends restore_structure_step {
                 }
             }
         }
-        $rs->close();
 
         // We need to update the calculations for calculated grade items that may reference old
         // grade item ids using ##gi\d+##.
@@ -547,10 +543,11 @@ class restore_review_pending_block_positions extends restore_execution_step {
         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, info');
+        $id_cache = backup_muc_manager::get('block_position');
+        $cache_items = $id_cache->get_store()->find_all();
         // Process block positions, creating them or accumulating for final step
-        foreach($rs as $posrec) {
+        foreach($cache_items as $pos_id) {
+            $posrec = $id_cache->get($pos_id);
             // Get the complete position object out of the info field.
             $position = backup_controller_dbops::decode_backup_temp_info($posrec->info);
             // If position is for one already mapped (known) contextid
@@ -562,7 +559,6 @@ class restore_review_pending_block_positions extends restore_execution_step {
                 $DB->insert_record('block_positions', $position);
             }
         }
-        $rs->close();
     }
 }
 
@@ -596,10 +592,11 @@ class restore_update_availability extends restore_execution_step {
         $dateoffset = $this->apply_date_offset(1) - 1;
 
         // Update all sections that were restored.
-        $params = array('backupid' => $this->get_restoreid(), 'itemname' => 'course_section');
-        $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'newitemid');
+        $id_cache = backup_muc_manager::get('course_section');
+        $cache_items = $id_cache->get_store()->find_all();
         $sectionsbyid = null;
-        foreach ($rs as $rec) {
+        foreach ($cache_items as $rec_id) {
+            $rec = (object) $id_cache->get($rec_id);
             if (is_null($sectionsbyid)) {
                 $sectionsbyid = array();
                 foreach ($modinfo->get_section_info_all() as $section) {
@@ -620,12 +617,13 @@ class restore_update_availability extends restore_execution_step {
                         $this->get_courseid(), $this->get_logger(), $dateoffset);
             }
         }
-        $rs->close();
 
         // Update all modules that were restored.
-        $params = array('backupid' => $this->get_restoreid(), 'itemname' => 'course_module');
-        $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'newitemid');
-        foreach ($rs as $rec) {
+        $id_cache = backup_muc_manager::get('course_module');
+        $cache_items = $id_cache->get_store()->find_all();
+
+        foreach ($cache_items as $rec_id) {
+            $rec = (object) $id_cache->get($rec_id);
             if (!array_key_exists($rec->newitemid, $modinfo->cms)) {
                 // If the module was not fully restored for some reason
                 // (e.g. due to an earlier error), skip it.
@@ -640,7 +638,6 @@ class restore_update_availability extends restore_execution_step {
                         $this->get_courseid(), $this->get_logger(), $dateoffset);
             }
         }
-        $rs->close();
     }
 }
 
@@ -671,10 +668,11 @@ class restore_process_course_modules_availability extends restore_execution_step
         // Do both modules and sections.
         foreach (array('module', 'section') as $table) {
             // Get all the availability objects to process.
-            $params = array('backupid' => $this->get_restoreid(), 'itemname' => $table . '_availability');
-            $rs = $DB->get_recordset('backup_ids_temp', $params, '', 'itemid, info');
+            $id_cache = backup_muc_manager::get('availability');
+            $cache_items = $id_cache->get_store()->find_all();
             // Process availabilities, creating them if everything matches ok.
-            foreach ($rs as $availrec) {
+            foreach ($cache_items as $availrec_id) {
+                $availrec = (object) $id_cache->get($availrec_id);
                 $allmatchesok = true;
                 // Get the complete legacy availability object.
                 $availability = backup_controller_dbops::decode_backup_temp_info($availrec->info);
@@ -703,7 +701,6 @@ class restore_process_course_modules_availability extends restore_execution_step
                         array('id' => $thingid));
             }
         }
-        $rs->close();
     }
 }
 
@@ -1634,18 +1631,22 @@ class restore_course_legacy_files_step extends restore_execution_step {
         global $DB;
 
         // Do a check for legacy files and skip if there are none.
-        $sql = 'SELECT count(*)
-                  FROM {backup_files_temp}
-                 WHERE backupid = ?
-                   AND contextid = ?
-                   AND component = ?
-                   AND filearea  = ?';
-        $params = array($this->get_restoreid(), $this->task->get_old_contextid(), 'course', 'legacy');
-
-        if ($DB->count_records_sql($sql, $params)) {
+        $cache = backup_muc_manager::get('file_temp');
+
+        $count = 0;
+        $oldcontextid = $this->task->get_old_contextid();
+        foreach($cache->get_store()->find_all() as $id) {
+            $data = $cache->get($id);
+            if ($data->contextid == $oldcontextid &&
+                $data->component == 'course' &&
+                $data->filearea == 'legacy')
+                $count++;
+        }
+
+        if ($count) {
             $DB->set_field('course', 'legacyfiles', 2, array('id' => $this->get_courseid()));
             restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'course',
-                'legacy', $this->task->get_old_contextid(), $this->task->get_userid());
+                'legacy', $oldcontextid, $this->task->get_userid());
         }
     }
 }
@@ -3465,7 +3466,7 @@ abstract class restore_activity_structure_step extends restore_structure_step {
  * Structure step in charge of creating/mapping all the qcats and qs
  * by parsing the questions.xml file and checking it against the
  * results calculated by {@link restore_process_categories_and_questions}
- * and stored in backup_ids_temp
+ * and stored in backup ids cache
  */
 class restore_create_categories_and_questions extends restore_structure_step {
 
@@ -3666,21 +3667,48 @@ class restore_create_categories_and_questions extends restore_structure_step {
         global $DB;
 
         // First of all, recode all the created question_categories->parent fields
-        $qcats = $DB->get_records('backup_ids_temp', array(
-                     'backupid' => $this->get_restoreid(),
-                     'itemname' => 'question_category_created'));
-        foreach ($qcats as $qcat) {
-            $newparent = 0;
-            $dbcat = $DB->get_record('question_categories', array('id' => $qcat->newitemid));
+        $qcats_created_cache = backup_muc_manager::get('question_category_created');
+        $qcats_created = $qcats_created_cache->get_store()->find_all();
+
+        $qcats_cache = backup_muc_manager::get('question_category');
+
+        $categories = array();
+        $qcats2 = array();
+        foreach ($qcats_created as $qcat_id) {
+            $qcat = $qcats_created_cache->get($qcat_id);
+            $qcats2[$qcat_id] = $qcat;
+            $categories[$qcat['newitemid']] = 1;
+        }
+
+        if (empty($categories)) {
+            return;
+        }
+
+        list($sql, $params) = $DB->get_in_or_equal(array_keys($categories));
+        $rs = $DB->get_records_sql("SELECT * FROM {question_categories} WHERE id $sql", $params);
+
+        $dbcats = array();
+        foreach ($rs as $record) {
+            $dbcats[$record->id] = $record;
+        }
+
+        foreach ($qcats2 as $id => $qcat) {
+            $dbcat = $dbcats[$qcat['newitemid']];
+
             // Get new parent (mapped or created, so we look in quesiton_category mappings)
-            if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
-                                 'backupid' => $this->get_restoreid(),
-                                 'itemname' => 'question_category',
-                                 'itemid'   => $dbcat->parent))) {
+            if ($newparent = $qcats_cache->get($dbcat->parent)) {
                 // contextids must match always, as far as we always include complete qbanks, just check it
-                $newparentctxid = $DB->get_field('question_categories', 'contextid', array('id' => $newparent));
+                $newparent_newitemid = $newparent['newitemid'];
+                if (isset($dbcats[$newparent_newitemid])) {
+                    $newparentctxid = $dbcats[$newparent_newitemid]->contextid;
+                } else {
+                    $newparentctxid = $DB->get_field('question_categories', 'contextid', array('id' => $newparent_newitemid));
+
+                    // Save to avoid requerying.
+                    $dbcats[$newparent_newitemid] = (object) array('contenxtid' => $newparentctxid);
+                }
                 if ($dbcat->contextid == $newparentctxid) {
-                    $DB->set_field('question_categories', 'parent', $newparent, array('id' => $dbcat->id));
+                    $DB->set_field('question_categories', 'parent', $newparent_newitemid, array('id' => $dbcat->id));
                 } else {
                     $newparent = 0; // No ctx match for both cats, no parent relationship
                 }
@@ -3692,18 +3720,18 @@ class restore_create_categories_and_questions extends restore_structure_step {
         }
 
         // Now, recode all the created question->parent fields
-        $qs = $DB->get_records('backup_ids_temp', array(
-                  'backupid' => $this->get_restoreid(),
-                  'itemname' => 'question_created'));
-        foreach ($qs as $q) {
+        $qn_created_cache = backup_muc_manager::get('question_created');
+        $qs = $qn_created_cache->get_store()->find_all();
+
+        $qn_cache = backup_muc_manager::get('question');
+
+        foreach ($qs as $q_id) {
+            $q = $qn_created_cache->get($q_id);
             $newparent = 0;
-            $dbq = $DB->get_record('question', array('id' => $q->newitemid));
+            $dbq = $DB->get_record('question', array('id' => $q['newitemid']));
             // Get new parent (mapped or created, so we look in question mappings)
-            if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
-                                 'backupid' => $this->get_restoreid(),
-                                 'itemname' => 'question',
-                                 'itemid'   => $dbq->parent))) {
-                $DB->set_field('question', 'parent', $newparent, array('id' => $dbq->id));
+            if ($newparent = $qn_cache->get($dbq->parent)) {
+                $DB->set_field('question', 'parent', $newparent['newitemid'], array('id' => $dbq->id));
             }
         }
 
@@ -3726,20 +3754,25 @@ class restore_move_module_questions_categories extends restore_execution_step {
         global $DB;
 
         $contexts = restore_dbops::restore_get_question_banks($this->get_restoreid(), CONTEXT_MODULE);
+        $qn_cat_cache = backup_muc_manager::get('question_category');
+        $qn_cat_ids = $qn_cat_cache->get_store()->find_all();
+        $qn_cats = array();
+        foreach ($qn_cat_ids as $qn_cat_id) {
+            $qn_cats[$qn_cat_id] = $qn_cat_cache->get($qn_cat_id);
+        }
         foreach ($contexts as $contextid => $contextlevel) {
             // Only if context mapping exists (i.e. the module has been restored)
             if ($newcontext = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $contextid)) {
                 // Update all the qcats having their parentitemid set to the original contextid
-                $modulecats = $DB->get_records_sql("SELECT itemid, newitemid
-                                                      FROM {backup_ids_temp}
-                                                     WHERE backupid = ?
-                                                       AND itemname = 'question_category'
-                                                       AND parentitemid = ?", array($this->get_restoreid(), $contextid));
-                foreach ($modulecats as $modulecat) {
-                    $DB->set_field('question_categories', 'contextid', $newcontext->newitemid, array('id' => $modulecat->newitemid));
+                $modulecats = array();
+                foreach ($qn_cat_ids as $qn_cat_id) {
+                    if ($qn_cats[$qn_cat_id]['parentitemid'] != $contextid) {
+                        continue;
+                    }
+                    $DB->set_field('question_categories', 'contextid', $newcontext->newitemid, array('id' => $qn_cat_id));
                     // And set new contextid also in question_category mapping (will be
                     // used by {@link restore_create_question_files} later
-                    restore_dbops::set_backup_ids_record($this->get_restoreid(), 'question_category', $modulecat->itemid, $modulecat->newitemid, $newcontext->newitemid);
+                    restore_dbops::set_backup_ids_record($this->get_restoreid(), 'question_category', $qn_cat_id, $qn_cats[$qn_cat_id]['newitemid'], $newcontext->newitemid);
                 }
             }
         }
@@ -3767,42 +3800,58 @@ class restore_create_question_files extends restore_execution_step {
         $progress = $this->task->get_progress();
         $progress->start_progress($this->get_name(), \core\progress\base::INDETERMINATE);
 
-        // Parentitemids of question_createds in backup_ids_temp are the category it is in.
-        // MUST use a recordset, as there is no unique key in the first (or any) column.
-        $catqtypes = $DB->get_recordset_sql("SELECT DISTINCT bi.parentitemid AS categoryid, q.qtype as qtype
-                                               FROM {backup_ids_temp} bi
-                                               JOIN {question} q ON q.id = bi.newitemid
-                                              WHERE bi.backupid = ?
-                                                AND bi.itemname = 'question_created'
-                                           ORDER BY categoryid ASC", array($this->get_restoreid()));
-
-        $currentcatid = -1;
-        foreach ($catqtypes as $categoryid => $row) {
-            $qtype = $row->qtype;
-
-            // Check if we are in a new category.
-            if ($currentcatid !== $categoryid) {
-                // Report progress for each category.
-                $progress->progress();
-
-                if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(),
-                        'question_category', $categoryid)) {
-                    // Something went really wrong, cannot find the question_category for the question_created records.
-                    debugging('Error fetching target context for question', DEBUG_DEVELOPER);
-                    continue;
-                }
+        // The parentitemid in the cached object is the category the question is in.
+        $qn_created_cache = backup_muc_manager::get('question_created');
+        $qns_created_ids = $qn_created_cache->get_store()->find_all();
 
-                // Calculate source and target contexts.
-                $oldctxid = $qcatmapping->info->contextid;
-                $newctxid = $qcatmapping->parentitemid;
+        // Get the actual data and list of question ids to match.
+        $qns_created = array();
+        $qids = array();
+        foreach($qns_created_ids as $qn_created) {
+            $temp = $qn_created_cache->get($qn_created);
+            $qns_created[$temp['newitemid']] = $temp;
+            $qids[$temp['newitemid']] = $temp['newitemid'];
+        }
 
-                $this->send_common_files($oldctxid, $newctxid, $progress);
-                $currentcatid = $categoryid;
+        if (empty($qids)) {
+            $progress->end_progress();
+            return;
+        }
+
+        list($sql, $params) = $DB->get_in_or_equal($qids);
+        $questions_rs = $DB->get_recordset_sql("SELECT id, qtype FROM {question} WHERE id $sql", $params);
+
+        // Build an array of [categoryid][qtype]
+        $questions = array();
+        foreach($questions_rs as $question) {
+            $categoryid = $qns_created[$question->id]['parentitemid'];
+            if (!isset($questions[$categoryid])) {
+                $questions[$categoryid] = array();
             }
+            $questions[$categoryid][$question->qtype] = 1;
+        }
 
-            $this->send_qtype_files($qtype, $oldctxid, $newctxid, $progress);
+        foreach ($questions as $categoryid => $rows) {
+            // Report progress for each category.
+            $progress->progress();
+
+            if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(),
+                        'question_category', $categoryid)) {
+                // Something went really wrong, cannot find the question_category for the question_created records.
+                debugging('Error fetching target context for question', DEBUG_DEVELOPER);
+                continue;
+            }
+
+            // Calculate source and target contexts.
+            $oldctxid = $qcatmapping->info->contextid;
+            $newctxid = $qcatmapping->parentitemid;
+
+            $this->send_common_files($oldctxid, $newctxid, $progress);
+
+            foreach($rows as $qtype => $yesitsset) {
+                $this->send_qtype_files($qtype, $oldctxid, $newctxid, $progress);
+            }
         }
-        $catqtypes->close();
         $progress->end_progress();
     }
 
@@ -3888,12 +3937,12 @@ class restore_process_file_aliases_queue extends restore_execution_step {
         $fs = get_file_storage();
 
         // Load the queue.
-        $rs = $DB->get_recordset('backup_ids_temp',
-            array('backupid' => $this->get_restoreid(), 'itemname' => 'file_aliases_queue'),
-            '', 'info');
+        $id_cache = backup_muc_manager::get('file_aliases_queue');
+        $rs = $id_cache->get_store()->find_all();
 
         // Iterate over aliases in the queue.
-        foreach ($rs as $record) {
+        foreach ($rs as $rid) {
+            $record = (object) $id_cache->get($rid);
             $info = backup_controller_dbops::decode_backup_temp_info($record->info);
 
             // Try to pick a repository instance that should serve the alias.
@@ -3916,17 +3965,18 @@ class restore_process_file_aliases_queue extends restore_execution_step {
                 }
 
                 // Let's see if the referred source file was also included in the backup.
-                $candidates = $DB->get_recordset('backup_files_temp', array(
-                        'backupid' => $this->get_restoreid(),
-                        'contextid' => $reference['contextid'],
-                        'component' => $reference['component'],
-                        'filearea' => $reference['filearea'],
-                        'itemid' => $reference['itemid'],
-                    ), '', 'info, newcontextid, newitemid');
-
+                $cache_c = backup_muc_manager::get('files_temp');
+                $candidates = $cache_c->get_store()->find_all();
                 $source = null;
 
-                foreach ($candidates as $candidate) {
+                foreach ($candidates as $candidateid) {
+                    $candidate = $cache_c->get($candidateid);
+                    if ($candidate->contextid != $reference['contextid'] ||
+                        $candidate->component != $reference['component'] ||
+                        $candidate->filearea  != $reference['filearea'] ||
+                        $candidate->itemid    != $reference['itemid']) {
+                        continue;
+                    }
                     $candidateinfo = backup_controller_dbops::decode_backup_temp_info($candidate->info);
                     if ($candidateinfo->filename === $reference['filename']
                             and $candidateinfo->filepath === $reference['filepath']
@@ -4017,7 +4067,6 @@ class restore_process_file_aliases_queue extends restore_execution_step {
                 }
             }
         }
-        $rs->close();
     }
 
     /**
diff --git a/backup/moodle2/restore_subplugin.class.php b/backup/moodle2/restore_subplugin.class.php
index bf21779..204e22a 100644
--- a/backup/moodle2/restore_subplugin.class.php
+++ b/backup/moodle2/restore_subplugin.class.php
@@ -102,7 +102,7 @@ abstract class restore_subplugin {
      * To send ids pairs to backup_ids_table and to store them into paths
      *
      * This method will send the given itemname and old/new ids to the
-     * backup_ids_temp table, and, at the same time, will save the new id
+     * backup ids cache, and, at the same time, will save the new id
      * into the corresponding restore_path_element for easier access
      * by children. Also will inject the known old context id for the task
      * in case it's going to be used for restoring files later
diff --git a/backup/upgrade.txt b/backup/upgrade.txt
index 26e2e00..e475db1 100644
--- a/backup/upgrade.txt
+++ b/backup/upgrade.txt
@@ -1,6 +1,11 @@
 This files describes API changes in /backup/*,
 information provided here is intended especially for developers.
 
+=== Catalyst Patch ===
+
+* The backup_ids_temp table has been replaced by a MUC cache, named
+  'backup_' plus the itemname. Cache keys are itemids.
+
 === 2.6 ===
 
 * The backup_controller_dbops::create_temptable_from_real_table()
diff --git a/backup/util/dbops/backup_controller_dbops.class.php b/backup/util/dbops/backup_controller_dbops.class.php
index 41ff9d3..0bddfe2 100644
--- a/backup/util/dbops/backup_controller_dbops.class.php
+++ b/backup/util/dbops/backup_controller_dbops.class.php
@@ -112,60 +112,6 @@ abstract class backup_controller_dbops extends backup_dbops {
         return $controller;
     }
 
-    public static function create_backup_ids_temp_table($backupid) {
-        global $CFG, $DB;
-        $dbman = $DB->get_manager(); // We are going to use database_manager services
-
-        $xmldb_table = new xmldb_table('backup_ids_temp');
-        $xmldb_table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        // Set default backupid (not needed but this enforce any missing backupid). That's hackery in action!
-        $xmldb_table->add_field('backupid', XMLDB_TYPE_CHAR, 32, null, XMLDB_NOTNULL, null, $backupid);
-        $xmldb_table->add_field('itemname', XMLDB_TYPE_CHAR, 160, null, XMLDB_NOTNULL, null, null);
-        $xmldb_table->add_field('itemid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null);
-        $xmldb_table->add_field('newitemid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, '0');
-        $xmldb_table->add_field('parentitemid', XMLDB_TYPE_INTEGER, 10, null, null, null, null);
-        $xmldb_table->add_field('info', XMLDB_TYPE_TEXT, null, null, null, null, null);
-        $xmldb_table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $xmldb_table->add_key('backupid_itemname_itemid_uk', XMLDB_KEY_UNIQUE, array('backupid','itemname','itemid'));
-        $xmldb_table->add_index('backupid_parentitemid_ix', XMLDB_INDEX_NOTUNIQUE, array('backupid','itemname','parentitemid'));
-        $xmldb_table->add_index('backupid_itemname_newitemid_ix', XMLDB_INDEX_NOTUNIQUE, array('backupid','itemname','newitemid'));
-
-        $dbman->create_temp_table($xmldb_table); // And create it
-
-    }
-
-    public static function create_backup_files_temp_table($backupid) {
-        global $CFG, $DB;
-        $dbman = $DB->get_manager(); // We are going to use database_manager services
-
-        $xmldb_table = new xmldb_table('backup_files_temp');
-        $xmldb_table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
-        // Set default backupid (not needed but this enforce any missing backupid). That's hackery in action!
-        $xmldb_table->add_field('backupid', XMLDB_TYPE_CHAR, 32, null, XMLDB_NOTNULL, null, $backupid);
-        $xmldb_table->add_field('contextid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null);
-        $xmldb_table->add_field('component', XMLDB_TYPE_CHAR, 100, null, XMLDB_NOTNULL, null, null);
-        $xmldb_table->add_field('filearea', XMLDB_TYPE_CHAR, 50, null, XMLDB_NOTNULL, null, null);
-        $xmldb_table->add_field('itemid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null);
-        $xmldb_table->add_field('info', XMLDB_TYPE_TEXT, null, null, null, null, null);
-        $xmldb_table->add_field('newcontextid', XMLDB_TYPE_INTEGER, 10, null, null, null, null);
-        $xmldb_table->add_field('newitemid', XMLDB_TYPE_INTEGER, 10, null, null, null, null);
-        $xmldb_table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
-        $xmldb_table->add_index('backupid_contextid_component_filearea_itemid_ix', XMLDB_INDEX_NOTUNIQUE, array('backupid','contextid','component','filearea','itemid'));
-
-        $dbman->create_temp_table($xmldb_table); // And create it
-    }
-
-    public static function drop_backup_ids_temp_table($backupid) {
-        global $DB;
-        $dbman = $DB->get_manager(); // We are going to use database_manager services
-
-        $targettablename = 'backup_ids_temp';
-        if ($dbman->table_exists($targettablename)) {
-            $table = new xmldb_table($targettablename);
-            $dbman->drop_table($table); // And drop it
-        }
-    }
-
     /**
      * Decode the info field from backup_ids_temp or backup_files_temp.
      *
@@ -462,13 +408,19 @@ abstract class backup_controller_dbops extends backup_dbops {
     public static function backup_includes_mnet_remote_users($backupid) {
         global $CFG, $DB;
 
-        $sql = "SELECT COUNT(*)
-                  FROM {backup_ids_temp} b
-                  JOIN {user} u ON u.id = b.itemid
-                 WHERE b.backupid = ?
-                   AND b.itemname = 'userfinal'
-                   AND u.mnethostid != ?";
-        $count = $DB->count_records_sql($sql, array($backupid, $CFG->mnet_localhost_id));
+        $cache = backup_muc_manager::get('userfinal');
+        $cache_content = $cache->get_store()->find_all();
+
+        if (empty($cache_content)) {
+            return 0;
+        }
+
+        list($sql, $params) = $DB->get_in_or_equal($cache_content);
+        $params[] = $CFG->mnet_localhost_id;
+        $sql = "SELECT COUNT(*) FROM {user} u
+                               WHERE u.id $sql
+                                 AND u.mnethostid != ?";
+        $count = $DB->count_records_sql($sql, $params);
         return (int)(bool)$count;
     }
 
@@ -505,15 +457,19 @@ abstract class backup_controller_dbops extends backup_dbops {
     public static function backup_includes_file_references($backupid) {
         global $CFG, $DB;
 
+        $cache = backup_muc_manager::get('filefinal');
+        $fileids = $cache->get_store()->find_all();
+        if (empty($fileids)) {
+            return 0;
+        }
+
+        list($sql, $params) = $DB->get_in_or_equal($fileids);
         $sql = "SELECT count(r.repositoryid)
                   FROM {files} f
                   LEFT JOIN {files_reference} r
                        ON r.id = f.referencefileid
-                  JOIN {backup_ids_temp} bi
-                       ON f.id = bi.itemid
-                 WHERE bi.backupid = ?
-                       AND bi.itemname = 'filefinal'";
-        $count = $DB->count_records_sql($sql, array($backupid));
+                 WHERE f.id $sql";
+        $count = $DB->count_records_sql($sql, $params);
         return (int)(bool)$count;
     }
 
diff --git a/backup/util/dbops/backup_plan_dbops.class.php b/backup/util/dbops/backup_plan_dbops.class.php
index 5307637..3bbc261 100644
--- a/backup/util/dbops/backup_plan_dbops.class.php
+++ b/backup/util/dbops/backup_plan_dbops.class.php
@@ -261,18 +261,27 @@ abstract class backup_plan_dbops extends backup_dbops {
     public static function require_gradebook_backup($courseid, $backupid) {
         global $DB;
 
-        $sql = "SELECT count(id)
-                  FROM {grade_items}
-                 WHERE courseid=:courseid
-                   AND itemtype = 'mod'
-                   AND id NOT IN (
-                       SELECT bi.itemid
-                         FROM {backup_ids_temp} bi
-                        WHERE bi.itemname = 'grade_itemfinal'
-                          AND bi.backupid = :backupid)";
-        $params = array('courseid'=>$courseid, 'backupid'=>$backupid);
+        $cache = backup_muc_manager::get('grade_itemfinal');
 
+        $itemids = array();
+        foreach($cache->get_store()->find_all() as $id) {
+            $item = $cache->get($id);
+            $itemids[$id] = $id;
+        }
 
+        if (empty($itemids)) {
+            $sql = '< 0';
+            $params = array();
+        } else {
+            list($sql, $params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_QM, 'param', false);
+        }
+        $params[] = $courseid;
+
+        $sql = "SELECT count(id)
+                  FROM {grade_items}
+                 WHERE itemtype = 'mod'
+                   AND id $sql
+                   AND courseid= ?";
         $count = $DB->count_records_sql($sql, $params);
 
         //if there are 0 activity grade items not already included in the backup
diff --git a/backup/util/dbops/backup_question_dbops.class.php b/backup/util/dbops/backup_question_dbops.class.php
index 67ff54f..516651e 100644
--- a/backup/util/dbops/backup_question_dbops.class.php
+++ b/backup/util/dbops/backup_question_dbops.class.php
@@ -42,31 +42,42 @@ abstract class backup_question_dbops extends backup_dbops {
 
         // First step, annotate all the categories for the given context (course/module)
         // i.e. the whole context questions bank
-        $DB->execute("INSERT INTO {backup_ids_temp} (backupid, itemname, itemid)
-                      SELECT ?, 'question_category', id
-                        FROM {question_categories}
-                       WHERE contextid = ?", array($backupid, $contextid));
+        $rs = $DB->get_recordset_sql("SELECT id FROM {question_categories} WHERE contextid = ?",
+                array($contextid));
+        $cache_qc = backup_muc_manager::get('question_category');
+        foreach($rs as $record) {
+            $cache_qc->set($record->id, 1);
+        }
+        $rs->close();
 
         // Now, based in the annotated questions, annotate all the categories they
         // belong to (whole context question banks too)
         // First, get all the contexts we are going to save their question bank (no matter
         // where they are in the contexts hierarchy, transversals... whatever)
+        $cache_q = backup_muc_manager::get('question');
+        $questions = $cache_q->get_store()->find_all();
+        if (empty($questions)) {
+            return;
+        }
+
+        list($sql, $params) = $DB->get_in_or_equal($questions);
+        $params = array_merge(array($contextid), $params);
         $contexts = $DB->get_fieldset_sql("SELECT DISTINCT qc2.contextid
-                                             FROM {question_categories} qc2
-                                             JOIN {question} q ON q.category = qc2.id
-                                             JOIN {backup_ids_temp} bi ON bi.itemid = q.id
-                                            WHERE bi.backupid = ?
-                                              AND bi.itemname = 'question'
-                                              AND qc2.contextid != ?", array($backupid, $contextid));
+                                                 FROM {question_categories} qc2
+                                                 JOIN {question} q on q.category = qc2.id
+                                                WHERE qc2.contextid != ?
+                                                  AND q.id $sql", $params);
+
         // And now, simply insert all the question categories (complete question bank)
         // for those contexts if we have found any
         if ($contexts) {
             list($contextssql, $contextparams) = $DB->get_in_or_equal($contexts);
-            $params = array_merge(array($backupid), $contextparams);
-            $DB->execute("INSERT INTO {backup_ids_temp} (backupid, itemname, itemid)
-                          SELECT ?, 'question_category', id
-                            FROM {question_categories}
-                           WHERE contextid $contextssql", $params);
+            $rs = $DB->get_fieldset_sql("SELECT id
+                                            FROM {question_categories}
+                                           WHERE contextid $contextssql", $contextparams);
+            foreach($rs as $record) {
+                $cache_qc->set($record, 1);
+            }
         }
     }
 
@@ -74,7 +85,7 @@ abstract class backup_question_dbops extends backup_dbops {
      * Delete all the annotated questions present in backup_ids_temp
      */
     public static function delete_temp_questions($backupid) {
-        global $DB;
-        $DB->delete_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'question'));
+        $cache = backup_muc_manager::get('question');
+        $cache->purge();
     }
 }
diff --git a/backup/util/dbops/backup_structure_dbops.class.php b/backup/util/dbops/backup_structure_dbops.class.php
index 4ceb221..ca492ac 100644
--- a/backup/util/dbops/backup_structure_dbops.class.php
+++ b/backup/util/dbops/backup_structure_dbops.class.php
@@ -34,13 +34,30 @@ abstract class backup_structure_dbops extends backup_dbops {
 
     public static function get_iterator($element, $params, $processor) {
         global $DB;
+
         // Check we are going to get_iterator for one backup_nested_element
         if (! $element instanceof backup_nested_element) {
             throw new base_element_struct_exception('backup_nested_element_expected');
         }
+
+        if ($params) {
+            $param_values = self::convert_params_to_values($params, $processor);
+        } else {
+            $param_values = array();
+        }
+
+        if ($element->procparams2) {
+            $param_values2 = self::convert_params_to_values($element->procparams2, $processor);
+        } else {
+            $param_values2 = array();
+        }
+
+        list($sql2, $params2) =  $element->get_source_sql_using_backup_ids($param_values2);
+
         // If var_array, table and sql are null, and element has no final elements it is one nested element without source
         // Just return one 1 element iterator without information
         if ($element->get_source_array() === null && $element->get_source_table() === null &&
+            $element->get_source_cache() === null && $sql2 === null &&
             $element->get_source_sql() === null && count($element->get_final_elements()) == 0) {
             return new backup_array_iterator(array(0 => null));
 
@@ -48,10 +65,16 @@ abstract class backup_structure_dbops extends backup_dbops {
             return new backup_array_iterator($element->get_source_array());
 
         } else if ($element->get_source_table() !== null) { // It's one table, return recordset iterator
-            return $DB->get_recordset($element->get_source_table(), self::convert_params_to_values($params, $processor), $element->get_source_table_sortby());
+            return $DB->get_recordset($element->get_source_table(), $param_values, $element->get_source_table_sortby());
 
         } else if ($element->get_source_sql() !== null) { // It's one sql, return recordset iterator
-            return $DB->get_recordset_sql($element->get_source_sql(), self::convert_params_to_values($params, $processor));
+            return $DB->get_recordset_sql($element->get_source_sql(), $param_values);
+
+        } else if (!is_null($sql2)) { // It's one sql, return recordset iterator but using backup ids cache
+            return $DB->get_recordset_sql($sql2, $params2);
+
+        } else if ($element->get_source_cache() !== null) { // Purely out of the cache
+            return new backup_array_iterator($element->get_source_cache());
 
         } else { // No sources, supress completely, using null iterator
             return new backup_null_iterator();
@@ -96,11 +119,9 @@ abstract class backup_structure_dbops extends backup_dbops {
         if ($itemid <= 0 || is_null($itemid)) {
             return;
         }
-        // TODO: Analyze if some static (and limited) cache by the 3 params could save us a bunch of record_exists() calls
-        // Note: Sure it will!
-        if (!$DB->record_exists('backup_ids_temp', array('backupid' => $backupid, 'itemname' => $itemname, 'itemid' => $itemid))) {
-            $DB->insert_record('backup_ids_temp', array('backupid' => $backupid, 'itemname' => $itemname, 'itemid' => $itemid));
-        }
+
+        $cache = backup_muc_manager::get('' . $itemname);
+        $cache->set($itemid, 1);
     }
 
     /**
@@ -158,20 +179,23 @@ abstract class backup_structure_dbops extends backup_dbops {
     public static function move_annotations_to_final($backupid, $itemname, \core\progress\base $progress) {
         global $DB;
         $progress->start_progress('move_annotations_to_final');
-        $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $backupid, 'itemname' => $itemname));
+        $cache = backup_muc_manager::get('' . $itemname);
+        $cache_content = $cache->get_store()->find_all();
+
+        $cache_final = backup_muc_manager::get('' . $itemname . 'final');
+        $cache_content_final = $cache_final->get_store()->find_all();
+
         $progress->progress();
-        foreach($rs as $annotation) {
+        foreach($cache_content as $annotation_id) {
+            $annotation = $cache->get($annotation_id);
             // If corresponding 'itemfinal' annotation does not exist, update 'item' to 'itemfinal'
-            if (! $DB->record_exists('backup_ids_temp', array('backupid' => $backupid,
-                                                              'itemname' => $itemname . 'final',
-                                                              'itemid' => $annotation->itemid))) {
-                $DB->set_field('backup_ids_temp', 'itemname', $itemname . 'final', array('id' => $annotation->id));
+            if (!isset($cache_content_final[$annotation_id])) {
+                $cache_final->set($annotation_id, $annotation);
             }
             $progress->progress();
         }
-        $rs->close();
         // All the remaining $itemname annotations can be safely deleted
-        $DB->delete_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => $itemname));
+        $cache->purge();
         $progress->end_progress();
     }
 
@@ -179,7 +203,8 @@ abstract class backup_structure_dbops extends backup_dbops {
      * Returns true/false if there are annotations for a given item
      */
     public static function annotations_exist($backupid, $itemname) {
-        global $DB;
-        return (bool)$DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => $itemname));
+        $cache = backup_muc_manager::get('' . $itemname);
+        $cache_content = $cache->get_store()->find_all();
+        return !empty($cache_content);
     }
 }
diff --git a/backup/util/dbops/restore_controller_dbops.class.php b/backup/util/dbops/restore_controller_dbops.class.php
index b68358e..5ac7cdb 100644
--- a/backup/util/dbops/restore_controller_dbops.class.php
+++ b/backup/util/dbops/restore_controller_dbops.class.php
@@ -103,28 +103,10 @@ abstract class restore_controller_dbops extends restore_dbops {
     }
 
     public static function create_restore_temp_tables($restoreid) {
-        global $CFG, $DB;
-        $dbman = $DB->get_manager(); // We are going to use database_manager services
-
-        if ($dbman->table_exists('backup_ids_temp')) { // Table exists, from restore prechecks
-            // TODO: Improve this by inserting/selecting some record to see there is restoreid match
-            // TODO: If not match, exception, table corresponds to another backup/restore operation
-            return true;
-        }
-        backup_controller_dbops::create_backup_ids_temp_table($restoreid);
-        backup_controller_dbops::create_backup_files_temp_table($restoreid);
         return false;
     }
 
     public static function drop_restore_temp_tables($backupid) {
-        global $DB;
-        $dbman = $DB->get_manager(); // We are going to use database_manager services
-
-        $targettablenames = array('backup_ids_temp', 'backup_files_temp');
-        foreach ($targettablenames as $targettablename) {
-            $table = new xmldb_table($targettablename);
-            $dbman->drop_table($table); // And drop it
-        }
         // Invalidate the backup_ids caches.
         restore_dbops::reset_backup_ids_cached();
     }
diff --git a/backup/util/dbops/restore_dbops.class.php b/backup/util/dbops/restore_dbops.class.php
index bd00c32..d3b0ea5 100644
--- a/backup/util/dbops/restore_dbops.class.php
+++ b/backup/util/dbops/restore_dbops.class.php
@@ -29,39 +29,6 @@
  */
 abstract class restore_dbops {
     /**
-     * Keep cache of backup records.
-     * @var array
-     * @todo MDL-25290 static should be replaced with MUC code.
-     */
-    private static $backupidscache = array();
-    /**
-     * Keep track of backup ids which are cached.
-     * @var array
-     * @todo MDL-25290 static should be replaced with MUC code.
-     */
-    private static $backupidsexist = array();
-    /**
-     * Count is expensive, so manually keeping track of
-     * backupidscache, to avoid memory issues.
-     * @var int
-     * @todo MDL-25290 static should be replaced with MUC code.
-     */
-    private static $backupidscachesize = 2048;
-    /**
-     * Count is expensive, so manually keeping track of
-     * backupidsexist, to avoid memory issues.
-     * @var int
-     * @todo MDL-25290 static should be replaced with MUC code.
-     */
-    private static $backupidsexistsize = 10240;
-    /**
-     * Slice backupids cache to add more data.
-     * @var int
-     * @todo MDL-25290 static should be replaced with MUC code.
-     */
-    private static $backupidsslice = 512;
-
-    /**
      * Return one array containing all the tasks that have been included
      * in the restore process. Note that these tasks aren't built (they
      * haven't steps nor ids data available)
@@ -158,7 +125,7 @@ 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. At the same time, store all the mapping into backup_ids_temp
+     * with any problem found. At the same time, store all the mapping into backup_ids cache
      * 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
      */
@@ -168,8 +135,10 @@ abstract class restore_dbops {
         $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, info');
-        foreach ($rs as $recrole) {
+        $cache = backup_muc_manager::get('role');
+        $rs = $cache->get_store()->find_all();
+        foreach ($rs as $itemid) {
+            $recrole = (object) $cache->get($itemid);
             // 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) {
@@ -194,7 +163,6 @@ abstract class restore_dbops {
                 }
             }
         }
-        $rs->close();
         return $problems;
     }
 
@@ -205,45 +173,18 @@ abstract class restore_dbops {
      * @param string $itemname name of the item
      * @param int $itemid id of item
      * @return array backup id's
-     * @todo MDL-25290 replace static backupids* with MUC code
      */
     protected static function get_backup_ids_cached($restoreid, $itemname, $itemid) {
-        global $DB;
-
-        $key = "$itemid $itemname $restoreid";
+        $cache_ids = backup_muc_manager::get('' . $itemname);
 
         // If record exists in cache then return.
-        if (isset(self::$backupidsexist[$key]) && isset(self::$backupidscache[$key])) {
-            // Return a copy of cached data, to avoid any alterations in cached data.
-            return clone self::$backupidscache[$key];
-        }
-
-        // Clean cache, if it's full.
-        if (self::$backupidscachesize <= 0) {
-            // Remove some records, to keep memory in limit.
-            self::$backupidscache = array_slice(self::$backupidscache, self::$backupidsslice, null, true);
-            self::$backupidscachesize = self::$backupidscachesize + self::$backupidsslice;
-        }
-        if (self::$backupidsexistsize <= 0) {
-            self::$backupidsexist = array_slice(self::$backupidsexist, self::$backupidsslice, null, true);
-            self::$backupidsexistsize = self::$backupidsexistsize + self::$backupidsslice;
-        }
-
-        // Retrive record from database.
-        $record = array(
-            'backupid' => $restoreid,
-            'itemname' => $itemname,
-            'itemid'   => $itemid
-        );
-        if ($dbrec = $DB->get_record('backup_ids_temp', $record)) {
-            self::$backupidsexist[$key] = $dbrec->id;
-            self::$backupidscache[$key] = $dbrec;
-            self::$backupidscachesize--;
-            self::$backupidsexistsize--;
-            return $dbrec;
-        } else {
-            return false;
+        $result = $cache_ids->get($itemid);
+        if ($result) {
+            $result = (object) $result;
+            $result->itemname = $itemname;
+            $result->itemid = $itemid;
         }
+        return $result;
     }
 
     /**
@@ -254,12 +195,9 @@ abstract class restore_dbops {
      * @param int $itemid id of item
      * @param array $extrarecord extra record which needs to be updated
      * @return void
-     * @todo MDL-25290 replace static BACKUP_IDS_* with MUC code
      */
     protected static function set_backup_ids_cached($restoreid, $itemname, $itemid, $extrarecord) {
-        global $DB;
-
-        $key = "$itemid $itemname $restoreid";
+        $cache = backup_muc_manager::get('' . $itemname);
 
         $record = array(
             'backupid' => $restoreid,
@@ -268,30 +206,16 @@ abstract class restore_dbops {
         );
 
         // If record is not cached then add one.
-        if (!isset(self::$backupidsexist[$key])) {
-            // If we have this record in db, then just update this.
-            if ($existingrecord = $DB->get_record('backup_ids_temp', $record)) {
-                self::$backupidsexist[$key] = $existingrecord->id;
-                self::$backupidsexistsize--;
-                self::update_backup_cached_record($record, $extrarecord, $key, $existingrecord);
-            } else {
-                // Add new record to cache and db.
-                $recorddefault = array (
+        if (!$cache->get($itemid)) {
+            // Add new record to cache.
+            $recorddefault = array (
                     'newitemid' => 0,
                     'parentitemid' => null,
                     'info' => null);
-                $record = array_merge($record, $recorddefault, $extrarecord);
-                $record['id'] = $DB->insert_record('backup_ids_temp', $record);
-                self::$backupidsexist[$key] = $record['id'];
-                self::$backupidsexistsize--;
-                if (self::$backupidscachesize > 0) {
-                    // Cache new records if we haven't got many yet.
-                    self::$backupidscache[$key] = (object) $record;
-                    self::$backupidscachesize--;
-                }
-            }
+            $record = array_merge($record, $recorddefault, $extrarecord);
+            $cache->set($itemid, $record);
         } else {
-            self::update_backup_cached_record($record, $extrarecord, $key);
+            self::update_backup_cached_record($record, $extrarecord);
         }
     }
 
@@ -303,27 +227,22 @@ abstract class restore_dbops {
      * @param string $key unique key which is used to identify cached record
      * @param stdClass $existingrecord (optional) existing record
      */
-    protected static function update_backup_cached_record($record, $extrarecord, $key, $existingrecord = null) {
-        global $DB;
+    protected static function update_backup_cached_record($record, $extrarecord, $existingrecord = null) {
+        $cache = backup_muc_manager::get('' . $record['itemname']);
         // Update only if extrarecord is not empty.
         if (!empty($extrarecord)) {
-            $extrarecord['id'] = self::$backupidsexist[$key];
-            $DB->update_record('backup_ids_temp', $extrarecord);
             // Update existing cache or add new record to cache.
-            if (isset(self::$backupidscache[$key])) {
-                $record = array_merge((array)self::$backupidscache[$key], $extrarecord);
-                self::$backupidscache[$key] = (object) $record;
-            } else if (self::$backupidscachesize > 0) {
+            if ($cache->get($record['itemid'])) {
+                $trecord = array_merge($cache->get($record['itemid']), $extrarecord);
+            } else {
                 if ($existingrecord) {
-                    self::$backupidscache[$key] = $existingrecord;
+                    $trecord = $existingrecord;
                 } else {
-                    // Retrive record from database and cache updated records.
-                    self::$backupidscache[$key] = $DB->get_record('backup_ids_temp', $record);
+                    $trecord = array();
                 }
-                $record = array_merge((array)self::$backupidscache[$key], $extrarecord);
-                self::$backupidscache[$key] = (object) $record;
-                self::$backupidscachesize--;
+                $trecord = array_merge($record, $extrarecord);
             }
+            $cache->set($record['itemid'], $trecord);
         }
     }
 
@@ -338,18 +257,9 @@ abstract class restore_dbops {
      * (drop & restore) of the table that may happen once the prechecks have ended. All
      * the rest of operations are always routed via {@link set_backup_ids_record()}, 1 by 1,
      * keeping the caches on sync.
-     *
-     * @todo MDL-25290 static should be replaced with MUC code.
      */
     public static function reset_backup_ids_cached() {
         // Reset the ids cache.
-        $cachetoadd = count(self::$backupidscache);
-        self::$backupidscache = array();
-        self::$backupidscachesize = self::$backupidscachesize + $cachetoadd;
-        // Reset the exists cache.
-        $existstoadd = count(self::$backupidsexist);
-        self::$backupidsexist = array();
-        self::$backupidsexistsize = self::$backupidsexistsize + $existstoadd;
     }
 
     /**
@@ -701,14 +611,16 @@ abstract class restore_dbops {
      * that level are returned
      */
     public static function restore_get_question_banks($restoreid, $contextlevel = null) {
-        global $DB;
-
         $results = array();
-        $qcats = $DB->get_recordset_sql("SELECT itemid, parentitemid AS contextid, info
-                                         FROM {backup_ids_temp}
-                                       WHERE backupid = ?
-                                         AND itemname = 'question_category'", array($restoreid));
-        foreach ($qcats as $qcat) {
+        $cache = backup_muc_manager::get('question_category');
+        $qcats = $cache->get_store()->find_all();
+        foreach ($qcats as $catid) {
+            $temp = $cache->get($catid);
+            $qcat = (object) array(
+                    'itemid' => $catid,
+                    'contextid' => $temp['parentitemid'],
+                    'info' => $temp['info'],
+                    );
             // If this qcat context haven't been acummulated yet, do that
             if (!isset($results[$qcat->contextid])) {
                 $info = backup_controller_dbops::decode_backup_temp_info($qcat->info);
@@ -718,7 +630,6 @@ abstract class restore_dbops {
                 }
             }
         }
-        $qcats->close();
         // Sort by value (contextlevel from CONTEXT_SYSTEM downto CONTEXT_MODULE)
         asort($results);
         return $results;
@@ -732,15 +643,16 @@ abstract class restore_dbops {
         global $DB;
 
         $results = array();
-        $qcats = $DB->get_recordset_sql("SELECT itemid, info
-                                         FROM {backup_ids_temp}
-                                        WHERE backupid = ?
-                                          AND itemname = 'question_category'
-                                          AND parentitemid = ?", array($restoreid, $contextid));
-        foreach ($qcats as $qcat) {
-            $results[$qcat->itemid] = backup_controller_dbops::decode_backup_temp_info($qcat->info);
+        $cache = backup_muc_manager::get('question_category');
+        $qcats = $cache->get_store()->find_all();
+        foreach ($qcats as $qcat_id) {
+            $qcat = $cache->get($qcat_id);
+            if ($qcat['parentitemid'] != $contextid) {
+                continue;
+            }
+
+            $results[$qcat_id] = backup_controller_dbops::decode_backup_temp_info($qcat['info']);
         }
-        $qcats->close();
 
         return $results;
     }
@@ -831,15 +743,16 @@ abstract class restore_dbops {
         global $DB;
 
         $results = array();
-        $qs = $DB->get_recordset_sql("SELECT itemid, info
-                                      FROM {backup_ids_temp}
-                                     WHERE backupid = ?
-                                       AND itemname = 'question'
-                                       AND parentitemid = ?", array($restoreid, $qcatid));
-        foreach ($qs as $q) {
-            $results[$q->itemid] = backup_controller_dbops::decode_backup_temp_info($q->info);
+        $cache = backup_muc_manager::get('question');
+        $qs = $cache->get_store()->find_all();
+        foreach ($qs as $q_id) {
+            $q = $cache->get($q_id);
+            if ($q['parentitemid'] != $qcatid) {
+                continue;
+            }
+
+            $results[$q_id] = backup_controller_dbops::decode_backup_temp_info($q['info']);
         }
-        $qs->close();
         return $results;
     }
 
@@ -889,56 +802,51 @@ abstract class restore_dbops {
             $newcontextid = $newcontextrecord->newitemid;
         }
 
-        // Sometimes it's possible to have not the oldcontextids stored into backup_ids_temp->parentitemid
-        // columns (because we have used them to store other information). This happens usually with
-        // all the question related backup_ids_temp records. In that case, it's safe to ignore that
-        // matching as far as we are always restoring for well known oldcontexts and olditemids
-        $parentitemctxmatchsql = ' AND i.parentitemid = f.contextid ';
-        if ($skipparentitemidctxmatch) {
-            $parentitemctxmatchsql = '';
-        }
-
         // Important: remember how files have been loaded to backup_files_temp
         //   - info: contains the whole original object (times, names...)
         //   (all them being original ids as loaded from xml)
 
-        // itemname = null, we are going to match only by context, no need to use itemid (all them are 0)
-        if ($itemname == null) {
-            $sql = "SELECT id AS bftid, contextid, component, filearea, itemid, itemid AS newitemid, info
-                      FROM {backup_files_temp}
-                     WHERE backupid = ?
-                       AND contextid = ?
-                       AND component = ?
-                       AND filearea  = ?";
-            $params = array($restoreid, $oldcontextid, $component, $filearea);
-
-        // itemname not null, going to join with backup_ids to perform the old-new mapping of itemids
-        } else {
-            $sql = "SELECT f.id AS bftid, f.contextid, f.component, f.filearea, f.itemid, i.newitemid, f.info
-                      FROM {backup_files_temp} f
-                      JOIN {backup_ids_temp} i ON i.backupid = f.backupid
-                                              $parentitemctxmatchsql
-                                              AND i.itemid = f.itemid
-                     WHERE f.backupid = ?
-                       AND f.contextid = ?
-                       AND f.component = ?
-                       AND f.filearea = ?
-                       AND i.itemname = ?";
-            $params = array($restoreid, $oldcontextid, $component, $filearea, $itemname);
-            if ($olditemid !== null) { // Just process ONE olditemid intead of the whole itemname
-                $sql .= ' AND i.itemid = ?';
-                $params[] = $olditemid;
-            }
-        }
-
         $fs = get_file_storage();         // Get moodle file storage
         $basepath = $basepath . '/files/';// Get backup file pool base
         // Report progress before query.
         if ($progress) {
             $progress->progress();
         }
-        $rs = $DB->get_recordset_sql($sql, $params);
-        foreach ($rs as $rec) {
+
+        $files_cache = backup_muc_manager::get('file_temp');
+        $rs = $files_cache->get_store()->find_all();
+
+        if ($itemname !== null) {
+            $ids_cache = backup_muc_manager::get('' . $itemname);
+        }
+        foreach ($rs as $rec_id) {
+            $rec = $files_cache->get($rec_id);
+            // Filter out items we don't want.
+            if ($rec->contextid != $oldcontextid || $rec->component != $component || $rec->filearea != $filearea) {
+                continue;
+            }
+            // If itemname = null, we are going to match only by context, no need to use itemid (all them are 0)
+            // itemname not null, going to compare with backup_ids to perform the old-new mapping of itemids
+            if (is_null($itemname)) {
+                $rec->newitemid = $rec->itemid;
+            } else {
+                $id_item = $ids_cache->get($rec->itemid);
+
+                // Sometimes it's possible to have not the oldcontextids stored into backup_ids_temp->parentitemid
+                // columns (because we have used them to store other information). This happens usually with
+                // all the question related backup_ids_temp records. In that case, it's safe to ignore that
+                // matching as far as we are always restoring for well known oldcontexts and olditemids
+                if (!$id_item || (!$skipparentitemidctxmatch && $rec->contextid != $id_item['parentitemid'])) {
+                    continue;
+                }
+
+                // Specific olditemid?
+                if (!is_null($olditemid) && $rec->itemid != $olditemid) {
+                    continue;
+                }
+
+                $rec->newitemid = $id_item['newitemid'];
+            }
             // Report progress each time around loop.
             if ($progress) {
                 $progress->progress();
@@ -1033,11 +941,8 @@ abstract class restore_dbops {
 
                 // store the the new contextid and the new itemid in case we need to remap
                 // references to this file later
-                $DB->update_record('backup_files_temp', array(
-                    'id' => $rec->bftid,
-                    'newcontextid' => $newcontextid,
-                    'newitemid' => $rec->newitemid), true);
-
+                $rec->newcontextid = $newcontextid;
+                $files_cache->set($rec_id, $rec);
             } else {
                 // this is an alias - we can't create it yet so we stash it in a temp
                 // table and will let the final task to deal with it
@@ -1052,7 +957,6 @@ abstract class restore_dbops {
                 }
             }
         }
-        $rs->close();
         return $results;
     }
 
@@ -1097,8 +1001,14 @@ abstract class restore_dbops {
         $themes    = get_list_of_themes(); // Get themes for quick search later
 
         // Iterate over all the included users with newitemid = 0, have to create them
-        $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'user', 'newitemid' => 0), '', 'itemid, parentitemid, info');
-        foreach ($rs as $recuser) {
+        $user_cache = backup_muc_manager::get('user');
+        $rs = $user_cache->get_store()->find_all();
+        foreach ($rs as $uid) {
+            $recuser = (object) $user_cache->get($uid);
+            if ($recuser->newitemid) {
+                continue;
+            }
+
             $progress->progress();
             $user = (object)backup_controller_dbops::decode_backup_temp_info($recuser->info);
 
@@ -1182,7 +1092,7 @@ abstract class restore_dbops {
 
             // Done, let's create the user and annotate its id
             $newuserid = $DB->insert_record('user', $user);
-            self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, $newuserid);
+            self::set_backup_ids_record($restoreid, 'user', $uid, $newuserid);
             // Let's create the user context and annotate it (we need it for sure at least for files)
             // but for deleted users that don't have a context anymore (MDL-30192). We are done for them
             // and nothing else (custom fields, prefs, tags, files...) will be created.
@@ -1248,7 +1158,6 @@ abstract class restore_dbops {
                         $recuser->parentitemid, $userid, null, null, null, false, $progress);
             }
         }
-        $rs->close();
         $progress->end_progress();
     }
 
@@ -1512,15 +1421,16 @@ abstract class restore_dbops {
         }
 
         // Prepare for reporting progress.
-        $conditions = array('backupid' => $restoreid, 'itemname' => 'user');
-        $max = $DB->count_records('backup_ids_temp', $conditions);
+        $user_cache = backup_muc_manager::get('user');
+        $rs = $user_cache->get_store()->find_all();
+        $max = sizeof($rs);
         $done = 0;
         $progress->start_progress('Checking users', $max);
 
         // Iterate over all the included users
-        $rs = $DB->get_recordset('backup_ids_temp', $conditions, '', 'itemid, info');
-        foreach ($rs as $recuser) {
-            $user = (object)backup_controller_dbops::decode_backup_temp_info($recuser->info);
+        foreach ($rs as $uid) {
+            $recuser =  $user_cache->get($uid);
+            $user = (object)backup_controller_dbops::decode_backup_temp_info($recuser['info']);
 
             // Find the correct mnethostid for user before performing any further check
             if (empty($user->mnethosturl) || $user->mnethosturl === $CFG->wwwroot) {
@@ -1539,14 +1449,14 @@ abstract class restore_dbops {
 
             if (is_object($usercheck)) { // No problem, we have found one user in DB to be mapped to
                 // Annotate it, for later process. Set newitemid to mapping user->id
-                self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, $usercheck->id);
+                self::set_backup_ids_record($restoreid, 'user', $uid, $usercheck->id);
 
             } else if ($usercheck === false) { // Found conflict, report it as problem
                  $problems[] = get_string('restoreuserconflict', '', $user->username);
 
             } else if ($usercheck === true) { // User needs to be created, check if we are able
                 if ($cancreateuser) { // Can create user, set newitemid to 0 so will be created later
-                    self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, 0, null, (array)$user);
+                    self::set_backup_ids_record($restoreid, 'user', $uid, 0, null, (array)$user);
 
                 } else { // Cannot create user, report it as problem
                     $problems[] = get_string('restorecannotcreateuser', '', $user->username);
@@ -1558,7 +1468,6 @@ abstract class restore_dbops {
             $done++;
             $progress->progress($done);
         }
-        $rs->close();
         $progress->end_progress();
         return $problems;
     }
@@ -1613,11 +1522,14 @@ abstract class restore_dbops {
 
     public static function set_backup_files_record($restoreid, $filerec) {
         global $DB;
+        static $num_calls = 0;
 
         // Store external files info in `info` field
         $filerec->info     = backup_controller_dbops::encode_backup_temp_info($filerec); // Encode the whole record into info.
-        $filerec->backupid = $restoreid;
-        $DB->insert_record('backup_files_temp', $filerec);
+
+        $cache = backup_muc_manager::get('file_temp');
+        $this_key = str_replace('-', '_', md5(json_encode($filerec)));
+        $cache->set($this_key, $filerec);
     }
 
     public static function set_backup_ids_record($restoreid, $itemname, $itemid, $newitemid = 0, $parentitemid = null, $info = null) {
@@ -1686,14 +1598,17 @@ abstract class restore_dbops {
         // Get the course context
         $coursectx = context_course::instance($courseid);
         // Get all the mapped roles we have
-        $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $restoreid, 'itemname' => 'role'), '', 'itemid, info, newitemid');
-        foreach ($rs as $recrole) {
-            $info = backup_controller_dbops::decode_backup_temp_info($recrole->info);
+
+        $role_cache = backup_muc_manager::get('role');
+        $rs = $role_cache->get_store()->find_all();
+        foreach ($rs as $recrole_id) {
+            $recrole = $role_cache->get($recrole_id);
+            $info = backup_controller_dbops::decode_backup_temp_info($recrole['info']);
             // If it's one mapped role and we have one name for it
-            if (!empty($recrole->newitemid) && !empty($info['nameincourse'])) {
+            if (!empty($recrole['newitemid']) && !empty($info['nameincourse'])) {
                 // If role name doesn't exist, add it
                 $rolename = new stdclass();
-                $rolename->roleid = $recrole->newitemid;
+                $rolename->roleid = $recrole['newitemid'];
                 $rolename->contextid = $coursectx->id;
                 if (!$DB->record_exists('role_names', (array)$rolename)) {
                     $rolename->name = $info['nameincourse'];
@@ -1701,7 +1616,6 @@ abstract class restore_dbops {
                 }
             }
         }
-        $rs->close();
     }
 
     /**
diff --git a/backup/util/dbops/tests/restore_dbops_test.php b/backup/util/dbops/tests/restore_dbops_test.php
index d0fd2cc..a74f812 100644
--- a/backup/util/dbops/tests/restore_dbops_test.php
+++ b/backup/util/dbops/tests/restore_dbops_test.php
@@ -67,37 +67,6 @@ class restore_dbops_testcase extends advanced_testcase {
         $this->assertSame(null, $result->parentitemid);
         $this->assertSame(null, $result->info);
 
-        // Drop the backup_xxx_temp temptables manually, so memory cache won't be invalidated.
-        $dbman->drop_table(new xmldb_table('backup_ids_temp'));
-        $dbman->drop_table(new xmldb_table('backup_files_temp'));
-
-        // Verify the mapping continues returning the same info,
-        // now from cache (the table does not exist).
-        $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
-        $this->assertSame($mapping->itemname, $result->itemname);
-        $this->assertSame($mapping->itemid, $result->itemid);
-        $this->assertSame(0, $result->newitemid);
-        $this->assertSame(null, $result->parentitemid);
-        $this->assertSame(null, $result->info);
-
-        // Recreate the temp table, just to drop it using the restore API in
-        // order to check that, then, the cache becomes invalid for the same request.
-        restore_controller_dbops::create_restore_temp_tables($restoreid);
-        restore_controller_dbops::drop_restore_temp_tables($restoreid);
-
-        // No cached info anymore, so the mapping request will arrive to
-        // DB leading to error (temp table does not exist).
-        try {
-            $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
-            $this->fail('Expecting an exception, none occurred');
-        } catch (Exception $e) {
-            $this->assertTrue($e instanceof dml_exception);
-            $this->assertSame('Table "backup_ids_temp" does not exist', $e->getMessage());
-        }
-
-        // Create the backup_ids temp tables once more.
-        restore_controller_dbops::create_restore_temp_tables($restoreid);
-
         // Send one mapping using the public api with complete values.
         restore_dbops::set_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid,
                 $mapping->newitemid, $mapping->parentitemid, $mapping->info);
@@ -109,7 +78,7 @@ class restore_dbops_testcase extends advanced_testcase {
         $this->assertSame($mapping->parentitemid, $result->parentitemid);
         $this->assertSame($mapping->info, $result->info);
 
-        // Finally, drop the temp tables properly and get the DB error again (memory caches empty).
+        // Finally, drop the temp cache and get the DB error (memory caches empty).
         restore_controller_dbops::drop_restore_temp_tables($restoreid);
         try {
             $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
diff --git a/backup/util/helper/muc_helper.class.php b/backup/util/helper/muc_helper.class.php
new file mode 100644
index 0000000..518bfc9
--- /dev/null
+++ b/backup/util/helper/muc_helper.class.php
@@ -0,0 +1,62 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    moodlecore
+ * @subpackage backup-helper
+ * @copyright  2015 Catalyst-IT
+ * @author     Nigel Cunningham
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+abstract class backup_muc_manager {
+    private static $stores = array();
+
+    /**
+     * get - get a MUC per-request cache for this itemname
+     */
+    public static function get($itemname)
+    {
+        if (!isset(self::$stores[$itemname])) {
+            self::$stores[$itemname] = true;
+        }
+
+        return cache::make_from_params(cache_store::MODE_REQUEST, 'core', 'backup_' . $itemname,
+                array(), array('simplekeys' => true));
+    }
+
+    /**
+     * get_stores - get a list of stores that we have instantiated
+     */
+    public static function get_stores()
+    {
+        return self::$stores;
+    }
+
+    /**
+     * reset - clear all stores and forget them
+     */
+    public static function reset()
+    {
+        foreach (self::get_stores() as $store) {
+            $cache = self::get($store);
+            $store->purge();
+        }
+
+        self::$stores = array();
+    }
+}
diff --git a/backup/util/helper/restore_decode_content.class.php b/backup/util/helper/restore_decode_content.class.php
index 9b1e091..6cc0312 100644
--- a/backup/util/helper/restore_decode_content.class.php
+++ b/backup/util/helper/restore_decode_content.class.php
@@ -92,15 +92,28 @@ class restore_decode_content implements processable {
     protected function get_iterator() {
         global $DB;
 
+        $cache = backup_muc_manager::get('' . $this->mapping);
+        $cache_ids = $cache->get_store()->find_all();
+
+        if (empty($cache_ids)) {
+            $sql = " < 0";
+            $params = array();
+        } else {
+            $newitemids = array();
+            foreach ($cache_ids as $cache_id) {
+                $data = $cache->get($cache_id);
+                $newitemids[$data['newitemid']] = $data['newitemid'];
+            }
+
+            list($sql, $params) = $DB->get_in_or_equal($newitemids);
+        }
+
         // Build the SQL dynamically here
         $fieldslist = 't.' . implode(', t.', $this->fields);
         $sql = "SELECT t.id, $fieldslist
                   FROM {" . $this->tablename . "} t
-                  JOIN {backup_ids_temp} b ON b.newitemid = t.id
-                 WHERE b.backupid = ?
-                   AND b.itemname = ?";
-        $params = array($this->restoreid, $this->mapping);
-        return ($DB->get_recordset_sql($sql, $params));
+                 WHERE t.id $sql";
+        return $DB->get_recordset_sql($sql, $params);
     }
 
     protected function update_iterator_row($row) {
diff --git a/backup/util/helper/restore_prechecks_helper.class.php b/backup/util/helper/restore_prechecks_helper.class.php
index a80a80b..384908d 100644
--- a/backup/util/helper/restore_prechecks_helper.class.php
+++ b/backup/util/helper/restore_prechecks_helper.class.php
@@ -36,7 +36,7 @@
  *
  * TODO: Finish phpdocs
  */
-abstract class restore_prechecks_helper {
+class restore_prechecks_helper {
 
     /**
      * Entry point for all the prechecks to be performed before restore
diff --git a/backup/util/includes/backup_includes.php b/backup/util/includes/backup_includes.php
index 653556f..149c8bd 100644
--- a/backup/util/includes/backup_includes.php
+++ b/backup/util/includes/backup_includes.php
@@ -60,6 +60,7 @@ require_once($CFG->dirroot . '/backup/util/helper/backup_null_iterator.class.php
 require_once($CFG->dirroot . '/backup/util/helper/backup_array_iterator.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_anonymizer_helper.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_file_manager.class.php');
+require_once($CFG->dirroot . '/backup/util/helper/muc_helper.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_moodlexml_parser_processor.class.php'); // Required by backup_general_helper::get_backup_information().
 require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php');
 require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php');
diff --git a/backup/util/includes/restore_includes.php b/backup/util/includes/restore_includes.php
index 70ec44e..df9cb2a 100644
--- a/backup/util/includes/restore_includes.php
+++ b/backup/util/includes/restore_includes.php
@@ -36,6 +36,7 @@ require_once($CFG->dirroot . '/backup/backup.class.php');
 require_once($CFG->dirroot . '/backup/util/structure/restore_path_element.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_anonymizer_helper.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_file_manager.class.php');
+require_once($CFG->dirroot . '/backup/util/helper/muc_helper.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_prechecks_helper.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_moodlexml_parser_processor.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_inforef_parser_processor.class.php');
diff --git a/backup/util/plan/restore_structure_step.class.php b/backup/util/plan/restore_structure_step.class.php
index 94e5ad6..fc9bcbd 100644
--- a/backup/util/plan/restore_structure_step.class.php
+++ b/backup/util/plan/restore_structure_step.class.php
@@ -159,7 +159,7 @@ abstract class restore_structure_step extends restore_step {
      * To send ids pairs to backup_ids_table and to store them into paths
      *
      * This method will send the given itemname and old/new ids to the
-     * backup_ids_temp table, and, at the same time, will save the new id
+     * backup ids cache, and, at the same time, will save the new id
      * into the corresponding restore_path_element for easier access
      * by children. Also will inject the known old context id for the task
      * in case it's going to be used for restoring files later
diff --git a/backup/util/structure/backup_nested_element.class.php b/backup/util/structure/backup_nested_element.class.php
index 32abd9a..a643781 100644
--- a/backup/util/structure/backup_nested_element.class.php
+++ b/backup/util/structure/backup_nested_element.class.php
@@ -33,6 +33,7 @@ class backup_nested_element extends base_nested_element implements processable {
     protected $table;     // Table (without prefix) to fetch records from
     protected $tablesortby; // The field to sort by when using the table methods
     protected $sql;       // Raw SQL to fetch records from
+    protected $sql2;       // Raw SQL to fetch records from
     protected $params;    // Unprocessed params as specified in the set_source() call
     protected $procparams;// Processed (path resolved) params array
     protected $aliases;   // Define DB->final element aliases
@@ -40,6 +41,9 @@ class backup_nested_element extends base_nested_element implements processable {
     protected $counter;   // Number of instances of this element that have been processed
     protected $results;  // Logs the results we encounter during the process.
     protected $logs;     // Some log messages that could be retrieved later.
+    protected $sqlparambackupid; // The backupid to use in finding the cache. Not called backupid because clashes with file_nested_element field
+    protected $cachename; // The name of the backup ids cache used for the query
+    protected $cachematchfield; // The field in the cache object that provides values to match
 
     /**
      * Constructor - instantiates one backup_nested_element, specifying its basic info.
@@ -54,13 +58,18 @@ class backup_nested_element extends base_nested_element implements processable {
         $this->table     = null;
         $this->tablesortby = null;
         $this->sql       = null;
+        $this->sql2      = null;
         $this->params    = null;
         $this->procparams= null;
+        $this->procparams2   = null;
         $this->aliases   = array();
         $this->fileannotations = array();
         $this->counter   = 0;
         $this->results  = array();
         $this->logs     = array();
+        $this->cachename = null;
+        $this->cachematchfield = null;
+        $this->sqlparambackupid = null;
     }
 
     /**
@@ -214,6 +223,24 @@ class backup_nested_element extends base_nested_element implements processable {
         $this->procparams = $this->convert_sql_params($params);
     }
 
+    public function set_source_sql_using_backup_ids($sql, $params, $bid, $cachename, $matchfield) {
+
+        if (!is_array($params)) { // Check we are passing array
+            throw new base_element_struct_exception('setsourcerequiresarrayofparams');
+        }
+        // TODO: Only elements having final elements can set source
+        $this->sql2 = $sql;
+        $this->procparams2 = $this->convert_sql_params($params);
+
+        $this->sqlparambackupid = $bid;
+        $this->cachename = $cachename;
+        $this->cachematchfield = $matchfield;
+    }
+
+    public function set_source_cache($cachename) {
+        $this->cachename = $cachename;
+    }
+
     public function set_source_alias($dbname, $finalelementname) {
         // Get final element
         $finalelement = $this->get_final_element($finalelementname);
@@ -272,10 +299,52 @@ class backup_nested_element extends base_nested_element implements processable {
         return $this->sql;
     }
 
+    public function get_source_sql_using_backup_ids($params) {
+        global $DB;
+
+        /* A cache source will have sqlparambackupid and cachename set but not cachematchfield */
+        if (!$this->cachematchfield) {
+            return null;
+        }
+
+        $cache = backup_muc_manager::get('' . $this->cachename);
+        $cache_content = $cache->get_store()->find_all();
+
+        if (empty($cache_content)) {
+            // No data to match => Empty result set.
+            return array(NULL, NULL);
+        } else {
+            if (is_object($cache_content[0])) {
+                $ids = array_map(function($item) { return $item->{$this->cachematchfield}; }, $cache_content);
+            } else {
+                $ids = array_values($cache_content);
+            }
+            list($sql, $sql_params) = $DB->get_in_or_equal($ids);
+        }
+        $sql = str_replace('*SQL*', $sql, $this->sql2);
+        $params = array_merge($this->procparams2, $sql_params);
+        return array($sql, $params);
+    }
+
     public function get_counter() {
         return $this->counter;
     }
 
+    public function get_source_cache() {
+        if (!$this->cachename || !is_null($this->sql2)) {
+            return null;
+        }
+
+        $cache = backup_muc_manager::get('' . $this->cachename);
+        $cache_content = $cache->get_store()->find_all();
+        if (is_object($cache_content[0])) {
+            return $cache_content;
+        } else {
+            sort ($cache_content);
+            return array_map(function ($id) { $temp = new StdClass(); $temp->id = $id; return $temp; }, $cache_content);
+        }
+    }
+
     /**
      * Simple filler that, matching by name, will fill both attributes and final elements
      * depending of this nested element, debugging info about non-matching elements and/or
diff --git a/backup/util/structure/tests/structure_test.php b/backup/util/structure/tests/structure_test.php
index c8870cc..6eee986 100644
--- a/backup/util/structure/tests/structure_test.php
+++ b/backup/util/structure/tests/structure_test.php
@@ -280,7 +280,7 @@ class backup_structure_testcase extends advanced_testcase {
         $rating->annotate_ids('user2', 'userid');
         $rating->annotate_ids('forum_post', 'itemid');
 
-        // Create the backup_ids_temp table
+        // Create the backup ids cache
         backup_controller_dbops::create_backup_ids_temp_table($backupid);
 
         // Instantiate in memory xml output
@@ -418,26 +418,28 @@ class backup_structure_testcase extends advanced_testcase {
         $c_dissuserid = $DB->count_records_sql('SELECT COUNT(DISTINCT userid) FROM {forum_discussions}');
         $c_ratuserid  = $DB->count_records_sql('SELECT COUNT(DISTINCT userid) FROM {rating}');
         // Count records in backup_ids_table
-        $f_forumpost = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'forum_post'));
-        $f_user1     = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'user1'));
-        $f_user2     = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'user2'));
-        $c_notbackupid = $DB->count_records_select('backup_ids_temp', 'backupid != ?', array($backupid));
+        $cache = backup_muc_manager::get('forum_post');
+        $f_forumpost = sizeof($cache->get_store()->find_all());
+        $cache = backup_muc_manager::get('user1');
+        $f_user1 = sizeof($cache->get_store()->find_all());
+        $cache = backup_muc_manager::get('user2');
+        $f_user2 = sizeof($cache->get_store()->find_all());
         // Peform tests by comparing counts
-        $this->assertEquals($c_notbackupid, 0); // there isn't any record with incorrect backupid
         $this->assertEquals($c_postsid, $f_forumpost); // All posts have been registered
         $this->assertEquals($c_dissuserid, $f_user1); // All users coming from discussions have been registered
         $this->assertEquals($c_ratuserid, $f_user2); // All users coming from ratings have been registered
 
         // Check file annotations against DB
-        $fannotations = $DB->get_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'file'));
+        $cache = backup_muc_manager::get('file');
+        $fannotations = $cache->get_store()->find_all();
         $ffiles       = $DB->get_records('files', array('contextid' => $this->contextid));
         $this->assertEquals(count($fannotations), count($ffiles)); // Same number of recs in both (all files have been annotated)
         foreach ($fannotations as $annotation) { // Check ids annotated
             $this->assertTrue($DB->record_exists('files', array('id' => $annotation->itemid)));
         }
 
-        // Drop the backup_ids_temp table
-        backup_controller_dbops::drop_backup_ids_temp_table('testingid');
+        // Drop the backup ids cache
+        backup_controller_dbops::purge_backup_ids_temp_cache('testingid');
     }
 
     /**
diff --git a/backup/util/xml/parser/processors/grouped_parser_processor.class.php b/backup/util/xml/parser/processors/grouped_parser_processor.class.php
index 748daac..4a195ec 100644
--- a/backup/util/xml/parser/processors/grouped_parser_processor.class.php
+++ b/backup/util/xml/parser/processors/grouped_parser_processor.class.php
@@ -162,14 +162,23 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
      * false if not
      */
     protected function grouped_parent_exists($path) {
+        static $last_path, $last_result;
+
+        if ($path == $last_path) {
+            return $last_result;
+        }
+
+        $last_path = $path;
         $parentpath = $this->get_parent_path($path);
 
         while ($parentpath != '/') {
             if ($this->path_is_grouped($parentpath)) {
+                $last_result = $parentpath;
                 return $parentpath;
             }
             $parentpath = $this->get_parent_path($parentpath);
         }
+        $last_result = false;
         return false;
     }
 
diff --git a/backup/util/xml/parser/progressive_parser.class.php b/backup/util/xml/parser/progressive_parser.class.php
index a5ebc4d..90ebeb1 100644
--- a/backup/util/xml/parser/progressive_parser.class.php
+++ b/backup/util/xml/parser/progressive_parser.class.php
@@ -153,11 +153,23 @@ class progressive_parser {
             throw new progressive_parser_exception('progressive_parser_already_used');
         }
         if ($this->file) {
+            $blocks = (int) ((filesize($this->file) + 8191) / 8192);
+            if ($this->progress) {
+                $this->progress->start_progress('Loading XML file', $blocks);
+            }
+            $block_num = 0;
             $fh = fopen($this->file, 'r');
             while ($buffer = fread($fh, 8192)) {
                 $this->parse($buffer, feof($fh));
+                $block_num++;
+                if ($this->progress && !($block_num % 50)) {
+                    $this->progress->progress($block_num);
+                }
             }
             fclose($fh);
+            if ($this->progress) {
+                $this->progress->end_progress();
+            }
         } else {
             $this->parse($this->contents, true);
         }
@@ -188,7 +200,7 @@ class progressive_parser {
 
     protected function publish($data) {
         $this->processor->receive_chunk($data);
-        if (!empty($this->progress)) {
+        if (FALSE && !empty($this->progress)) {
             // Report indeterminate progress.
             $this->progress->progress();
         }
diff --git a/blocks/html/backup/moodle2/restore_html_block_task.class.php b/blocks/html/backup/moodle2/restore_html_block_task.class.php
index c3ce29b..bc358ef 100644
--- a/blocks/html/backup/moodle2/restore_html_block_task.class.php
+++ b/blocks/html/backup/moodle2/restore_html_block_task.class.php
@@ -71,14 +71,35 @@ class restore_html_block_decode_content extends restore_decode_content {
 
         // Build the SQL dynamically here
         $fieldslist = 't.' . implode(', t.', $this->fields);
+        $cache = backup_muc_manager::get($this->mapping);
+        $cache_ids = $cache->get_store()->find_all();
+
+        if (empty($cache_ids)) {
+            $sql = "< 0";
+            $params = array();
+        } else {
+            $newitemids = array();
+            foreach($cache_ids as $id) {
+                $data = $cache->get($id);
+                $newitemids[$data['newitemid']] = $data['newitemid'];
+            }
+            list($sql, $params) = $DB->get_in_or_equal($newitemids);
+        }
+
         $sql = "SELECT t.id, $fieldslist
-                  FROM {" . $this->tablename . "} t
-                  JOIN {backup_ids_temp} b ON b.newitemid = t.id
-                 WHERE b.backupid = ?
-                   AND b.itemname = ?
-                   AND t.blockname = 'html'";
-        $params = array($this->restoreid, $this->mapping);
-        return ($DB->get_recordset_sql($sql, $params));
+            FROM {" . $this->tablename . "} t
+            WHERE t.blockname = 'html'
+            AND t.id $sql";
+        $result2_rs = ($DB->get_recordset_sql($sql, $params));
+
+        $result2 = array();
+        foreach($result2_rs as $result) {
+            $result2[] = $result;
+        }
+        $result2_rs->close();
+
+        $result2_rs = ($DB->get_recordset_sql($sql, $params));
+        return $result2_rs;
     }
 
     protected function preprocess_field($field) {
diff --git a/cache/classes/helper.php b/cache/classes/helper.php
index 4f0586b..35628523 100644
--- a/cache/classes/helper.php
+++ b/cache/classes/helper.php
@@ -526,11 +526,15 @@ class cache_helper {
      */
     public static function hash_key($key, cache_definition $definition) {
         if ($definition->uses_simple_keys()) {
-            if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key)) {
+            if (preg_match('#[^a-zA-Z0-9_]#', $key) && debugging()) {
                 throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key);
             }
             // We put the key first so that we can be sure the start of the key changes.
-            return (string)$key . '-' . $definition->generate_single_key_prefix();
+            if (strlen((string) $key) <= 32) {
+                return (string) $key;
+            } else {
+                return (string)$key . '-' . $definition->generate_single_key_prefix();
+            }
         }
         $key = $definition->generate_single_key_prefix() . '-' . $key;
         return sha1($key);
diff --git a/cache/classes/loaders.php b/cache/classes/loaders.php
index 6cd588a..f424858 100644
--- a/cache/classes/loaders.php
+++ b/cache/classes/loaders.php
@@ -885,7 +885,7 @@ class cache implements cache_loader {
      *
      * @return cache_store
      */
-    protected function get_store() {
+    public function get_store() {
         return $this->store;
     }
 
diff --git a/cache/tests/cache_test.php b/cache/tests/cache_test.php
index 5a85f14..4507cda 100644
--- a/cache/tests/cache_test.php
+++ b/cache/tests/cache_test.php
@@ -893,8 +893,7 @@ class core_cache_testcase extends advanced_testcase {
 
         // OK data added, data invalidated, and invalidation time has been set.
         // Now we need to manually add back the data and adjust the invalidation time.
-        $hash = md5(cache_store::MODE_APPLICATION.'/phpunit/eventinvalidationtest/'.$CFG->wwwroot.'phpunit');
-        $timefile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/las-cache/lastinvalidation-$hash.cache";
+        $timefile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/las-cache/lastinvalidation.cache";
         // Make sure the file is correct.
         $this->assertTrue(file_exists($timefile));
         $timecont = serialize(cache::now() - 60); // Back 60sec in the past to force it to re-invalidate.
@@ -902,7 +901,7 @@ class core_cache_testcase extends advanced_testcase {
         file_put_contents($timefile, $timecont);
         $this->assertTrue(file_exists($timefile));
 
-        $datafile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/tes-cache/testkey1-$hash.cache";
+        $datafile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/tes-cache/testkey1.cache";
         $datacont = serialize("test data 1");
         make_writable_directory(dirname($datafile));
         file_put_contents($datafile, $datacont);
diff --git a/local/automatedemails b/local/automatedemails
index 1e61526..49b4a1a 160000
--- a/local/automatedemails
+++ b/local/automatedemails
@@ -1 +1 @@
-Subproject commit 1e61526218e8de7588234703db9432e8d7529ed5
+Subproject commit 49b4a1abf96a24a7d72e0820b19ce2bbad6aa4c9
diff --git a/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php b/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php
index b8daa03..f6a29af 100644
--- a/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php
+++ b/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php
@@ -94,13 +94,26 @@ class restore_qtype_multianswer_plugin extends restore_qtype_plugin {
         global $DB;
         // Now that all the questions have been restored, let's process
         // the created question_multianswer sequences (list of question ids).
+        $id_cache = backup_muc_manager::get('question_created');
+        $cache_items = $id_cache->get_store()->find_all();
+
+        if (empty($cache_items)) {
+            return;
+        }
+
+        $newitemids = array();
+        foreach($cache_items as $item_id) {
+            $record = $id_cache->get($item_id);
+            $newitemids[] = $record['newitemid'];
+        }
+
+        list($sql, $params) = $DB->get_in_or_equal($newitemids);
         $rs = $DB->get_recordset_sql("
                 SELECT qma.id, qma.sequence
-                  FROM {question_multianswer} qma
-                  JOIN {backup_ids_temp} bi ON bi.newitemid = qma.question
-                 WHERE bi.backupid = ?
-                   AND bi.itemname = 'question_created'",
-                array($this->get_restoreid()));
+                FROM {question_multianswer} qma
+                WHERE qma.question $sql",
+                $params);
+
         foreach ($rs as $rec) {
             $sequencearr = explode(',', $rec->sequence);
             foreach ($sequencearr as $key => $question) {
diff --git a/question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php b/question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php
index 633d298..d6e775e 100644
--- a/question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php
+++ b/question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php
@@ -104,15 +104,28 @@ class restore_qtype_random_plugin extends restore_qtype_plugin {
         global $DB;
 
         // Update any blank random questiontexts to 0.
+        $id_cache = backup_muc_manager::get('question_created');
+        $cache_items = $id_cache->get_store()->find_all();
+
+        if (empty($cache_items)) {
+            return;
+        }
+
+        $newitemids = array();
+        foreach($cache_items as $item) {
+            $data = $id_cache->get($item);
+            $newitemids[$data['newitemid']] = $data['newitemid'];
+        }
+
+        list($in_sql, $params) = $DB->get_in_or_equal($newitemids);
+        $params[] = '';
+
         $sql = "UPDATE {question}
                    SET questiontext = '0'
                  WHERE qtype = 'random'
-                   AND " . $DB->sql_compare_text('questiontext') . " = ?
-                   AND id IN (SELECT bi.newitemid
-                                FROM {backup_ids_temp} bi
-                               WHERE bi.backupid = ?
-                                 AND bi.itemname = 'question_created')";
+                   AND id $in_sql
+                   AND " . $DB->sql_compare_text('questiontext') . " = ?";
 
-        $DB->execute($sql, array('', $this->get_restoreid()));
+        $DB->execute($sql, $params);
     }
 }
-- 
2.3.5

