From 454f853b67874f7fa1fee9530b673d104a347bf0 Mon Sep 17 00:00:00 2001 From: Matt Davidson Date: Mon, 30 Jun 2025 11:23:13 -0400 Subject: [PATCH] security and visual fixes for Moodle 5.x --- classes/processor.php | 114 ++++++++++++++++++++------------ classes/step2_form.php | 4 +- classes/step3_form.php | 4 +- classes/step4_form.php | 16 ++--- classes/tracker.php | 2 +- db/upgrade.php | 16 +++++ index.php | 31 +++++---- optin.php | 4 +- optout.php | 2 +- removesavepoint.php | 2 +- step2.php | 77 +++++++++++---------- step3.php | 71 +++++++++++--------- step4.php | 46 +++++++------ styles.css | 6 +- templates/archive_view.mustache | 2 +- version.php | 6 +- 16 files changed, 239 insertions(+), 164 deletions(-) diff --git a/classes/processor.php b/classes/processor.php index f391662..5ad4212 100644 --- a/classes/processor.php +++ b/classes/processor.php @@ -1018,7 +1018,7 @@ public static function save_state($stepid, $title, $data) { $record = new stdClass(); $record->title = $title; - $record->content = serialize($data); + $record->content = json_encode($data); $record->step = $stepid; $record->savedate = $date->getTimestamp(); if ($DB->insert_record('tool_coursearchiver_saves', $record)) { @@ -1070,11 +1070,16 @@ public static function get_saves() { * @return string */ public static function get_savestatelist() { - global $CFG, $DB, $OUTPUT; + global $DB, $OUTPUT; // Back button. $savelist = html_writer::link(new moodle_url('/admin/tool/coursearchiver/index.php'), get_string('back')); + + $savelist .= html_writer::tag('h3', + get_string('savestatelist', 'tool_coursearchiver'), + ['style' => 'text-align: center']); + // Table. $savelist .= html_writer::start_tag('table', ['style' => 'border-collapse: collapse;width: 100%;', 'cellpadding' => '5', @@ -1099,7 +1104,7 @@ public static function get_savestatelist() { if ($saves) { foreach ($saves as $savepoint) { // Create security key for each link. - $key = sha1($CFG->dbpass . $savepoint->id); + $key = sha1(self::get_coursearchiver_keyid() . $savepoint->id); $link = new moodle_url('/admin/tool/coursearchiver/removesavepoint.php', ['savepointid' => $savepoint->id, 'key' => $key]); @@ -1352,6 +1357,21 @@ public function get_list_of_admins_and_managers() { return $adminsandmanagers; } + /** + * Get a unique server id to serve as the key. ID must not change between key creation and use. + * + * @return string + */ + public static function get_coursearchiver_keyid() { + global $DB; + + return $DB->get_field( + "config_plugins", + "id", + ["plugin" => "tool_coursearchiver", "name" => "version"], + ); + } + /** * Static method to get list of the userid's of admin and other users with course view capability * @@ -1370,8 +1390,6 @@ public static function get_list_of_admins_and_managers_static() { * @return array Full HTML table listing the $courses */ public function get_email_courses($obj, $links = true) { - global $CFG; - if ($this->mode == self::MODE_HIDEEMAIL) { $optoutbutton = get_string('optouthide', 'tool_coursearchiver'); } else if ($this->mode == self::MODE_ARCHIVEEMAIL) { @@ -1387,7 +1405,7 @@ public function get_email_courses($obj, $links = true) { $rowcolor = "#FFF"; foreach ($obj["courses"] as $course) { // Create security key for each link. - $key = sha1($CFG->dbpass . $course->id . $obj["user"]->id); + $key = sha1(self::get_coursearchiver_keyid() . $course->id . $obj["user"]->id); // Only add courses that are not hidden if mode is HIDEEMAIL. if ($this->mode == self::MODE_HIDEEMAIL && !$course->visible) { @@ -1493,41 +1511,43 @@ public static function optout_course($courseid, $userid) { * @return string */ public static function get_optoutlist() { - global $CFG, $DB, $OUTPUT; + global $DB, $OUTPUT; - $sql = "SELECT * - FROM {tool_coursearchiver_optout} - ORDER BY optouttime"; + $sql = "SELECT * FROM {tool_coursearchiver_optout} ORDER BY optouttime"; $optouts = $DB->get_records_sql($sql); // Back button. - $courses = html_writer::link(new moodle_url('/admin/tool/coursearchiver/index.php'), - get_string('back')); + $courses = html_writer::link(new moodle_url('/admin/tool/coursearchiver/index.php'), get_string('back')); + + $courses .= html_writer::tag('h3', + get_string('optoutlist', 'tool_coursearchiver'), + ['style' => 'text-align: center']); + // Archive table. $courses .= html_writer::start_tag('table', ['style' => 'border-collapse: collapse;width: 100%;', 'cellpadding' => '5', ]); $rowcolor = "#FFF"; $courses .= html_writer::tag('tr', - html_writer::tag('th', - get_string('course')) . - html_writer::tag('th', - get_string('optouttime', 'tool_coursearchiver'), - ['style' => 'text-align: center']) . - html_writer::tag('th', - get_string('optoutby', 'tool_coursearchiver'), - ['style' => 'text-align: center']) . - html_writer::tag('th', - get_string('actions'), - ['width' => '100px', 'style' => 'text-align: center']), - ['style' => 'background-color:' . $rowcolor]); + html_writer::tag('th', + get_string('course')) . + html_writer::tag('th', + get_string('optouttime', 'tool_coursearchiver'), + ['style' => 'text-align: center']) . + html_writer::tag('th', + get_string('optoutby', 'tool_coursearchiver'), + ['style' => 'text-align: center']) . + html_writer::tag('th', + get_string('actions'), + ['width' => '100px', 'style' => 'text-align: center']), + ['style' => 'background-color:' . $rowcolor]); if ($optouts) { foreach ($optouts as $optout) { $user = $DB->get_record('user', ['id' => $optout->userid]); if ($course = $DB->get_record('course', ['id' => $optout->courseid], '*', IGNORE_MISSING)) { // Create security key for each link. - $key = sha1($CFG->dbpass . $course->id . $optout->userid); + $key = sha1(self::get_coursearchiver_keyid() . $course->id . $optout->userid); if ($optout->optoutlength == 0) { $ago = "∞"; @@ -1609,31 +1629,39 @@ public static function get_archivelist($search, $recover = false) { "/\\"); // Form start. $rowcolor = "#FFF"; - $data = ["formstart" => true, - "isadmin" => $isadmin, - "recover" => $recover, - "searchterm" => $search, - "rowcolor" => $rowcolor, - "limiter" => $config->archivelimit, - ]; + $data = [ + "formstart" => true, + "isadmin" => $isadmin, + "recover" => $recover, + "searchterm" => $search, + "rowcolor" => $rowcolor, + "limiter" => $config->archivelimit, + ]; $courses = $OUTPUT->render_from_template('tool_coursearchiver/archive_view', $data); + $params = []; + // Get either archives that are not marked for deletion or those that have been. - $select = !$recover ? 'timetodelete = 0' : 'timetodelete > 0'; + $sql = !$recover ? 'timetodelete = 0' : 'timetodelete > 0'; // Search criteria. - $select .= !empty($search) ? " AND filename LIKE '%$search%'" : ''; + $params['filename'] = '%' . $DB->sql_like_escape($search) . '%'; + $sql .= !empty($search) ? " AND " . $DB->sql_like("filename", ":filename", false, false) : ''; // Only show user files. - $select .= $isadmin ? '' : " AND owners LIKE '%|$USER->id|%'"; - - $archives = $DB->get_records_select('tool_coursearchiver_archived', - $select, - null, - 'filename', - '*', - 0, - $config->archivelimit); + $params['owners'] = '%|' . $DB->sql_like_escape($USER->id) . '|%'; + $sql .= $isadmin ? '' : " AND " . $DB->sql_like("owners", ":owners", false, false); + + $archives = $DB->get_records_select( + 'tool_coursearchiver_archived', + $sql, + $params, + 'filename', + '*', + 0, + $config->archivelimit + ); + if ($archives) { foreach ($archives as $archive) { $pathinfo = pathinfo($archive->filename); diff --git a/classes/step2_form.php b/classes/step2_form.php index 68dbe91..594dab1 100644 --- a/classes/step2_form.php +++ b/classes/step2_form.php @@ -41,8 +41,8 @@ public function definition() { $data = $this->_customdata['processor_data']; $mform->addElement('hidden', 'formdata'); - $mform->setType('formdata', PARAM_RAW); - $mform->setDefault('formdata', serialize($data['searches'])); + $mform->setType('formdata', PARAM_TEXT); + $mform->setDefault('formdata', json_encode($data['searches'])); $mform->addElement('header', 'searchresultshdr', get_string('courseselector', 'tool_coursearchiver')); diff --git a/classes/step3_form.php b/classes/step3_form.php index 9a88ca5..3b3060b 100644 --- a/classes/step3_form.php +++ b/classes/step3_form.php @@ -41,8 +41,8 @@ public function definition() { $data = $this->_customdata['processor_data']; $mform->addElement('hidden', 'formdata'); - $mform->setType('formdata', PARAM_RAW); - $mform->setDefault('formdata', serialize($data['courses'])); + $mform->setType('formdata', PARAM_TEXT); + $mform->setDefault('formdata', json_encode($data['courses'])); $mform->addElement('header', 'emaillist', get_string('emailselector', 'tool_coursearchiver')); diff --git a/classes/step4_form.php b/classes/step4_form.php index 14469f4..a418cb2 100644 --- a/classes/step4_form.php +++ b/classes/step4_form.php @@ -41,14 +41,14 @@ public function definition() { $data = $this->_customdata['processor_data']; $mform->addElement('hidden', 'formdata'); - $mform->setType('formdata', PARAM_RAW); + $mform->setType('formdata', PARAM_TEXT); $mform->setDefault('formdata', $data['formdata']); - $mform->addElement('hidden', 'mode'); - $mform->setType('mode', PARAM_INT); - $mform->setDefault('mode', $data['mode']); + $mform->addElement('hidden', 'coursearchiver_mode'); + $mform->setType('coursearchiver_mode', PARAM_INT); + $mform->setDefault('coursearchiver_mode', $data['mode']); - $count = count(unserialize($data["formdata"])); + $count = count(json_decode($data["formdata"])); if (empty($count)) { $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php', ["error" => get_string('unknownerror', 'tool_coursearchiver')]); @@ -57,7 +57,7 @@ public function definition() { switch($data["mode"]) { case tool_coursearchiver_processor::MODE_HIDEEMAIL: - foreach (unserialize($data["formdata"]) as $r) { // Loop through every possible user. + foreach (json_decode($data["formdata"]) as $r) { // Loop through every possible user. if (substr($r, 0, 1) == 'x') { // Determine if they were NOT selected. $count--; // Remove 1 from count. } @@ -65,7 +65,7 @@ public function definition() { $message = get_string('confirmmessagehideemail', 'tool_coursearchiver', $count); break; case tool_coursearchiver_processor::MODE_ARCHIVEEMAIL: - foreach (unserialize($data["formdata"]) as $r) { // Loop through every possible user. + foreach (json_decode($data["formdata"]) as $r) { // Loop through every possible user. if (substr($r, 0, 1) == 'x') { // Determine if they were NOT selected. $count--; // Remove 1 from count. } @@ -73,7 +73,7 @@ public function definition() { $message = get_string('confirmmessagearchiveemail', 'tool_coursearchiver', $count); break; case tool_coursearchiver_processor::MODE_DELETEEMAIL: - foreach (unserialize($data["formdata"]) as $r) { // Loop through every possible user. + foreach (json_decode($data["formdata"]) as $r) { // Loop through every possible user. if (substr($r, 0, 1) == 'x') { // Determine if they were NOT selected. $count--; // Remove 1 from count. } diff --git a/classes/tracker.php b/classes/tracker.php index d316df3..306ecb9 100644 --- a/classes/tracker.php +++ b/classes/tracker.php @@ -424,7 +424,7 @@ public function start() { * @return void */ public function output($data, $info = false) { - global $CFG, $DB; + global $CFG; $return = 1; // By default we are returning the a single process as finished. diff --git a/db/upgrade.php b/db/upgrade.php index cc53829..2bb136b 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -204,5 +204,21 @@ function xmldb_tool_coursearchiver_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2020022700, 'tool', 'coursearchiver'); } + // Update saves table to use json_encoding instead of serialize. + if ($oldversion < 2025070102) { + if ($saves = $DB->get_records('tool_coursearchiver_saves')) { + foreach ($saves as $save) { + $content = unserialize($save->content); + if (!empty($content)) { + $save->content = json_encode($content); + $DB->update_record('tool_coursearchiver_saves', $save); + } + } + } + + // Monitor savepoint reached. + upgrade_plugin_savepoint(true, 2025070102, 'tool', 'coursearchiver'); + } + return true; } diff --git a/index.php b/index.php index a314a13..ea24ceb 100644 --- a/index.php +++ b/index.php @@ -33,14 +33,17 @@ admin_externalpage_setup('toolcoursearchiver'); global $SESSION; -$error = isset($SESSION->error) ? $SESSION->error : optional_param('error', false, PARAM_RAW); -$submitted = optional_param('submitbutton', false, PARAM_RAW); +$error = $SESSION->coursearchiver_error ?? optional_param('coursearchiver_error', false, PARAM_TEXT); +$error = htmlspecialchars($error, ENT_COMPAT); + +$submitted = optional_param('submitbutton', false, PARAM_TEXT); +$submitted = htmlspecialchars($submitted, ENT_COMPAT); // Button to start over has been pressed. -if ($submitted == get_string('back', 'tool_coursearchiver')) { - unset($SESSION->formdata); - unset($SESSION->mode); - unset($SESSION->error); +if ($submitted == htmlspecialchars(get_string('back', 'tool_coursearchiver'), ENT_COMPAT)) { + unset($SESSION->coursearchiver_formdata); + unset($SESSION->coursearchiver_mode); + unset($SESSION->coursearchiver_error); unset($SESSION->selected); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); @@ -48,23 +51,23 @@ // View optouts list button. if (!empty($submitted)) { - if ($submitted == get_string('optoutlist', 'tool_coursearchiver')) { + if ($submitted == htmlspecialchars(get_string('optoutlist', 'tool_coursearchiver'), ENT_COMPAT)) { $returnurl = new moodle_url('/admin/tool/coursearchiver/optoutlist.php'); redirect($returnurl); } - if ($submitted == get_string('archivelist', 'tool_coursearchiver')) { + if ($submitted == htmlspecialchars(get_string('archivelist', 'tool_coursearchiver'), ENT_COMPAT)) { $returnurl = new moodle_url('/admin/tool/coursearchiver/archivelist.php'); redirect($returnurl); } - if ($submitted == get_string('savestatelist', 'tool_coursearchiver')) { + if ($submitted == htmlspecialchars(get_string('savestatelist', 'tool_coursearchiver'), ENT_COMPAT)) { $returnurl = new moodle_url('/admin/tool/coursearchiver/savestatelist.php'); redirect($returnurl); } } -unset($SESSION->error); +unset($SESSION->coursearchiver_error); $mform = new tool_coursearchiver_step1_form(null); @@ -78,12 +81,12 @@ if (!empty($formdata->savestates)) { $formdata->searches["savestates"] = $formdata->savestates; if ($save = tool_coursearchiver_processor::get_save($formdata->savestates)) { - $SESSION->formdata = $save->content; - $SESSION->resume = true; + $SESSION->coursearchiver_formdata = $save->content; + $SESSION->coursearchiver_resume = true; $returnurl = new moodle_url('/admin/tool/coursearchiver/step'.$save->step.'.php'); redirect($returnurl); } else { - $SESSION->error = get_string('unknownerror', 'tool_coursearchiver'); + $SESSION->coursearchiver_error = get_string('unknownerror', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); } @@ -139,7 +142,7 @@ $formdata->searches["subcats"] = true; } - $SESSION->formdata = serialize($formdata->searches); + $SESSION->coursearchiver_formdata = json_encode($formdata->searches); $returnurl = new moodle_url('/admin/tool/coursearchiver/step2.php'); redirect($returnurl); } else { // Form 1 data did not come across correctly. diff --git a/optin.php b/optin.php index 79452e3..869da6c 100644 --- a/optin.php +++ b/optin.php @@ -25,7 +25,7 @@ define('NO_OUTPUT_BUFFERING', true); require(__DIR__ . '/../../../config.php'); -require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir . '/adminlib.php'); header('X-Accel-Buffering: no'); @@ -46,7 +46,7 @@ echo $OUTPUT->heading_with_help(get_string('coursearchiver', 'tool_coursearchiver'), 'coursearchiver', 'tool_coursearchiver'); // Check to see if the attempt is coming from a valid email. -if (sha1($CFG->dbpass . $courseid . $userid) == $key) { +if (sha1(tool_coursearchiver_processor::get_coursearchiver_keyid() . $courseid . $userid) == $key) { if ($course = get_course($courseid)) { $date = new DateTime("now", core_date::get_user_timezone_object()); $optouttime = $date->getTimestamp(); diff --git a/optout.php b/optout.php index 561a6dd..6252025 100644 --- a/optout.php +++ b/optout.php @@ -54,7 +54,7 @@ } // Check to see if the attempt is coming from a valid email. -if (!sha1($CFG->dbpass . $courseid . $userid) == $key) { +if (!sha1(tool_coursearchiver_processor::get_coursearchiver_keyid() . $courseid . $userid) == $key) { echo $OUTPUT->container(get_string('error_key', 'tool_coursearchiver'), 'coursearchiver_myformerror'); echo $OUTPUT->footer(); die(); diff --git a/removesavepoint.php b/removesavepoint.php index 3ddedbb..ed1fc9c 100644 --- a/removesavepoint.php +++ b/removesavepoint.php @@ -45,7 +45,7 @@ echo $OUTPUT->heading_with_help(get_string('coursearchiver', 'tool_coursearchiver'), 'coursearchiver', 'tool_coursearchiver'); // Check to see if the attempt is coming from a valid email. -if (sha1($CFG->dbpass . $savepointid) == $key) { +if (sha1(tool_coursearchiver_processor::get_coursearchiver_keyid() . $savepointid) == $key) { if ($savepoint = $DB->get_record('tool_coursearchiver_saves', ['id' => $savepointid])) { $params = ["id" => $savepointid]; $DB->delete_records('tool_coursearchiver_saves', $params); diff --git a/step2.php b/step2.php index 64e5f18..e3e6786 100644 --- a/step2.php +++ b/step2.php @@ -33,26 +33,35 @@ admin_externalpage_setup('toolcoursearchiver'); global $SESSION; -$formdata = isset($SESSION->formdata) ? $SESSION->formdata : optional_param('formdata', false, PARAM_RAW); -$error = isset($SESSION->error) ? $SESSION->error : optional_param('error', false, PARAM_RAW); -$resume = isset($SESSION->resume) ? $SESSION->resume : optional_param('resume', false, PARAM_RAW); +$formdata = $SESSION->coursearchiver_formdata ?? optional_param('formdata', false, PARAM_TEXT); + +$error = $SESSION->coursearchiver_error ?? optional_param('coursearchiver_error', false, PARAM_TEXT); +$error = htmlspecialchars($error, ENT_COMPAT); + +$resume = $SESSION->coursearchiver_resume ?? optional_param('coursearchiver_resume', false, PARAM_TEXT); +$resume = htmlspecialchars($resume, ENT_COMPAT); + $title = optional_param('save_title', false, PARAM_TEXT); + $selected = optional_param_array('course_selected', [], PARAM_INT); -$submitted = optional_param('submit_button', false, PARAM_RAW); -unset($SESSION->formdata); -unset($SESSION->error); -unset($SESSION->resume); +$submitted = optional_param('submit_button', false, PARAM_TEXT); +$submitted = htmlspecialchars($submitted, ENT_COMPAT); + +unset($SESSION->coursearchiver_formdata); +unset($SESSION->coursearchiver_error); +unset($SESSION->coursearchiver_resume); tool_coursearchiver_processor::select_deselect_javascript(); if (!empty($submitted)) { // FORM 2 SUBMITTED. - if ($submitted == get_string('save', 'tool_coursearchiver')) { // Save has been pressed. + // Save has been pressed. + if ($submitted == htmlspecialchars(get_string('save', 'tool_coursearchiver'), ENT_COMPAT)) { tool_coursearchiver_processor::save_state(2, $title, $selected); - $SESSION->resume = true; - $SESSION->formdata = serialize($selected); - $SESSION->error = get_string('saved', 'tool_coursearchiver'); + $SESSION->coursearchiver_resume = true; + $SESSION->coursearchiver_formdata = json_encode($selected); + $SESSION->coursearchiver_error = get_string('saved', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/step2.php'); redirect($returnurl); } @@ -66,50 +75,50 @@ } if (empty($courses)) { // If 0 courses are selected, show message and form again. - $SESSION->formdata = $formdata; - $SESSION->error = get_string('nocoursesselected', 'tool_coursearchiver'); + $SESSION->coursearchiver_formdata = $formdata; + $SESSION->coursearchiver_error = get_string('nocoursesselected', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/step2.php'); redirect($returnurl); } switch($submitted){ - case get_string('email', 'tool_coursearchiver'): - $SESSION->formdata = serialize($courses); + case htmlspecialchars(get_string('email', 'tool_coursearchiver'), ENT_COMPAT): + $SESSION->coursearchiver_formdata = json_encode($courses); $returnurl = new moodle_url('/admin/tool/coursearchiver/step3.php'); redirect($returnurl); break; - case get_string('hide', 'tool_coursearchiver'): - $SESSION->mode = tool_coursearchiver_processor::MODE_HIDE; - $SESSION->formdata = serialize($courses); + case htmlspecialchars(get_string('hide', 'tool_coursearchiver'), ENT_COMPAT): + $SESSION->coursearchiver_mode = tool_coursearchiver_processor::MODE_HIDE; + $SESSION->coursearchiver_formdata = json_encode($courses); $returnurl = new moodle_url('/admin/tool/coursearchiver/step4.php'); redirect($returnurl); break; - case get_string('backup', 'tool_coursearchiver'): - $SESSION->mode = tool_coursearchiver_processor::MODE_BACKUP; - $SESSION->formdata = serialize($courses); + case htmlspecialchars(get_string('backup', 'tool_coursearchiver'), ENT_COMPAT): + $SESSION->coursearchiver_mode = tool_coursearchiver_processor::MODE_BACKUP; + $SESSION->coursearchiver_formdata = json_encode($courses); $returnurl = new moodle_url('/admin/tool/coursearchiver/step4.php'); redirect($returnurl); break; - case get_string('archive', 'tool_coursearchiver'): - $SESSION->mode = tool_coursearchiver_processor::MODE_ARCHIVE; - $SESSION->formdata = serialize($courses); + case htmlspecialchars(get_string('archive', 'tool_coursearchiver'), ENT_COMPAT): + $SESSION->coursearchiver_mode = tool_coursearchiver_processor::MODE_ARCHIVE; + $SESSION->coursearchiver_formdata = json_encode($courses); $returnurl = new moodle_url('/admin/tool/coursearchiver/step4.php'); redirect($returnurl); break; - case get_string('delete', 'tool_coursearchiver'): - $SESSION->mode = tool_coursearchiver_processor::MODE_DELETE; - $SESSION->formdata = serialize($courses); + case htmlspecialchars(get_string('delete', 'tool_coursearchiver'), ENT_COMPAT): + $SESSION->coursearchiver_mode = tool_coursearchiver_processor::MODE_DELETE; + $SESSION->coursearchiver_formdata = json_encode($courses); $returnurl = new moodle_url('/admin/tool/coursearchiver/step4.php'); redirect($returnurl); break; - case get_string('optout', 'tool_coursearchiver'): - $SESSION->mode = tool_coursearchiver_processor::MODE_OPTOUT; - $SESSION->formdata = serialize($courses); + case htmlspecialchars(get_string('optout', 'tool_coursearchiver'), ENT_COMPAT): + $SESSION->coursearchiver_mode = tool_coursearchiver_processor::MODE_OPTOUT; + $SESSION->coursearchiver_formdata = json_encode($courses); $returnurl = new moodle_url('/admin/tool/coursearchiver/step4.php'); redirect($returnurl); break; default: - $SESSION->error = get_string('unknownerror', 'tool_coursearchiver'); + $SESSION->coursearchiver_error = get_string('unknownerror', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); } @@ -122,10 +131,10 @@ echo $OUTPUT->container($error, 'coursearchiver_myformerror'); } - $data = unserialize($formdata); + $data = json_decode($formdata); if (!empty($resume)) { // Resume from save point. $searches = $data; - $searches["resume"] = true; + $searches->resume = true; } else { $searches = []; foreach ($data as $key => $value) { @@ -140,7 +149,7 @@ $mform->display(); echo $OUTPUT->footer(); } else { // IN THE EVENT OF A FAILURE, JUST GO BACK TO THE BEGINNING. - $SESSION->error = get_string('unknownerror', 'tool_coursearchiver'); + $SESSION->coursearchiver_error = get_string('unknownerror', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); } diff --git a/step3.php b/step3.php index 00f2ca0..2f710bb 100644 --- a/step3.php +++ b/step3.php @@ -33,28 +33,37 @@ admin_externalpage_setup('toolcoursearchiver'); global $SESSION; -$formdata = isset($SESSION->formdata) ? $SESSION->formdata : optional_param('formdata', false, PARAM_RAW); -$mode = isset($SESSION->mode) ? $SESSION->mode : optional_param('mode', false, PARAM_INT); -$error = isset($SESSION->error) ? $SESSION->error : optional_param('error', false, PARAM_RAW); -$resume = isset($SESSION->resume) ? $SESSION->resume : optional_param('resume', false, PARAM_RAW); +$formdata = $SESSION->coursearchiver_formdata ?? optional_param('formdata', false, PARAM_TEXT); +$mode = $SESSION->coursearchiver_mode ?? optional_param('coursearchiver_mode', false, PARAM_INT); + +$error = $SESSION->coursearchiver_error ?? optional_param('coursearchiver_error', false, PARAM_TEXT); +$error = htmlspecialchars($error, ENT_COMPAT); + +$resume = $SESSION->coursearchiver_resume ?? optional_param('coursearchiver_resume', false, PARAM_TEXT); +$resume = htmlspecialchars($resume, ENT_COMPAT); + $title = optional_param('save_title', false, PARAM_TEXT); -$selected = optional_param_array('user_selected', [], PARAM_RAW); -$submitted = optional_param('submit_button', false, PARAM_RAW); -unset($SESSION->formdata); -unset($SESSION->error); -unset($SESSION->mode); -unset($SESSION->resume); +$selected = optional_param_array('user_selected', [], PARAM_TEXT); + +$submitted = optional_param('submit_button', false, PARAM_TEXT); +$submitted = htmlspecialchars($submitted, ENT_COMPAT); + +unset($SESSION->coursearchiver_formdata); +unset($SESSION->coursearchiver_error); +unset($SESSION->coursearchiver_mode); +unset($SESSION->coursearchiver_resume); tool_coursearchiver_processor::select_deselect_javascript(); if (!empty($submitted)) { // FORM 3 SUBMITTED. - if ($submitted == get_string('save', 'tool_coursearchiver')) { // Save has been pressed. + // Save has been pressed. + if ($submitted == htmlspecialchars(get_string('save', 'tool_coursearchiver'), ENT_COMPAT)) { tool_coursearchiver_processor::save_state(3, $title, $selected); - $SESSION->resume = true; - $SESSION->formdata = serialize($selected); - $SESSION->error = get_string('saved', 'tool_coursearchiver'); + $SESSION->coursearchiver_resume = true; + $SESSION->coursearchiver_formdata = json_encode($selected); + $SESSION->coursearchiver_error = get_string('saved', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/step3.php'); redirect($returnurl); } @@ -85,36 +94,36 @@ } if (empty($owners)) { // If 0 courses are selected, show message and form again. - $SESSION->formdata = $formdata; - $SESSION->error = get_string('nousersselected', 'tool_coursearchiver'); + $SESSION->coursearchiver_formdata = $formdata; + $SESSION->coursearchiver_error = get_string('nousersselected', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/step3.php'); redirect($returnurl); } switch($submitted){ - case get_string('hideemail', 'tool_coursearchiver'): + case htmlspecialchars(get_string('hideemail', 'tool_coursearchiver'), ENT_COMPAT): $mode = tool_coursearchiver_processor::MODE_HIDEEMAIL; - $SESSION->formdata = serialize($users); - $SESSION->mode = $mode; + $SESSION->coursearchiver_formdata = json_encode($users); + $SESSION->coursearchiver_mode = $mode; $returnurl = new moodle_url('/admin/tool/coursearchiver/step4.php'); redirect($returnurl); break; - case get_string('archiveemail', 'tool_coursearchiver'): + case htmlspecialchars(get_string('archiveemail', 'tool_coursearchiver'), ENT_COMPAT): $mode = tool_coursearchiver_processor::MODE_ARCHIVEEMAIL; - $SESSION->formdata = serialize($users); - $SESSION->mode = $mode; + $SESSION->coursearchiver_formdata = json_encode($users); + $SESSION->coursearchiver_mode = $mode; $returnurl = new moodle_url('/admin/tool/coursearchiver/step4.php'); redirect($returnurl); break; - case get_string('deleteemail', 'tool_coursearchiver'): + case htmlspecialchars(get_string('deleteemail', 'tool_coursearchiver'), ENT_COMPAT): $mode = tool_coursearchiver_processor::MODE_DELETEEMAIL; - $SESSION->formdata = serialize($users); - $SESSION->mode = $mode; + $SESSION->coursearchiver_formdata = json_encode($users); + $SESSION->coursearchiver_mode = $mode; $returnurl = new moodle_url('/admin/tool/coursearchiver/step4.php'); redirect($returnurl); break; default: - $SESSION->error = get_string('unknownerror', 'tool_coursearchiver'); + $SESSION->coursearchiver_error = get_string('unknownerror', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); } @@ -127,14 +136,14 @@ echo $OUTPUT->container($error, 'coursearchiver_myformerror'); } - $data = unserialize($formdata); + $data = json_decode($formdata); if (!empty($resume)) { // Resume from save point. - $data["resume"] = true; + $data->resume = true; } // Check again to make sure courses are coming across correctly. - if (!is_array($data) || empty($data)) { - $SESSION->error = get_string('nocoursesselected', 'tool_coursearchiver'); + if (!is_object($data) || empty($data)) { + $SESSION->coursearchiver_error = get_string('nocoursesselected', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/step1.php'); redirect($returnurl); } @@ -146,7 +155,7 @@ echo $OUTPUT->footer(); } else { // IN THE EVENT OF A FAILURE, JUST GO BACK TO THE BEGINNING. - $SESSION->error = get_string('unknownerror', 'tool_coursearchiver'); + $SESSION->coursearchiver_error = get_string('unknownerror', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); } diff --git a/step4.php b/step4.php index ffaade2..23630f7 100644 --- a/step4.php +++ b/step4.php @@ -34,22 +34,28 @@ admin_externalpage_setup('toolcoursearchiver'); global $SESSION; -$formdata = isset($SESSION->formdata) ? $SESSION->formdata : optional_param('formdata', false, PARAM_RAW); -$error = isset($SESSION->error) ? $SESSION->error : optional_param('error', false, PARAM_RAW); -$mode = isset($SESSION->mode) ? $SESSION->mode : optional_param('mode', false, PARAM_INT); +$formdata = $SESSION->coursearchiver_formdata ?? optional_param('formdata', false, PARAM_TEXT); + +$error = $SESSION->coursearchiver_error ?? optional_param('coursearchiver_error', false, PARAM_TEXT); +$error = htmlspecialchars($error, ENT_COMPAT); + +$mode = $SESSION->coursearchiver_mode ?? optional_param('coursearchiver_mode', false, PARAM_INT); $folder = optional_param('folder', false, PARAM_TEXT); -$submitted = optional_param('submit_button', false, PARAM_RAW); -unset($SESSION->formdata); -unset($SESSION->error); -unset($SESSION->mode); +$submitted = optional_param('submit_button', false, PARAM_TEXT); +$submitted = htmlspecialchars($submitted, ENT_COMPAT); + +unset($SESSION->coursearchiver_formdata); +unset($SESSION->coursearchiver_error); +unset($SESSION->coursearchiver_mode); if (!empty($submitted) && !empty($formdata) && !empty($mode)) { // FORM 4 SUBMITTED. - if ($submitted == get_string('back', 'tool_coursearchiver')) { // Button to start over has been pressed. - unset($SESSION->formdata); - unset($SESSION->mode); - unset($SESSION->error); + // Button to start over has been pressed. + if ($submitted == htmlspecialchars(get_string('back', 'tool_coursearchiver'), ENT_COMPAT)) { + unset($SESSION->coursearchiver_formdata); + unset($SESSION->coursearchiver_mode); + unset($SESSION->coursearchiver_error); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); } @@ -58,7 +64,7 @@ echo $OUTPUT->container($error, 'coursearchiver_myformerror'); } - if ($submitted == get_string('confirm', 'tool_coursearchiver')) { + if ($submitted == htmlspecialchars(get_string('confirm', 'tool_coursearchiver'), ENT_COMPAT)) { if (!isset($mode) || !in_array($mode, [tool_coursearchiver_processor::MODE_HIDE, tool_coursearchiver_processor::MODE_BACKUP, tool_coursearchiver_processor::MODE_ARCHIVE, @@ -80,7 +86,7 @@ 'coursearchiver', 'tool_coursearchiver'); - $selected = unserialize($formdata); + $selected = json_decode($formdata); $owners = []; foreach ($selected as $s) { $t = explode("_", $s); @@ -98,8 +104,8 @@ } if (!is_array($owners) || empty($owners)) { // If 0 courses are selected, show message and form again. - $SESSION->formdata = $formdata; - $SESSION->error = get_string('nousersselected', 'tool_coursearchiver'); + $SESSION->coursearchiver_formdata = $formdata; + $SESSION->coursearchiver_error = get_string('nousersselected', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/step3.php'); redirect($returnurl); } @@ -117,10 +123,10 @@ 'coursearchiver', 'tool_coursearchiver'); - $courses = unserialize($formdata); + $courses = json_decode($formdata); if (!is_array($courses) || empty($courses)) { // If 0 courses are selected, show message and form again. - $SESSION->formdata = $formdata; - $SESSION->error = get_string('nocoursesselected', 'tool_coursearchiver'); + $SESSION->coursearchiver_formdata = $formdata; + $SESSION->coursearchiver_error = get_string('nocoursesselected', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/step2.php'); redirect($returnurl); } @@ -140,7 +146,7 @@ echo $OUTPUT->footer(); break; default: - $SESSION->error = get_string('unknownerror', 'tool_coursearchiver'); + $SESSION->coursearchiver_error = get_string('unknownerror', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); } @@ -160,7 +166,7 @@ $mform->display(); echo $OUTPUT->footer(); } else { // IN THE EVENT OF A FAILURE, JUST GO BACK TO THE BEGINNING. - $SESSION->error = get_string('unknownerror', 'tool_coursearchiver'); + $SESSION->coursearchiver_error = get_string('unknownerror', 'tool_coursearchiver'); $returnurl = new moodle_url('/admin/tool/coursearchiver/index.php'); redirect($returnurl); } diff --git a/styles.css b/styles.css index 98779f7..5d3ea2d 100644 --- a/styles.css +++ b/styles.css @@ -73,11 +73,15 @@ } .mform .coursearchiver_checkbox .fitem { - margin: 0; + margin: 0 !important; padding: 4px; display: inline-block; } +.mform .coursearchiver_checkbox .fitem label { + display: none; +} + .path-admin-tool-coursearchiver .btn-primary { margin: auto; } diff --git a/templates/archive_view.mustache b/templates/archive_view.mustache index 4784703..16e1176 100644 --- a/templates/archive_view.mustache +++ b/templates/archive_view.mustache @@ -43,7 +43,7 @@ {{# str }} back, moodle {{/ str }} {{/isadmin}} -

+

{{#recover}} {{# str }} archiverecoverform, tool_coursearchiver {{/ str }} {{/recover}} diff --git a/version.php b/version.php index b917e23..2064013 100644 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); $plugin = new stdClass(); -$plugin->version = 2025060200; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2014111000; // Requires this Moodle version. +$plugin->version = 2025070102; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'tool_coursearchiver'; // Full name of the plugin (used for diagnostics). -$plugin->release = '4.5.0 (Build: 2016090200)'; +$plugin->release = '5.0.0'; $plugin->maturity = MATURITY_STABLE;