/**
  * 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);
 }
 /**
  * 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;
 }
 /**
  * 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;
 }
 /**
  * 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;
 }
 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;
 }