From f338eb1b623294e86739fc18954b3d3ddad5596a Mon Sep 17 00:00:00 2001 From: Rogier van Dongen Date: Wed, 24 Jul 2024 11:56:04 +0200 Subject: [PATCH] Backup/restore of icons #8 and #9. --- .../backup_format_vsf_plugin.class.php | 135 ++++++++++++++++++ .../restore_format_vsf_plugin.class.php | 120 +++++++++++++++- classes/local/modicon/caticonsform.php | 1 + classes/local/modicon/iconsform.php | 1 + iconused.php | 1 + modicon.php | 1 + modicons.php | 1 + 7 files changed, 253 insertions(+), 7 deletions(-) create mode 100644 backup/moodle2/backup_format_vsf_plugin.class.php diff --git a/backup/moodle2/backup_format_vsf_plugin.class.php b/backup/moodle2/backup_format_vsf_plugin.class.php new file mode 100644 index 0000000..afa73fa --- /dev/null +++ b/backup/moodle2/backup_format_vsf_plugin.class.php @@ -0,0 +1,135 @@ +. + +/** + * Specialised backup for Progress Section course format. + * + * @package format_vsf + * @category backup + * @copyright © 2022-onwards G J Barnard in respect to modifications of standard topics format. + * @copyright © 2024-onwards RvD in respect to modifications for custom icons. + * @copyright 2017 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Specialised backup for Progress Section course format. + * + * Processes 'numsections' from the old backup files and hides sections that used to be "orphaned". + * + * @package format_vsf + * @category backup + * @copyright © 2022-onwards G J Barnard in respect to modifications of standard topics format. + * @copyright © 2024-onwards RvD in respect to modifications for custom icons. + * @copyright 2017 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class backup_format_vsf_plugin extends backup_format_plugin { + + /** + * Define course plugin structure for format_vsf + * + * @return array|void + */ + protected function define_course_plugin_structure() { + global $DB; + // Define the virtual plugin element with the condition to fulfill. + $plugin = $this->get_plugin_element(null, $this->get_format_condition(), 'vsf'); + + // Create one standard named plugin element (the visible container). + // The courseid not required as populated on restore. + $pluginwrapper = new backup_nested_element($this->get_recommended_name()); + // Connect the visible container ASAP. + $plugin->add_child($pluginwrapper); + + // We'll simply do ALL installed modules. + // It will be too much hassle to see what we may wish to include or not, + // because this is "high level behaviour". Customizing icons also will not + // see what may or may not be wishful. Custom icons is NOT dependent on + // which modules are in use in the course. + // Customisation can be done for ALL activity types. + $courseiconwrapper = new backup_nested_element('courseicons'); + $courseicon = new backup_nested_element('courseicon', [], ['name']); + $sql = 'SELECT DISTINCT m.name FROM {modules} m'; + $params = []; + $courseicon->set_source_sql($sql, $params); + + $pluginwrapper->add_child($courseiconwrapper); + $courseiconwrapper->add_child($courseicon); + + // Task context is course context. + $ctxid = $this->task->get_contextid(); + $modules = $DB->get_fieldset_sql($sql, $params); + foreach ($modules as $module) { + $pluginwrapper->annotate_files('format_vsf', 'modicon_' . $module, null, $ctxid); + } + + return $plugin; + } + + /** + * Define module plugin structure for format_vsf + * + * @return array|void + */ + protected function define_module_plugin_structure() { + // We'll start off by detecting whether we WANT to include a customicon. + // In other words: is there a file at all? + $actid = $this->task->get_activityid(); + $modulecontextid = $this->task->get_contextid(); + + $fs = get_file_storage(); + $files = $fs->get_area_files($modulecontextid, 'format_vsf', 'modicon', 0, 'itemid', false); + if (count($files) == 0) { + // No custom icons. Break early. + return; + } + + // We have a file, so we'll add the "base essentials": itemid (0), module name and ORIGINAL CONTEXT ID. + // This seems unnatural, but we're hooking into the MODULE. + // This is not the same as the activity, which holds and stores the original context. + // However, this context is not known on the restore process callback. + // See the restore class for the rest of the details. + + // Define/get the virtual plugin element with the condition to fulfill. + $plugin = $this->get_plugin_element(null, $this->get_format_condition(), 'vsf'); + + // Create one standard named plugin element (the visible container) using the base recommended name. + $pluginwrapper = new backup_nested_element($this->get_recommended_name()); + // Connect to container. + $plugin->add_child($pluginwrapper); + + // Create an element for the modicon. Since half the information I expect is NOT present in restore, + // such as the CRITICAL OLD CONTEXT we need for simple file restores, we'll add that here. + // Please note we "hardcode" the source data, which we absolutely require to make this work. + // GAWD, I hate backup/restore. Unclear, messy, lacking. Just... plain... wrong. + // ELOY, YOUR IMPLEMENTATION SUCKS! + $customicon = new backup_nested_element('modicon', [], ['itemid', 'modname', 'oldctxid']); + $customicon->set_source_array([[ + 'itemid' => 0, + 'modname' => $this->task->get_modulename(), + 'oldctxid' => $modulecontextid, + ]]); + // Connect to conainer. + $pluginwrapper->add_child($customicon); + + // And finally, we can annotate the file ids so they'll be included in the MBZ. + $customicon->annotate_files('format_vsf', 'modicon', null, $modulecontextid); + + return $plugin; + } + +} diff --git a/backup/moodle2/restore_format_vsf_plugin.class.php b/backup/moodle2/restore_format_vsf_plugin.class.php index 92a78de..9e2c275 100644 --- a/backup/moodle2/restore_format_vsf_plugin.class.php +++ b/backup/moodle2/restore_format_vsf_plugin.class.php @@ -20,6 +20,7 @@ * @package format_vsf * @category backup * @copyright © 2022-onwards G J Barnard in respect to modifications of standard topics format. + * @copyright © 2024-onwards RvD in respect to modifications for custom icons. * @copyright 2017 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -32,11 +33,19 @@ * @package format_vsf * @category backup * @copyright © 2022-onwards G J Barnard in respect to modifications of standard topics format. + * @copyright © 2024-onwards RvD in respect to modifications for custom icons. * @copyright 2017 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class restore_format_vsf_plugin extends restore_format_plugin { + /** + * Holds data objects that refer to custom module instance icons. + * + * @var array + */ + protected static $modicons = []; + /** @var int */ protected $originalnumsections = 0; @@ -66,13 +75,28 @@ public function define_course_plugin_structure() { if (($target == backup::TARGET_CURRENT_ADDING || $target == backup::TARGET_EXISTING_ADDING) && $this->need_restore_numsections()) { $maxsection = $DB->get_field_sql( - 'SELECT max(section) FROM {course_sections} WHERE course = ?', - [$this->step->get_task()->get_courseid()]); - $this->originalnumsections = (int)$maxsection; + 'SELECT max(section) FROM {course_sections} WHERE course = ?', + [$this->step->get_task()->get_courseid()]); + $this->originalnumsections = (int) $maxsection; + } + + $paths = []; + // Since this method is executed before the restore we can do some pre-checks here. + // In case of merging backup into existing course find the current number of sections. + // We will ONLY perform the specifics if we're NOT importing etc etc. + $allowrestore = [backup::TARGET_NEW_COURSE]; + if (in_array($target, $allowrestore)) { + // We'll restore :). + $elename = 'courseicon'; // This defines the postfix of 'process_*' below. + $elepath = $this->get_pathfor('/courseicons/courseicon'); + $paths[] = new restore_path_element($elename, $elepath); } // Dummy path element is needed in order for after_restore_course() to be called. - return [new restore_path_element('dummy_course', $this->get_pathfor('/dummycourse'))]; + return array_merge( + [new restore_path_element('dummy_course', $this->get_pathfor('/dummycourse'))], + $paths + ); } /** @@ -92,8 +116,17 @@ public function process_dummy_course() { * @return void */ public function after_restore_course() { - global $DB; + $this->vsf_after_restore_course_numsections(); + $this->vsf_after_restore_course_modicons(); + } + /** + * Restore numsections + * + * @return void + */ + protected function vsf_after_restore_course_numsections() { + global $DB; if (!$this->need_restore_numsections()) { // Backup file was made in Moodle 4.0 or later, we don't need to process 'numsecitons'. return; @@ -106,7 +139,7 @@ public function after_restore_course() { return; } - $numsections = (int)$data['tags']['numsections']; + $numsections = (int) $data['tags']['numsections']; foreach ($backupinfo->sections as $key => $section) { // For each section from the backup file check if it was restored and if was "orphaned" in the original // course and mark it as hidden. This will leave all activities in it visible and available just as it was @@ -114,7 +147,7 @@ public function after_restore_course() { // Exception is when we restore with merging and the course already had a section with this section number, // in this case we don't modify the visibility. if ($this->step->get_task()->get_setting_value($key . '_included')) { - $sectionnum = (int)$section->title; + $sectionnum = (int) $section->title; if ($sectionnum > $numsections && $sectionnum > $this->originalnumsections) { $DB->execute("UPDATE {course_sections} SET visible = 0 WHERE course = ? AND section = ?", [$this->step->get_task()->get_courseid(), $sectionnum]); @@ -122,4 +155,77 @@ public function after_restore_course() { } } } + + /** + * Creates a dummy path element in order to be able to execute code after restore + * + * @return restore_path_element[] + */ + public function define_module_plugin_structure() { + // Since this method is executed before the restore we can do some pre-checks here. + // In case of merging backup into existing course find the current number of sections. + $target = $this->step->get_task()->get_target(); + + $paths = []; + // We will ONLY perform the specifics if we're NOT importing etc etc. + $allowrestore = [backup::TARGET_NEW_COURSE, backup::TARGET_CURRENT_ADDING, backup::TARGET_EXISTING_ADDING]; + if (in_array($target, $allowrestore)) { + // We'll restore :). + $elename = 'modicon'; // This defines the postfix of 'process_*' below. + $elepath = $this->get_pathfor('/modicon'); + $paths[] = new restore_path_element($elename, $elepath); + } + + return $paths; + } + + /** + * Process course level icon customisations. + */ + public function process_courseicon($data) { + if (!is_object($data)) { + $data = (object) $data; + } + $modname = $data->name; + $filearea = "modicon_{$modname}"; + $itemid = 0; + $mappingitemname = null; + $filesctxid = $this->task->get_old_contextid(); + $this->add_related_files('format_vsf', $filearea, $mappingitemname, $filesctxid, $itemid); + } + + /** + * Process module level icon customisations. + */ + public function process_modicon($data) { + if (!is_object($data)) { + $data = (object) $data; + } + // Data, as stated in the backup class, holds the itemid, module name and original context. + // The reason for the original context is because at this stage, we DO NOT have + // an original context. + // It is due to $this->task->get_old_contextid() returning 0, consequently failing $this->add_related_files(). + // The only way to get this right now, is to hook into "after_restore_course()". + // We can achieve this by filling a STATIC property (we CANNOT use an instance property, it WILL fail) with the data object. + // Then, in "after_restore_course()", we can simply call "add_related_files()" with the correct data. + + // Add data object to static property for use later (see below). + static::$modicons[] = $data; + } + + /** + * Restore the modicons that were added by this/in format. + */ + protected function vsf_after_restore_course_modicons() { + // For all the data objects that were added in "process_modicon()", + // try to add the related files. + foreach (static::$modicons as $data) { + $filearea = "modicon"; + $itemid = 0; + $mappingitemname = null; + $filesctxid = $data->oldctxid; + $this->add_related_files('format_vsf', $filearea, $mappingitemname, $filesctxid, $itemid); + } + } + } diff --git a/classes/local/modicon/caticonsform.php b/classes/local/modicon/caticonsform.php index f3f566e..e956229 100644 --- a/classes/local/modicon/caticonsform.php +++ b/classes/local/modicon/caticonsform.php @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . + /** * Icon form. * diff --git a/classes/local/modicon/iconsform.php b/classes/local/modicon/iconsform.php index 68bdfcc..5a56fee 100644 --- a/classes/local/modicon/iconsform.php +++ b/classes/local/modicon/iconsform.php @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . + /** * Icon form. * diff --git a/iconused.php b/iconused.php index 2c99904..8ec4334 100644 --- a/iconused.php +++ b/iconused.php @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . + /** * Icon modification for course format * diff --git a/modicon.php b/modicon.php index a9afc06..c37d880 100644 --- a/modicon.php +++ b/modicon.php @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . + /** * Icon modification for course format * diff --git a/modicons.php b/modicons.php index a90c753..5bbc9e2 100644 --- a/modicons.php +++ b/modicons.php @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . + /** * Global icon modification *