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;