/**
  * Sets up a restore plan and stores it within the current restore controller
  */
 protected function load_plan()
 {
     // First of all, we need to introspect the moodle_backup.xml file
     // in order to detect all the required stuff. So, create the
     // monster $info structure where everything will be defined
     $this->log('loading backup info', backup::LOG_DEBUG);
     $this->info = backup_general_helper::get_backup_information($this->tempdir);
     // Set the controller type to the one found in the information
     $this->type = $this->info->type;
     // Set the controller samesite flag as needed
     $this->samesite = backup_general_helper::backup_is_samesite($this->info);
     // Now we load the plan that will be configured following the
     // information provided by the $info
     $this->log('loading controller plan', backup::LOG_DEBUG);
     $this->plan = new rollover_restore_plan($this);
     $this->plan->build();
     // Build plan for this controller
     $this->set_status(backup::STATUS_PLANNED);
 }
 /**
  * Converts the given directory with the backup into moodle2 format
  *
  * @param string $tempdir The directory to convert
  * @param string $format The current format, if already detected
  * @param base_logger|null if the conversion should be logged, use this logger
  * @throws convert_helper_exception
  * @return bool false if unable to find the conversion path, true otherwise
  */
 public static function to_moodle2_format($tempdir, $format = null, $logger = null)
 {
     if (is_null($format)) {
         $format = backup_general_helper::detect_backup_format($tempdir);
     }
     // get the supported conversion paths from all available converters
     $converters = self::available_converters();
     $descriptions = array();
     foreach ($converters as $name) {
         $classname = "{$name}_converter";
         if (!class_exists($classname)) {
             throw new convert_helper_exception('class_not_loaded', $classname);
         }
         if ($logger instanceof base_logger) {
             backup_helper::log('available converter', backup::LOG_DEBUG, $classname, 1, false, $logger);
         }
         $descriptions[$name] = call_user_func($classname . '::description');
     }
     // choose the best conversion path for the given format
     $path = self::choose_conversion_path($format, $descriptions);
     if (empty($path)) {
         if ($logger instanceof base_logger) {
             backup_helper::log('unable to find the conversion path', backup::LOG_ERROR, null, 0, false, $logger);
         }
         return false;
     }
     if ($logger instanceof base_logger) {
         backup_helper::log('conversion path established', backup::LOG_INFO, implode(' => ', array_merge($path, array('moodle2'))), 0, false, $logger);
     }
     foreach ($path as $name) {
         if ($logger instanceof base_logger) {
             backup_helper::log('running converter', backup::LOG_INFO, $name, 0, false, $logger);
         }
         $converter = convert_factory::get_converter($name, $tempdir, $logger);
         $converter->convert();
     }
     // make sure we ended with moodle2 format
     if (!self::detect_moodle2_format($tempdir)) {
         throw new convert_helper_exception('conversion_failed');
     }
     return true;
 }
示例#3
0
/**
 * Rename old backup files to current backup files.
 *
 * When added the setting 'backup_shortname' (MDL-28657) the backup file names did not contain the id of the course.
 * Further we fixed that behaviour by forcing the id to be always present in the file name (MDL-33812).
 * This function will explore the backup directory and attempt to rename the previously created files to include
 * the id in the name. Doing this will put them back in the process of deleting the excess backups for each course.
 *
 * This function manually recreates the file name, instead of using
 * {@link backup_plan_dbops::get_default_backup_filename()}, use it carefully if you're using it outside of the
 * usual upgrade process.
 *
 * @see backup_cron_automated_helper::remove_excess_backups()
 * @link http://tracker.moodle.org/browse/MDL-35116
 * @return void
 * @since Moodle 2.4
 */
