From 7482013cd2706e018fc8d58051e3a306b102bba6 Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Thu, 30 May 2024 13:05:10 +0200 Subject: [PATCH 1/4] MDL-80191 backup: prepare UI for subsections Backup and restore settings needs to be refactored before introducing subsections. With the current code adding two new hierachy levels (subsections and activities in subsections) will require many unnecessary lines of code because most of the structure is hard-coded. With this patch when backup/restore present the course structure uses a stack to control how mani divs are opened, instead of relying on adhoc class attributes per each div. About the tasks for sections and activities, the class has been refactored. This way when subsections are introduced it will require less lines of code and the final patch will be more comprehensible. --- backup/moodle2/backup_activity_task.class.php | 93 +++++++++---- backup/moodle2/backup_section_task.class.php | 70 ++++++++-- .../moodle2/restore_activity_task.class.php | 95 ++++++++----- backup/moodle2/restore_section_task.class.php | 71 +++++++--- backup/util/plan/base_task.class.php | 13 ++ backup/util/ui/base_moodleform.class.php | 125 +++++++++--------- 6 files changed, 319 insertions(+), 148 deletions(-) diff --git a/backup/moodle2/backup_activity_task.class.php b/backup/moodle2/backup_activity_task.class.php index f0df9d0ab2e..bc84291b733 100644 --- a/backup/moodle2/backup_activity_task.class.php +++ b/backup/moodle2/backup_activity_task.class.php @@ -123,12 +123,12 @@ abstract class backup_activity_task extends backup_task { } // Add some extra settings that related processors are going to need - $this->add_setting(new backup_activity_generic_setting(backup::VAR_MODID, base_setting::IS_INTEGER, $this->moduleid)); - $this->add_setting(new backup_activity_generic_setting(backup::VAR_COURSEID, base_setting::IS_INTEGER, $this->get_courseid())); - $this->add_setting(new backup_activity_generic_setting(backup::VAR_SECTIONID, base_setting::IS_INTEGER, $this->sectionid)); - $this->add_setting(new backup_activity_generic_setting(backup::VAR_MODNAME, base_setting::IS_FILENAME, $this->modulename)); - $this->add_setting(new backup_activity_generic_setting(backup::VAR_ACTIVITYID, base_setting::IS_INTEGER, $this->activityid)); - $this->add_setting(new backup_activity_generic_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $this->contextid)); + $this->add_section_setting(backup::VAR_MODID, base_setting::IS_INTEGER, $this->moduleid); + $this->add_section_setting(backup::VAR_COURSEID, base_setting::IS_INTEGER, $this->get_courseid()); + $this->add_section_setting(backup::VAR_SECTIONID, base_setting::IS_INTEGER, $this->sectionid); + $this->add_section_setting(backup::VAR_MODNAME, base_setting::IS_FILENAME, $this->modulename); + $this->add_section_setting(backup::VAR_ACTIVITYID, base_setting::IS_INTEGER, $this->activityid); + $this->add_section_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $this->contextid); // Create the activity directory $this->add_step(new create_taskbasepath_directory('create_activity_directory')); @@ -269,58 +269,99 @@ abstract class backup_activity_task extends backup_task { $settingprefix = $this->modulename . '_' . $this->moduleid . '_'; // All these are common settings to be shared by all activities. - - // Define activity_include (to decide if the whole task must be really executed) - // Dependent of: - // - activities root setting. - // - section_included setting (if exists). - $settingname = $settingprefix . 'included'; - $activityincluded = new backup_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); - $activityincluded->get_ui()->set_icon(new image_icon('monologo', get_string('pluginname', $this->modulename), - $this->modulename, array('class' => 'iconlarge icon-post ml-1'))); - $this->add_setting($activityincluded); - // Look for "activities" root setting. - $activities = $this->plan->get_setting('activities'); - $activities->add_dependency($activityincluded); + $activityincluded = $this->add_activity_included_setting($settingprefix); if (question_module_uses_questions($this->modulename)) { $questionbank = $this->plan->get_setting('questionbank'); $questionbank->add_dependency($activityincluded); } + $this->add_activity_userinfo_setting($settingprefix, $activityincluded); + + // End of common activity settings, let's add the particular ones. + $this->define_my_settings(); + } + + /** + * Add a setting to the task. This method is used to add a setting to the task + * + * @param int|string $identifier the identifier of the setting + * @param string $type the type of the setting + * @param string|int $value the value of the setting + * @return section_backup_setting the setting added + */ + protected function add_section_setting(int|string $identifier, string $type, string|int $value): activity_backup_setting { + $setting = new backup_activity_generic_setting($identifier, $type, $value); + $this->add_setting($setting); + return $setting; + } + + /** + * Add the section include setting to the task. + * + * @param string $settingprefix the identifier of the setting + * @return activity_backup_setting the setting added + */ + protected function add_activity_included_setting(string $settingprefix): activity_backup_setting { + // Define activity_include (to decide if the whole task must be really executed) + // Dependent of: + // - activities root setting. + // - sectionincluded setting (if exists). + $settingname = $settingprefix . 'included'; + $activityincluded = new backup_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); + $activityincluded->get_ui()->set_icon(new image_icon('monologo', get_string('pluginname', $this->modulename), + $this->modulename, array('class' => 'iconlarge icon-post ml-1'))); + $this->add_setting($activityincluded); + + // Look for "activities" root setting. + $activities = $this->plan->get_setting('activities'); + $activities->add_dependency($activityincluded); + // Look for "sectionincluded" section setting (if exists). $settingname = 'section_' . $this->sectionid . '_included'; if ($this->plan->setting_exists($settingname)) { $sectionincluded = $this->plan->get_setting($settingname); $sectionincluded->add_dependency($activityincluded); } + return $activityincluded; + } - // Define activityuserinfo. Dependent of: + /** + * Add the section userinfo setting to the task. + * + * @param string $settingprefix the identifier of the setting + * @param activity_backup_setting $includefield the setting to depend on + * @return activity_backup_setting the setting added + */ + protected function add_activity_userinfo_setting( + string $settingprefix, + activity_backup_setting $includefield + ): activity_backup_setting { + // Define activity_userinfo. Dependent of: // - users root setting. // - sectionuserinfo setting (if exists). - // - activityincluded setting. + // - includefield setting. $settingname = $settingprefix . 'userinfo'; $activityuserinfo = new backup_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true); + $activityuserinfo->get_ui()->set_label('-'); $activityuserinfo->get_ui()->set_visually_hidden_label( get_string('includeuserinfo_instance', 'core_backup', $this->name) ); $this->add_setting($activityuserinfo); - // Look for "users" root setting. $users = $this->plan->get_setting('users'); $users->add_dependency($activityuserinfo); + // Look for "sectionuserinfo" section setting (if exists). $settingname = 'section_' . $this->sectionid . '_userinfo'; if ($this->plan->setting_exists($settingname)) { $sectionuserinfo = $this->plan->get_setting($settingname); $sectionuserinfo->add_dependency($activityuserinfo); } - // Look for "activityincluded" setting. - $activityincluded->add_dependency($activityuserinfo); - // End of common activity settings, let's add the particular ones. - $this->define_my_settings(); + $includefield->add_dependency($activityuserinfo); + return $activityuserinfo; } /** diff --git a/backup/moodle2/backup_section_task.class.php b/backup/moodle2/backup_section_task.class.php index cdd76c85a2f..16f33a70387 100644 --- a/backup/moodle2/backup_section_task.class.php +++ b/backup/moodle2/backup_section_task.class.php @@ -37,6 +37,11 @@ class backup_section_task extends backup_task { protected $sectionid; + /** + * @var stdClass $section The database section object. + */ + protected stdClass $section; + /** * Constructor - instantiates one object of this class */ @@ -48,6 +53,7 @@ class backup_section_task extends backup_task { throw new backup_task_exception('section_task_section_not_found', $sectionid); } + $this->section = $section; $this->sectionid = $sectionid; parent::__construct($name, $plan); @@ -72,11 +78,11 @@ class backup_section_task extends backup_task { // Set the backup::VAR_CONTEXTID setting to course context as far as next steps require that $coursectxid = context_course::instance($this->get_courseid())->id; - $this->add_setting(new backup_activity_generic_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $coursectxid)); + $this->add_section_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $coursectxid); // Add some extra settings that related processors are going to need - $this->add_setting(new backup_activity_generic_setting(backup::VAR_SECTIONID, base_setting::IS_INTEGER, $this->sectionid)); - $this->add_setting(new backup_activity_generic_setting(backup::VAR_COURSEID, base_setting::IS_INTEGER, $this->get_courseid())); + $this->add_section_setting(backup::VAR_SECTIONID, base_setting::IS_INTEGER, $this->sectionid); + $this->add_section_setting(backup::VAR_COURSEID, base_setting::IS_INTEGER, $this->get_courseid()); // Create the section directory $this->add_step(new create_taskbasepath_directory('create_section_directory')); @@ -145,32 +151,74 @@ class backup_section_task extends backup_task { // All the settings related to this activity will include this prefix. $settingprefix = 'section_' . $this->sectionid . '_'; - // All these are common settings to be shared by all sections. + $incudefield = $this->add_section_included_setting($settingprefix); + $this->add_section_userinfo_setting($settingprefix, $incudefield); + } - $section = $DB->get_record('course_sections', array('id' => $this->sectionid), '*', MUST_EXIST); - $course = $DB->get_record('course', array('id' => $section->course), '*', MUST_EXIST); + /** + * Add a setting to the task. This method is used to add a setting to the task + * + * @param int|string $identifier the identifier of the setting + * @param string $type the type of the setting + * @param string|int $value the value of the setting + * @return section_backup_setting the setting added + */ + protected function add_section_setting(int|string $identifier, string $type, string|int $value): section_backup_setting { + $setting = new backup_section_generic_setting($identifier, $type, $value); + $this->add_setting($setting); + return $setting; + } + + /** + * Add the section included setting to the task. + * + * @param string $settingprefix the identifier of the setting + * @return section_backup_setting the setting added + */ + protected function add_section_included_setting(string $settingprefix): section_backup_setting { + global $DB; + $course = $DB->get_record('course', ['id' => $this->section->course], '*', MUST_EXIST); // Define sectionincluded (to decide if the whole task must be really executed). $settingname = $settingprefix . 'included'; + $sectionincluded = new backup_section_included_setting($settingname, base_setting::IS_BOOLEAN, true); - $sectionincluded->get_ui()->set_label(get_section_name($course, $section)); + + $sectionincluded->get_ui()->set_label(get_section_name($course, $this->section)); $this->add_setting($sectionincluded); + return $sectionincluded; + } + + /** + * Add the section userinfo setting to the task. + * + * @param string $settingprefix the identifier of the setting + * @param section_backup_setting $includefield the setting to depend on + * @return section_backup_setting the setting added + */ + protected function add_section_userinfo_setting( + string $settingprefix, + section_backup_setting $includefield + ): section_backup_setting { // Define sectionuserinfo. Dependent of: // - users root setting. - // - sectionincluded setting. + // - section_included setting. $settingname = $settingprefix . 'userinfo'; + $sectionuserinfo = new backup_section_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true); + $sectionuserinfo->get_ui()->set_label(get_string('includeuserinfo', 'backup')); $sectionuserinfo->get_ui()->set_visually_hidden_label( - get_string('section_prefix', 'core_backup', $section->name ?: $section->section) + get_string('section_prefix', 'core_backup', $this->section->name ?: $this->section->section) ); $this->add_setting($sectionuserinfo); - // Look for "users" root setting. $users = $this->plan->get_setting('users'); $users->add_dependency($sectionuserinfo); // Look for "section_included" section setting. - $sectionincluded->add_dependency($sectionuserinfo); + $includefield->add_dependency($sectionuserinfo); + + return $sectionuserinfo; } } diff --git a/backup/moodle2/restore_activity_task.class.php b/backup/moodle2/restore_activity_task.class.php index 12f6006f114..4948516cd11 100644 --- a/backup/moodle2/restore_activity_task.class.php +++ b/backup/moodle2/restore_activity_task.class.php @@ -288,42 +288,71 @@ abstract class restore_activity_task extends restore_task { * Define the common setting that any restore activity will have */ protected function define_settings() { - // All the settings related to this activity will include this prefix $settingprefix = $this->info->modulename . '_' . $this->info->moduleid . '_'; // All these are common settings to be shared by all activities + $activityincluded = $this->add_activity_included_setting($settingprefix); + $this->add_activity_userinfo_setting($settingprefix, $activityincluded); - // Define activity_include (to decide if the whole task must be really executed) + // End of common activity settings, let's add the particular ones. + $this->define_my_settings(); + } + + /** + * Add the activity included setting to the task. + * + * @param string $settingprefix the identifier of the setting + * @return activity_backup_setting the setting added + */ + protected function add_activity_included_setting(string $settingprefix): activity_backup_setting { + // Define activity_included (to decide if the whole task must be really executed) // Dependent of: - // - activities root setting - // - section_included setting (if exists) + // - activities root setting. + // - sectionincluded setting (if exists). $settingname = $settingprefix . 'included'; - $activity_included = new restore_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); - $activity_included->get_ui()->set_icon(new image_icon('monologo', get_string('pluginname', $this->modulename), - $this->modulename, array('class' => 'iconlarge icon-post ml-1'))); - $this->add_setting($activity_included); - // Look for "activities" root setting + + $activityincluded = new restore_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); + + $activityincluded->get_ui()->set_icon(new image_icon('monologo', get_string('pluginname', $this->modulename), + $this->modulename, ['class' => 'iconlarge icon-post ml-1'])); + $this->add_setting($activityincluded); + // Look for "activities" root setting. $activities = $this->plan->get_setting('activities'); - $activities->add_dependency($activity_included); - // Look for "section_included" section setting (if exists) + $activities->add_dependency($activityincluded); + // Look for "sectionincluded" section setting (if exists). $settingname = 'section_' . $this->info->sectionid . '_included'; if ($this->plan->setting_exists($settingname)) { - $section_included = $this->plan->get_setting($settingname); - $section_included->add_dependency($activity_included); + $sectionincluded = $this->plan->get_setting($settingname); + $sectionincluded->add_dependency($activityincluded); } - // Define activity_userinfo. Dependent of: - // - users root setting - // - section_userinfo setting (if exists) - // - activity_included setting. + return $activityincluded; + } + + /** + * Add the activity userinfo setting to the task. + * + * @param string $settingprefix the identifier of the setting + * @param activity_backup_setting $includefield the activity included setting + * @return activity_backup_setting the setting added + */ + protected function add_activity_userinfo_setting( + string $settingprefix, + activity_backup_setting $includefield + ): activity_backup_setting { + // Define activityuserinfo. Dependent of: + // - users root setting. + // - sectionuserinfo setting (if exists). + // - activity included setting. $settingname = $settingprefix . 'userinfo'; $defaultvalue = false; if (isset($this->info->settings[$settingname]) && $this->info->settings[$settingname]) { // Only enabled when available $defaultvalue = true; } - $activity_userinfo = new restore_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + $activityuserinfo = new restore_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + if (!$defaultvalue) { // This is a bit hacky, but if there is no user data to restore, then // we replace the standard check-box with a select menu with the @@ -333,30 +362,34 @@ abstract class restore_activity_task extends restore_task { // It would probably be better design to have a special UI class // setting_ui_checkbox_or_no, rather than this hack, but I am not // going to do that today. - $activity_userinfo->set_ui(new backup_setting_ui_select($activity_userinfo, '-', - array(0 => get_string('no')))); + $activityuserinfo->set_ui( + new backup_setting_ui_select( + $activityuserinfo, + '-', + [0 => get_string('no')] + ) + ); } else { - $activity_userinfo->get_ui()->set_label('-'); + $activityuserinfo->get_ui()->set_label('-'); } - $this->add_setting($activity_userinfo); + $this->add_setting($activityuserinfo); - // Look for "users" root setting + // Look for "users" root setting. $users = $this->plan->get_setting('users'); - $users->add_dependency($activity_userinfo); + $users->add_dependency($activityuserinfo); - // Look for "section_userinfo" section setting (if exists) + // Look for "sectionuserinfo" section setting (if exists). $settingname = 'section_' . $this->info->sectionid . '_userinfo'; if ($this->plan->setting_exists($settingname)) { - $section_userinfo = $this->plan->get_setting($settingname); - $section_userinfo->add_dependency($activity_userinfo); + $sectionuserinfo = $this->plan->get_setting($settingname); + $sectionuserinfo->add_dependency($activityuserinfo); } - // Look for "activity_included" setting. - $activity_included->add_dependency($activity_userinfo); + // Look for "activity included" setting. + $includefield->add_dependency($activityuserinfo); - // End of common activity settings, let's add the particular ones. - $this->define_my_settings(); + return $activityuserinfo; } /** diff --git a/backup/moodle2/restore_section_task.class.php b/backup/moodle2/restore_section_task.class.php index 2913836a7b8..924c309c676 100644 --- a/backup/moodle2/restore_section_task.class.php +++ b/backup/moodle2/restore_section_task.class.php @@ -150,36 +150,67 @@ class restore_section_task extends restore_task { * Define the common setting that any restore section will have */ protected function define_settings() { - // All the settings related to this activity will include this prefix $settingprefix = 'section_' . $this->info->sectionid . '_'; - // All these are common settings to be shared by all sections + // All these are common settings to be shared by all sections. + $sectionincluded = $this->add_section_included_setting($settingprefix); + $this->add_section_userinfo_setting($settingprefix, $sectionincluded); + } - // Define section_included (to decide if the whole task must be really executed) + /** + * Add the section included setting to the task. + * + * @param string $settingprefix the identifier of the setting + * @return section_backup_setting the setting added + */ + protected function add_section_included_setting(string $settingprefix): section_backup_setting { + global $DB; + // Define sectionincluded (to decide if the whole task must be really executed). $settingname = $settingprefix . 'included'; - $section_included = new restore_section_included_setting($settingname, base_setting::IS_BOOLEAN, true); + + $sectionincluded = new restore_section_included_setting($settingname, base_setting::IS_BOOLEAN, true); + if (is_number($this->info->title)) { $label = get_string('includesection', 'backup', $this->info->title); } elseif (empty($this->info->title)) { // Don't throw error if title is empty, gracefully continue restore. - $this->log('Section title missing in backup for section id '.$this->info->sectionid, backup::LOG_WARNING, $this->name); + $this->log( + 'Section title missing in backup for section id ' . $this->info->sectionid, + backup::LOG_WARNING, + $this->name + ); $label = get_string('unnamedsection', 'backup'); } else { $label = $this->info->title; } - $section_included->get_ui()->set_label($label); - $this->add_setting($section_included); + $sectionincluded->get_ui()->set_label($label); + $this->add_setting($sectionincluded); - // Define section_userinfo. Dependent of: - // - users root setting - // - section_included setting. + return $sectionincluded; + } + + /** + * Add the section userinfo setting to the task. + * + * @param string $settingprefix the identifier of the setting + * @param section_backup_setting $includefield the section included setting + * @return section_backup_setting the setting added + */ + protected function add_section_userinfo_setting( + string $settingprefix, + section_backup_setting $includefield + ): section_backup_setting { + // Define sectionuserinfo. Dependent of: + // - users root setting. + // - sectionincluded setting. $settingname = $settingprefix . 'userinfo'; $defaultvalue = false; if (isset($this->info->settings[$settingname]) && $this->info->settings[$settingname]) { // Only enabled when available $defaultvalue = true; } - $section_userinfo = new restore_section_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + $sectionuserinfo = new restore_section_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + if (!$defaultvalue) { // This is a bit hacky, but if there is no user data to restore, then // we replace the standard check-box with a select menu with the @@ -189,19 +220,23 @@ class restore_section_task extends restore_task { // It would probably be better design to have a special UI class // setting_ui_checkbox_or_no, rather than this hack, but I am not // going to do that today. - $section_userinfo->set_ui(new backup_setting_ui_select($section_userinfo, get_string('includeuserinfo','backup'), - array(0 => get_string('no')))); + $sectionuserinfo->set_ui( + new backup_setting_ui_select($sectionuserinfo, + get_string('includeuserinfo', 'backup'), [0 => get_string('no')]) + ); } else { - $section_userinfo->get_ui()->set_label(get_string('includeuserinfo','backup')); + $sectionuserinfo->get_ui()->set_label(get_string('includeuserinfo', 'backup')); } - $this->add_setting($section_userinfo); + $this->add_setting($sectionuserinfo); // Look for "users" root setting. $users = $this->plan->get_setting('users'); - $users->add_dependency($section_userinfo); + $users->add_dependency($sectionuserinfo); - // Look for "section_included" section setting. - $section_included->add_dependency($section_userinfo); + // Look for "section included" section setting. + $includefield->add_dependency($sectionuserinfo); + + return $sectionuserinfo; } } diff --git a/backup/util/plan/base_task.class.php b/backup/util/plan/base_task.class.php index 37d001e1a78..148c0bbe883 100644 --- a/backup/util/plan/base_task.class.php +++ b/backup/util/plan/base_task.class.php @@ -103,7 +103,20 @@ abstract class base_task implements checksumable, executable, loggable { } } + /** + * Check if a setting with the given name exists. + * + * This method find first in the current settings and then in the plan settings. + * + * @param string $name Setting name + * @return bool + */ public function setting_exists($name) { + foreach ($this->settings as $setting) { + if ($setting->get_name() == $name) { + return true; + } + } return $this->plan->setting_exists($name); } diff --git a/backup/util/ui/base_moodleform.class.php b/backup/util/ui/base_moodleform.class.php index 88925ce8cdc..b607390fa87 100644 --- a/backup/util/ui/base_moodleform.class.php +++ b/backup/util/ui/base_moodleform.class.php @@ -48,22 +48,10 @@ abstract class base_moodleform extends moodleform { protected $uistage = null; /** - * True if we have a course div open, false otherwise - * @var bool + * Group stack to control open and closed div groups. + * @var array */ - protected $coursediv = false; - - /** - * True if we have a section div open, false otherwise - * @var bool - */ - protected $sectiondiv = false; - - /** - * True if we have an activity div open, false otherwise - * @var bool - */ - protected $activitydiv = false; + protected array $groupstack = []; /** * Creates the form @@ -177,20 +165,12 @@ abstract class base_moodleform extends moodleform { } /** - * Closes any open divs + * Closes any open divs. */ public function close_task_divs() { - if ($this->activitydiv) { + while (!empty($this->groupstack)) { $this->_form->addElement('html', html_writer::end_tag('div')); - $this->activitydiv = false; - } - if ($this->sectiondiv) { - $this->_form->addElement('html', html_writer::end_tag('div')); - $this->sectiondiv = false; - } - if ($this->coursediv) { - $this->_form->addElement('html', html_writer::end_tag('div')); - $this->coursediv = false; + array_pop($this->groupstack); } } @@ -244,7 +224,7 @@ abstract class base_moodleform extends moodleform { 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->pop_group(); } $this->_form->setDefaults($defaults); return true; @@ -265,54 +245,32 @@ abstract class base_moodleform extends moodleform { * @param backup_setting $setting */ protected function add_html_formatting(backup_setting $setting) { - $mform = $this->_form; $isincludesetting = (strpos($setting->get_name(), '_include') !== false); if ($isincludesetting && $setting->get_level() != backup_setting::ROOT_LEVEL) { switch ($setting->get_level()) { case backup_setting::COURSE_LEVEL: - if ($this->activitydiv) { - $this->_form->addElement('html', html_writer::end_tag('div')); - $this->activitydiv = false; - } - if ($this->sectiondiv) { - $this->_form->addElement('html', html_writer::end_tag('div')); - $this->sectiondiv = false; - } - if ($this->coursediv) { - $this->_form->addElement('html', html_writer::end_tag('div')); - } - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'grouped_settings course_level'))); - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'include_setting course_level'))); - $this->coursediv = true; + $this->pop_groups_to('course'); + $this->push_group_start('course', 'grouped_settings course_level'); + $this->push_group_start(null, 'include_setting course_level'); break; case backup_setting::SECTION_LEVEL: - if ($this->activitydiv) { - $this->_form->addElement('html', html_writer::end_tag('div')); - $this->activitydiv = false; - } - if ($this->sectiondiv) { - $this->_form->addElement('html', html_writer::end_tag('div')); - } - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'grouped_settings section_level'))); - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'include_setting section_level'))); - $this->sectiondiv = true; + $this->pop_groups_to('course'); + $this->push_group_start('section', 'grouped_settings section_level'); + $this->push_group_start(null, 'include_setting section_level'); break; case backup_setting::ACTIVITY_LEVEL: - if ($this->activitydiv) { - $this->_form->addElement('html', html_writer::end_tag('div')); - } - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'grouped_settings activity_level'))); - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'include_setting activity_level'))); - $this->activitydiv = true; + $this->pop_groups_to('section'); + $this->push_group_start('activity', 'grouped_settings activity_level'); + $this->push_group_start(null, 'include_setting activity_level'); break; default: - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'normal_setting'))); + $this->push_group_start(null, 'normal_setting'); break; } } else if ($setting->get_level() == backup_setting::ROOT_LEVEL) { - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'root_setting'))); + $this->push_group_start('root', 'root_setting'); } else { - $mform->addElement('html', html_writer::start_tag('div', array('class' => 'normal_setting'))); + $this->push_group_start(null, 'normal_setting'); } } @@ -350,12 +308,55 @@ abstract class base_moodleform extends moodleform { $label .= $OUTPUT->render($labelicon); } $this->_form->addElement('static', 'static_'.$settingui->get_name(), $label, $settingui->get_static_value().$icon); - $this->_form->addElement('html', html_writer::end_tag('div')); + $this->pop_group(); } $this->_form->addElement('hidden', $settingui->get_name(), $settingui->get_value()); $this->_form->setType($settingui->get_name(), $settingui->get_param_validation()); } + /** + * Pushes a group start to the form. + * + * This method will create a new group div in the form and add it to the group stack. + * The name can be used to close all stacked groups up to a certain group. + * + * @param string|null $name The name of the group, if any. + * @param string $classes The classes to add to the div. + */ + protected function push_group_start(?string $name, string $classes) { + $mform = $this->_form; + $this->groupstack[] = $name; + $mform->addElement('html', html_writer::start_tag('div', ['class' => $classes])); + } + + /** + * Pops groups from the stack until the given group name is reached. + * + * @param string $name The name of the group to pop to. + */ + protected function pop_groups_to(string $name) { + if (empty($this->groupstack)) { + return; + } + while (!empty($this->groupstack) && end($this->groupstack) !== $name) { + $this->pop_group(); + } + } + + /** + * Pops a group from the stack and closes the div. + * + * @return string|null The name of the group that was popped, or null if the stack is empty. + */ + protected function pop_group(): ?string { + if (empty($this->groupstack)) { + return null; + } + $mform = $this->_form; + $mform->addElement('html', html_writer::end_tag('div')); + return array_pop($this->groupstack); + } + /** * Adds dependencies to the form recursively * From b4f03323886b59b32580aeb416654d742ca25eef Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Thu, 30 May 2024 15:13:31 +0200 Subject: [PATCH 2/4] MDL-80191 backup: delegate section backup and restore This commit adds all the logic to allow delegate sections backup and restore. The backup and restore process is quite complex and it is not designed to have task hierarchy. To solve the subsection problem, the backup/restore planners do not include delegated sections at a course level, but they are included when the activity with delegated section is processed. To allow restoing, the activity is responsible to store the component/itemid mapping in the backup_structure_dbops. This way, when the delegated section is restored (delegated sections are always processed right after the parent activity) it can use the itemid mapping. --- backup/moodle2/backup_plan_builder.class.php | 50 ++++++++++- backup/moodle2/backup_section_task.class.php | 38 ++++++++ backup/moodle2/backup_stepslib.php | 21 ++++- backup/moodle2/restore_plan_builder.class.php | 31 +++++++ backup/moodle2/restore_stepslib.php | 90 ++++++++++++++++++- .../moodle2/tests/restore_stepslib_test.php | 12 +-- .../dbops/backup_controller_dbops.class.php | 5 +- backup/util/dbops/backup_plan_dbops.class.php | 11 +++ .../format/classes/sectiondelegatemodule.php | 23 +++++ lang/en/backup.php | 1 + 10 files changed, 269 insertions(+), 13 deletions(-) diff --git a/backup/moodle2/backup_plan_builder.class.php b/backup/moodle2/backup_plan_builder.class.php index 49244870e4e..508635997d8 100644 --- a/backup/moodle2/backup_plan_builder.class.php +++ b/backup/moodle2/backup_plan_builder.class.php @@ -133,6 +133,9 @@ abstract class backup_plan_builder { try { $plan->add_task(backup_factory::get_backup_activity_task($controller->get_format(), $id)); + // Some activities may have delegated section integrations. + self::build_delegated_section_plan($controller, $id); + // For the given activity, add as many block tasks as necessary $blockids = backup_plan_dbops::get_blockids_from_moduleid($id); foreach ($blockids as $blockid) { @@ -150,6 +153,44 @@ abstract class backup_plan_builder { } } + /** + * Build a course module delegated section backup plan. + * @param backup_controller $controller + * @param int $cmid the parent course module id. + */ + protected static function build_delegated_section_plan($controller, $cmid) { + global $CFG, $DB; + + // Check moduleid exists. + if (!$coursemodule = get_coursemodule_from_id(false, $cmid)) { + $controller->log(get_string('error_course_module_not_found', 'backup', $cmid), backup::LOG_WARNING); + } + $classname = 'mod_' . $coursemodule->modname . '\courseformat\sectiondelegate'; + if (!class_exists($classname)) { + return; + } + $sectionid = null; + try { + $sectionid = $classname::delegated_section_id($coursemodule); + } catch (dml_exception $error) { + $controller->log(get_string('error_delegate_section_not_found', 'backup', $cmid), backup::LOG_WARNING); + return; + } + + $plan = $controller->get_plan(); + $sectiontask = backup_factory::get_backup_section_task($controller->get_format(), $sectionid); + $sectiontask->set_delegated_cm($cmid); + $plan->add_task($sectiontask); + + // For the given section, add as many activity tasks as necessary. + $coursemodules = backup_plan_dbops::get_modules_from_sectionid($sectionid); + foreach ($coursemodules as $coursemodule) { + if (plugin_supports('mod', $coursemodule->modname, FEATURE_BACKUP_MOODLE2)) { + self::build_activity_plan($controller, $coursemodule->id); + } + } + } + /** * Build one 1-section backup */ @@ -185,8 +226,13 @@ abstract class backup_plan_builder { // For the given course, add as many section tasks as necessary $sections = backup_plan_dbops::get_sections_from_courseid($id); - foreach ($sections as $section) { - self::build_section_plan($controller, $section); + foreach ($sections as $sectionid) { + // Delegated sections are not course responsability. + $sectiondata = backup_plan_dbops::get_section_from_id($sectionid); + if (!empty($sectiondata->component)) { + continue; + } + self::build_section_plan($controller, $sectionid); } // For the given course, add as many block tasks as necessary diff --git a/backup/moodle2/backup_section_task.class.php b/backup/moodle2/backup_section_task.class.php index 16f33a70387..6389c3e6a87 100644 --- a/backup/moodle2/backup_section_task.class.php +++ b/backup/moodle2/backup_section_task.class.php @@ -42,6 +42,11 @@ class backup_section_task extends backup_task { */ protected stdClass $section; + /** + * @var int|null $delegatedcmid the course module that is delegating this section (if any) + */ + protected ?int $delegatedcmid = null; + /** * Constructor - instantiates one object of this class */ @@ -59,6 +64,39 @@ class backup_section_task extends backup_task { parent::__construct($name, $plan); } + /** + * Set the course module that is delegating this section. + * + * Delegated section can belong to any kind of plugin. However, when a delegated + * section belongs to a course module, the UI will present all settings according. + * + * @param int $cmid the course module id that is delegating this section + */ + public function set_delegated_cm(int $cmid) { + $this->delegatedcmid = $cmid; + } + + /** + * Get the course module that is delegating this section. + * + * @return int|null the course module id that is delegating this section + */ + public function get_delegated_cm(): ?int { + return $this->delegatedcmid; + } + + /** + * Get the delegate activity modname (if any). + * + * @return string|null the modname of the delegated activity + */ + public function get_modname(): ?string { + if (empty($this->section->component)) { + return null; + } + return core_component::normalize_component($this->section->component)[1]; + } + public function get_sectionid() { return $this->sectionid; } diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index e5496e40874..a2b99c4b997 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -119,6 +119,20 @@ abstract class backup_activity_structure_step extends backup_structure_step { // Return the root element (activity) return $activity; } + + /** + * Set a delegate section itemid mapping. + * + * @param string $pluginname the name of the plugin that is delegating the section. + * @param int $itemid the itemid of the section being delegated. + */ + protected function set_delegated_section_mapping(string $pluginname, int $itemid) { + backup_structure_dbops::insert_backup_ids_record( + $this->get_backupid(), + "course_section::$pluginname::$itemid", + $this->task->get_moduleid() + ); + } } /** @@ -2128,8 +2142,11 @@ class backup_main_structure_step extends backup_structure_step { $sections = new backup_nested_element('sections'); - $section = new backup_nested_element('section', null, array( - 'sectionid', 'title', 'directory')); + $section = new backup_nested_element( + 'section', + null, + ['sectionid', 'title', 'directory', 'parentcmid', 'modname'] + ); $course = new backup_nested_element('course', null, array( 'courseid', 'title', 'directory')); diff --git a/backup/moodle2/restore_plan_builder.class.php b/backup/moodle2/restore_plan_builder.class.php index 9246dc41c5a..b5cb20d6d5e 100644 --- a/backup/moodle2/restore_plan_builder.class.php +++ b/backup/moodle2/restore_plan_builder.class.php @@ -144,6 +144,9 @@ abstract class restore_plan_builder { $plan->add_task($task); $controller->get_progress()->progress(); + // Some activities may have delegated section integrations. + self::build_delegated_section_plan($controller, $infoactivity->moduleid); + // For the given activity 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()); @@ -161,6 +164,30 @@ abstract class restore_plan_builder { } + /** + * Build a course module delegated section backup plan. + * @param restore_controller $controller + * @param int $cmid the parent course module id. + */ + protected static function build_delegated_section_plan($controller, $cmid) { + $info = $controller->get_info(); + + // Find if some section depends on that course module. + $delegatedsectionid = null; + foreach ($info->sections as $sectionid => $section) { + // Delegated sections are not course responsability. + if (isset($section->parentcmid) && $section->parentcmid == $cmid) { + $delegatedsectionid = $sectionid; + break; + } + } + + if (!$delegatedsectionid) { + return; + } + self::build_section_plan($controller, $delegatedsectionid); + } + /** * Restore one 1-section backup */ @@ -215,6 +242,10 @@ abstract class restore_plan_builder { // For the given course, add as many section tasks as necessary foreach ($info->sections as $sectionid => $section) { + // Delegated sections are not course responsability. + if (isset($section->parentcmid) && !empty($section->parentcmid)) { + continue; + } self::build_section_plan($controller, $sectionid); } } diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 22015c82005..98cac314e73 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -1612,8 +1612,28 @@ class restore_section_structure_step extends restore_structure_step { $section->course = $this->get_courseid(); $section->section = $data->number; $section->timemodified = $data->timemodified ?? 0; + $section->component = null; + $section->itemid = null; + + $secrec = $DB->get_record( + 'course_sections', + ['course' => $this->get_courseid(), 'section' => $data->number, 'component' => null] + ); + $createsection = empty($secrec); + + // Delegated sections are always restored as new sections. + if (!empty($data->component)) { + $section->itemid = $this->get_delegated_section_mapping($data->component, $data->itemid); + // If the delegate component does not set the mapping id, the section must be converted + // into a regular section. Otherwise, it won't be accessible. + $createsection = $createsection || $section->itemid !== null; + $section->component = ($section->itemid !== null) ? $data->component : null; + // The section number will be always the last of the course, no matter the case. + $section->section = $this->get_last_section_number($this->get_courseid()) + 1; + + } // Section doesn't exist, create it with all the info from backup - if (!$secrec = $DB->get_record('course_sections', ['course' => $this->get_courseid(), 'section' => $data->number])) { + if ($createsection) { $section->name = $data->name; $section->summary = $data->summary; $section->summaryformat = $data->summaryformat; @@ -1629,8 +1649,10 @@ class restore_section_structure_step extends restore_structure_step { $data, true); } } - $section->component = $data->component ?? null; - $section->itemid = $data->itemid ?? null; + + // Delegated sections should be always after the normal sections. + $this->displace_delegated_sections_after($section->section); + $newitemid = $DB->insert_record('course_sections', $section); $section->id = $newitemid; @@ -1802,6 +1824,57 @@ class restore_section_structure_step extends restore_structure_step { // Add section related files, with 'course_section' itemid to match $this->add_related_files('course', 'section', 'course_section'); } + + /** + * Create a delegate section mapping. + * + * @param string $component the component name (frankenstyle) + * @param int $oldsectionid The old section id. + * @return int|null The new section id or null if not found. + */ + protected function get_delegated_section_mapping($component, $oldsectionid): ?int { + $result = $this->get_mappingid("course_section::$component", $oldsectionid, null); + return $result; + } + + /** + * Displace delegated sections after the given section number. + * + * @param int $sectionnum The section number. + */ + protected function displace_delegated_sections_after(int $sectionnum): void { + global $DB; + + $sectionstomove = $DB->get_records_select( + 'course_sections', + 'course = ? AND component IS NOT NULL', + [$this->get_courseid()], + 'section DESC', 'id, section' + ); + foreach ($sectionstomove as $section) { + $sectionnum++; + $section->section = $sectionnum; + $DB->update_record('course_sections', $section); + } + } + + /** + * Get the last section number in the course. + * + * @param int $courseid The course id. + * @param bool $includedelegated If true, include delegated sections in the count. + * @return int The last section number. + */ + protected function get_last_section_number(int $courseid, bool $includedelegated = false): int { + global $DB; + + $delegtadefilter = $includedelegated ? '' : ' AND component IS NULL'; + + return (int) $DB->get_field_sql( + 'SELECT max(section) from {course_sections} WHERE course = ?' . $delegtadefilter, + [$courseid] + ); + } } /** @@ -4905,6 +4978,17 @@ abstract class restore_activity_structure_step extends restore_structure_step { $oldid = $this->task->get_old_activityid(); $this->set_mapping($modulename, $oldid, $newitemid, true); } + + /** + * Create a delegate section mapping. + * + * @param string $component The component name (frankenstyle) + * @param int $olditemid The old section id. + * @param int $newitemid The new section id. + */ + protected function set_delegated_section_mapping($component, $olditemid, $newitemid) { + $this->set_mapping("course_section::$component", $olditemid, $newitemid); + } } /** diff --git a/backup/moodle2/tests/restore_stepslib_test.php b/backup/moodle2/tests/restore_stepslib_test.php index 6a8eb5e162f..d5a2685823c 100644 --- a/backup/moodle2/tests/restore_stepslib_test.php +++ b/backup/moodle2/tests/restore_stepslib_test.php @@ -103,7 +103,7 @@ class restore_stepslib_test extends \advanced_testcase { } /** - * Test for the section structure step included elements. + * Test for delegate section behaviour. * * @covers \restore_section_structure_step::process_section */ @@ -114,7 +114,7 @@ class restore_stepslib_test extends \advanced_testcase { $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(['numsections' => 2, 'format' => 'topics']); - // Section 2 has an existing delegate class. + // Section 2 has an existing delegate class for component that is not an activity. course_update_section( $course, $DB->get_record('course_sections', ['course' => $course->id, 'section' => 2]), @@ -130,16 +130,18 @@ class restore_stepslib_test extends \advanced_testcase { $originalsections = get_fast_modinfo($course->id)->get_section_info_all(); $restoredsections = get_fast_modinfo($newcourseid)->get_section_info_all(); - $this->assertEquals(count($originalsections), count($restoredsections)); + // Delegated sections depends on the plugin to be backuped and restored. + // In this case, the plugin is not backuped and restored, so the section is not restored. + $this->assertEquals(3, count($originalsections)); + $this->assertEquals(2, count($restoredsections)); $validatefields = ['name', 'summary', 'summaryformat', 'visible', 'component', 'itemid']; $this->assertEquals($originalsections[1]->name, $restoredsections[1]->name); foreach ($validatefields as $field) { + $this->assertEquals($originalsections[0]->$field, $restoredsections[0]->$field); $this->assertEquals($originalsections[1]->$field, $restoredsections[1]->$field); - $this->assertEquals($originalsections[2]->$field, $restoredsections[2]->$field); } - } } diff --git a/backup/util/dbops/backup_controller_dbops.class.php b/backup/util/dbops/backup_controller_dbops.class.php index 2bc367fd476..4992fb2e703 100644 --- a/backup/util/dbops/backup_controller_dbops.class.php +++ b/backup/util/dbops/backup_controller_dbops.class.php @@ -268,7 +268,10 @@ abstract class backup_controller_dbops extends backup_dbops { $contentinfo = array( 'sectionid' => $task->get_sectionid(), 'title' => $task->get_name(), - 'directory' => 'sections/' . 'section_' . $task->get_sectionid()); + 'directory' => 'sections/' . 'section_' . $task->get_sectionid(), + 'parentcmid' => $task->get_delegated_cm() ?? '', + 'modname' => $task->get_modname() ?? '', + ); // Now get section settings // Calculate prefix to find valid settings diff --git a/backup/util/dbops/backup_plan_dbops.class.php b/backup/util/dbops/backup_plan_dbops.class.php index d3a650bf91e..4a962ddfa6e 100644 --- a/backup/util/dbops/backup_plan_dbops.class.php +++ b/backup/util/dbops/backup_plan_dbops.class.php @@ -120,6 +120,17 @@ abstract class backup_plan_dbops extends backup_dbops { return $sectionsarr; } + /** + * Given one section id, returns the full section record. + * + * @param int $sectionid + * @return stdClass + */ + public static function get_section_from_id($sectionid): stdClass { + global $DB; + return $DB->get_record('course_sections', ['id' => $sectionid]); + } + /** * Given one course id, return its format in DB */ diff --git a/course/format/classes/sectiondelegatemodule.php b/course/format/classes/sectiondelegatemodule.php index 124ad1b34f8..61e5a2c7363 100644 --- a/course/format/classes/sectiondelegatemodule.php +++ b/course/format/classes/sectiondelegatemodule.php @@ -52,6 +52,29 @@ abstract class sectiondelegatemodule extends sectiondelegate { ); } + /** + * Get the delegated section id controlled by a specific cm. + * + * This method is used when reverse search is needed bu we cannot access the database. + * This happens mostly on backup and restore. Do NOT use for normal operations. + * + * @param stdClass|cm_info $cm a course module compatible data structure. + * @return int the section id. + */ + public static function delegated_section_id(stdClass|cm_info $cm): int { + global $DB; + return $DB->get_field( + 'course_sections', + 'id', + [ + 'course' => $cm->course, + 'component' => explode('\\', static::class)[0], + 'itemid' => $cm->instance, + ], + MUST_EXIST + ); + } + /** * Get the parent section of the current delegated section. * diff --git a/lang/en/backup.php b/lang/en/backup.php index 9fa54d69ea6..6f819156d76 100644 --- a/lang/en/backup.php +++ b/lang/en/backup.php @@ -198,6 +198,7 @@ $string['enableasyncbackup_help'] = 'If enabled, backup and restore operations w $string['enterasearch'] = 'Enter a search'; $string['error_block_for_module_not_found'] = 'Orphan block instance (id: {$a->bid}) for course module (id: {$a->mid}) found. This block will not be backed up'; $string['error_course_module_not_found'] = 'Orphan course module (id: {$a}) found. This module will not be backed up.'; +$string['error_delegate_section_not_found'] = 'Missing delegate section form course module (id: {$a}. The section will not be backed up.'; $string['errorcopyingbackupfile'] = "Failed to copy the backup file to the temporary folder before restoring."; $string['errorfilenamerequired'] = 'You must enter a valid filename for this backup'; $string['errorfilenametoolong'] = 'The filename must be less than 255 characters in length.'; From c997f91db6c427fe251b49214c8cbbdd7339940e Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Thu, 30 May 2024 15:51:18 +0200 Subject: [PATCH 3/4] MDL-80191 backup: add subsections to backup form The patch adds two new levels to the backup form structure: subsection (a delegated section which parent is a course module) and subactivity (an activity inside a subsection). Those new elements are displayed as a inner section below the parent activitiy. For now the UI is the same as a regular section but future issues will define how the UX should be. The new two levels have their own classes to represents settings and have extra dependencies. --- backup/moodle2/backup_activity_task.class.php | 33 +++++++- backup/moodle2/backup_section_task.class.php | 39 ++++++++- backup/moodle2/backup_settingslib.php | 79 +++++++++++++++++++ .../dbops/backup_controller_dbops.class.php | 4 +- backup/util/settings/backup_setting.class.php | 13 ++- backup/util/ui/backup_ui_setting.class.php | 7 +- backup/util/ui/base_moodleform.class.php | 10 +++ lang/en/backup.php | 1 + theme/boost/scss/moodle/backup-restore.scss | 10 ++- theme/boost/style/moodle.css | 12 +++ theme/classic/style/moodle.css | 12 +++ 11 files changed, 207 insertions(+), 13 deletions(-) diff --git a/backup/moodle2/backup_activity_task.class.php b/backup/moodle2/backup_activity_task.class.php index bc84291b733..5ab96e73632 100644 --- a/backup/moodle2/backup_activity_task.class.php +++ b/backup/moodle2/backup_activity_task.class.php @@ -38,6 +38,9 @@ abstract class backup_activity_task extends backup_task { protected $moduleid; protected $sectionid; + + /** @var stdClass the section object */ + protected $section; protected $modulename; protected $activityid; protected $contextid; @@ -50,6 +53,7 @@ abstract class backup_activity_task extends backup_task { * @param backup_plan|null $plan the backup plan instance this task is part of */ public function __construct($name, $moduleid, $plan = null) { + global $DB; // Check moduleid exists if (!$coursemodule = get_coursemodule_from_id(false, $moduleid)) { @@ -65,6 +69,7 @@ abstract class backup_activity_task extends backup_task { $this->modulename = $coursemodule->modname; $this->activityid = $coursemodule->instance; $this->contextid = context_module::instance($this->moduleid)->id; + $this->section = $DB->get_record('course_sections', ['id' => $this->sectionid]); parent::__construct($name, $plan); } @@ -90,6 +95,16 @@ abstract class backup_activity_task extends backup_task { return $this->modulename; } + /** + * Return if the activity is inside a subsection. + * + * @return bool + */ + public function is_in_subsection(): bool { + return !empty($this->section->component); + } + + /** * @return int the id of the activity instance (id in the activity's instances table) */ @@ -291,7 +306,11 @@ abstract class backup_activity_task extends backup_task { * @return section_backup_setting the setting added */ protected function add_section_setting(int|string $identifier, string $type, string|int $value): activity_backup_setting { - $setting = new backup_activity_generic_setting($identifier, $type, $value); + if ($this->is_in_subsection()) { + $setting = new backup_subactivity_generic_setting($identifier, $type, $value); + } else { + $setting = new backup_activity_generic_setting($identifier, $type, $value); + } $this->add_setting($setting); return $setting; } @@ -308,7 +327,11 @@ abstract class backup_activity_task extends backup_task { // - activities root setting. // - sectionincluded setting (if exists). $settingname = $settingprefix . 'included'; - $activityincluded = new backup_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); + if ($this->is_in_subsection()) { + $activityincluded = new backup_subactivity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); + } else { + $activityincluded = new backup_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); + } $activityincluded->get_ui()->set_icon(new image_icon('monologo', get_string('pluginname', $this->modulename), $this->modulename, array('class' => 'iconlarge icon-post ml-1'))); $this->add_setting($activityincluded); @@ -342,7 +365,11 @@ abstract class backup_activity_task extends backup_task { // - sectionuserinfo setting (if exists). // - includefield setting. $settingname = $settingprefix . 'userinfo'; - $activityuserinfo = new backup_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true); + if ($this->is_in_subsection()) { + $activityuserinfo = new backup_subactivity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true); + } else { + $activityuserinfo = new backup_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true); + } $activityuserinfo->get_ui()->set_label('-'); $activityuserinfo->get_ui()->set_visually_hidden_label( diff --git a/backup/moodle2/backup_section_task.class.php b/backup/moodle2/backup_section_task.class.php index 6389c3e6a87..f676fba88dc 100644 --- a/backup/moodle2/backup_section_task.class.php +++ b/backup/moodle2/backup_section_task.class.php @@ -202,7 +202,11 @@ class backup_section_task extends backup_task { * @return section_backup_setting the setting added */ protected function add_section_setting(int|string $identifier, string $type, string|int $value): section_backup_setting { - $setting = new backup_section_generic_setting($identifier, $type, $value); + if ($this->get_delegated_cm()) { + $setting = new backup_subsection_generic_setting($identifier, $type, $value); + } else { + $setting = new backup_section_generic_setting($identifier, $type, $value); + } $this->add_setting($setting); return $setting; } @@ -220,9 +224,23 @@ class backup_section_task extends backup_task { // Define sectionincluded (to decide if the whole task must be really executed). $settingname = $settingprefix . 'included'; - $sectionincluded = new backup_section_included_setting($settingname, base_setting::IS_BOOLEAN, true); + $delegatedcmid = $this->get_delegated_cm(); + if ($delegatedcmid) { + $sectionincluded = new backup_subsection_included_setting($settingname, base_setting::IS_BOOLEAN, true); + // Subsections depends on the parent activity included setting. + $settingname = $this->get_modname() . '_' . $delegatedcmid . '_included'; + if ($this->plan->setting_exists($settingname)) { + $cmincluded = $this->plan->get_setting($settingname); + $cmincluded->add_dependency( + $sectionincluded, + ); + } + $sectionincluded->get_ui()->set_label(get_string('subsectioncontent', 'backup')); + } else { + $sectionincluded = new backup_section_included_setting($settingname, base_setting::IS_BOOLEAN, true); + $sectionincluded->get_ui()->set_label(get_section_name($course, $this->section)); + } - $sectionincluded->get_ui()->set_label(get_section_name($course, $this->section)); $this->add_setting($sectionincluded); return $sectionincluded; @@ -244,7 +262,20 @@ class backup_section_task extends backup_task { // - section_included setting. $settingname = $settingprefix . 'userinfo'; - $sectionuserinfo = new backup_section_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true); + $delegatedcmid = $this->get_delegated_cm(); + if ($delegatedcmid) { + $sectionuserinfo = new backup_subsection_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true); + // Subsections depends on the parent activity included setting. + $settingname = $this->get_modname() . '_' . $delegatedcmid . '_userinfo'; + if ($this->plan->setting_exists($settingname)) { + $cmincluded = $this->plan->get_setting($settingname); + $cmincluded->add_dependency( + $sectionuserinfo, + ); + } + } else { + $sectionuserinfo = new backup_section_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true); + } $sectionuserinfo->get_ui()->set_label(get_string('includeuserinfo', 'backup')); $sectionuserinfo->get_ui()->set_visually_hidden_label( diff --git a/backup/moodle2/backup_settingslib.php b/backup/moodle2/backup_settingslib.php index 6767d01f057..770c3813ce5 100644 --- a/backup/moodle2/backup_settingslib.php +++ b/backup/moodle2/backup_settingslib.php @@ -185,6 +185,45 @@ class backup_section_included_setting extends section_backup_setting {} */ class backup_section_userinfo_setting extends section_backup_setting {} +/** + * Subsection base class (section delegated to a course module). + */ +class subsection_backup_setting extends section_backup_setting { + /** + * Class constructor. + * + * @param string $name Name of the setting + * @param string $vtype Type of the setting, for example base_setting::IS_TEXT + * @param mixed $value Value of the setting + * @param bool $visibility Is the setting visible in the UI, for example base_setting::VISIBLE + * @param int $status Status of the setting with regards to the locking, for example base_setting::NOT_LOCKED + */ + public function __construct($name, $vtype, $value = null, $visibility = self::VISIBLE, $status = self::NOT_LOCKED) { + parent::__construct($name, $vtype, $value, $visibility, $status); + $this->level = self::SUBSECTION_LEVEL; + } +} + +/** + * generic section setting to pass various settings between tasks and steps + */ +class backup_subsection_generic_setting extends subsection_backup_setting { +} + +/** + * Setting to define if one section is included or no. Activities _included + * settings depend of them if available + */ +class backup_subsection_included_setting extends subsection_backup_setting { +} + +/** + * section backup setting to control if section will include + * user information or no, depends of @backup_users_setting + */ +class backup_subsection_userinfo_setting extends subsection_backup_setting { +} + // Activity backup settings @@ -206,6 +245,46 @@ class backup_activity_included_setting extends activity_backup_setting {} */ class backup_activity_userinfo_setting extends activity_backup_setting {} +/** + * Subactivity base class (activity inside a delegated section). + */ +class subactivity_backup_setting extends activity_backup_setting { + /** + * Class constructor. + * + * @param string $name Name of the setting + * @param string $vtype Type of the setting, for example base_setting::IS_TEXT + * @param mixed $value Value of the setting + * @param bool $visibility Is the setting visible in the UI, for example base_setting::VISIBLE + * @param int $status Status of the setting with regards to the locking, for example base_setting::NOT_LOCKED + */ + public function __construct($name, $vtype, $value = null, $visibility = self::VISIBLE, $status = self::NOT_LOCKED) { + parent::__construct($name, $vtype, $value, $visibility, $status); + $this->level = self::SUBACTIVITY_LEVEL; + } +} + +/** + * Generic subactivity (activity inside a delegated section) setting to pass various settings between tasks and steps + */ +class backup_subactivity_generic_setting extends subactivity_backup_setting { +} + +/** + * Subactivity (activity inside a delegated section) backup setting to control if activity will + * be included or no, depends of @backup_activities_setting and + * optionally parent section included setting + */ +class backup_subactivity_included_setting extends subactivity_backup_setting { +} + +/** + * Subactivity (activity inside a delegated section) backup setting to control if activity will include + * user information or no, depends of @backup_users_setting + */ +class backup_subactivity_userinfo_setting extends subactivity_backup_setting { +} + /** * Root setting to control if backup will include content bank content or no */ diff --git a/backup/util/dbops/backup_controller_dbops.class.php b/backup/util/dbops/backup_controller_dbops.class.php index 4992fb2e703..612daedf8ad 100644 --- a/backup/util/dbops/backup_controller_dbops.class.php +++ b/backup/util/dbops/backup_controller_dbops.class.php @@ -246,7 +246,7 @@ abstract class backup_controller_dbops extends backup_dbops { continue; } // Validate level is correct (activity) - if ($setting->get_level() != backup_setting::ACTIVITY_LEVEL) { + if (!in_array($setting->get_level(), [backup_setting::ACTIVITY_LEVEL, backup_setting::SUBACTIVITY_LEVEL])) { throw new backup_controller_exception('setting_not_activity_level', $setting); } $settinginfo = array( @@ -283,7 +283,7 @@ abstract class backup_controller_dbops extends backup_dbops { continue; } // Validate level is correct (section) - if ($setting->get_level() != backup_setting::SECTION_LEVEL) { + if (!in_array($setting->get_level(), [backup_setting::SECTION_LEVEL, backup_setting::SUBSECTION_LEVEL])) { throw new backup_controller_exception('setting_not_section_level', $setting); } $settinginfo = array( diff --git a/backup/util/settings/backup_setting.class.php b/backup/util/settings/backup_setting.class.php index d32fdc36566..7856dd46774 100644 --- a/backup/util/settings/backup_setting.class.php +++ b/backup/util/settings/backup_setting.class.php @@ -37,6 +37,12 @@ abstract class backup_setting extends base_setting implements checksumable { const SECTION_LEVEL = 9; const ACTIVITY_LEVEL = 13; + /** @var int the subsection level. */ + const SUBSECTION_LEVEL = 17; + + /** @var int the activity inside a subsection level. */ + const SUBACTIVITY_LEVEL = 21; + /** @var int Level of the setting, eg {@link self::ROOT_LEVEL} */ protected $level; @@ -96,7 +102,12 @@ abstract class backup_setting extends base_setting implements checksumable { } // Check the dependency level is >= current level if ($dependentsetting->get_level() < $this->level) { - throw new backup_setting_exception('cannot_add_upper_level_dependency'); + throw new backup_setting_exception('cannot_add_upper_level_dependency', [ + $dependentsetting->get_level(), + $dependentsetting->get_name(), + $this->level, + $this->get_name(), + ]); } parent::add_dependency($dependentsetting, $type, $options); } diff --git a/backup/util/ui/backup_ui_setting.class.php b/backup/util/ui/backup_ui_setting.class.php index c1f248ff471..ed82227e23c 100644 --- a/backup/util/ui/backup_ui_setting.class.php +++ b/backup/util/ui/backup_ui_setting.class.php @@ -256,9 +256,11 @@ abstract class backup_setting_ui extends base_setting_ui { $this->name = 'course_'.$setting->get_name(); break; case backup_setting::SECTION_LEVEL : + case backup_setting::SUBSECTION_LEVEL: $this->name = 'section_'.$setting->get_name(); break; case backup_setting::ACTIVITY_LEVEL : + case backup_setting::SUBACTIVITY_LEVEL: $this->name = 'activity_'.$setting->get_name(); break; } @@ -329,9 +331,10 @@ abstract class backup_setting_ui extends base_setting_ui { // If a task has been provided and the label is not already set meaningfully // we will attempt to improve it. if (!is_null($task) && $this->label == $this->setting->get_name() && strpos($this->setting->get_name(), '_include') !== false) { - if ($this->setting->get_level() == backup_setting::SECTION_LEVEL) { + $level = $this->setting->get_level(); + if ($level == backup_setting::SECTION_LEVEL || $level == backup_setting::SUBSECTION_LEVEL) { $this->label = get_string('includesection', 'backup', $task->get_name()); - } else if ($this->setting->get_level() == backup_setting::ACTIVITY_LEVEL) { + } else if ($level == backup_setting::ACTIVITY_LEVEL || $level == backup_setting::SUBACTIVITY_LEVEL) { $this->label = $task->get_name(); } } diff --git a/backup/util/ui/base_moodleform.class.php b/backup/util/ui/base_moodleform.class.php index b607390fa87..f4a0ed5cebd 100644 --- a/backup/util/ui/base_moodleform.class.php +++ b/backup/util/ui/base_moodleform.class.php @@ -263,6 +263,16 @@ abstract class base_moodleform extends moodleform { $this->push_group_start('activity', 'grouped_settings activity_level'); $this->push_group_start(null, 'include_setting activity_level'); break; + case backup_setting::SUBSECTION_LEVEL: + $this->pop_groups_to('section'); + $this->push_group_start('subsection', 'grouped_settings subsection_level'); + $this->push_group_start(null, 'normal_setting'); + break; + case backup_setting::SUBACTIVITY_LEVEL: + $this->pop_groups_to('subsection'); + $this->push_group_start('subactivity', 'grouped_settings activity_level'); + $this->push_group_start(null, 'include_setting activity_level'); + break; default: $this->push_group_start(null, 'normal_setting'); break; diff --git a/lang/en/backup.php b/lang/en/backup.php index 6f819156d76..8a9002a0488 100644 --- a/lang/en/backup.php +++ b/lang/en/backup.php @@ -417,6 +417,7 @@ $string['skipmodifdayshelp'] = 'Choose to skip courses that have not been modifi $string['skipmodifprev'] = 'Skip courses not modified since previous backup'; $string['skipmodifprevhelp'] = 'Choose whether to skip courses that have not been modified since the last automatic backup. This requires logging to be enabled.'; $string['status'] = 'Status'; +$string['subsectioncontent'] = 'Subsection content'; $string['successful'] = 'Backup successful'; $string['successfulcopy'] = 'Copy successful'; $string['successfulrestore'] = 'Restore successful'; diff --git a/theme/boost/scss/moodle/backup-restore.scss b/theme/boost/scss/moodle/backup-restore.scss index ed1a75c4b19..780a0177524 100644 --- a/theme/boost/scss/moodle/backup-restore.scss +++ b/theme/boost/scss/moodle/backup-restore.scss @@ -12,6 +12,15 @@ padding: $card-spacer-x; margin-bottom: $card-spacer-x; } + + &.subsection_level { + background-color: $card-bg; + @include border-radius($card-border-radius); + border: $card-border-width solid $card-border-color; + @include clearfix; + padding: $card-spacer-x; + margin: 0 $card-spacer-x $card-spacer-x $card-spacer-x; + } } /* These are long labels with checkboxes on the right. */ @@ -202,4 +211,3 @@ background: $backup-restore-state12-bg; } } - diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index bba2e639d67..2073f92e35c 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -34869,6 +34869,18 @@ img.userpicture { clear: both; content: ""; } +.path-backup .mform .grouped_settings.subsection_level { + background-color: #fff; + border-radius: 0.5rem; + border: 1px solid rgba(0, 0, 0, 0.125); + padding: 1.25rem; + margin: 0 1.25rem 1.25rem 1.25rem; +} +.path-backup .mform .grouped_settings.subsection_level::after { + display: block; + clear: both; + content: ""; +} .path-backup .mform .include_setting { width: 50%; display: inline-block; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 6607b1a5b66..f8e5c56273f 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -34869,6 +34869,18 @@ img.userpicture { clear: both; content: ""; } +.path-backup .mform .grouped_settings.subsection_level { + background-color: #fff; + border-radius: 0.25rem; + border: 1px solid rgba(0, 0, 0, 0.125); + padding: 1.25rem; + margin: 0 1.25rem 1.25rem 1.25rem; +} +.path-backup .mform .grouped_settings.subsection_level::after { + display: block; + clear: both; + content: ""; +} .path-backup .mform .include_setting { width: 50%; display: inline-block; From b5f14e895a31915ad9e909e8ca1d6fb8bdb95cd3 Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Thu, 30 May 2024 16:17:26 +0200 Subject: [PATCH 4/4] MDL-80191 backup: add subsections to restore form This commit adds two new levels to the restore course structure form: subsection (a delegated section that belongs to a course module) and subactivity (a course module in a subsection). Restore form can only use information from the backup file. To allow activities to know if they are inside a subsection, the backup now incorporates an "insubsection" attribute. This attribute is used only for the form display but not for the restore logic. --- backup/moodle2/backup_stepslib.php | 8 +- .../moodle2/restore_activity_task.class.php | 21 ++++- backup/moodle2/restore_section_task.class.php | 78 +++++++++++++++---- backup/moodle2/restore_settingslib.php | 68 ++++++++++++++++ .../dbops/backup_controller_dbops.class.php | 4 +- 5 files changed, 160 insertions(+), 19 deletions(-) diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index a2b99c4b997..e1c6db33da3 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -2136,9 +2136,11 @@ class backup_main_structure_step extends backup_structure_step { $activities = new backup_nested_element('activities'); - $activity = new backup_nested_element('activity', null, array( - 'moduleid', 'sectionid', 'modulename', 'title', - 'directory')); + $activity = new backup_nested_element( + 'activity', + null, + ['moduleid', 'sectionid', 'modulename', 'title', 'directory', 'insubsection'] + ); $sections = new backup_nested_element('sections'); diff --git a/backup/moodle2/restore_activity_task.class.php b/backup/moodle2/restore_activity_task.class.php index 4948516cd11..6038fa03d7b 100644 --- a/backup/moodle2/restore_activity_task.class.php +++ b/backup/moodle2/restore_activity_task.class.php @@ -100,6 +100,15 @@ abstract class restore_activity_task extends restore_task { return $this->moduleid; } + /** + * Return if the activity is inside a subsection. + * + * @return bool + */ + public function is_in_subsection(): bool { + return !empty($this->info->insubsection); + } + /** * Returns the old course module id (cmid of activity which will be restored) * @@ -312,7 +321,11 @@ abstract class restore_activity_task extends restore_task { // - sectionincluded setting (if exists). $settingname = $settingprefix . 'included'; - $activityincluded = new restore_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); + if ($this->is_in_subsection()) { + $activityincluded = new restore_subactivity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); + } else { + $activityincluded = new restore_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true); + } $activityincluded->get_ui()->set_icon(new image_icon('monologo', get_string('pluginname', $this->modulename), $this->modulename, ['class' => 'iconlarge icon-post ml-1'])); @@ -351,7 +364,11 @@ abstract class restore_activity_task extends restore_task { $defaultvalue = true; } - $activityuserinfo = new restore_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + if ($this->is_in_subsection()) { + $activityuserinfo = new restore_subactivity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + } else { + $activityuserinfo = new restore_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + } if (!$defaultvalue) { // This is a bit hacky, but if there is no user data to restore, then diff --git a/backup/moodle2/restore_section_task.class.php b/backup/moodle2/restore_section_task.class.php index 924c309c676..a9eb5730d3c 100644 --- a/backup/moodle2/restore_section_task.class.php +++ b/backup/moodle2/restore_section_task.class.php @@ -56,6 +56,30 @@ class restore_section_task extends restore_task { return $this->get_basepath() . '/sections/section_' . $this->info->sectionid; } + /** + * Get the course module that is delegating this section. + * + * @return int|null the course module id that is delegating this section + */ + public function get_delegated_cm(): ?int { + if (!isset($this->info->parentcmid) || empty($this->info->parentcmid)) { + return null; + } + return intval($this->info->parentcmid); + } + + /** + * Get the delegated activity modname if any. + * + * @return string|null the modname of the delegated activity + */ + public function get_modname(): ?string { + if (!isset($this->info->modname) || empty($this->info->modname)) { + return null; + } + return $this->info->modname; + } + public function set_sectionid($sectionid) { $this->sectionid = $sectionid; } @@ -169,20 +193,35 @@ class restore_section_task extends restore_task { // Define sectionincluded (to decide if the whole task must be really executed). $settingname = $settingprefix . 'included'; - $sectionincluded = new restore_section_included_setting($settingname, base_setting::IS_BOOLEAN, true); - - if (is_number($this->info->title)) { - $label = get_string('includesection', 'backup', $this->info->title); - } elseif (empty($this->info->title)) { // Don't throw error if title is empty, gracefully continue restore. - $this->log( - 'Section title missing in backup for section id ' . $this->info->sectionid, - backup::LOG_WARNING, - $this->name - ); - $label = get_string('unnamedsection', 'backup'); + $delegatedcmid = $this->get_delegated_cm(); + if ($delegatedcmid) { + $sectionincluded = new restore_subsection_included_setting($settingname, base_setting::IS_BOOLEAN, true); + // Subsections depends on the parent activity included setting. + $settingname = $this->get_modname() . '_' . $delegatedcmid . '_included'; + if ($this->plan->setting_exists($settingname)) { + $cmincluded = $this->plan->get_setting($settingname); + $cmincluded->add_dependency( + $sectionincluded, + ); + } + $label = get_string('subsectioncontent', 'backup'); } else { - $label = $this->info->title; + $sectionincluded = new restore_section_included_setting($settingname, base_setting::IS_BOOLEAN, true); + + if (is_number($this->info->title)) { + $label = get_string('includesection', 'backup', $this->info->title); + } else if (empty($this->info->title)) { // Don't throw error if title is empty, gracefully continue restore. + $this->log( + 'Section title missing in backup for section id ' . $this->info->sectionid, + backup::LOG_WARNING, + $this->name + ); + $label = get_string('unnamedsection', 'backup'); + } else { + $label = $this->info->title; + } } + $sectionincluded->get_ui()->set_label($label); $this->add_setting($sectionincluded); @@ -209,7 +248,20 @@ class restore_section_task extends restore_task { $defaultvalue = true; } - $sectionuserinfo = new restore_section_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + $delegatedcmid = $this->get_delegated_cm(); + if ($delegatedcmid) { + $sectionuserinfo = new restore_subsection_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + // Subsections depends on the parent activity included setting. + $settingname = $this->get_modname() . '_' . $delegatedcmid . '_userinfo'; + if ($this->plan->setting_exists($settingname)) { + $cmincluded = $this->plan->get_setting($settingname); + $cmincluded->add_dependency( + $sectionuserinfo, + ); + } + } else { + $sectionuserinfo = new restore_section_userinfo_setting($settingname, base_setting::IS_BOOLEAN, $defaultvalue); + } if (!$defaultvalue) { // This is a bit hacky, but if there is no user data to restore, then diff --git a/backup/moodle2/restore_settingslib.php b/backup/moodle2/restore_settingslib.php index cce77a98dec..f62a7e433c7 100644 --- a/backup/moodle2/restore_settingslib.php +++ b/backup/moodle2/restore_settingslib.php @@ -222,6 +222,39 @@ class restore_section_included_setting extends restore_section_generic_setting { */ class restore_section_userinfo_setting extends restore_section_generic_setting {} +/** + * Subsection base class (delegated section). + */ +class restore_subsection_generic_setting extends restore_section_generic_setting { + /** + * Class constructor. + * + * @param string $name Name of the setting + * @param string $vtype Type of the setting, for example base_setting::IS_TEXT + * @param mixed $value Value of the setting + * @param bool $visibility Is the setting visible in the UI, for example base_setting::VISIBLE + * @param int $status Status of the setting with regards to the locking, for example base_setting::NOT_LOCKED + */ + public function __construct($name, $vtype, $value = null, $visibility = self::VISIBLE, $status = self::NOT_LOCKED) { + parent::__construct($name, $vtype, $value, $visibility, $status); + $this->level = self::SUBSECTION_LEVEL; + } +} + +/** + * Setting to define if one subsection is included or no. + * + * Activities _included settings depend of them if available. + */ +class restore_subsection_included_setting extends restore_subsection_generic_setting { +} + +/** + * Subsection backup setting to control if section will include + * user information or no, depends of @restore_users_setting. + */ +class restore_subsection_userinfo_setting extends restore_subsection_generic_setting { +} // Activity backup settings @@ -243,6 +276,41 @@ class restore_activity_included_setting extends restore_activity_generic_setting */ class restore_activity_userinfo_setting extends restore_activity_generic_setting {} +/** + * Generic subactivity setting to pass various settings between tasks and steps + */ +class restore_subactivity_generic_setting extends restore_activity_generic_setting { + /** + * Class constructor. + * + * @param string $name Name of the setting + * @param string $vtype Type of the setting, for example base_setting::IS_TEXT + * @param mixed $value Value of the setting + * @param bool $visibility Is the setting visible in the UI, for example base_setting::VISIBLE + * @param int $status Status of the setting with regards to the locking, for example base_setting::NOT_LOCKED + */ + public function __construct($name, $vtype, $value = null, $visibility = self::VISIBLE, $status = self::NOT_LOCKED) { + parent::__construct($name, $vtype, $value, $visibility, $status); + $this->level = self::SUBACTIVITY_LEVEL; + } +} + +/** + * Subactivity backup setting to control if activity will be included or no. + * + * Depends of restore_activities_setting and optionally parent section included setting. + */ +class restore_subactivity_included_setting extends restore_subactivity_generic_setting { +} + +/** + * Subactivity backup setting to control if activity will include user information. + * + * Depends of restore_users_setting. + */ +class restore_subactivity_userinfo_setting extends restore_subactivity_generic_setting { +} + /** * root setting to control if restore will create content bank content or no */ diff --git a/backup/util/dbops/backup_controller_dbops.class.php b/backup/util/dbops/backup_controller_dbops.class.php index 612daedf8ad..f022b76b70d 100644 --- a/backup/util/dbops/backup_controller_dbops.class.php +++ b/backup/util/dbops/backup_controller_dbops.class.php @@ -234,7 +234,9 @@ abstract class backup_controller_dbops extends backup_dbops { 'sectionid' => $task->get_sectionid(), 'modulename' => $task->get_modulename(), 'title' => $task->get_name(), - 'directory' => 'activities/' . $task->get_modulename() . '_' . $task->get_moduleid()); + 'directory' => 'activities/' . $task->get_modulename() . '_' . $task->get_moduleid(), + 'insubsection' => ($task->is_in_subsection()) ? 1 : '', + ); // Now get activity settings // Calculate prefix to find valid settings