diff --git a/backup/moodle2/backup_xml_transformer.class.php b/backup/moodle2/backup_xml_transformer.class.php index a3770d03528..25c8503bcf7 100644 --- a/backup/moodle2/backup_xml_transformer.class.php +++ b/backup/moodle2/backup_xml_transformer.class.php @@ -27,6 +27,13 @@ defined('MOODLE_INTERNAL') || die(); +// Cache for storing link encoders, so that we don't need to call +// register_link_encoders each time backup_xml_transformer is constructed +// TODO MDL-25290 replace global with MUC code. +global $LINKS_ENCODERS_CACHE; + +$LINKS_ENCODERS_CACHE = array(); + /** * Class implementing the @xml_contenttrasnformed logic to be applied in moodle2 backups * @@ -131,7 +138,19 @@ class backup_xml_transformer extends xml_contenttransformer { return $result; } + /** + * Register all available content link encoders + * + * @return array encoder + * @todo MDL-25290 replace LINKS_ENCODERS_CACHE global with MUC code + */ private function register_link_encoders() { + global $LINKS_ENCODERS_CACHE; + // If encoder is linked, then return cached encoder. + if (!empty($LINKS_ENCODERS_CACHE)) { + return $LINKS_ENCODERS_CACHE; + } + $encoders = array(); // Add the course encoder @@ -160,6 +179,7 @@ class backup_xml_transformer extends xml_contenttransformer { // Add local encodes // TODO: Any interest? 1.9 never had that. + $LINKS_ENCODERS_CACHE = $encoders; return $encoders; } } diff --git a/backup/util/dbops/restore_dbops.class.php b/backup/util/dbops/restore_dbops.class.php index e50c6e607df..f9496ea04b2 100644 --- a/backup/util/dbops/restore_dbops.class.php +++ b/backup/util/dbops/restore_dbops.class.php @@ -28,6 +28,38 @@ * TODO: Finish phpdocs */ 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 @@ -151,6 +183,134 @@ abstract class restore_dbops { return $problems; } + /** + * Return cached backup id's + * + * @param int $restoreid id of backup + * @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"; + + // If record exists in cache then return. + if (isset(self::$backupidsexist[$key]) && isset(self::$backupidscache[$key])) { + return 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 self::$backupidscache[$key]; + } else { + return false; + } + } + + /** + * Cache backup ids' + * + * @param int $restoreid id of backup + * @param string $itemname name of the item + * @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"; + + $record = array( + 'backupid' => $restoreid, + 'itemname' => $itemname, + 'itemid' => $itemid, + ); + + // 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 ( + '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--; + } + } + } else { + self::update_backup_cached_record($record, $extrarecord, $key); + } + } + + /** + * Updates existing backup record + * + * @param array $record record which needs to be updated + * @param array $extrarecord extra record which needs to be updated + * @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; + // 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 ($existingrecord) { + self::$backupidscache[$key] = $existingrecord; + } else { + // Retrive record from database and cache updated records. + self::$backupidscache[$key] = $DB->get_record('backup_ids_temp', $record); + } + $record = array_merge((array)self::$backupidscache[$key], $extrarecord); + self::$backupidscache[$key] = (object) $record; + self::$backupidscachesize--; + } + } + } + /** * Given one role, as loaded from XML, perform the best possible matching against the assignable * roles, using different fallback alternatives (shortname, archetype, editingteacher => teacher, defaultcourseroleid) @@ -1219,16 +1379,7 @@ abstract class restore_dbops { $DB->insert_record('backup_files_temp', $filerec); } - public static function set_backup_ids_record($restoreid, $itemname, $itemid, $newitemid = 0, $parentitemid = null, $info = null) { - global $DB; - - // Build the basic (mandatory) record info - $record = array( - 'backupid' => $restoreid, - 'itemname' => $itemname, - 'itemid' => $itemid - ); // Build conditionally the extra record info $extrarecord = array(); if ($newitemid != 0) { @@ -1241,34 +1392,16 @@ abstract class restore_dbops { $extrarecord['info'] = base64_encode(serialize($info)); } - // TODO: Analyze if some static (and limited) cache by the 3 params could save us a bunch of get_record() calls - // Note: Sure it will! And also will improve getter - if (!$dbrec = $DB->get_record('backup_ids_temp', $record)) { // Need to insert the complete record - $DB->insert_record('backup_ids_temp', array_merge($record, $extrarecord)); - - } else { // Need to update the extra record info if there is something to - if (!empty($extrarecord)) { - $extrarecord['id'] = $dbrec->id; - $DB->update_record('backup_ids_temp', $extrarecord); - } - } + self::set_backup_ids_cached($restoreid, $itemname, $itemid, $extrarecord); } public static function get_backup_ids_record($restoreid, $itemname, $itemid) { - global $DB; + $dbrec = self::get_backup_ids_cached($restoreid, $itemname, $itemid); - // Build the basic (mandatory) record info to look for - $record = array( - 'backupid' => $restoreid, - 'itemname' => $itemname, - 'itemid' => $itemid - ); - // TODO: Analyze if some static (and limited) cache by the 3 params could save us a bunch of get_record() calls - if ($dbrec = $DB->get_record('backup_ids_temp', $record)) { - if ($dbrec->info != null) { - $dbrec->info = unserialize(base64_decode($dbrec->info)); - } + if ($dbrec && isset($dbrec->info) && is_string($dbrec->info)) { + $dbrec->info = unserialize(base64_decode($dbrec->info)); } + return $dbrec; } diff --git a/backup/util/plan/base_plan.class.php b/backup/util/plan/base_plan.class.php index 3ed176b0bbf..eab1869d6d3 100644 --- a/backup/util/plan/base_plan.class.php +++ b/backup/util/plan/base_plan.class.php @@ -61,7 +61,12 @@ abstract class base_plan implements checksumable, executable { // Append task settings to plan array, if not present, for comodity foreach ($task->get_settings() as $key => $setting) { if (!in_array($setting, $this->settings)) { - $this->settings[] = $setting; + $name = $setting->get_name(); + if (!isset($this->settings[$name])) { + $this->settings[$name] = $setting; + } else { + throw new base_plan_exception('multiple_settings_by_name_found', $name); + } } } } @@ -84,23 +89,16 @@ abstract class base_plan implements checksumable, executable { /** * return one setting by name, useful to request root/course settings - * that are, by definition, unique by name. Throws exception if multiple - * are found + * that are, by definition, unique by name. * - * TODO: Change this to string indexed array for quicker lookup. Not critical + * @param string $name name of the setting + * @throws base_plan_exception if setting name is not found. */ public function get_setting($name) { $result = null; - foreach ($this->settings as $key => $setting) { - if ($setting->get_name() == $name) { - if ($result != null) { - throw new base_plan_exception('multiple_settings_by_name_found', $name); - } else { - $result = $setting; - } - } - } - if (!$result) { + if (isset($this->settings[$name])) { + $result = $this->settings[$name]; + } else { throw new base_plan_exception('setting_by_name_not_found', $name); } return $result; diff --git a/backup/util/ui/backup_ui_stage.class.php b/backup/util/ui/backup_ui_stage.class.php index 1ce9053dd52..799556157e0 100644 --- a/backup/util/ui/backup_ui_stage.class.php +++ b/backup/util/ui/backup_ui_stage.class.php @@ -124,6 +124,8 @@ class backup_ui_stage_initial extends backup_ui_stage { // Store as a variable so we can iterate by reference $tasks = $this->ui->get_tasks(); // Iterate all tasks by reference + $add_settings = array(); + $dependencies = array(); foreach ($tasks as &$task) { // For the initial stage we are only interested in the root settings if ($task instanceof backup_root_task) { @@ -134,17 +136,23 @@ class backup_ui_stage_initial extends backup_ui_stage { if ($setting->get_name() == 'filename') { continue; } - $form->add_setting($setting, $task); + $add_settings[] = array($setting, $task); } // Then add all dependencies foreach ($settings as &$setting) { if ($setting->get_name() == 'filename') { continue; } - $form->add_dependencies($setting); + $dependencies[] = $setting; } } } + // Add all settings at once. + $form->add_settings($add_settings); + // Add dependencies. + foreach ($dependencies as $depsetting) { + $form->add_dependencies($depsetting); + } $this->stageform = $form; } // Return the form @@ -226,6 +234,8 @@ class backup_ui_stage_schema extends backup_ui_stage { $tasks = $this->ui->get_tasks(); $content = ''; $courseheading = false; + $add_settings = array(); + $dependencies = array(); foreach ($tasks as $task) { if (!($task instanceof backup_root_task)) { if (!$courseheading) { @@ -235,11 +245,11 @@ class backup_ui_stage_schema extends backup_ui_stage { } // First add each setting foreach ($task->get_settings() as $setting) { - $form->add_setting($setting, $task); + $add_settings[] = array($setting, $task); } // The add all the dependencies foreach ($task->get_settings() as $setting) { - $form->add_dependencies($setting); + $dependencies[] = $setting; } } else if ($this->ui->enforce_changed_dependencies()) { // Only show these settings if dependencies changed them. @@ -254,6 +264,10 @@ class backup_ui_stage_schema extends backup_ui_stage { } } } + $form->add_settings($add_settings); + foreach ($dependencies as $depsetting) { + $form->add_dependencies($depsetting); + } $this->stageform = $form; } return $this->stageform; diff --git a/backup/util/ui/base_moodleform.class.php b/backup/util/ui/base_moodleform.class.php index ad992602083..e0ffc0f9fcf 100644 --- a/backup/util/ui/base_moodleform.class.php +++ b/backup/util/ui/base_moodleform.class.php @@ -136,24 +136,38 @@ abstract class base_moodleform extends moodleform { * @return bool */ function add_setting(backup_setting $setting, base_task $task=null) { + return $this->add_settings(array(array($setting, $task))); + } + /** + * Adds multiple backup_settings as elements to the form + * @param array $settingstasks Consists of array($setting, $task) elements + * @return bool + */ + public function add_settings(array $settingstasks) { global $OUTPUT; - // If the setting cant be changed or isn't visible then add it as a fixed setting. - if (!$setting->get_ui()->is_changeable() || $setting->get_visibility() != backup_setting::VISIBLE) { - return $this->add_fixed_setting($setting, $task); - } + $defaults = array(); + foreach ($settingstasks as $st) { + list($setting, $task) = $st; + // If the setting cant be changed or isn't visible then add it as a fixed setting. + if (!$setting->get_ui()->is_changeable() || $setting->get_visibility() != backup_setting::VISIBLE) { + $this->add_fixed_setting($setting, $task); + continue; + } - // First add the formatting for this setting - $this->add_html_formatting($setting); + // First add the formatting for this setting + $this->add_html_formatting($setting); - // The call the add method with the get_element_properties array - call_user_func_array(array($this->_form, 'addElement'), $setting->get_ui()->get_element_properties($task, $OUTPUT)); - $this->_form->setDefault($setting->get_ui_name(), $setting->get_value()); - if ($setting->has_help()) { - list($identifier, $component) = $setting->get_help(); - $this->_form->addHelpButton($setting->get_ui_name(), $identifier, $component); + // Then call the add method with the get_element_properties array + call_user_func_array(array($this->_form, 'addElement'), $setting->get_ui()->get_element_properties($task, $OUTPUT)); + $defaults[$setting->get_ui_name()] = $setting->get_value(); + if ($setting->has_help()) { + list($identifier, $component) = $setting->get_help(); + $this->_form->addHelpButton($setting->get_ui_name(), $identifier, $component); + } + $this->_form->addElement('html', html_writer::end_tag('div')); } - $this->_form->addElement('html', html_writer::end_tag('div')); + $this->_form->setDefaults($defaults); return true; } /** @@ -317,4 +331,4 @@ abstract class base_moodleform extends moodleform { $this->definition_after_data(); } } -} \ No newline at end of file +}