function upgrade_rename_old_backup_files_using_shortname() {
    global $CFG;
    $dir = get_config('backup', 'backup_auto_destination');
    $useshortname = get_config('backup', 'backup_shortname');
    if (empty($dir) || !is_dir($dir) || !is_writable($dir)) {
        return;
    }

    require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
    $backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename')));
    $backupword = trim(clean_filename($backupword), '_');
    $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-';
    $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
    $thirtyapril = strtotime('30 April 2012 00:00');

    // Reading the directory.
    if (!$files = scandir($dir)) {
        return;
    }
    foreach ($files as $file) {
        // Skip directories and files which do not start with the common prefix.
        // This avoids working on files which are not related to this issue.
        if (!is_file($dir . '/' . $file) || !preg_match($regex, $file)) {
            continue;
        }

        // Extract the information from the XML file.
        try {
            $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file);
        } catch (backup_helper_exception $e) {
            // Some error while retrieving the backup informations, skipping...
            continue;
        }

        // Make sure this a course backup.
        if ($bcinfo->format !== backup::FORMAT_MOODLE || $bcinfo->type !== backup::TYPE_1COURSE) {
            continue;
        }

        // Skip the backups created before the short name option was initially introduced (MDL-28657).
        // This was integrated on the 2nd of May 2012. Let's play safe with timezone and use the 30th of April.
        if ($bcinfo->backup_date < $thirtyapril) {
            continue;
        }

        // Let's check if the file name contains the ID where it is supposed to be, if it is the case then
        // we will skip the file. Of course it could happen that the course ID is identical to the course short name
        // even though really unlikely, but then renaming this file is not necessary. If the ID is not found in the
        // file name then it was probably the short name which was used.
        $idfilename = $filename . $bcinfo->original_course_id . '-';
        $idregex = '#^'.preg_quote($idfilename, '#').'.*\.mbz$#';
        if (preg_match($idregex, $file)) {
            continue;
        }

        // Generating the file name manually. We do not use backup_plan_dbops::get_default_backup_filename() because
        // it will query the database to get some course information, and the course could not exist any more.
        $newname = $filename . $bcinfo->original_course_id . '-';
        if ($useshortname) {
            $shortname = str_replace(' ', '_', $bcinfo->original_course_shortname);
            $shortname = core_text::strtolower(trim(clean_filename($shortname), '_'));
            $newname .= $shortname . '-';
        }

        $backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig'));
        $date = userdate($bcinfo->backup_date, $backupdateformat, 99, false);
        $date = core_text::strtolower(trim(clean_filename($date), '_'));
        $newname .= $date;

        if (isset($bcinfo->root_settings['users']) && !$bcinfo->root_settings['users']) {
            $newname .= '-nu';
        } else if (isset($bcinfo->root_settings['anonymize']) && $bcinfo->root_settings['anonymize']) {
            $newname .= '-an';
        }
        $newname .= '.mbz';

        // Final check before attempting the renaming.
        if ($newname == $file || file_exists($dir . '/' . $newname)) {
            continue;
        }
        @rename($dir . '/' . $file, $dir . '/' . $newname);
    }
}
示例#4
0
 public function test_get_restore_content_dir()
 {
     global $CFG;
     $this->resetAfterTest(true);
     $this->setAdminUser();
     $c1 = $this->getDataGenerator()->create_course();
     $c2 = $this->getDataGenerator()->create_course((object) array('shortname' => 'Yay'));
     // Creating backup file.
     $bc = new backup_controller(backup::TYPE_1COURSE, $c1->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2);
     $bc->execute_plan();
     $result = $bc->get_results();
     $this->assertTrue(isset($result['backup_destination']));
     $c1backupfile = $result['backup_destination']->copy_content_to_temp();
     $bc->destroy();
     unset($bc);
     // File logging is a mess, we can only try to rely on gc to close handles.
     // Creating backup file.
     $bc = new backup_controller(backup::TYPE_1COURSE, $c2->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2);
     $bc->execute_plan();
     $result = $bc->get_results();
     $this->assertTrue(isset($result['backup_destination']));
     $c2backupfile = $result['backup_destination']->copy_content_to_temp();
     $bc->destroy();
     unset($bc);
     // File logging is a mess, we can only try to rely on gc to close handles.
     $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false;
     $CFG->keeptempdirectoriesonbackup = true;
     // Checking restore dir.
     $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
     $bcinfo = backup_general_helper::get_backup_information($dir);
     $this->assertEquals($bcinfo->original_course_id, $c1->id);
     $this->assertEquals($bcinfo->original_course_fullname, $c1->fullname);
     // Do it again, it should be the same directory.
     $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
     $this->assertEquals($dir, $dir2);
     // Get the second course.
     $dir = tool_uploadcourse_helper::get_restore_content_dir($c2backupfile, null);
     $bcinfo = backup_general_helper::get_backup_information($dir);
     $this->assertEquals($bcinfo->original_course_id, $c2->id);
     $this->assertEquals($bcinfo->original_course_fullname, $c2->fullname);
     // Checking with a shortname.
     $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
     $bcinfo = backup_general_helper::get_backup_information($dir);
     $this->assertEquals($bcinfo->original_course_id, $c1->id);
     $this->assertEquals($bcinfo->original_course_fullname, $c1->fullname);
     // Do it again, it should be the same directory.
     $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
     $this->assertEquals($dir, $dir2);
     // Get the second course.
     $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c2->shortname);
     $bcinfo = backup_general_helper::get_backup_information($dir);
     $this->assertEquals($bcinfo->original_course_id, $c2->id);
     $this->assertEquals($bcinfo->original_course_fullname, $c2->fullname);
     // Get a course that does not exist.
     $errors = array();
     $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
     $this->assertFalse($dir);
     $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors);
     // Trying again without caching. $CFG->keeptempdirectoriesonbackup is required for caching.
     $CFG->keeptempdirectoriesonbackup = false;
     // Checking restore dir.
     $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
     $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null);
     $this->assertNotEquals($dir, $dir2);
     // Checking with a shortname.
     $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
     $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname);
     $this->assertNotEquals($dir, $dir2);
     // Get a course that does not exist.
     $errors = array();
     $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
     $this->assertFalse($dir);
     $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors);
     $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors);
     $this->assertEquals($dir, $dir2);
     $CFG->keeptempdirectoriesonbackup = $oldcfg;
 }
