Merge branch 'mdl65478-fix-master' of https://github.com/matthewhilton/moodle
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user