Skip to content

Commit e768077

Browse files
committed
refactor: move filename inside the file_metadata
This causes fewer problems with PHP auto-casting filenames which just happen to be numerical to ints.
1 parent 8b202f4 commit e768077

File tree

5 files changed

+32
-26
lines changed

5 files changed

+32
-26
lines changed

classes/local/api/wysiwyg_editor_data.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
use qtype_questionpy\local\array_converter\attributes\array_element_class;
2020
use qtype_questionpy\local\array_converter\attributes\array_key;
21-
use qtype_questionpy\local\array_converter\attributes\array_to_object;
2221
use qtype_questionpy\local\files\file_metadata;
2322

2423
/**
@@ -45,10 +44,9 @@ public function __construct(
4544
/** @var string $markupformat */
4645
#[array_key('markup_format')]
4746
public string $markupformat,
48-
/** @var array<string, file_metadata> $files */
47+
/** @var file_metadata[] $files */
4948
#[array_key('files')]
5049
#[array_element_class(file_metadata::class)]
51-
#[array_to_object]
5250
public array $files = [],
5351
/** @var string|null $html */
5452
#[array_key('html')]

classes/local/files/file_metadata.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@ class file_metadata {
3131
/**
3232
* Trivial constructor.
3333
*
34+
* @param string $filename
3435
* @param string $fileref
3536
* @param DateTimeImmutable $uploadedat
3637
* @param string $mimetype
3738
*/
3839
public function __construct(
40+
/** @var string $filename */
41+
public string $filename,
3942
/** @var string $fileref */
4043
#[array_key('file_ref')]
4144
public string $fileref,

classes/local/files/options_file_service.php

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public function save_draft_area_files(int $contextid, int $questionid, int $user
6969
$draftfiles = $fs->get_area_files(context_user::instance($userid)->id, 'user', 'draft', $draftitemid, includedirs: false);
7070
foreach ($draftfiles as $draftfile) {
7171
$fileref = qpy_file_ref::from_stored_file($draftfile);
72-
// If the user didn't modify/remove the file, it would already be saved and the file ref not changed.
72+
// If the file ref already exists, the user just didn't modify/remove the file.
7373
if (!in_array(strval($fileref), $existingfilerefs)) {
7474
$fs->create_file_from_storedfile([
7575
'component' => 'qtype_questionpy',
@@ -84,34 +84,36 @@ public function save_draft_area_files(int $contextid, int $questionid, int $user
8484
}
8585

8686
/**
87-
* Populates the given draft area with files listed in `$metadata` and stored in the permanent question file area.
87+
* Populates the given draft area with files listed in `$filemetas` and stored in the permanent question file area.
8888
*
8989
* (The inverse of {@see save_draft_area_files}.)
9090
*
9191
* @param int $contextid Context id of the question (NOT the draft area).
9292
* @param int $questionid
93-
* @param array $metadata array of {@see file_metadata} by filename
93+
* @param file_metadata[] $filemetas
9494
* @param int $userid
9595
* @param int $draftitemid
9696
* @throws file_exception
9797
* @throws stored_file_creation_exception
9898
* @throws coding_exception
9999
*/
100-
public function prepare_draft_area(int $contextid, int $questionid, array $metadata, int $userid, int $draftitemid): void {
100+
public function prepare_draft_area(int $contextid, int $questionid, array $filemetas, int $userid, int $draftitemid): void {
101101
$fs = get_file_storage();
102102
$files = $fs->get_area_files($contextid, 'qtype_questionpy', self::FILEAREA_UPLOADS, $questionid, includedirs: false);
103103

104-
foreach ($metadata as $mfilename => $filemetadata) {
104+
foreach ($filemetas as $filemetadata) {
105105
$matchingfiles = array_filter($files, fn($file) => $file->get_filename() === $filemetadata->fileref);
106106
if (!$matchingfiles) {
107-
debugging("Options file '$mfilename' with file_ref '$filemetadata->fileref' could be found in storage.");
107+
debugging("Options file '$filemetadata->filename' with file_ref '$filemetadata->fileref' could not be found in "
108+
. 'storage.');
108109
continue;
109110
}
110111

111112
$count = count($matchingfiles);
112113
if ($count > 1) {
113114
// This would mean that the file area contains two files with the same filename, which I'm not sure is possible.
114-
debugging("Options file '$mfilename' has ambiguous file_ref '$filemetadata->fileref' which matches $count files.");
115+
debugging("Options file '$filemetadata->filename' has ambiguous file_ref '$filemetadata->fileref' which matches "
116+
. "$count files.");
115117
}
116118

117119
$file = reset($matchingfiles);
@@ -122,7 +124,7 @@ public function prepare_draft_area(int $contextid, int $questionid, array $metad
122124
'itemid' => $draftitemid,
123125
'contextid' => context_user::instance($userid)->id,
124126
'filepath' => '/',
125-
'filename' => $mfilename,
127+
'filename' => $filemetadata->filename,
126128
], $file);
127129
}
128130
}
@@ -132,7 +134,7 @@ public function prepare_draft_area(int $contextid, int $questionid, array $metad
132134
*
133135
* @param int $userid
134136
* @param int $draftitemid
135-
* @return array<string, file_metadata>
137+
* @return file_metadata[]
136138
* @throws moodle_exception
137139
* @throws coding_exception
138140
*/
@@ -143,7 +145,8 @@ public function get_qpy_files_metadata_from_draftitem(int $userid, int $draftite
143145
$metadata = [];
144146
foreach ($files as $file) {
145147
$fileref = qpy_file_ref::from_stored_file($file);
146-
$metadata[$file->get_filename()] = new file_metadata(
148+
$metadata[] = new file_metadata(
149+
filename: $file->get_filename(),
147150
fileref: $fileref,
148151
uploadedat: DateTimeImmutable::createFromFormat('U', $file->get_timemodified()),
149152
mimetype: $file->get_mimetype()

classes/local/form/elements/file_upload_element.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ public function render_to(render_context $context): void {
9292
}
9393

9494
global $USER;
95-
/** @var array<string, file_metadata> $metadatabyname */
96-
$metadatabyname = di::get(options_file_service::class)->get_qpy_files_metadata_from_draftitem($USER->id, $draftitemid);
95+
/** @var file_metadata[] $filemetas */
96+
$filemetas = di::get(options_file_service::class)->get_qpy_files_metadata_from_draftitem($USER->id, $draftitemid);
9797

9898
// At this time, we don't know whether the question will be saved or the draft validated etc., and we don't know the
9999
// question id, so we don't save the draft files ourselves. But we do need to let question_service know which draft
@@ -103,7 +103,7 @@ public function render_to(render_context $context): void {
103103
utils::array_set_nested(
104104
$alldata,
105105
$element->getName(),
106-
$metadatabyname ? array_converter::to_array($metadatabyname) : new stdClass()
106+
array_converter::to_array($filemetas)
107107
);
108108
});
109109

classes/local/form/elements/wysiwyg_editor_element.php

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,13 @@ public function render_to(render_context $context): void {
128128
file_remove_editor_orphaned_files($mydata);
129129

130130
global $USER;
131-
/** @var array<string, file_metadata> $metadatabyname */
132-
$metadatabyname = di::get(options_file_service::class)->get_qpy_files_metadata_from_draftitem($USER->id, $draftitemid);
131+
/** @var file_metadata[] $filemetas */
132+
$filemetas = di::get(options_file_service::class)->get_qpy_files_metadata_from_draftitem($USER->id, $draftitemid);
133133

134134
// Since we had to format_text before URL replacement, we need to do it to both $markup and $html :(.
135-
$markup = self::replace_draftfile_urls_with_qpy_urls($markup, $metadatabyname, $draftitemid);
135+
$markup = self::replace_draftfile_urls_with_qpy_urls($markup, $filemetas, $draftitemid);
136136
if ($html) {
137-
$html = self::replace_draftfile_urls_with_qpy_urls($html, $metadatabyname, $draftitemid);
137+
$html = self::replace_draftfile_urls_with_qpy_urls($html, $filemetas, $draftitemid);
138138
}
139139

140140
$mappedformat = self::FORMAT_MAP[$format] ?? null;
@@ -145,7 +145,7 @@ public function render_to(render_context $context): void {
145145
$resultdata = new wysiwyg_editor_data(
146146
markup: $markup,
147147
markupformat: $mappedformat,
148-
files: $metadatabyname,
148+
files: $filemetas,
149149
html: $html
150150
);
151151

@@ -177,13 +177,13 @@ public function render_to(render_context $context): void {
177177
$ofs->prepare_draft_area($context->question->contextid, $questionid, $mydata->files, $USER->id, $draftitemid);
178178
}
179179

180-
$filenamebyfileref = array_flip(array_map(fn($fmeta) => $fmeta->fileref, $mydata->files));
180+
$filenamebyfileref = array_column($mydata->files, 'filename', 'fileref');
181181

182182
$replacedtext = preg_replace_callback(
183183
constants::QPY_OPTIONS_URL_PATTERN,
184184
function (array $match) use ($draftitemid, $filenamebyfileref) {
185185
$filename = $filenamebyfileref[strtolower($match['fileref'])] ?? null;
186-
if (!$filename) {
186+
if ($filename === null) {
187187
debugging("Editor text contains QPy URL for nonexistent options file: '$match[0]'");
188188
return $match[0];
189189
}
@@ -212,13 +212,15 @@ function (array $match) use ($draftitemid, $filenamebyfileref) {
212212
* In a similar manner to {@see file_rewrite_urls_to_pluginfile()}, this method rewrites draftfile URLs to `qpy://options/...`.
213213
*
214214
* @param string $text
215-
* @param array $metadatabyname As returned by {@see options_file_service::get_qpy_files_metadata_from_draftitem()}.
215+
* @param array $files As returned by {@see options_file_service::get_qpy_files_metadata_from_draftitem()}.
216216
* @param int $draftitemid
217217
* @return string
218218
*/
219-
private static function replace_draftfile_urls_with_qpy_urls(string $text, array $metadatabyname, int $draftitemid): string {
219+
private static function replace_draftfile_urls_with_qpy_urls(string $text, array $files, int $draftitemid): string {
220220
global $USER;
221221

222+
$filesbyname = array_column($files, null, 'filename');
223+
222224
$draftfileurls = extract_draft_file_urls_from_text($text);
223225
$expectedparts = [
224226
'contextid' => context_user::instance($USER->id)->id,
@@ -236,7 +238,7 @@ private static function replace_draftfile_urls_with_qpy_urls(string $text, array
236238
}
237239
}
238240

239-
$fileref = $metadatabyname[$draftfileurl['filename']]->fileref ?? null;
241+
$fileref = $filesbyname[$draftfileurl['filename']]->fileref ?? null;
240242
if (!$fileref) {
241243
debugging("Editor text contains draftfile link to nonexistent file {$draftfileurl['filename']}.", DEBUG_DEVELOPER);
242244
continue;

0 commit comments

Comments
 (0)