Merge branch 'wip_MDL-79802_line_v8' of https://github.com/gjb2048/moodle

This commit is contained in:
Sara Arjona 2024-02-21 17:29:39 +01:00
commit 4e341bfc97
No known key found for this signature in database
8 changed files with 262 additions and 60 deletions

View File

@ -41,4 +41,18 @@ if (!empty($defaulth5plib)) {
$settings->add(new admin_settings_h5plib_handler_select('h5plibraryhandler', new lang_string('h5plibraryhandler', 'core_h5p'),
new lang_string('h5plibraryhandler_help', 'core_h5p'), $defaulth5plib));
$setting = new admin_setting_configtextarea(
'core_h5p/h5pcustomcss',
new lang_string('h5pcustomcss', 'core_h5p'),
new lang_string('h5pcustomcss_help', 'core_h5p'),
'',
PARAM_NOTAGS
);
$setting->set_updatedcallback(function () {
// Enables use of file_storage constants.
\core_h5p\local\library\autoloader::register();
\core_h5p\file_storage::generate_custom_styles();
});
$settings->add($setting);
}

View File

@ -393,6 +393,14 @@ class api {
$settings->tool_dataprivacy_showdataretentionsummary = get_config('tool_dataprivacy', 'showdataretentionsummary');
}
if (empty($section) || $section === 'h5psettings') {
\core_h5p\local\library\autoloader::register();
$customcss = \core_h5p\file_storage::get_custom_styles();
if (!empty($customcss)) {
$settings->h5pcustomcssurl = $customcss['cssurl']->out() . '?ver=' . $customcss['cssversion'];
}
}
return $settings;
}

View File

@ -273,6 +273,18 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
$this->assertEquals($expected, $result['settings']);
// H5P custom CSS.
set_config('h5pcustomcss', '.debug { color: #fab; }', 'core_h5p');
\core_h5p\local\library\autoloader::register();
\core_h5p\file_storage::generate_custom_styles();
$result = external::get_config();
$result = external_api::clean_returnvalue(external::get_config_returns(), $result);
$customcss = \core_h5p\file_storage::get_custom_styles();
$expected[] = ['name' => 'h5pcustomcssurl', 'value' => $customcss['cssurl']->out() . '?ver=' . $customcss['cssversion']];
$this->assertCount(0, $result['warnings']);
$this->assertEquals($expected, $result['settings']);
// Change a value and retrieve filtering by section.
set_config('commentsperpage', 1);
$expected[10]['value'] = 1;

View File

@ -54,6 +54,8 @@ class file_storage implements H5PFileStorage {
public const CSS_FILEAREA = 'css';
/** The icon filename */
public const ICON_FILENAME = 'icon.svg';
/** The custom CSS filename */
private const CUSTOM_CSS_FILENAME = 'custom_h5p.css';
/**
* @var \context $context Currently we use the system context everywhere.
@ -879,4 +881,104 @@ class file_storage implements H5PFileStorage {
$this->fs->create_file_from_pathname($record, $sourcefile);
}
/**
* Generate H5P custom styles if any.
*/
public static function generate_custom_styles(): void {
$record = self::get_custom_styles_file_record();
$cssfile = self::get_custom_styles_file($record);
if ($cssfile) {
// The CSS file needs to be updated, so delete and recreate it
// if there is CSS in the 'h5pcustomcss' setting.
$cssfile->delete();
}
$css = get_config('core_h5p', 'h5pcustomcss');
if (!empty($css)) {
$fs = get_file_storage();
$fs->create_file_from_string($record, $css);
}
}
/**
* Get H5P custom styles if any.
*
* @throws \moodle_exception If the CSS setting is empty but there is a file to serve
* or there is no file but the CSS setting is not empty.
* @return array|null If there is CSS then an array with the keys 'cssurl'
* and 'cssversion' is returned otherwise null. 'cssurl' is a link to the
* generated 'custom_h5p.css' file and 'cssversion' the md5 hash of its contents.
*/
public static function get_custom_styles(): ?array {
$record = self::get_custom_styles_file_record();
$css = get_config('core_h5p', 'h5pcustomcss');
if (self::get_custom_styles_file($record)) {
if (empty($css)) {
// The custom CSS file exists and yet the setting 'h5pcustomcss' is empty.
// This prevents an invalid content hash.
throw new \moodle_exception(
'The H5P \'h5pcustomcss\' setting is empty and yet the custom CSS file \''.
$record['filename'].
'\' exists.',
'core_h5p'
);
}
// File exists, so generate the url and version hash.
$cssurl = \moodle_url::make_pluginfile_url(
$record['contextid'],
$record['component'],
$record['filearea'],
null,
$record['filepath'],
$record['filename']
);
return ['cssurl' => $cssurl, 'cssversion' => md5($css)];
} else if (!empty($css)) {
// The custom CSS file does not exist and yet should do.
throw new \moodle_exception(
'The H5P custom CSS file \''.
$record['filename'].
'\' does not exist and yet there is CSS in the \'h5pcustomcss\' setting.',
'core_h5p'
);
}
return null;
}
/**
* Get H5P custom styles file record.
*
* @return array File record for the CSS custom styles.
*/
private static function get_custom_styles_file_record(): array {
return [
'contextid' => \context_system::instance()->id,
'component' => self::COMPONENT,
'filearea' => self::CSS_FILEAREA,
'itemid' => 0,
'filepath' => '/',
'filename' => self::CUSTOM_CSS_FILENAME,
];
}
/**
* Get H5P custom styles file.
*
* @param array $record The H5P custom styles file record.
*
* @return stored_file|bool stored_file instance if exists, false if not.
*/
private static function get_custom_styles_file($record): stored_file|bool {
$fs = get_file_storage();
return $fs->get_file(
$record['contextid'],
$record['component'],
$record['filearea'],
$record['itemid'],
$record['filepath'],
$record['filename']
);
}
}

