From 5ccca059d9d9b1e833175ffd95bb00094508f535 Mon Sep 17 00:00:00 2001
From: Sara Arjona
Date: Mon, 12 Jan 2026 14:48:33 +0100
Subject: [PATCH] MDL-87281 subsection: Add description cleanup links to
settings page
---
public/mod/subsection/cleandescriptions.php | 52 +++++++++++++
public/mod/subsection/lang/en/subsection.php | 22 ++++++
public/mod/subsection/settings.php | 73 +++++++++++++++++-
.../subsection_clean_descriptions.feature | 46 +++++++++++
.../subsections_with_descriptions.mbz | Bin 0 -> 8177 bytes
5 files changed, 192 insertions(+), 1 deletion(-)
create mode 100644 public/mod/subsection/cleandescriptions.php
create mode 100644 public/mod/subsection/tests/behat/subsection_clean_descriptions.feature
create mode 100644 public/mod/subsection/tests/fixtures/subsections_with_descriptions.mbz
diff --git a/public/mod/subsection/cleandescriptions.php b/public/mod/subsection/cleandescriptions.php
new file mode 100644
index 00000000000..f5170e0323a
--- /dev/null
+++ b/public/mod/subsection/cleandescriptions.php
@@ -0,0 +1,52 @@
+.
+
+/**
+ * Remove or migrate subsection descriptions
+ *
+ * @copyright 2026 Sara Arjona
+ * @package mod_subsection
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../../config.php');
+
+require_admin();
+
+$action = required_param('action', PARAM_ALPHA);
+$count = optional_param('count', 0, PARAM_INT);
+$return = new moodle_url('/admin/settings.php', ['section' => 'mod_subsection_settings']);
+
+$PAGE->set_url('/mod/subsection/cleandescriptions.php');
+$PAGE->set_context(context_system::instance());
+
+require_sesskey();
+if ($action === 'delete') {
+ // Remove all existing subsection descriptions.
+ $DB->set_field('course_sections', 'summary', '', ['component' => 'mod_subsection']);
+ redirect(
+ $return,
+ get_string('descriptionsdeletedsuccess', 'mod_subsection', $count),
+ null,
+ \core\output\notification::NOTIFY_SUCCESS
+ );
+} else if ($action === 'migrate') {
+ // Schedule the ad-hoc task to migrate subsection descriptions.
+ \core\task\manager::queue_adhoc_task(new \mod_subsection\task\migrate_subsection_descriptions_task(), true);
+ redirect($return);
+} else {
+ throw new moodle_exception('invalidaction', 'mod_subsection');
+}
diff --git a/public/mod/subsection/lang/en/subsection.php b/public/mod/subsection/lang/en/subsection.php
index 3893fbe662a..b82aff3bd7a 100644
--- a/public/mod/subsection/lang/en/subsection.php
+++ b/public/mod/subsection/lang/en/subsection.php
@@ -23,6 +23,28 @@
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['cleandescriptionsdetail'] = 'Subsection pages and descriptions are no longer supported in Moodle 5.2.
+This site has {$a->count} subsection descriptions that are no longer visible to users.
+You can choose to:
+
+ - Migrate these descriptions to Text and Media areas.
The Text and media areas will be displayed at the top of each subsection.
{$a->migratelink}
+
+ - Delete these descriptions permanently.
This will completely erase descriptions from the database.
{$a->deletelink}
+
+
+
';
+$string['deleteconfirmbutton'] = 'Delete all descriptions';
+$string['deleteconfirmtext'] = 'This will permanently delete {$a} subsection descriptions from the database.
You can\'t undo this. Are you sure you want to delete all descriptions?';
+$string['deleteconfirmtitle'] = 'Delete all subsection descriptions?';
+$string['deletelinktext'] = 'Delete descriptions';
+$string['descriptionsdeletedsuccess'] = '{$a} subsection descriptions deleted.';
+$string['descriptionsmigratedpending'] = 'Subsection descriptions waiting to be migrated: {$a}';
+$string['descriptionsmigratedsuccess'] = 'The migration task for all subsection descriptions has been created. This task will run in the background and may take a few minutes.';
+$string['invalidaction'] = 'Invalid action specified.';
+$string['migrateconfirmbutton'] = 'Migrate all descriptions';
+$string['migrateconfirmtext'] = 'This will migrate {$a} subsection descriptions to Text and Media areas.
You can\'t undo this. Are you sure you want to migrate all descriptions?';
+$string['migrateconfirmtitle'] = 'Migrate all subsection descriptions?';
+$string['migratelinktext'] = 'Migrate descriptions';
$string['modulename'] = 'Subsection';
$string['modulenameplural'] = 'Subsections';
$string['pluginadministration'] = 'Subsection administration';
diff --git a/public/mod/subsection/settings.php b/public/mod/subsection/settings.php
index 9025c8122d6..41ef83e9d16 100644
--- a/public/mod/subsection/settings.php
+++ b/public/mod/subsection/settings.php
@@ -28,7 +28,78 @@ defined('MOODLE_INTERNAL') || die();
if ($hassiteconfig) {
$settings = new admin_settingpage('mod_subsection_settings', new lang_string('pluginname', 'mod_subsection'));
- // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
if ($ADMIN->fulltree) {
+ // Add description cleanup and migration links.
+ $count = $DB->count_records_select(
+ table: 'course_sections',
+ select: 'component = :component AND summary != :empty',
+ params: ['component' => 'mod_subsection', 'empty' => ''],
+ );
+ $task = \core\task\manager::get_queued_adhoc_task_record(new \mod_subsection\task\migrate_subsection_descriptions_task());
+ if ($task) {
+ // There is a pending migration task, show notification and pending count.
+ $notification = $OUTPUT->notification(
+ get_string('descriptionsmigratedsuccess', 'mod_subsection'),
+ \core\output\notification::NOTIFY_SUCCESS,
+ );
+ $settings->add(new admin_setting_heading(
+ 'migratedescriptionsnotification',
+ '',
+ $notification,
+ ));
+ $settings->add(new admin_setting_heading(
+ 'pendingcleandescriptions',
+ '',
+ new lang_string('descriptionsmigratedpending', 'mod_subsection', $count),
+ ));
+ } else if ($count > 0) {
+ // Show migration and deletion links.
+ $migrateaction = new \confirm_action(
+ message: get_string('migrateconfirmtext', 'mod_subsection', $count),
+ continuelabel: get_string('migrateconfirmbutton', 'mod_subsection'),
+ title: get_string('migrateconfirmtitle', 'mod_subsection'),
+ );
+ $migrateurl = new moodle_url(
+ '/mod/subsection/cleandescriptions.php',
+ ['action' => 'migrate', 'count' => $count, 'sesskey' => sesskey()],
+ );
+ $migratelink = $OUTPUT->action_link(
+ url: $migrateurl,
+ text: get_string('migratelinktext', 'mod_subsection'),
+ action: $migrateaction,
+ attributes: ['class' => 'btn btn-secondary'],
+ );
+
+ $deleteaction = new \confirm_action(
+ message: get_string('deleteconfirmtext', 'mod_subsection', $count),
+ continuelabel: get_string('deleteconfirmbutton', 'mod_subsection'),
+ title: get_string('deleteconfirmtitle', 'mod_subsection'),
+ dialogtype: 'delete',
+ );
+ $deleteurl = new moodle_url(
+ '/mod/subsection/cleandescriptions.php',
+ ['action' => 'delete', 'count' => $count, 'sesskey' => sesskey()],
+ );
+ $deletelink = $OUTPUT->action_link(
+ url: $deleteurl,
+ text: get_string('deletelinktext', 'mod_subsection'),
+ action: $deleteaction,
+ attributes: ['class' => 'btn btn-secondary'],
+ );
+
+ $settings->add(new admin_setting_heading(
+ 'cleandescriptions',
+ '',
+ new lang_string(
+ 'cleandescriptionsdetail',
+ 'mod_subsection',
+ [
+ 'count' => $count,
+ 'migratelink' => $migratelink,
+ 'deletelink' => $deletelink,
+ ],
+ ),
+ ));
+ }
}
}
diff --git a/public/mod/subsection/tests/behat/subsection_clean_descriptions.feature b/public/mod/subsection/tests/behat/subsection_clean_descriptions.feature
new file mode 100644
index 00000000000..7aa8f8ad2c8
--- /dev/null
+++ b/public/mod/subsection/tests/behat/subsection_clean_descriptions.feature
@@ -0,0 +1,46 @@
+@mod @mod_subsection @_file_upload
+Feature: Subsection clean descriptions
+ In order to manage subsection descriptions
+ As an administrator
+ I want to be able to delete or migrate subsection descriptions
+
+ Background:
+ Given the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following config values are set as admin:
+ | enableasyncbackup | 0 |
+ And I am on the "Course 1" "restore" page logged in as "admin"
+ And I press "Manage course backups"
+ And I upload "mod/subsection/tests/fixtures/subsections_with_descriptions.mbz" file to "Files" filemanager
+ And I press "Save changes"
+ And I restore "subsections_with_descriptions.mbz" backup into a new course using this options:
+ | Schema | Course name | Course 2 |
+ | Schema | Course short name | C2 |
+
+ @javascript
+ Scenario: Migrate subsection descriptions
+ Given I navigate to "Plugins > Activity modules > Subsection" in site administration
+ And I should see "This site has 2 subsection descriptions that are no longer visible to users."
+ When I click on "Migrate descriptions" "link" in the "region-main" "region"
+ And I should see "This will migrate 2 subsection descriptions to Text and Media areas." in the "Migrate all subsection descriptions?" "dialogue"
+ And I click on "Migrate all descriptions" "button" in the "Migrate all subsection descriptions?" "dialogue"
+ Then I should see "The migration task for all subsection descriptions has been created." in the "region-main" "region"
+ And I should see "Subsection descriptions waiting to be migrated: 2" in the "region-main" "region"
+ And I reload the page
+ And I should see "The migration task for all subsection descriptions has been created." in the "region-main" "region"
+ And I should see "Subsection descriptions waiting to be migrated: 2" in the "region-main" "region"
+ And I run all adhoc tasks
+ And I reload the page
+ And I should not see "Subsection descriptions waiting to be migrated:" in the "region-main" "region"
+
+ @javascript
+ Scenario: Delete subsection descriptions
+ Given I navigate to "Plugins > Activity modules > Subsection" in site administration
+ And I should see "This site has 2 subsection descriptions that are no longer visible to users."
+ When I click on "Delete descriptions" "link" in the "region-main" "region"
+ And I should see "This will permanently delete 2 subsection descriptions from the database." in the "Delete all subsection descriptions?" "dialogue"
+ And I click on "Delete all descriptions" "button" in the "Delete all subsection descriptions?" "dialogue"
+ Then I should see "2 subsection descriptions deleted." in the "region-main" "region"
+ And I reload the page
+ And I should not see "Subsection pages and descriptions are no longer supported in Moodle 5.2"
diff --git a/public/mod/subsection/tests/fixtures/subsections_with_descriptions.mbz b/public/mod/subsection/tests/fixtures/subsections_with_descriptions.mbz
new file mode 100644
index 0000000000000000000000000000000000000000..2550090994f318dc2abe255605157d5ee9537170
GIT binary patch
literal 8177
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
literal 0
HcmV?d00001