示例#5
0
 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 (in OS temp area it's always backup.mbz)
     $zipfile = $basepath . '/backup.mbz';
     // Get the zip packer
     $zippacker = get_file_packer('application/vnd.moodle.backup');
     // Track overall progress for the 2 long-running steps (archive to
     // pathname, get backup information).
     $reporter = $this->task->get_progress();
     $reporter->start_progress('backup_zip_contents', 2);
     // Zip files
     $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this);
     // If any sub-progress happened, end it.
     if ($this->startedprogress) {
         $this->task->get_progress()->end_progress();
         $this->startedprogress = false;
     } else {
         // No progress was reported, manually move it on to the next overall task.
         $reporter->progress(1);
     }
     // Something went wrong.
     if ($result === false) {
         @unlink($zipfile);
         throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip');
     }
     // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid.
     try {
         backup_general_helper::get_backup_information_from_mbz($zipfile, $this);
     } catch (backup_helper_exception $e) {
         @unlink($zipfile);
         throw new backup_step_exception('error_zip_packing', '', $e->debuginfo);
     }
     // If any sub-progress happened, end it.
     if ($this->startedprogress) {
         $this->task->get_progress()->end_progress();
         $this->startedprogress = false;
     } else {
         $reporter->progress(2);
     }
     $reporter->end_progress();
 }
 public function calculate_checksum()
 {
     // Let's do it using name and tasks (settings are part of tasks)
     return md5($this->name . '-' . backup_general_helper::array_checksum_recursive($this->tasks));
 }
 private function get_backupinfo($info)
 {
     $backupinfo = backup_general_helper::get_backup_information(basename($this->get_task()->get_basepath()));
     if (object_property_exists($backupinfo, $info)) {
         return $backupinfo->{$info};
     }
     return false;
 }
 /**
  * Removes excess backups from the external system and the local file system.
  *
  * The number of backups keep comes from $config->backup_auto_keep.
  *
  * @param stdClass $course object
  * @return bool
  */
 public static function remove_excess_backups($course)
 {
     $config = get_config('backup');
     $keep = (int) $config->backup_auto_keep;
     $storage = $config->backup_auto_storage;
     $dir = $config->backup_auto_destination;
     if ($keep == 0) {
         // Means keep all backup files.
         return true;
     }
     if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
         $dir = null;
     }
     // Clean up excess backups in the course backup filearea.
     if ($storage == 0 || $storage == 2) {
         $fs = get_file_storage();
         $context = context_course::instance($course->id);
         $component = 'backup';
         $filearea = 'automated';
         $itemid = 0;
         $files = array();
         // Store all the matching files into timemodified => stored_file array.
         foreach ($fs->get_area_files($context->id, $component, $filearea, $itemid) as $file) {
             $files[$file->get_timemodified()] = $file;
         }
         if (count($files) <= $keep) {
             // There are less matching files than the desired number to keep there is nothing to clean up.
             return 0;
         }
         // Sort by keys descending (newer to older filemodified).
         krsort($files);
         $remove = array_splice($files, $keep);
         foreach ($remove as $file) {
             $file->delete();
         }
         //mtrace('Removed '.count($remove).' old backup file(s) from the automated filearea');
     }
     // Clean up excess backups in the specified external directory.
     if (!empty($dir) && ($storage == 1 || $storage == 2)) {
         // Calculate backup filename regex, ignoring the date/time/info parts that can be
         // variable, depending of languages, formats and automated backup settings.
         $filename = backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $course->id . '-';
         $regex = '#' . preg_quote($filename, '#') . '.*\\.mbz$#';
         // Store all the matching files into filename => timemodified array.
         $files = array();
         foreach (scandir($dir) as $file) {
             // Skip files not matching the naming convention.
             if (!preg_match($regex, $file, $matches)) {
                 continue;
             }
             // Read the information contained in the backup itself.
             try {
                 $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file);
             } catch (backup_helper_exception $e) {
                 mtrace('Error: ' . $file . ' does not appear to be a valid backup (' . $e->errorcode . ')');
                 continue;
             }
             // Make sure this backup concerns the course and site we are looking for.
             if ($bcinfo->format === backup::FORMAT_MOODLE && $bcinfo->type === backup::TYPE_1COURSE && $bcinfo->original_course_id == $course->id && backup_general_helper::backup_is_samesite($bcinfo)) {
                 $files[$file] = $bcinfo->backup_date;
             }
         }
         if (count($files) <= $keep) {
             // There are less matching files than the desired number to keep there is nothing to clean up.
             return 0;
         }
         // Sort by values descending (newer to older filemodified).
         arsort($files);
         $remove = array_splice($files, $keep);
         foreach (array_keys($remove) as $file) {
             unlink($dir . '/' . $file);
         }
         //mtrace('Removed '.count($remove).' old backup file(s) from external directory');
     }
     return true;
 }
 /**
  * Given one component/filearea/context and
  * optionally one source itemname to match itemids
  * put the corresponding files in the pool
  *
  * If you specify a progress reporter, it will get called once per file with
  * indeterminate progress.
  *
  * @param string $basepath the full path to the root of unzipped backup file
  * @param string $restoreid the restore job's identification
  * @param string $component
  * @param string $filearea
  * @param int $oldcontextid
  * @param int $dfltuserid default $file->user if the old one can't be mapped
  * @param string|null $itemname
  * @param int|null $olditemid
  * @param int|null $forcenewcontextid explicit value for the new contextid (skip mapping)
  * @param bool $skipparentitemidctxmatch
  * @param \core\progress\base $progress Optional progress reporter
  * @return array of result object
  */
 public static function send_files_to_pool($basepath, $restoreid, $component, $filearea, $oldcontextid, $dfltuserid, $itemname = null, $olditemid = null, $forcenewcontextid = null, $skipparentitemidctxmatch = false, \core\progress\base $progress = null)
 {
     global $DB, $CFG;
     $backupinfo = backup_general_helper::get_backup_information(basename($basepath));
     $includesfiles = $backupinfo->include_files;
     $results = array();
     if ($forcenewcontextid) {
         // Some components can have "forced" new contexts (example: questions can end belonging to non-standard context mappings,
         // with questions originally at system/coursecat context in source being restored to course context in target). So we need
         // to be able to force the new contextid
         $newcontextid = $forcenewcontextid;
     } else {
         // Get new context, must exist or this will fail
         $newcontextrecord = self::get_backup_ids_record($restoreid, 'context', $oldcontextid);
         if (!$newcontextrecord || !$newcontextrecord->newitemid) {
             throw new restore_dbops_exception('unknown_context_mapping', $oldcontextid);
         }
         $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\n                      FROM {backup_files_temp}\n                     WHERE backupid = ?\n                       AND contextid = ?\n                       AND component = ?\n                       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\n                      FROM {backup_files_temp} f\n                      JOIN {backup_ids_temp} i ON i.backupid = f.backupid\n                                              {$parentitemctxmatchsql}\n                                              AND i.itemid = f.itemid\n                     WHERE f.backupid = ?\n                       AND f.contextid = ?\n                       AND f.component = ?\n                       AND f.filearea = ?\n                       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) {
         // Report progress each time around loop.
         if ($progress) {
             $progress->progress();
         }
         $file = (object) backup_controller_dbops::decode_backup_temp_info($rec->info);
         // ignore root dirs (they are created automatically)
         if ($file->filepath == '/' && $file->filename == '.') {
             continue;
         }
         // set the best possible user
         $mappeduser = self::get_backup_ids_record($restoreid, 'user', $file->userid);
         $mappeduserid = !empty($mappeduser) ? $mappeduser->newitemid : $dfltuserid;
         // dir found (and not root one), let's create it
         if ($file->filename == '.') {
             $fs->create_directory($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $mappeduserid);
             continue;
         }
         // The file record to restore.
         $file_record = array('contextid' => $newcontextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $rec->newitemid, 'filepath' => $file->filepath, 'filename' => $file->filename, 'timecreated' => $file->timecreated, 'timemodified' => $file->timemodified, 'userid' => $mappeduserid, 'source' => $file->source, 'author' => $file->author, 'license' => $file->license, 'sortorder' => $file->sortorder);
         if (empty($file->repositoryid)) {
             // If contenthash is empty then gracefully skip adding file.
             if (empty($file->contenthash)) {
                 $result = new stdClass();
                 $result->code = 'file_missing_in_backup';
                 $result->message = sprintf('missing file (%s) contenthash in backup for component %s', $file->filename, $component);
                 $result->level = backup::LOG_WARNING;
                 $results[] = $result;
                 continue;
             }
             // this is a regular file, it must be present in the backup pool
             $backuppath = $basepath . backup_file_manager::get_backup_content_file_location($file->contenthash);
             // Some file types do not include the files as they should already be
             // present. We still need to create entries into the files table.
             if ($includesfiles) {
                 // The file is not found in the backup.
                 if (!file_exists($backuppath)) {
                     $results[] = self::get_missing_file_result($file);
                     continue;
                 }
                 // create the file in the filepool if it does not exist yet
                 if (!$fs->file_exists($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->filename)) {
                     // If no license found, use default.
                     if ($file->license == null) {
                         $file->license = $CFG->sitedefaultlicense;
                     }
                     $fs->create_file_from_pathname($file_record, $backuppath);
                 }
             } else {
                 // This backup does not include the files - they should be available in moodle filestorage already.
                 // Create the file in the filepool if it does not exist yet.
                 if (!$fs->file_exists($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->filename)) {
                     // Even if a file has been deleted since the backup was made, the file metadata will remain in the
                     // files table, and the file will not be moved to the trashdir.
                     // Files are not cleared from the files table by cron until several days after deletion.
                     if ($foundfiles = $DB->get_records('files', array('contenthash' => $file->contenthash), '', '*', 0, 1)) {
                         // Only grab one of the foundfiles - the file content should be the same for all entries.
                         $foundfile = reset($foundfiles);
                         $fs->create_file_from_storedfile($file_record, $foundfile->id);
                     } else {
                         // A matching existing file record was not found in the database.
                         $results[] = self::get_missing_file_result($file);
                         continue;
                     }
                 }
             }
             // 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);
         } 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
             if (!$fs->file_exists($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->filename)) {
                 $info = new stdClass();
                 // oldfile holds the raw information stored in MBZ (including reference-related info)
                 $info->oldfile = $file;
                 // newfile holds the info for the new file_record with the context, user and itemid mapped
                 $info->newfile = (object) $file_record;
                 restore_dbops::set_backup_ids_record($restoreid, 'file_aliases_queue', $file->id, 0, null, $info);
             }
         }
     }
     $rs->close();
     return $results;
 }
 /**
  * Removes excess backups in the specified external directory from a specified course.
  *
  * @param stdClass $course Course object
  * @param int $now Starting time of the process
  * @return bool Whether or not backups are being removed
  */
 protected static function remove_excess_backups_from_directory($course, $now)
 {
     $config = get_config('backup');
     $dir = $config->backup_auto_destination;
     $isnotvaliddir = !file_exists($dir) || !is_dir($dir) || !is_writable($dir);
     if ($isnotvaliddir) {
         mtrace('Error: ' . $dir . ' does not appear to be a valid directory');
         return false;
     }
     // Calculate backup filename regex, ignoring the date/time/info parts that can be
     // variable, depending of languages, formats and automated backup settings.
     $filename = backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $course->id . '-';
     $regex = '#' . preg_quote($filename, '#') . '.*\\.mbz$#';
     // Store all the matching files into filename => timemodified array.
     $backupfiles = array();
     foreach (scandir($dir) as $backupfile) {
         // Skip files not matching the naming convention.
         if (!preg_match($regex, $backupfile)) {
             continue;
         }
         // Read the information contained in the backup itself.
         try {
             $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $backupfile);
         } catch (backup_helper_exception $e) {
             mtrace('Error: ' . $backupfile . ' does not appear to be a valid backup (' . $e->errorcode . ')');
             continue;
         }
         // Make sure this backup concerns the course and site we are looking for.
         if ($bcinfo->format === backup::FORMAT_MOODLE && $bcinfo->type === backup::TYPE_1COURSE && $bcinfo->original_course_id == $course->id && backup_general_helper::backup_is_samesite($bcinfo)) {
             $backupfiles[$bcinfo->backup_date] = $backupfile;
         }
     }
     $backupstodelete = self::get_backups_to_delete($backupfiles, $now);
     if ($backupstodelete) {
         foreach ($backupstodelete as $backuptodelete) {
             unlink($dir . '/' . $backuptodelete);
         }
         mtrace('Deleted ' . count($backupstodelete) . ' old backup file(s) from external directory');
         return true;
     } else {
         return false;
     }
 }
 /**
  * Return an array of owners and a list of each course they are teachers of.
  *
  * @param object $obj course obj
  * @param string $folder name of folder (optional)
  * @return bool of courses that match the search
  */
 protected function archivecourse($obj, $folder = false)
 {
     global $CFG;
     require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php';
     require_once $CFG->dirroot . '/backup/controller/backup_controller.class.php';
     if (empty($CFG->siteadmins)) {
         // Should not happen on an ordinary site.
         return false;
     } else {
         $admin = get_admin();
     }
     $coursetobackup = $obj["course"]->id;
     // Set this to one existing choice cmid in your dev site.
     $userdoingthebackup = $admin->id;
     // Set this to the id of your admin account.
     try {
         // Prepare path.
         $matchers = array('/\\s/', '/\\//');
         $safeshort = preg_replace($matchers, '-', $obj["course"]->shortname);
         if (empty($obj["course"]->idnumber)) {
             $suffix = '-ID-' . $obj["course"]->id;
         } else {
             $suffix = '-ID-' . $obj["course"]->id . '-IDNUM-' . $obj["course"]->idnumber;
         }
         $archivefile = date("Y-m-d") . "{$suffix}-{$safeshort}.zip";
         $archivepath = str_replace(str_split('\\/:*?"<>|'), '', get_config('tool_coursearchiver', 'coursearchiverpath'));
         // Check for custom folder.
         if (!empty($folder)) {
             $folder = str_replace(str_split('\\/:*?"<>|'), '', $folder);
         }
         // If no custom folder is given, use the current year.
         if (empty($folder)) {
             $folder = date('Y');
         }
         // Final full path of file.
         $path = $CFG->dataroot . '/' . $archivepath . '/' . $folder;
         // If the path doesn't exist, make it so!
         if (!is_dir($path)) {
             umask(00);
             // Create the directory for CourseArchival.
             if (!mkdir($path, $CFG->directorypermissions, true)) {
                 throw new Exception('Archive path could not be created');
             }
             @chmod($path, $dirpermissions);
         }
         // Perform Backup.
         $bc = new backup_controller(backup::TYPE_1COURSE, $coursetobackup, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userdoingthebackup);
         $bc->execute_plan();
         // Execute backup.
         $results = $bc->get_results();
         // Get the file information needed.
         $config = get_config('backup');
         $dir = $config->backup_auto_destination;
         // The backup file will have already been moved, so I have to find it.
         if (!empty($dir)) {
             // Calculate backup filename regex, ignoring the date/time/info parts that can be
             // variable, depending of languages, formats and automated backup settings.
             $filename = backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $obj["course"]->id . '-';
             $regex = '#' . preg_quote($filename, '#') . '.*\\.mbz#';
             // Store all the matching files into filename => timemodified array.
             $files = array();
             foreach (scandir($dir) as $file) {
                 // Skip files not matching the naming convention.
                 if (!preg_match($regex, $file, $matches)) {
                     continue;
                 }
                 // Read the information contained in the backup itself.
                 try {
                     $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file);
                 } catch (backup_helper_exception $e) {
                     throw new Exception('Error: ' . $file . ' does not appear to be a valid backup (' . $e->errorcode . ')');
                     continue;
                 }
                 // Make sure this backup concerns the course and site we are looking for.
                 if ($bcinfo->format === backup::FORMAT_MOODLE && $bcinfo->type === backup::TYPE_1COURSE && $bcinfo->original_course_id == $obj["course"]->id && backup_general_helper::backup_is_samesite($bcinfo)) {
                     $files[$file] = $bcinfo->backup_date;
                 }
             }
             // Sort by values descending (newer to older filemodified).
             arsort($files);
             foreach ($files as $filename => $backupdate) {
                 // Make sure the backup is from today.
                 if (date('m/d/Y', $backupdate) == date('m/d/Y')) {
                     rename($dir . '/' . $filename, $path . '/' . $archivefile);
                 }
                 break;
                 // Just the last backup...thanks!
             }
         } else {
             $file = $results['backup_destination'];
             if (!empty($file)) {
                 $file->copy_content_to($path . '/' . $archivefile);
             } else {
                 throw new Exception('Backup failed');
             }
         }
         $bc->destroy();
         unset($bc);
         if (file_exists($path . '/' . $archivefile)) {
             // Make sure file got moved.
             // Remove Course.
             delete_course($obj["course"]->id, false);
         } else {
             throw new Exception('Course archive file does not exist');
         }
     } catch (Exception $e) {
         return false;
     }
     return true;
 }
