diff --git a/backup/backup.class.php b/backup/backup.class.php index 2f932d35e93..1cc16842318 100644 --- a/backup/backup.class.php +++ b/backup/backup.class.php @@ -98,7 +98,7 @@ abstract class backup implements checksumable { /* * Exception class used by all the @backup stuff */ -class backup_exception extends moodle_exception { +abstract class backup_exception extends moodle_exception { public function __construct($errorcode, $a=NULL, $debuginfo=null) { parent::__construct($errorcode, 'error', '', $a, null, $debuginfo); diff --git a/backup/controller/backup_controller.class.php b/backup/controller/backup_controller.class.php index c3580cadb6a..ddb857f517c 100644 --- a/backup/controller/backup_controller.class.php +++ b/backup/controller/backup_controller.class.php @@ -254,6 +254,10 @@ class backup_controller extends backup implements loggable { return $this->plan->execute(); } + public function get_results() { + return $this->plan->get_results(); + } + public function log($message, $level, $a = null, $depth = null, $display = false) { backup_helper::log($message, $level, $a, $depth, $display, $this->logger); } diff --git a/backup/moodle2/backup_final_task.class.php b/backup/moodle2/backup_final_task.class.php index b28860a9266..52778ada9f2 100644 --- a/backup/moodle2/backup_final_task.class.php +++ b/backup/moodle2/backup_final_task.class.php @@ -84,6 +84,12 @@ class backup_final_task extends backup_task { // to the backup, settings, license, versions and other useful information $this->add_step(new backup_main_structure_step('mainfile', 'moodle_backup.xml')); + // Generate the zip file + $this->add_step(new backup_zip_contents('zip_contents')); + + // Copy the generated zip file to final destination + $this->add_step(new backup_store_backup_file('save_backupfile')); + $this->built = true; } diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index 75937b972bd..bc34174f880 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -1049,6 +1049,59 @@ class backup_main_structure_step extends backup_structure_step { } +/** + * Execution step that will generate the final zip file with all the contents + */ +class backup_zip_contents extends backup_execution_step { + + protected function define_execution() { + + // Get basepath + $basepath = $this->get_basepath(); + + // Get the list of files in directory + $filestemp = get_directory_list($basepath, '', false, true, true); + $files = array(); + foreach ($filestemp as $file) { // Add zip paths and fs paths to all them + $files[$file] = $basepath . '/' . $file; + } + + // Add the log file if exists + $logfilepath = $basepath . '.log'; + if (file_exists($logfilepath)) { + $files['moodle_backup.log'] = $logfilepath; + } + + // Calculate the zip fullpath + $zipfile = $basepath . '/' . $this->get_setting_value('filename'); + + // Get the zip packer + $zippacker = get_file_packer('application/zip'); + + // Zip files + $zippacker->archive_to_pathname($files, $zipfile); + } +} + +/** + * This step will send the generated backup file to its final destination + */ +class backup_store_backup_file extends backup_execution_step { + + protected function define_execution() { + + // Get basepath + $basepath = $this->get_basepath(); + + // Calculate the zip fullpath + $zipfile = $basepath . '/' . $this->get_setting_value('filename'); + + // Perform storage and return it (TODO: shouldn't be array but proper result object) + return array('backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile)); + } +} + + /** * This step will search for all the activity (not calculations, categories nor aggregations) grade items * and put them to the backup_ids tables, to be used later as base to backup them diff --git a/backup/util/dbops/backup_controller_dbops.class.php b/backup/util/dbops/backup_controller_dbops.class.php index 7ceabbed662..01d12d90ec6 100644 --- a/backup/util/dbops/backup_controller_dbops.class.php +++ b/backup/util/dbops/backup_controller_dbops.class.php @@ -178,7 +178,7 @@ abstract class backup_controller_dbops extends backup_dbops { 'activity' => $prefix, 'name' => $setting->get_name(), 'value' => $setting->get_value()); - $settingsinfo[] = (object)$settinginfo; + $settingsinfo[$setting->get_name()] = (object)$settinginfo; } return array($contentinfo, $settingsinfo); } @@ -212,7 +212,7 @@ abstract class backup_controller_dbops extends backup_dbops { 'section ' => $prefix, 'name' => $setting->get_name(), 'value' => $setting->get_value()); - $settingsinfo[] = (object)$settinginfo; + $settingsinfo[$setting->get_name()] = (object)$settinginfo; } return array($contentinfo, $settingsinfo); } @@ -245,7 +245,7 @@ abstract class backup_controller_dbops extends backup_dbops { 'level' => 'course', 'name' => $setting->get_name(), 'value' => $setting->get_value()); - $settingsinfo[] = (object)$settinginfo; + $settingsinfo[$setting->get_name()] = (object)$settinginfo; } return array($contentinfo, $settingsinfo); } @@ -267,7 +267,7 @@ abstract class backup_controller_dbops extends backup_dbops { 'level' => 'root', 'name' => $setting->get_name(), 'value' => $setting->get_value()); - $settingsinfo[] = (object)$settinginfo; + $settingsinfo[$setting->get_name()] = (object)$settinginfo; } return array(null, $settingsinfo); } @@ -291,6 +291,7 @@ abstract class backup_controller_dbops extends backup_dbops { $detailsinfo['mode'] = $bc->get_mode(); $detailsinfo['execution'] = $bc->get_execution(); $detailsinfo['executiontime'] = $bc->get_executiontime(); + $detailsinfo['userid'] = $bc->get_userid(); // Init content placeholders diff --git a/backup/util/factories/backup_factory.class.php b/backup/util/factories/backup_factory.class.php index fe15e0d0497..7e334b8c1c3 100644 --- a/backup/util/factories/backup_factory.class.php +++ b/backup/util/factories/backup_factory.class.php @@ -60,7 +60,7 @@ abstract class backup_factory { // Create file_logger, observing $CFG->backup_file_logger_level check_dir_exists($CFG->dataroot . '/temp/backup', true, true); // need to ensure that temp/backup already exists $fllevel = isset($CFG->backup_file_logger_level) ? $CFG->backup_file_logger_level : $dfltloglevel; - $enabledloggers[] = new file_logger($fllevel, true, true, $CFG->dataroot . '/temp/backup/' . $backupid . '.log.html'); + $enabledloggers[] = new file_logger($fllevel, true, true, $CFG->dataroot . '/temp/backup/' . $backupid . '.log'); // Create database_logger, observing $CFG->backup_database_logger_level and defaulting to LOG_WARNING // and pointing to the backup_logs table diff --git a/backup/util/helper/backup_helper.class.php b/backup/util/helper/backup_helper.class.php index d770c063700..782fa667f77 100644 --- a/backup/util/helper/backup_helper.class.php +++ b/backup/util/helper/backup_helper.class.php @@ -163,6 +163,44 @@ abstract class backup_helper { output_controller::get_instance()->output($message, 'backup', $a, $depth); } } + + /** + * Given one backupid and the (FS) final generated file, perform its final storage + * into Moodle file storage + */ + static public function store_backup_file($backupid, $filepath) { + + // First of all, get some information from the backup_controller to help us decide + list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($backupid); + + // Extract useful information to decide + $hasusers = (bool)$sinfo['users']->value; // Backup has users + $isannon = (bool)$sinfo['anonymize']->value; // Backup is annonymzed + $backupmode= $dinfo[0]->mode; // Backup mode backup::MODE_GENERAL/IMPORT/HUB + $userid = $dinfo[0]->userid; // User->id executing the backup + + // Backups of type IMPORT aren't stored ever + if ($backupmode == backup::MODE_IMPORT) { + return true; + } + + // Backups of type HUB (by definition never have user info) + // are sent to user's "user_tohub" file area. The upload process + // will be responsible for cleaning that filearea once finished + if ($backupmode == backup::MODE_HUB) { + $ctxid = get_context_instance(CONTEXT_USER, $userid)->id; + $fs = get_file_storage(); + $fr = array( + 'contextid' => $ctxid, + 'filearea' => 'user_tohub', + 'itemid' => 0, + 'filepath' => '/', + 'filename' => basename($filepath), + 'timecreated' => time(), + 'timemodified'=> time()); + return $fs->create_file_from_pathname($fr, $filepath); + } + } } /* diff --git a/backup/util/plan/backup_execution_step.class.php b/backup/util/plan/backup_execution_step.class.php index 20a44504607..178a9d97035 100644 --- a/backup/util/plan/backup_execution_step.class.php +++ b/backup/util/plan/backup_execution_step.class.php @@ -31,7 +31,7 @@ abstract class backup_execution_step extends backup_step { public function execute() { // Simple, for now - $this->define_execution(); + return $this->define_execution(); } // Protected API starts here diff --git a/backup/util/plan/base_plan.class.php b/backup/util/plan/base_plan.class.php index fa2f767ac5b..97e8e387f05 100644 --- a/backup/util/plan/base_plan.class.php +++ b/backup/util/plan/base_plan.class.php @@ -32,6 +32,7 @@ abstract class base_plan implements checksumable, executable { protected $name; // One simple name for identification purposes protected $settings; // One array of (accumulated from tasks) base_setting elements protected $tasks; // One array of base_task elements + protected $results; // One array of results received from tasks protected $built; // Flag to know if one plan has been built @@ -42,6 +43,7 @@ abstract class base_plan implements checksumable, executable { $this->name = $name; $this->settings = array(); $this->tasks = array(); + $this->results = array(); $this->built = false; } @@ -68,6 +70,14 @@ abstract class base_plan implements checksumable, executable { return $this->tasks; } + public function add_result($result) { + $this->results = array_merge($this->results, $result); + } + + public function get_results() { + return $this->results; + } + public function get_settings() { return $this->settings; } diff --git a/backup/util/plan/base_task.class.php b/backup/util/plan/base_task.class.php index 0aafe72cac9..1ac7014216d 100644 --- a/backup/util/plan/base_task.class.php +++ b/backup/util/plan/base_task.class.php @@ -144,7 +144,12 @@ abstract class base_task implements checksumable, executable, loggable { throw new base_task_exception('base_task_not_built', $this->name); } foreach ($this->steps as $step) { - $step->execute(); + $result = $step->execute(); + // If step returns array, it will be forwarded to plan + // (TODO: shouldn't be array but proper result object) + if (is_array($result) and !empty($result)) { + $this->plan->add_result($result); + } } } diff --git a/backup/util/settings/base_setting.class.php b/backup/util/settings/base_setting.class.php index ae06436e9eb..41f1c62db2f 100644 --- a/backup/util/settings/base_setting.class.php +++ b/backup/util/settings/base_setting.class.php @@ -52,9 +52,10 @@ abstract class base_setting { const HIDDEN = 0; // Editable/locked (by different causes) - const NOT_LOCKED = 5; - const LOCKED_BY_PERMISSION = 6; + const NOT_LOCKED = 3; + const LOCKED_BY_CONFIG = 5; const LOCKED_BY_HIERARCHY = 7; + const LOCKED_BY_PERMISSION = 9; // Type of change to inform dependencies const CHANGED_VALUE = 1; @@ -237,8 +238,9 @@ abstract class base_setting { if (is_null($status)) { $status = self::NOT_LOCKED; } - if ($status !== self::NOT_LOCKED && $status !== self::LOCKED_BY_PERMISSION && $status !== self::LOCKED_BY_HIERARCHY) { - throw new base_setting_exception('setting_invalid_status'); + if ($status !== self::NOT_LOCKED && $status !== self::LOCKED_BY_CONFIG && + $status !== self::LOCKED_BY_PERMISSION && $status !== self::LOCKED_BY_HIERARCHY) { + throw new base_setting_exception('setting_invalid_status', $status); } return $status; }