Merge branch 'MDL-81437_allow-webp-file-extension-by-default' of https://github.com/ziegenberg/moodle into main

This commit is contained in:
Paul Holden 2025-03-31 17:46:09 +01:00
commit ff9ea3fe6d
No known key found for this signature in database
GPG Key ID: A81A96D6045F6164
8 changed files with 302 additions and 1 deletions

View File

@ -0,0 +1,8 @@
issueNumber: MDL-81437
notes:
core_files:
- message: |
Adds a new ad-hoc task `core_files\task\asynchronous_mimetype_upgrade_task` to upgrade the mimetype of files
asynchronously during core upgrades. The upgradelib also comes with a new utility function
`upgrade_create_async_mimetype_upgrade_task` for creating said ad-hoc task.
type: improved

View File

@ -0,0 +1,57 @@
<?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_files\task;
use core\task\adhoc_task;
/**
* Ad-hoc task that performs asynchronous upgrades of a given file type.
*
* This ad-hoc taks is used during core upgrades.
*
* @package core_files
* @copyright 2025 Daniel Ziegenberg
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class asynchronous_mimetype_upgrade_task extends adhoc_task {
/**
* Run the adhoc task and update the mime type of files.
*/
public function execute(): void {
global $DB;
// Upgrade mime type for existing files.
$customdata = $this->get_custom_data();
foreach ($customdata->extensions as $extension) {
mtrace("Updating mime type for files with extension *.{$extension} to {$customdata->mimetype}");
$condition = $DB->sql_like('filename', ":extension", false);
$select = "{$condition} AND mimetype <> :mimetype";
$params = [
'extension' => $DB->sql_like_escape("%.$extension"),
'mimetype' => $customdata->mimetype,
];
$count = $DB->count_records_select('files', $select, $params);
$DB->set_field_select('files', 'mimetype', $customdata->mimetype, $select, $params);
mtrace("Updated {$count} files with extension *.{$extension} to {$customdata->mimetype}");
}
}
}

View File

@ -0,0 +1,164 @@
<?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_files\task;
/**
* Tests for the asynchronous mimetype upgrade task.
*
* @package core_files
* @category test
* @copyright 2025 Daniel Ziegenberg
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core_files\task\asynchronous_mimetype_upgrade_task::execute
*/
final class asynchronous_mimetype_upgrade_task_test extends \advanced_testcase {
/**
* Data provider for test_upgrade_mimetype().
*
* @return array
*/
public static function upgrade_mimetype_provider(): array {
return [
'Single file, one extension' => [
'files' => [
'filename.extension1' => 'type/subtype',
],
'mimetype' => 'type/subtype',
'extensions' => ['extension1'],
],
'Single file, one extension, desired extension substring of actual extension' => [
'files' => [
'filename.extension1andsomemore' => 'text/plain',
],
'mimetype' => 'type/subtype',
'extensions' => ['extension1'],
],
'Single file, one extension, filename same as desired extension' => [
'files' => [
'extension1.bogus' => 'text/plain',
],
'mimetype' => 'type/subtype',
'extensions' => ['extension1'],
],
'Multiple files, one extension' => [
'files' => [
'filename_a.extension1' => 'type/subtype',
'filename_b.extension1' => 'type/subtype',
],
'mimetype' => 'type/subtype',
'extensions' => ['extension1'],
],
'Multiple files, multiple extensions' => [
'files' => [
'filename_a.extension1' => 'type/subtype',
'filename_b.extension1' => 'type/subtype',
'filename_a.extension2' => 'type/subtype',
'filename_b.extension2' => 'type/subtype',
],
'mimetype' => 'type/subtype',
'extensions' => [
'extension1',
'extension2',
],
],
'Multiple files, multiple extensions, some unrelated' => [
'files' => [
'filename_a.extension1' => 'type/subtype',
'filename_b.extension1' => 'type/subtype',
'filename_a.extension2' => 'type/subtype',
'filename_b.extension2' => 'type/subtype',
'filename_c.bogus' => 'text/plain',
'filename_c.bogus2' => 'text/plain',
],
'mimetype' => 'type/subtype',
'extensions' => [
'extension1',
'extension2',
'extension3',
],
],
];
}
/**
* Test upgrading the mimetype of files.
*
* @dataProvider upgrade_mimetype_provider
*
* @param array $files
* @param string $mimetype
* @param array $extensions
*/
public function test_upgrade_mimetype(array $files, string $mimetype, array $extensions): void {
global $DB;
$this->resetAfterTest();
// Create files with different extensions.
$fs = get_file_storage();
foreach (array_keys($files) as $filename) {
$filerecord = [
'contextid' => \core\context\system::instance()->id,
'component' => 'core',
'filearea' => 'unittest',
'itemid' => 0,
'filepath' => '/',
'filename' => $filename,
];
$fs->create_file_from_string($filerecord, 'content');
}
// Create and run the upgrade task.
$upgardetask = new asynchronous_mimetype_upgrade_task();
$upgardetask->set_custom_data([
'mimetype' => $mimetype,
'extensions' => $extensions,
]);
ob_start();
$upgardetask->execute();
$output = ob_get_clean();
// Check that the task output is correct.
foreach ($extensions as $extension) {
$this->assertStringContainsString(
"Updating mime type for files with extension *.{$extension} to {$mimetype}",
$output
);
$countfiles = count(array_filter(
array_keys($files),
function ($filename) use ($extension) {
return str_ends_with($filename, $extension);
}
));
$this->assertStringContainsString(
"Updated {$countfiles} files with extension *.{$extension} to {$mimetype}",
$output
);
}
// Check that the mimetype was updated and unrelated files remain untouched.
foreach ($files as $filename => $exptectedmimetype) {
$mimetypedb = $DB->get_field(
table: 'files',
return: 'mimetype',
conditions: ['filename' => $filename]
);
$this->assertEquals(expected: $exptectedmimetype, actual: $mimetypedb);
}
}
}