示例#12
0
 public function calculate_checksum()
 {
     // Let's do it using name and settings and steps
     return md5($this->name . '-' . backup_general_helper::array_checksum_recursive($this->settings) . backup_general_helper::array_checksum_recursive($this->steps));
 }
 protected function backup_details_list($details)
 {
     $out = $this->backup_detail_display('backuptype', $details->type);
     $out .= $this->backup_detail_display('backupformat', $details->format);
     $out .= $this->backup_detail_display('backupmode', $details->mode);
     $out .= $this->backup_detail_item('backupdate', userdate($details->backup_date));
     $out .= $this->backup_detail_extra_info('moodleversion', $details->moodle_release, $details->moodle_version);
     $out .= $this->backup_detail_extra_info('backupversion', $details->backup_release, $details->backup_version);
     $out .= $this->backup_detail_extra_info('originalwwwroot', $details->original_wwwroot, $details->original_site_identifier_hash);
     if (!empty($details->include_file_references_to_external_content)) {
         $message = '';
         if (backup_general_helper::backup_is_samesite($details)) {
             $message = label::yes() . ' ' . get_string('filereferencessamesite', 'backup');
         } else {
             $message = label::no() . ' ' . get_string('filereferencesnotsamesite', 'backup');
         }
         $out .= $this->backup_detail_item('includefilereferences', $message);
     }
     return $out;
 }
