MDL-36985 core_files: added option to remove draft files on save

text areas can now specify $options['removeorphaneddrafts'] when saving
their data using file_postupdate_standard_editor(). If set to true,
this option clears all user drafts which are not referenced in the text
This commit is contained in:
Jake Dallimore 2017-07-27 16:47:09 +08:00
parent 27466d7548
commit f3c2afd0e8
3 changed files with 91 additions and 6 deletions

View file

@ -237,6 +237,9 @@ function file_postupdate_standard_editor($data, $field, array $options, $context
if (!isset($options['maxbytes'])) { if (!isset($options['maxbytes'])) {
$options['maxbytes'] = 0; // unlimited $options['maxbytes'] = 0; // unlimited
} }
if (!isset($options['removeorphaneddrafts'])) {
$options['removeorphaneddrafts'] = false; // Don't remove orphaned draft files by default.
}
if ($options['trusttext']) { if ($options['trusttext']) {
$data->{$field.'trust'} = trusttext_trusted($context); $data->{$field.'trust'} = trusttext_trusted($context);
@ -249,6 +252,10 @@ function file_postupdate_standard_editor($data, $field, array $options, $context
if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) { if ($options['maxfiles'] == 0 or is_null($filearea) or is_null($itemid) or empty($editor['itemid'])) {
$data->{$field} = $editor['text']; $data->{$field} = $editor['text'];
} else { } else {
// Clean the user drafts area of any files not referenced in the editor text.
if ($options['removeorphaneddrafts']) {
file_remove_editor_orphaned_files($editor);
}
$data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']); $data->{$field} = file_save_draft_area_files($editor['itemid'], $context->id, $component, $filearea, $itemid, $options, $editor['text'], $options['forcehttps']);
} }
$data->{$field.'format'} = $editor['format']; $data->{$field.'format'} = $editor['format'];
@ -792,6 +799,38 @@ function file_restore_source_field_from_draft_file($storedfile) {
} }
return $storedfile; return $storedfile;
} }
/**
* Removes those files from the user drafts filearea which are not referenced in the editor text.
*
* @param stdClass $editor The online text editor element from the submitted form data.
*/
function file_remove_editor_orphaned_files($editor) {
global $CFG, $USER;
// Find those draft files included in the text, and generate their hashes.
$context = context_user::instance($USER->id);
$baseurl = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $editor['itemid'] . '/';
$pattern = "/" . preg_quote($baseurl, '/') . "(.+?)[\?\"']/";
preg_match_all($pattern, $editor['text'], $matches);
$usedfilehashes = [];
foreach ($matches[1] as $matchedfilename) {
$matchedfilename = urldecode($matchedfilename);
$usedfilehashes[] = \file_storage::get_pathname_hash($context->id, 'user', 'draft', $editor['itemid'], '/',
$matchedfilename);
}
// Now, compare the hashes of all draft files, and remove those which don't match used files.
$fs = get_file_storage();
$files = $fs->get_area_files($context->id, 'user', 'draft', $editor['itemid'], 'id', false);
foreach ($files as $file) {
$tmphash = $file->get_pathnamehash();
if (!in_array($tmphash, $usedfilehashes)) {
$file->delete();
}
}
}
/** /**
* Saves files from a draft file area to a real one (merging the list of files). * Saves files from a draft file area to a real one (merging the list of files).
* Can rewrite URLs in some content at the same time if desired. * Can rewrite URLs in some content at the same time if desired.

View file

@ -1340,6 +1340,51 @@ EOF;
$this->assertEquals(0, $fileinfo['foldercount']); // No subdirectories inside the directory. $this->assertEquals(0, $fileinfo['foldercount']); // No subdirectories inside the directory.
$this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']); $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);
} }
/**
* Test confirming that draft files not referenced in the editor text are removed.
*/
public function test_file_remove_editor_orphaned_files() {
global $USER, $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
// Create three draft files.
$filerecord = ['filename' => 'file1.png'];
$file = self::create_draft_file($filerecord);
$draftitemid = $file->get_itemid();
$filerecord['itemid'] = $draftitemid;
$filerecord['filename'] = 'file2.png';
self::create_draft_file($filerecord);
$filerecord['filename'] = 'file 3.png';
self::create_draft_file($filerecord);
// Confirm the user drafts area lists 3 files.
$fs = get_file_storage();
$usercontext = context_user::instance($USER->id);
$draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
$this->assertCount(3, $draftfiles);
// Now, spoof some editor text content, referencing 2 of the files; one requiring name encoding, one not.
$editor = [
'itemid' => $draftitemid,
'text' => '
<img src="'.$CFG->wwwroot.'/draftfile.php/'.$usercontext->id.'/user/draft/'.$draftitemid.'/file%203.png" alt="">
<img src="'.$CFG->wwwroot.'/draftfile.php/'.$usercontext->id.'/user/draft/'.$draftitemid.'/file1.png" alt="">'
];
// Run the remove orphaned drafts function and confirm that only the referenced files remain in the user drafts.
$expected = ['file1.png', 'file 3.png']; // The drafts we expect will not be removed (are referenced in the online text).
file_remove_editor_orphaned_files($editor);
$draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
$this->assertCount(2, $draftfiles);
foreach ($draftfiles as $file) {
$this->assertContains($file->get_filename(), $expected);
}
}
} }
/** /**

View file

@ -174,7 +174,8 @@ class assign_submission_onlinetext extends assign_submission_plugin {
'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxfiles' => EDITOR_UNLIMITED_FILES,
'maxbytes' => $this->assignment->get_course()->maxbytes, 'maxbytes' => $this->assignment->get_course()->maxbytes,
'context' => $this->assignment->get_context(), 'context' => $this->assignment->get_context(),
'return_types' => (FILE_INTERNAL | FILE_EXTERNAL | FILE_CONTROLLED_LINK) 'return_types' => (FILE_INTERNAL | FILE_EXTERNAL | FILE_CONTROLLED_LINK),
'removeorphaneddrafts' => true // Whether or not to remove any draft files which aren't referenced in the text.
); );
return $editoroptions; return $editoroptions;
} }