View File

@ -39,67 +39,14 @@ class renderer extends plugin_renderer_base {
* @param string $embedtype Possible values: div, iframe, external, editor
*/
public function h5p_alter_styles(&$styles, array $libraries, string $embedtype) {
global $CFG, $DB;
$record = [
'contextid' => \context_system::instance()->id,
'component' => \core_h5p\file_storage::COMPONENT,
'filearea' => \core_h5p\file_storage::CSS_FILEAREA,
'itemid' => 0,
'filepath' => '/',
'filename' => $CFG->theme . '_h5p.css',
];
$fs = get_file_storage();
// Check if the CSS file for the current theme needs to be updated (because the SCSS settings have changed recently).
if ($cssfile = $fs->get_file(
$record['contextid'],
$record['component'],
$record['filearea'],
$record['itemid'],
$record['filepath'],
$record['filename'])) {
// Get the last time when the SCSS and CSSPRE settings were updated for the current theme and compare it with the
// time modified of the H5P CSS file, to determine whether it needs to be updated.
$sql = "SELECT MAX(timemodified) as timemodified
FROM {config_log}
WHERE plugin = :theme AND (name = 'scss' OR name = 'scsspre')";
$params = ['theme' => 'theme_' . $CFG->theme];
$setting = $DB->get_record_sql($sql, $params);
if ($setting && $setting->timemodified > $cssfile->get_timemodified()) {
// The CSS file needs to be updated. First, delete it to recreate it later with the current CSS.
$cssfile->delete();
$cssfile = null;
}
$customcss = \core_h5p\file_storage::get_custom_styles();
if (!empty($customcss)) {
// Add the CSS file to the styles array, to load it from the H5P player.
$styles[] = (object) [
'path' => $customcss['cssurl']->out(),
'version' => '?ver='.$customcss['cssversion'],
];
}
$theme = \theme_config::load($CFG->theme);
// When 'Raw initial SCSS' and 'Raw SCSS' theme settings are empty, the file doesn't need to be created.
if (empty($theme->settings->scsspre) && empty($theme->settings->scss)) {
return;
}
// If the CSS file doesn't exist, create it with the styles defined in 'Raw initial SCSS' and 'Raw SCSS' theme settings.
// As these scss and scsspre settings might have dependencies on the theme, the whole CSS theme content will be used and
// passed to the H5P player.
if (!$cssfile) {
$css = $theme->get_css_content();
$cssfile = $fs->create_file_from_string($record, $css);
}
$cssurl = \moodle_url::make_pluginfile_url(
$record['contextid'],
$record['component'],
$record['filearea'],
null,
$record['filepath'],
$record['filename']
);
// Add the CSS file to the styles array, to load it from the H5P player.
$styles[] = (object) [
'path' => $cssurl->out(),
'version' => '?ver='.$cssfile->get_timemodified(),
];
}
/**

View File

@ -848,4 +848,115 @@ class file_storage_test extends \advanced_testcase {
$this->assertFalse($this->h5p_fs_fs->file_exists($this->h5p_fs_context->id, file_storage::COMPONENT,
file_storage::CONTENT_FILEAREA, $h5pcontentid, $filepath, $filename));
}
/**
* Test H5P custom styles generation.
*
* @covers ::generate_custom_styles
*/
public function test_generate_custom_styles(): void {
\set_config('h5pcustomcss', '.debug { color: #fab; }', 'core_h5p');
$h5pfsrc = new \ReflectionClass(file_storage::class);
$customcssfilename = $h5pfsrc->getConstant('CUSTOM_CSS_FILENAME');
// Test 'h5pcustomcss' with data.
file_storage::generate_custom_styles();
$this->assertTrue($this->h5p_fs_fs->file_exists(
\context_system::instance()->id,
file_storage::COMPONENT,
file_storage::CSS_FILEAREA,
0,
'/',
$customcssfilename)
);
$cssfile = $this->h5p_fs_fs->get_file(
\context_system::instance()->id,
file_storage::COMPONENT,
file_storage::CSS_FILEAREA,
0,
'/',
$customcssfilename
);
$this->assertInstanceOf('stored_file', $cssfile);
$csscontents = $cssfile->get_content();
$this->assertEquals($csscontents, '.debug { color: #fab; }');
// Test 'h5pcustomcss' without data.
\set_config('h5pcustomcss', '', 'core_h5p');
file_storage::generate_custom_styles();
$this->assertFalse($this->h5p_fs_fs->file_exists(
\context_system::instance()->id,
file_storage::COMPONENT,
file_storage::CSS_FILEAREA,
0,
'/',
$customcssfilename)
);
}
/**
* Test H5P custom styles retrieval.
*
* @covers ::get_custom_styles
*/
public function test_get_custom_styles(): void {
global $CFG;
$css = '.debug { color: #fab; }';
$cssurl = $CFG->wwwroot . '/pluginfile.php/1/core_h5p/css/custom_h5p.css';
\set_config('h5pcustomcss', $css, 'core_h5p');
$h5pfsrc = new \ReflectionClass(file_storage::class);
$customcssfilename = $h5pfsrc->getConstant('CUSTOM_CSS_FILENAME');
// Normal operation without data.
\set_config('h5pcustomcss', '', 'core_h5p');
file_storage::generate_custom_styles();
$style = file_storage::get_custom_styles();
$this->assertNull($style);
// Normal operation with data.
\set_config('h5pcustomcss', $css, 'core_h5p');
file_storage::generate_custom_styles();
$style = file_storage::get_custom_styles();
$this->assertNotEmpty($style);
$this->assertEquals($style['cssurl']->out(), $cssurl);
$this->assertEquals($style['cssversion'], md5($css));
// No CSS set when there is a file.
\set_config('h5pcustomcss', '', 'core_h5p');
try {
$style = file_storage::get_custom_styles();
$this->fail('moodle_exception for when there is no CSS and yet there is a file, was not thrown');
} catch (\moodle_exception $me) {
$this->assertEquals(
'The H5P \'h5pcustomcss\' setting is empty and yet the custom CSS file \''.$customcssfilename.'\' exists.',
$me->errorcode
);
}
\set_config('h5pcustomcss', $css, 'core_h5p'); // Reset for next assertion.
// No CSS file when there is CSS.
$cssfile = $this->h5p_fs_fs->get_file(
\context_system::instance()->id,
file_storage::COMPONENT,
file_storage::CSS_FILEAREA,
0,
'/',
$customcssfilename
);
$cssfile->delete();
try {
$style = file_storage::get_custom_styles();
$this->fail('moodle_exception for when there is CSS and yet there is a file, was not thrown');
} catch (\moodle_exception $me) {
$this->assertEquals(
'The H5P custom CSS file \''.$customcssfilename.
'\' does not exist and yet there is CSS in the \'h5pcustomcss\' setting.',
$me->errorcode
);
}
}
}

View File

@ -1,6 +1,12 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 4.4 ===
* Added new methods, 'generate_custom_styles' and 'get_custom_styles' to 'core_h5p\file_storage' to generate then
provide a CSS file to be used by the 'core_h5p\output\renderer\h5p_alter_styles' method, reference:
(https://h5p.org/documentation/for-developers/visual-changes) when there is custom CSS in the 'core_h5p\h5pcustomcss'
setting. This will then apply to the H5P content.
=== 4.3 ===
* The `\core_h5p\file_storage::EDITOR_FILEAREA` constant has been removed, as all editor files are stored in user draft

View File

@ -131,6 +131,8 @@ $string['h5pfilenotfound'] = 'H5P file not found';
$string['h5pinvalidurl'] = 'Invalid H5P content URL.';
$string['h5plibraryhandler'] = 'H5P framework handler';
$string['h5plibraryhandler_help'] = 'The H5P framework used to display H5P content. The latest version is recommended.';
$string['h5pcustomcss'] = 'Custom CSS';
$string['h5pcustomcss_help'] = 'Custom CSS to apply to your H5P modules.';
$string['h5pprivatefile'] = 'This H5P content can\'t be displayed because you don\'t have access to the .h5p file.';
$string['h5pmanage'] = 'Manage H5P content types';
$string['h5poverview'] = 'H5P overview';