示例#14
0
 /**
  * Displays the details of a backup file
  *
  * @param stdClass $details
  * @param moodle_url $nextstageurl
  * @return string
  */
 public function backup_details($details, $nextstageurl)
 {
     $yestick = $this->output->pix_icon('i/valid', get_string('yes'));
     $notick = $this->output->pix_icon('i/invalid', get_string('no'));
     $html = html_writer::start_tag('div', array('class' => 'backup-restore'));
     $html .= html_writer::start_tag('div', array('class' => 'backup-section'));
     $html .= $this->output->heading(get_string('backupdetails', 'backup'), 2, array('class' => 'header'));
     $html .= $this->backup_detail_pair(get_string('backuptype', 'backup'), get_string('backuptype' . $details->type, 'backup'));
     $html .= $this->backup_detail_pair(get_string('backupformat', 'backup'), get_string('backupformat' . $details->format, 'backup'));
     $html .= $this->backup_detail_pair(get_string('backupmode', 'backup'), get_string('backupmode' . $details->mode, 'backup'));
     $html .= $this->backup_detail_pair(get_string('backupdate', 'backup'), userdate($details->backup_date));
     $html .= $this->backup_detail_pair(get_string('moodleversion', 'backup'), html_writer::tag('span', $details->moodle_release, array('class' => 'moodle_release')) . html_writer::tag('span', '[' . $details->moodle_version . ']', array('class' => 'moodle_version sub-detail')));
     $html .= $this->backup_detail_pair(get_string('backupversion', 'backup'), html_writer::tag('span', $details->backup_release, array('class' => 'moodle_release')) . html_writer::tag('span', '[' . $details->backup_version . ']', array('class' => 'moodle_version sub-detail')));
     $html .= $this->backup_detail_pair(get_string('originalwwwroot', 'backup'), html_writer::tag('span', $details->original_wwwroot, array('class' => 'originalwwwroot')) . html_writer::tag('span', '[' . $details->original_site_identifier_hash . ']', array('class' => 'sitehash sub-detail')));
     if (!empty($details->include_file_references_to_external_content)) {
         $message = '';
         if (backup_general_helper::backup_is_samesite($details)) {
             $message = $yestick . ' ' . get_string('filereferencessamesite', 'backup');
         } else {
             $message = $notick . ' ' . get_string('filereferencesnotsamesite', 'backup');
         }
         $html .= $this->backup_detail_pair(get_string('includefilereferences', 'backup'), $message);
     }
     $html .= html_writer::end_tag('div');
     $html .= html_writer::start_tag('div', array('class' => 'backup-section settings-section'));
     $html .= $this->output->heading(get_string('backupsettings', 'backup'), 2, array('class' => 'header'));
     foreach ($details->root_settings as $label => $value) {
         if ($label == 'filename' or $label == 'user_files') {
             continue;
         }
         $html .= $this->backup_detail_pair(get_string('rootsetting' . str_replace('_', '', $label), 'backup'), $value ? $yestick : $notick);
     }
     $html .= html_writer::end_tag('div');
     if ($details->type === 'course') {
         $html .= html_writer::start_tag('div', array('class' => 'backup-section'));
         $html .= $this->output->heading(get_string('backupcoursedetails', 'backup'), 2, array('class' => 'header'));
         $html .= $this->backup_detail_pair(get_string('coursetitle', 'backup'), $details->course->title);
         $html .= $this->backup_detail_pair(get_string('courseid', 'backup'), $details->course->courseid);
         $html .= html_writer::start_tag('div', array('class' => 'backup-sub-section'));
         $html .= $this->output->heading(get_string('backupcoursesections', 'backup'), 3, array('class' => 'subheader'));
         foreach ($details->sections as $key => $section) {
             $included = $key . '_included';
             $userinfo = $key . '_userinfo';
             if ($section->settings[$included] && $section->settings[$userinfo]) {
                 $value = get_string('sectionincanduser', 'backup');
             } else {
                 if ($section->settings[$included]) {
                     $value = get_string('sectioninc', 'backup');
                 } else {
                     continue;
                 }
             }
             $html .= $this->backup_detail_pair(get_string('backupcoursesection', 'backup', $section->title), $value);
             $table = null;
             foreach ($details->activities as $activitykey => $activity) {
                 if ($activity->sectionid != $section->sectionid) {
                     continue;
                 }
                 if (empty($table)) {
                     $table = new html_table();
                     $table->head = array(get_string('module', 'backup'), get_string('title', 'backup'), get_string('userinfo', 'backup'));
                     $table->colclasses = array('modulename', 'moduletitle', 'userinfoincluded');
                     $table->align = array('left', 'left', 'center');
                     $table->attributes = array('class' => 'activitytable generaltable');
                     $table->data = array();
                 }
                 $name = get_string('pluginname', $activity->modulename);
                 $icon = new pix_icon('icon', $name, $activity->modulename, array('class' => 'iconlarge icon-pre'));
                 $table->data[] = array($this->output->render($icon) . $name, $activity->title, $activity->settings[$activitykey . '_userinfo'] ? $yestick : $notick);
             }
             if (!empty($table)) {
                 $html .= $this->backup_detail_pair(get_string('sectionactivities', 'backup'), html_writer::table($table));
             }
         }
         $html .= html_writer::end_tag('div');
         $html .= html_writer::end_tag('div');
     }
     $html .= $this->output->single_button($nextstageurl, get_string('continue'), 'post');
     $html .= html_writer::end_tag('div');
     return $html;
 }
