Skip to content

Commit

Permalink
Backup/restore of icons #8 and #9.
Browse files Browse the repository at this point in the history
  • Loading branch information
rogiervandongen authored and gjb2048 committed Jul 25, 2024
1 parent dd0606a commit c13727f
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 7 deletions.
135 changes: 135 additions & 0 deletions backup/moodle2/backup_format_vsf_plugin.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Specialised backup for Progress Section course format.
*
* @package format_vsf
* @category backup
* @copyright &copy; 2022-onwards G J Barnard in respect to modifications of standard topics format.
* @copyright &copy; 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 &copy; 2022-onwards G J Barnard in respect to modifications of standard topics format.
* @copyright &copy; 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;
}

}
120 changes: 113 additions & 7 deletions backup/moodle2/restore_format_vsf_plugin.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* @package format_vsf
* @category backup
* @copyright &copy; 2022-onwards G J Barnard in respect to modifications of standard topics format.
* @copyright &copy; 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
*/
Expand All @@ -32,11 +33,19 @@
* @package format_vsf
* @category backup
* @copyright &copy; 2022-onwards G J Barnard in respect to modifications of standard topics format.
* @copyright &copy; 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;

Expand Down Expand Up @@ -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
);
}

/**
Expand All @@ -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;
Expand All @@ -106,20 +139,93 @@ 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
// in the original 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]);
}
}
}
}

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

}
1 change: 1 addition & 0 deletions classes/local/modicon/caticonsform.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Icon form.
*
Expand Down
1 change: 1 addition & 0 deletions classes/local/modicon/iconsform.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Icon form.
*
Expand Down
1 change: 1 addition & 0 deletions iconused.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Icon modification for course format
*
Expand Down
1 change: 1 addition & 0 deletions modicon.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Icon modification for course format
*
Expand Down
1 change: 1 addition & 0 deletions modicons.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Global icon modification
*
Expand Down

0 comments on commit c13727f

Please sign in to comment.