Skip to content

Commit 5300ad9

Browse files
committed
refactor: use moodle draft area functions for options files
See: #219
1 parent b957d2f commit 5300ad9

File tree

8 files changed

+129
-35
lines changed

8 files changed

+129
-35
lines changed

classes/local/files/options_file_service.php

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use moodle_exception;
2525
use moodle_url;
2626
use qtype_questionpy\constants;
27+
use qtype_questionpy\local\form\context\render_context;
2728
use qtype_questionpy\local\form\elements\file_upload_element;
2829
use qtype_questionpy\local\form\elements\file_upload_options;
2930
use qtype_questionpy_question;
@@ -47,61 +48,77 @@ class options_file_service implements handles_qpy_url_type {
4748
* @param int $contextid Context id of the question (NOT the draft area).
4849
* @param int $questionid
4950
* @param int $userid User whose draft area should be used, which is most likely the current user.
50-
* @param int $draftitemid
51+
* @param int[] $draftitemids
5152
* @throws file_exception
5253
* @throws stored_file_creation_exception
5354
* @throws coding_exception
55+
* @throws moodle_exception
5456
*/
55-
public function save_draft_area_files(int $contextid, int $questionid, int $userid, int $draftitemid): void {
57+
public function save_draft_area_files(int $contextid, int $questionid, int $userid, array $draftitemids): void {
5658
$fs = get_file_storage();
59+
$usercontext = context_user::instance($userid);
5760

58-
$existingfiles = $fs->get_area_files(
59-
$contextid,
60-
'qtype_questionpy',
61-
constants::FILEAREA_OPTIONS,
62-
$questionid,
63-
includedirs: false
64-
);
65-
$existingfilerefs = array_map(fn($file) => $file->get_filename(), $existingfiles);
61+
// We copy the files in all the separate draft areas to one combined draft area first, so that we can use
62+
// file_save_draft_area_files, which does some magic concerning the file source.
6663

67-
// Copy all draft files to the "permanent" file area and collect their metadata.
68-
$draftfiles = $fs->get_area_files(context_user::instance($userid)->id, 'user', 'draft', $draftitemid, includedirs: false);
64+
$tempcombineddraftarea = file_get_unused_draft_itemid();
65+
66+
$draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemids, includedirs: false);
6967
foreach ($draftfiles as $draftfile) {
7068
$fileref = qpy_file_ref::from_stored_file($draftfile);
7169
// If the file ref already exists, the user just didn't modify/remove the file.
72-
if (!in_array(strval($fileref), $existingfilerefs)) {
73-
$fs->create_file_from_storedfile([
74-
'component' => 'qtype_questionpy',
75-
'filearea' => 'options',
76-
'itemid' => $questionid,
77-
'contextid' => $contextid,
78-
'filepath' => '/',
79-
'filename' => $fileref,
80-
], $draftfile);
81-
}
70+
$fs->create_file_from_storedfile([
71+
'component' => 'user',
72+
'filearea' => 'draft',
73+
'itemid' => $tempcombineddraftarea,
74+
'contextid' => $usercontext->id,
75+
'filepath' => '/',
76+
'filename' => $fileref,
77+
], $draftfile);
8278
}
79+
80+
// Save the combined draft area the question file area.
81+
file_save_draft_area_files(
82+
draftitemid: $tempcombineddraftarea,
83+
contextid: $contextid,
84+
component: 'qtype_questionpy',
85+
filearea: constants::FILEAREA_OPTIONS,
86+
itemid: $questionid,
87+
);
8388
}
8489

8590
/**
8691
* Populates the given draft area with files listed in `$filemetas` and stored in the permanent question file area.
8792
*
8893
* (The inverse of {@see save_draft_area_files}.)
8994
*
90-
* @param int $contextid Context id of the question (NOT the draft area).
91-
* @param int $questionid
9295
* @param file_metadata[] $filemetas
9396
* @param int $userid
94-
* @param int $draftitemid
97+
* @param int $combineddraftitemid The draft area returned by {@see render_context::prepare_combined_draft_area()}.
98+
* @param int $targetdraftitemid The (new, not prepared before) separate draft area belonging to a single form element.
99+
* @throws coding_exception
95100
* @throws file_exception
96101
* @throws stored_file_creation_exception
97-
* @throws coding_exception
98102
*/
99-
public function prepare_draft_area(int $contextid, int $questionid, array $filemetas, int $userid, int $draftitemid): void {
103+
public function prepare_split_draft_area(
104+
array $filemetas,
105+
int $userid,
106+
int $combineddraftitemid,
107+
int $targetdraftitemid
108+
): void {
100109
$fs = get_file_storage();
101-
$files = $fs->get_area_files($contextid, 'qtype_questionpy', constants::FILEAREA_OPTIONS, $questionid, includedirs: false);
110+
$usercontext = context_user::instance($userid);
111+
112+
$combinedfiles = $fs->get_area_files(
113+
$usercontext->id,
114+
'user',
115+
'draft',
116+
$combineddraftitemid,
117+
includedirs: false
118+
);
102119

103120
foreach ($filemetas as $filemetadata) {
104-
$matchingfiles = array_filter($files, fn($file) => $file->get_filename() === $filemetadata->fileref);
121+
$matchingfiles = array_filter($combinedfiles, fn($file) => $file->get_filename() === $filemetadata->fileref);
105122
if (!$matchingfiles) {
106123
debugging("Options file '$filemetadata->filename' with file_ref '$filemetadata->fileref' could not be found in "
107124
. 'storage.');
@@ -120,8 +137,8 @@ public function prepare_draft_area(int $contextid, int $questionid, array $filem
120137
$fs->create_file_from_storedfile([
121138
'component' => 'user',
122139
'filearea' => 'draft',
123-
'itemid' => $draftitemid,
124-
'contextid' => context_user::instance($userid)->id,
140+
'itemid' => $targetdraftitemid,
141+
'contextid' => $usercontext->id,
125142
'filepath' => '/',
126143
'filename' => $filemetadata->filename,
127144
], $file);

classes/local/form/context/array_render_context.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
use Closure;
2020
use HTML_QuickForm_element;
21+
use moodle_exception;
2122
use qtype_questionpy\local\form\elements\group_element;
2223
use qtype_questionpy\utils;
2324

@@ -225,4 +226,17 @@ public function on_export(Closure $onexport): void {
225226
public function on_import(Closure $onimport): void {
226227
$this->parent->on_import($onimport);
227228
}
229+
230+
/**
231+
* Uses {@see file_prepare_draft_area} to copy all options files to a new draft area.
232+
*
233+
* We do this because {@see file_prepare_draft_area} does some possibly important and hard-to-rewrite magic concerning the
234+
* file source.
235+
*
236+
* @return int
237+
* @throws moodle_exception
238+
*/
239+
public function prepare_combined_draft_area(): int {
240+
return $this->parent->prepare_combined_draft_area();
241+
}
228242
}

classes/local/form/context/render_context.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,15 @@ public function get_draft_area_for_upload(string $itemidname): array {
273273

274274
return [$draftitemid, $isnew];
275275
}
276+
277+
/**
278+
* Uses {@see file_prepare_draft_area} to copy all options files to a new draft area.
279+
*
280+
* We do this because {@see file_prepare_draft_area} does some possibly important and hard-to-rewrite magic concerning the
281+
* file source.
282+
*
283+
* @return int
284+
* @throws moodle_exception
285+
*/
286+
abstract public function prepare_combined_draft_area(): int;
276287
}

classes/local/form/context/root_render_context.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
use Closure;
2020
use core\uuid;
21+
use moodle_exception;
22+
use qtype_questionpy\constants;
2123

2224
/**
2325
* Uppermost render context.
@@ -40,6 +42,9 @@ class root_render_context extends mform_render_context {
4042
/** @var (Closure(array&): void)[] $onimportcallbacks */
4143
public array $onimportcallbacks = [];
4244

45+
/** @var int */
46+
public int $combineddraftitemid = 0;
47+
4348
/**
4449
* Get a unique and deterministic integer for use in generated element names and IDs.
4550
*
@@ -96,4 +101,34 @@ public function on_export(Closure $onexport): void {
96101
public function on_import(Closure $onimport): void {
97102
$this->onimportcallbacks[] = $onimport;
98103
}
104+
105+
/**
106+
* Uses {@see file_prepare_draft_area} to copy all options files to a new draft area.
107+
*
108+
* We do this because {@see file_prepare_draft_area} does some possibly important and hard-to-rewrite magic concerning the
109+
* file source.
110+
*
111+
* @return int
112+
* @throws moodle_exception
113+
*/
114+
public function prepare_combined_draft_area(): int {
115+
if ($this->combineddraftitemid) {
116+
return $this->combineddraftitemid;
117+
}
118+
if (!$this->question->id) {
119+
// New question -> no files yet.
120+
$this->combineddraftitemid = file_get_unused_draft_itemid();
121+
return $this->combineddraftitemid;
122+
}
123+
124+
file_prepare_draft_area(
125+
draftitemid: $this->combineddraftitemid,
126+
contextid: $this->question->contextid,
127+
component: 'qtype_questionpy',
128+
filearea: constants::FILEAREA_OPTIONS,
129+
itemid: $this->question->id
130+
);
131+
132+
return $this->combineddraftitemid;
133+
}
99134
}

classes/local/form/context/section_render_context.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
namespace qtype_questionpy\local\form\context;
1818

1919
use Closure;
20+
use core\exception\moodle_exception;
2021
use qtype_questionpy\utils;
2122

2223
/**
@@ -107,4 +108,17 @@ public function on_export(Closure $onexport): void {
107108
public function on_import(Closure $onimport): void {
108109
$this->parent->on_import($onimport);
109110
}
111+
112+
/**
113+
* Uses {@see file_prepare_draft_area} to copy all options files to a new draft area.
114+
*
115+
* We do this because {@see file_prepare_draft_area} does some possibly important and hard-to-rewrite magic concerning the
116+
* file source.
117+
*
118+
* @return int
119+
* @throws moodle_exception
120+
*/
121+
public function prepare_combined_draft_area(): int {
122+
return $this->parent->prepare_combined_draft_area();
123+
}
110124
}

classes/local/form/elements/file_upload_element.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ public function render_to(render_context $context): void {
140140
throw new coding_exception("We're loading a question, but its ID is unset.");
141141
}
142142

143+
$combineddraftarea = $context->prepare_combined_draft_area();
143144
$ofs = di::get(options_file_service::class);
144-
$ofs->prepare_draft_area($context->question->contextid, $questionid, $files, $USER->id, $draftitemid);
145+
$ofs->prepare_split_draft_area($files, $USER->id, $combineddraftarea, $draftitemid);
145146
}
146147

147148
utils::array_set_nested($alldata, $element->getName(), $draftitemid);

classes/local/form/elements/wysiwyg_editor_element.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,9 @@ public function render_to(render_context $context): void {
194194
throw new \core\exception\coding_exception("We're loading a question, but its ID is unset.");
195195
}
196196

197+
$combineddraftarea = $context->prepare_combined_draft_area();
197198
$ofs = di::get(options_file_service::class);
198-
$ofs->prepare_draft_area($context->question->contextid, $questionid, $mydata->files, $USER->id, $draftitemid);
199+
$ofs->prepare_split_draft_area($mydata->files, $USER->id, $combineddraftarea, $draftitemid);
199200
}
200201

201202
$filenamebyfileref = array_column($mydata->files, 'filename', 'fileref');

classes/question_service.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,10 @@ public function upsert_question(object $question): void {
217217

218218
// Save the draft area files belonging to the question.
219219
// file_upload_element and wysiwyg_editor_element add to qpy_options_draftitems.
220+
$draftitems = array_filter((array) $question->qpy_options_draftitems ?? [], fn($draftitemid) => is_int($draftitemid));
220221
global $USER;
221-
foreach ($question->qpy_options_draftitems ?? [] as $draftitemid) {
222-
$this->ofs->save_draft_area_files($question->context->id, $question->id, $USER->id, $draftitemid);
222+
if ($draftitems) {
223+
$this->ofs->save_draft_area_files($question->context->id, $question->id, $USER->id, $draftitems);
223224
}
224225
}
225226

0 commit comments

Comments
 (0)