Files in Forms
Files and their metadata are stored within the File API. Each file within the API is associated with:
- a context id;
- a component;
- a file area; and
- an optional item id.
This combination acts as a virtual bucket, within which any desired file structure may be used.
A common use case is to allow users to upload file content within a standard Moodle form.
Normally this works as follows:
- User starts to create, or re-edit an existing item - this may be a forum post, resource, glossary entry, and so on.
- User presses some sort of button to browse for new files to attach or embed
- User sees our "Choose file..." dialog, which contains one or more repository instances.
- User chooses a file, the corresponding Repository plugin takes care of copying the file into a "draft file area" within Moodle
- File appears in the text or as an attachment in the form.
- When the user hits save, the File API is invoked to move the file from the draft file area into a permanent file area associated with that data
This document shows you how to use Moodle forms to interact with users in a standard and secure way.
If you just want to write code to manipulate Moodle files internally (without user input) you can use the File API without involving form elements.
Form elements
There are three file-related form elements for interacting with users:
filepicker
- a way to specify one file for the case when you want to process the file and throw it awayfilemanager
- a way to attach one or more files as a collection, using the file picker interfaceeditor
- a way to specify a textarea with a HTML editor, and all the handling of images and movies within that HTML
File picker
The File picker may be used directly to allow a user to upload one file so that it can be processed, and then removed.
Example use-cases include allowing a single file to be uploaded for a purpose such as CSV import, or restoration of a backup.
A filepicker element does not typically support storing data within the File API.
If you want a file that remains part of the Moodle storage and will reappear when you reopen the form, then you should use a filemanager instead (and restrict it to a single file, if necessary).
Using the filepicker element
$mform->addElement(
'filepicker',
'userfile',
get_string('file'),
null,
[
'maxbytes' => $maxbytes,
'accepted_types' => '*',
]
);
Working with an uploaded file
To get the contents of the uploaded file:
$content = $mform->get_file_content('userfile');
To get the name of the uploaded file:
$name = $mform->get_new_filename('userfile');
To save the uploaded file to the server filesystem:
$success = $mform->save_file('userfile', $fullpath, $override);
Be wary of this approach if you need to use the file in multiple page requests.
A clustered server installation will need the file to be accessible on any server node.
To store the chosen file in the Moodle file API:
$storedfile = $mform->save_stored_file('userfile', ...);
File manager
The File Manager element improves on file picker by allowing you to manage more than one file. It is expected that the files will be stored permanently for future use.
Examples of the File manager can be found all over Moodle and include the Forum, and Glossary attachments, Course files, and more.
Add a file manager element
Example:
$mform->addElement(
'filemanager',
'attachments',
get_string('attachment', 'moodle'),
null,
[
'subdirs' => 0,
'maxbytes' => $maxbytes,
'areamaxbytes' => 10485760,
'maxfiles' => 50,
'accepted_types' => ['document'],
'return_types' => FILE_INTERNAL | FILE_EXTERNAL,
]
);
When a user uploads files, these are stored into a draft file area for that user. It is the developers responsibility to then move those files within the File API.
Loading existing files into draft area
If you are presenting a form which has previously had data saved to it, for example when editing an existing piece of content, you will need to copy all of the existing files into the draft file area used in the form. This can be achieved using the file_prepare_draft_area
function, for example:
// Fetch the entry being edited, or create a placeholder.
if (empty($id)) {
$entry = (object) [
'id' => null,
];
} else {
$entry = $DB->get_records('glossary_entries', ['id' => $id]);
}
// Get an unused draft itemid which will be used for this form.
$draftitemid = file_get_submitted_draft_itemid('attachments');
// Copy the existing files which were previously uploaded
// into the draft area used by this form.
file_prepare_draft_area(
// The $draftitemid is the target location.
$draftitemid,
// The combination of contextid / component / filearea / itemid
// form the virtual bucket that files are currently stored in
// and will be copied from.
$context->id,
'mod_glossary',
'attachment',
$entry->id,
[
'subdirs' => 0,
'maxbytes' => $maxbytes,
'maxfiles' => 50,
]
);
// Set the itemid of draft area that the files have been moved to.
$entry->attachments = $draftitemid;
$mform->set_data($entry);
Store updated set of files
During the processing of the submitted data, the developer handling the form will need to handle storing the files in an appropriate part of the File API.
This can be accomplished using the file_save_draft_area_files()
function, for example:
if ($data = $mform->get_data()) {
// ... store or update $entry.
// Now save the files in correct part of the File API.
file_save_draft_area_files(
// The $data->attachments property contains the itemid of the draft file area.
$data->attachments,
// The combination of contextid / component / filearea / itemid
// form the virtual bucket that file are stored in.
$context->id,
'mod_glossary',
'attachment',
$entry->id,
[
'subdirs' => 0,
'maxbytes' => $maxbytes,
'maxfiles' => 50,
]
);
}
Editors
Another common place to handle files is within an HTML editor, such as TinyMCE.
There are two ways of using the editor element in code, the first one is easier but expects some standardised fields. The second method is more low level.
All of the methods share key behaviours:
- When preparing the editor:
- You must create a draft file area to store the files while the user is making changes.
- When using the editor:
- Any files referenced use a full URL, for example
https://example.com/pluginfile.php/123/user/icon/456/filedir/filename.png
. - These files are stored in the draft file area.
- Any files referenced use a full URL, for example
- When processing the form submission:
- You must process the content so that part of the URL is replaced with a placeholder - usually
@@PLUGINFILE@@
. For example the URL may become@@PLUGINFILE@@/filedir/filename.png
. - You must pass it through a function to move the files from the draft file area into the correct file area for your code.
- You must process the content so that part of the URL is replaced with a placeholder - usually
- When displaying the editor content:
- You must pass it through
file_rewrite_pluginfile_urls()
to rewrite it back to a servable URL. - You must provide a
pluginfile
function to perform access control checks and serve the file.
- You must pass it through
- When fetching existing content for editing:
- You must copy it into a new draft file area so that changes can be made.
- You must rewrite the
@@PLUGINFILE@@
URL with the new draft file area.