示例#15
0
 /**
  *
  * @global moodle_database $DB
  * @param core_backup_renderer $renderer
  * @return string
  */
 public function display($renderer)
 {
     global $DB, $USER, $PAGE;
     $format = backup_general_helper::detect_backup_format($this->filepath);
     if ($format !== 'moodle2') {
         return $renderer->invalid_format($format);
     }
     $this->details = backup_general_helper::get_backup_information($this->filepath);
     $url = new moodle_url('/backup/restore.php', array('contextid' => $this->contextid, 'filepath' => $this->filepath, 'stage' => restore_ui::STAGE_SETTINGS));
     $context = get_context_instance_by_id($this->contextid);
     $currentcourse = $context->contextlevel == CONTEXT_COURSE && has_capability('moodle/restore:restorecourse', $context) ? $context->instanceid : false;
     $html = $renderer->course_selector($url, $this->details, $this->categorysearch, $this->coursesearch, $currentcourse);
     return $html;
 }
 /**
  * Renders the destination stage screen
  *
  * @param core_backup_renderer $renderer renderer instance to use
  * @return string HTML code
  */
 public function display(core_backup_renderer $renderer)
 {
     $format = backup_general_helper::detect_backup_format($this->filepath);
     if ($format === backup::FORMAT_MOODLE) {
         // Standard Moodle 2 format, let use get the type of the backup.
         $details = backup_general_helper::get_backup_information($this->filepath);
         if ($details->type === backup::TYPE_1COURSE) {
             $wholecourse = true;
         } else {
             $wholecourse = false;
         }
     } else {
         // Non-standard format to be converted. We assume it contains the
         // whole course for now. However, in the future there might be a callback
         // to the installed converters.
         $wholecourse = true;
     }
     $nextstageurl = new moodle_url('/backup/restore.php', array('contextid' => $this->contextid, 'filepath' => $this->filepath, 'stage' => restore_ui::STAGE_SETTINGS));
     $context = context::instance_by_id($this->contextid);
     if ($context->contextlevel == CONTEXT_COURSE and has_capability('moodle/restore:restorecourse', $context)) {
         $currentcourse = $context->instanceid;
     } else {
         $currentcourse = false;
     }
     return $renderer->course_selector($nextstageurl, $wholecourse, $this->categorysearch, $this->coursesearch, $currentcourse);
 }
 /**
  * Restore one 1-course backup
  */
 protected static function build_course_plan($controller, $courseid)
 {
     $plan = $controller->get_plan();
     $info = $controller->get_info();
     // Add the course task, responsible for restoring
     // all the course related information
     $task = restore_factory::get_restore_course_task($info->course, $courseid);
     $plan->add_task($task);
     $controller->get_progress()->progress();
     // For the given course path, add as many block tasks as necessary
     // TODO: Add blocks, we need to introspect xml here
     $blocks = backup_general_helper::get_blocks_from_path($task->get_taskbasepath());
     foreach ($blocks as $basepath => $name) {
         if ($task = restore_factory::get_restore_block_task($name, $basepath)) {
             $plan->add_task($task);
             $controller->get_progress()->progress();
         } else {
             // TODO: Debug information about block not supported
         }
     }
     // For the given course, add as many section tasks as necessary
     foreach ($info->sections as $sectionid => $section) {
         self::build_section_plan($controller, $sectionid);
     }
 }
