Merge branch 'mdl65478-fix-master' of https://github.com/matthewhilton/moodle

This commit is contained in:
Ilya Tregubov
2022-05-03 15:43:53 +06:00
8 changed files with 295 additions and 20 deletions
+12 -6
View File
@@ -475,6 +475,11 @@ class backup_course_structure_step extends backup_structure_step {
'shortname', 'type', 'value', 'valueformat'
));
$courseformatoptions = new backup_nested_element('courseformatoptions');
$courseformatoption = new backup_nested_element('courseformatoption', [], [
'courseid', 'format', 'sectionid', 'name', 'value'
]);
// attach format plugin structure to $course element, only one allowed
$this->add_plugin_structure('format', $course, false);
@@ -512,17 +517,14 @@ class backup_course_structure_step extends backup_structure_step {
$course->add_child($customfields);
$customfields->add_child($customfield);
$course->add_child($courseformatoptions);
$courseformatoptions->add_child($courseformatoption);
// Set the sources
$courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
$courserec->contextid = $this->task->get_contextid();
$formatoptions = course_get_format($courserec)->get_format_options();
$course->add_final_elements(array_keys($formatoptions));
foreach ($formatoptions as $key => $value) {
$courserec->$key = $value;
}
// Add 'numsections' in order to be able to restore in previous versions of Moodle.
// Even though Moodle does not officially support restore into older verions of Moodle from the
// version where backup was made, without 'numsections' restoring will go very wrong.
@@ -544,6 +546,10 @@ class backup_course_structure_step extends backup_structure_step {
backup_helper::is_sqlparam('course'),
backup::VAR_PARENTID));
$courseformatoption->set_source_sql('SELECT id, format, sectionid, name, value
FROM {course_format_options}
WHERE courseid = ?', [ backup::VAR_PARENTID ]);
$handler = core_course\customfield\course_handler::create();
$fieldsforbackup = $handler->get_instance_data_for_backup($this->task->get_courseid());
$customfield->set_source_array($fieldsforbackup);
+23 -4
View File
@@ -1806,7 +1806,8 @@ class restore_course_structure_step extends restore_structure_step {
$category = new restore_path_element('category', '/course/category');
$tag = new restore_path_element('tag', '/course/tags/tag');
$customfield = new restore_path_element('customfield', '/course/customfields/customfield');
$allowed_module = new restore_path_element('allowed_module', '/course/allowed_modules/module');
$courseformatoptions = new restore_path_element('course_format_option', '/course/courseformatoptions/courseformatoption');
$allowedmodule = new restore_path_element('allowed_module', '/course/allowed_modules/module');
// Apply for 'format' plugins optional paths at course level
$this->add_plugin_structure('format', $course);
@@ -1829,7 +1830,7 @@ class restore_course_structure_step extends restore_structure_step {
// Apply for admin tool plugins optional paths at course level.
$this->add_plugin_structure('tool', $course);
return array($course, $category, $tag, $customfield, $allowed_module);
return array($course, $category, $tag, $customfield, $allowedmodule, $courseformatoptions);
}
/**
@@ -1951,8 +1952,6 @@ class restore_course_structure_step extends restore_structure_step {
// Course record ready, update it
$DB->update_record('course', $data);
course_get_format($data)->update_course_format_options($data);
// Role name aliases
restore_dbops::set_course_role_names($this->get_restoreid(), $this->get_courseid());
}
@@ -1980,6 +1979,26 @@ class restore_course_structure_step extends restore_structure_step {
$handler->restore_instance_data_from_backup($this->task, $data);
}
/**
* Processes a course format option.
*
* @param array $data The record being restored.
* @throws base_step_exception
* @throws dml_exception
*/
public function process_course_format_option(array $data) : void {
global $DB;
$courseid = $this->get_courseid();
$record = $DB->get_record('course_format_options', [ 'courseid' => $courseid, 'name' => $data['name'] ], 'id');
if ($record !== false) {
$DB->update_record('course_format_options', (object) [ 'id' => $record->id, 'value' => $data['value'] ]);
} else {
$data['courseid'] = $courseid;
$DB->insert_record('course_format_options', (object) $data);
}
}
public function process_allowed_module($data) {
$data = (object)$data;
+7 -9
View File
@@ -908,15 +908,12 @@ abstract class base {
'format' => $this->format,
'sectionid' => $sectionid
), '', 'id,name,value');
$indexedrecords = [];
foreach ($records as $record) {
if (array_key_exists($record->name, $this->formatoptions[$sectionid])) {
$value = $record->value;
if ($value !== null && isset($options[$record->name]['type'])) {
// This will convert string value to number if needed.
$value = clean_param($value, $options[$record->name]['type']);
}
$this->formatoptions[$sectionid][$record->name] = $value;
}
$indexedrecords[$record->name] = $record->value;
}
foreach ($options as $optionname => $option) {
contract_value($this->formatoptions[$sectionid], $indexedrecords, $option, $optionname);
}
}
}
@@ -1011,7 +1008,7 @@ abstract class base {
$data = array_intersect_key($rawdata, $allformatoptions);
foreach ($data as $key => $value) {
$option = $allformatoptions[$key] + ['type' => PARAM_RAW, 'element_type' => null, 'element_attributes' => [[]]];
$data[$key] = clean_param($value, $option['type']);
expand_value($data, $data, $option, $key);
if ($option['element_type'] === 'select' && !array_key_exists($data[$key], $option['element_attributes'][0])) {
// Value invalid for select element, skip.
unset($data[$key]);
@@ -1060,6 +1057,7 @@ abstract class base {
if (array_key_exists('default', $option)) {
$defaultoptions[$key] = $option['default'];
}
expand_value($defaultoptions, $defaultoptions, $option, $key);
$cached[$key] = ($sectionid === 0 || !empty($option['cache']));
}
$records = $DB->get_records('course_format_options',
+64
View File
@@ -135,3 +135,67 @@ class format_site extends course_format {
return 1;
}
}
/**
* 'Converts' a value from what is stored in the database into what is used by edit forms.
*
* @param array $dest The destination array
* @param array $source The source array
* @param array $option The definition structure of the option.
* @param string $optionname The name of the option, as provided in the definition.
*/
function contract_value(array &$dest, array $source, array $option, string $optionname) : void {
if (substr($optionname, -7) == '_editor') { // Suffix '_editor' indicates that the element is an editor.
$name = substr($optionname, 0, -7);
if (isset($source[$name])) {
$dest[$optionname] = [
'text' => clean_param_if_not_null($source[$name], $option['type'] ?? PARAM_RAW),
'format' => clean_param_if_not_null($source[$name . 'format'], PARAM_INT),
];
}
} else {
if (isset($source[$optionname])) {
$dest[$optionname] = clean_param_if_not_null($source[$optionname], $option['type'] ?? PARAM_RAW);
}
}
}
/**
* Cleans the given param, unless it is null.
*
* @param mixed $param The variable we are cleaning.
* @param string $type Expected format of param after cleaning.
* @return mixed Null if $param is null, otherwise the cleaned value.
* @throws coding_exception
*/
function clean_param_if_not_null($param, string $type = PARAM_RAW) {
if ($param === null) {
return null;
} else {
return clean_param($param, $type);
}
}
/**
* 'Converts' a value from what is used in edit forms into a value(s) to be stored in the database.
*
* @param array $dest The destination array
* @param array $source The source array
* @param array $option The definition structure of the option.
* @param string $optionname The name of the option, as provided in the definition.
*/
function expand_value(array &$dest, array $source, array $option, string $optionname) : void {
if (substr($optionname, -7) == '_editor') { // Suffix '_editor' indicates that the element is an editor.
$name = substr($optionname, 0, -7);
if (is_string($source[$optionname])) {
$dest[$name] = clean_param($source[$optionname], $option['type'] ?? PARAM_RAW);
$dest[$name . 'format'] = 1;
} else {
$dest[$name] = clean_param($source[$optionname]['text'], $option['type'] ?? PARAM_RAW);
$dest[$name . 'format'] = clean_param($source[$optionname]['format'], PARAM_INT);
}
unset($dest[$optionname]);
} else {
$dest[$optionname] = clean_param($source[$optionname], $option['type'] ?? PARAM_RAW);
}
}
+29
View File
@@ -36,6 +36,35 @@ class base_test extends advanced_testcase {
require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest_output_course_format_invalidoutput.php');
}
/**
* Tests the save and load functionality.
*
* @author Jason den Dulk
* @covers \core_courseformat
*/
public function test_courseformat_saveandload() {
$this->resetAfterTest();
$courseformatoptiondata = (object) [
"hideoddsections" => 1,
'summary_editor' => [
'text' => '<p>Somewhere over the rainbow</p><p>The <b>quick</b> brown fox jumpos over the lazy dog.</p>',
'format' => 1
]
];
$generator = $this->getDataGenerator();
$course1 = $generator->create_course(array('format' => 'theunittest'));
$this->assertEquals('theunittest', $course1->format);
course_create_sections_if_missing($course1, array(0, 1));
$courseformat = course_get_format($course1);
$courseformat->update_course_format_options($courseformatoptiondata);
$savedcourseformatoptiondata = $courseformat->get_format_options();
$this->assertEqualsCanonicalizing($courseformatoptiondata, (object) $savedcourseformatoptiondata);
}
public function test_available_hook() {
global $DB;
$this->resetAfterTest();
+9 -1
View File
@@ -37,6 +37,10 @@ class format_theunittest extends core_courseformat\base {
'default' => 0,
'type' => PARAM_INT,
),
'summary_editor' => array(
'default' => '',
'type' => PARAM_RAW,
),
);
}
if ($foreditform && !isset($courseformatoptions['hideoddsections']['label'])) {
@@ -51,6 +55,10 @@ class format_theunittest extends core_courseformat\base {
'element_type' => 'select',
'element_attributes' => array($sectionmenu),
),
'summary_editor' => array(
'label' => 'Summary Text',
'element_type' => 'editor',
),
);
$courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit);
}
@@ -74,4 +82,4 @@ class format_theunittest extends core_courseformat\base {
}
}
}
}
}
+46
View File
@@ -26,6 +26,7 @@ global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest.php');
/**
* Course restore testcase.
@@ -534,4 +535,49 @@ class restore_test extends \advanced_testcase {
$this->assertEquals($chat2->chattime, $restoredchat2->chattime);
$this->assertEquals($c2->startdate + 1 * WEEKSECS, $restoredchat2->chattime);
}
/**
* Tests course restore with editor in course format.
*
* @author Matthew Hilton
* @covers \core_courseformat
*/
public function test_restore_editor_courseformat() {
$this->resetAfterTest();
// Setup user with restore permissions.
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$managers = get_archetype_roles('manager');
$manager = array_shift($managers);
$dg->role_assign($manager->id, $u1->id);
// Create a course with an editor item in the course format.
$courseformatoptiondata = (object) [
"hideoddsections" => 1,
'summary_editor' => [
'text' => '<p>Somewhere over the rainbow</p><p>The <b>quick</b> brown fox jumpos over the lazy dog.</p>',
'format' => 1
]
];
$course1 = $dg->create_course(['format' => 'theunittest']);
$course2 = $dg->create_course(['format' => 'theunittest']);
$this->assertEquals('theunittest', $course1->format);
course_create_sections_if_missing($course1, array(0, 1));
// Set the course format.
$courseformat = course_get_format($course1);
$courseformat->update_course_format_options($courseformatoptiondata);
// Backup and restore the course.
$backupid = $this->backup_course($course1->id);
$this->restore_to_existing_course($backupid, $course2->id, $u1->id);
// Get the restored course format.
$restoredformat = course_get_format($course2);
$restoredformatoptions = $restoredformat->get_format_options();
$this->assertEqualsCanonicalizing($courseformatoptiondata, (object) $restoredformatoptions);
}
}
@@ -0,0 +1,105 @@
<?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/>.
namespace core_course;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest.php');
require_once($CFG->dirroot . '/course/format/lib.php');
/**
* Course format function unit tests
*
* @package core_course
* @copyright 2021 Catalyst IT Pty Ltd
* @author Jason den Dulk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_format_function_test extends \basic_testcase {
/**
* Tests clean_param_if_not_null function
* @covers ::clean_param_if_not_null
*/
public function test_clean_param_if_not_null() {
$this->assertNull(clean_param_if_not_null(null));
$n = '3x';
$this->assertEquals(clean_param($n, PARAM_INT), clean_param_if_not_null($n, PARAM_INT));
$this->assertEquals(clean_param($n, PARAM_RAW), clean_param_if_not_null($n, PARAM_RAW));
$this->assertEquals(clean_param($n, PARAM_ALPHANUM), clean_param_if_not_null($n, PARAM_ALPHANUM));
$this->assertEquals(clean_param($n, PARAM_ALPHA), clean_param_if_not_null($n, PARAM_ALPHA));
$s = '<abc>xyz</abc>';
$this->assertEquals(clean_param($s, PARAM_ALPHANUM), clean_param_if_not_null($s, PARAM_ALPHANUM));
$this->assertEquals(clean_param($s, PARAM_RAW), clean_param_if_not_null($s, PARAM_RAW));
}
/**
* Tests contract_value function
* @covers ::contract_value
*/
public function test_contract_value() {
$input = [
'abc' => '<p>All together Now</p>',
'abcformat' => '1',
'jolly' => 'Roger'
];
$expected = [
'abc_editor' => [ 'text' => $input['abc'], 'format' => $input['abcformat'] ],
'jolly' => $input['jolly'],
];
$defs = [
'abc_editor' => [],
'jolly' => [ 'type' => PARAM_ALPHA ],
];
$dest = [];
foreach ($defs as $name => $def) {
contract_value($dest, $input, $def, $name);
}
$this->assertEquals($expected, $dest);
}
/**
* Tests expand_value function
* @covers ::expand_value
*/
public function test_expand_value() {
$input = [
'abc_editor' => [ 'text' => '<p>All together Now</p>', 'format' => '1' ],
'jolly' => 'Roger',
];
$expected = [
'abc' => $input['abc_editor']['text'],
'abcformat' => $input['abc_editor']['format'],
'jolly' => $input['jolly'],
];
$defs = [
'abc_editor' => [],
'jolly' => [ 'type' => PARAM_ALPHA ],
];
$dest = [];
foreach ($defs as $name => $def) {
expand_value($dest, $input, $def, $name);
}
$this->assertEquals($expected, $dest);
}
}