View File

@ -292,6 +292,8 @@ abstract class core_filetypes {
'string' => 'audio'),
'webm' => array('type' => 'video/webm', 'icon' => 'video', 'groups' => array('html_video', 'video', 'web_video'),
'string' => 'video'),
'webp' => ['type' => 'image/webp', 'icon' => 'image', 'groups' => ['image', 'web_image', 'optimised_image'],
'string' => 'image'],
'wmv' => array('type' => 'video/x-ms-wmv', 'icon' => 'video', 'groups' => array('video'), 'string' => 'video'),
'asf' => array('type' => 'video/x-ms-asf', 'icon' => 'video', 'groups' => array('video'), 'string' => 'video'),
'wma' => array('type' => 'audio/x-ms-wma', 'icon' => 'audio', 'groups' => array('audio'), 'string' => 'audio'),

View File

@ -1632,5 +1632,13 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2025031800.04);
}
if ($oldversion < 2025032800.01) {
// Upgrade webp mime type for existing webp files.
upgrade_create_async_mimetype_upgrade_task('image/webp', ['webp']);
// Main savepoint reached.
upgrade_main_savepoint(true, 2025032800.01);
}
return true;
}

View File

@ -2066,3 +2066,32 @@ function upgrade_add_explain_action_to_ai_providers() {
$currentrecords->close();
}
/**
* Creates a new ad-hoc task to upgrade the mime-type of files asynchronously.
* Thus, we can considerably reduce the time an upgrade takes.
*
* @param string $mimetype the desired mime-type
* @param string[] $extensions a list of file extensions, without the leading dot
* @return void
*/
function upgrade_create_async_mimetype_upgrade_task(string $mimetype, array $extensions): void {
global $DB;
// Create adhoc task for upgrading of existing files. Due to a code restriction on the upgrade, invoking any core
// functions is not permitted. Thus we craft our own ad-hoc task that will process all existing files.
$record = new \stdClass();
$record->classname = '\core_files\task\asynchronous_mimetype_upgrade_task';
$record->component = 'core';
$record->customdata = json_encode([
'mimetype' => $mimetype,
'extensions' => $extensions,
]);
// Next run time based from nextruntime computation in \core\task\manager::queue_adhoc_task().
$clock = \core\di::get(\core\clock::class);
$nextruntime = $clock->time() - 1;
$record->nextruntime = $nextruntime;
$DB->insert_record('task_adhoc', $record);
}

View File

@ -540,4 +540,37 @@ final class upgradelib_test extends \advanced_testcase {
$this->assertEquals($expected, $block);
}
}
/**
* Ensure that the upgrade_create_async_mimetype_upgrade_task function performs as expected.
*
* @covers ::upgrade_create_async_mimetype_upgrade_task
*/
public function test_upgrade_create_async_mimetype_upgrade_task(): void {
global $DB;
$this->resetAfterTest();
$clock = $this->mock_clock_with_frozen();
upgrade_create_async_mimetype_upgrade_task('type/subtype', ['extension1', 'extension2']);
// Ensure that the task was created with correct next runtime.
$nextruntime = $DB->get_field(
table: 'task_adhoc',
return: 'nextruntime',
conditions: ['component' => 'core', 'classname' => '\core_files\task\asynchronous_mimetype_upgrade_task'],
strictness: MUST_EXIST,
);
$this->assertEquals(expected: $clock->time() - 1, actual: $nextruntime);
// Ensure that the task has the correct custom data.
$customdata = $DB->get_field(
table: 'task_adhoc',
return: 'customdata',
conditions: ['component' => 'core', 'classname' => '\core_files\task\asynchronous_mimetype_upgrade_task'],
strictness: MUST_EXIST,
);
$customdata = json_decode($customdata);
$this->assertEquals(expected: 'type/subtype', actual: $customdata->mimetype);
$this->assertEquals(expected: ['extension1', 'extension2'], actual: $customdata->extensions);
}
}

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2025032800.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2025032800.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '5.0dev+ (Build: 20250328)'; // Human-friendly version name