示例#18
0
 public function calculate_checksum()
 {
     // Reset current checksum to take it out from calculations!
     $this->checksum = '';
     // Init checksum
     $tempchecksum = md5('backupid-' . $this->backupid . 'type-' . $this->type . 'id-' . $this->id . 'format-' . $this->format . 'interactive-' . $this->interactive . 'mode-' . $this->mode . 'userid-' . $this->userid . 'operation-' . $this->operation . 'status-' . $this->status . 'execution-' . $this->execution . 'plan-' . backup_general_helper::array_checksum_recursive(array($this->plan)) . 'destination-' . backup_general_helper::array_checksum_recursive(array($this->destination)) . 'logger-' . backup_general_helper::array_checksum_recursive(array($this->logger)));
     $this->log('calculating controller checksum', backup::LOG_DEBUG, $tempchecksum);
     return $tempchecksum;
 }
示例#19
0
 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 (in OS temp area it's always backup.mbz)
     $zipfile = $basepath . '/backup.mbz';
     // Get the zip packer
     $zippacker = get_file_packer('application/zip');
     // Zip files
     $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this);
     // Something went wrong.
     if ($result === false) {
         @unlink($zipfile);
         throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip');
     }
     // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid.
     try {
         backup_general_helper::get_backup_information_from_mbz($zipfile);
     } catch (backup_helper_exception $e) {
         @unlink($zipfile);
         throw new backup_step_exception('error_zip_packing', '', $e->debuginfo);
     }
 }