Quesitons about File API

Quesitons about File API

Kurt Armbruster -
Number of replies: 9

I've been working on a mod plugin that is a UI for students. The teacher can upload a text file (it's presently going to a cloud service), and the plugin uses it to generate the interface. The interface is intended to be used by several different courses.


Can I use the File API to store and retrieve the text file "locally?"


I've gone through https://docs.moodle.org/dev/File_API#Create_file and being relatively new to Moodle (and development), it's left me with some questions about the API that were unclear to me.

Block 1: Create a file.

  1. From my limited understanding of moodle, the contextid would provide a unique element for any given file as the context would be specific to that particular interface and that particular course. Is this understanding correct?
  2. If a teacher was using the interface on 3 separate courses, their upload creating that interface would be unique to each course?
  3. As well, since I only want one text file for a specific course, can I hardcode in the itemid, so that the file gets replaced every time the teacher uploads a new one for that course? 
  4. Finally, since there might be several files uploaded for several different courses, can I use a variable that takes in the user's original file name (or code that creates a unique one) to populate the filename?
$fs = get_file_storage();

// Prepare file record object
$fileinfo = array(
    'contextid' => $context->id, // ID of context
    'component' => 'mod_mymodule',     // usually = table name
    'filearea' => 'myarea',     // usually = table name
    'itemid' => 0,               // usually = ID of row in table
    'filepath' => '/',           // any path beginning and ending in /
    'filename' => 'myfile.txt'); // any filename

// Create file containing text 'hello world'
$fs->create_file_from_string($fileinfo, 'hello world');

The filearea is also somewhat confusing to me. Looking at the snippet below, the filearea in this case would be "post."
For my purposes, could I make a folder mod/mymodule/interface_files to store the txt files (in filepath) and then call the 'filearea' value 'interface_files'?
$url = $CFG->wwwroot/pluginfile.php/$forumcontextid/mod_forum/post/$postid/image.jpg The plugin needs to read the file every time the screen refreshes.
Can I use the Read File code, provided it's inside my mod/mymodule folder somewhere?
The moodle docs state "Please note you are allowed to do this ONLY from mod/mymodule/* code, it is not acceptable to do this anywhere else.
I'm assuming they mean the code must be inside the folder (say in my index.php file). Is this correct?
Block 2: Read a file:

$fs = get_file_storage();

// Prepare file record object
$fileinfo = array(
    'component' => 'mod_mymodule',     // usually = table name
    'filearea' => 'myarea',     // usually = table name
    'itemid' => 0,               // usually = ID of row in table
    'contextid' => $context->id, // ID of context
    'filepath' => '/',           // any path beginning and ending in /
    'filename' => 'myfile.txt'); // any filename

// Get file
$file = $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
                      $fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);

// Read contents
if ($file) {
    $contents = $file->get_content();
} else {
    // file doesn't exist - do something
}


Average of ratings: -
In reply to Kurt Armbruster

Re: Quesitons about File API

Davo Smith -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

A few pointers:

  • contextid - if you are wanting to store a single file per course, then this should be the context id of the course context (generated from $context = context_course::instance($courseid); ) - but note that the file would only be deleted when the course was deleted and not when the activity was deleted. The more usual use-case would be to use the activity context, with each instance of the activity having a separate file area of its own.
  • component - the name of your plugin ('mod_MYPLUGINNAME' for an activity, 'block_MYBLOCKNAME' for a block,  etc.)
  • filearea - it is your choice as to what this should be, if you only have a single "type" of file area in your block, then it really doesn't matter. Some cases you may have more than one "type" of file area (for example, a forum has a file area for files directly embedded in the posts, e.g. by clicking on the 'image' button when editing a post, and a file area for attachments, added via the file manager, below the post)
  • itemid - in your case, I think this should be 0; in general, this is for cases where you have more than one file area in the same context (going back to the 'forum' example - each forum post, for a particular activity instance, is in the same context, but each post has its own, separate, set of files; these are distinguished by setting the itemid to match the id of the post that the files belong to)


In reply to Davo Smith

Re: Quesitons about File API

Kurt Armbruster -
Thank you Davo.

The interface applies to the course as a whole, and not any specific module (I assume modules are the activity context) within the course.

For the filepath, can I create a folder such as a mod/myPlugin/myPluginUploads directory that stores the files?

There should only be one uploaded text file for each course that is using the interface.

The present system checks the cloud storage for a file and then loads the interface based on the file.
How would I change this to check specifically for a file related to the course context? Would it be better to standardize the way it names the files?

// Get file
$file = $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
$fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);
In reply to Kurt Armbruster

Re: Quesitons about File API

Davo Smith -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
If you mean the 'filepath' for the $fileinfo array - you could create a custom filepath, if you really wanted to, but I've rarely found a use case where this should be anything other than '/'.

This is the filepath inside your file area (as defined by the contextid / component / filearea / itemid) - not the path on the server to access the file. The only reason to have anything other than '/' would be if you were wanting to organise files within the area (e.g. if you had an HTML file at the top level within your file area and you wanted to store '/images/pic1.png' in there as well, so that the HTML file could reference the image by the relative link 'images/pic1.png').

In reply to Davo Smith

Re: Quesitons about File API

Kurt Armbruster -
Thank you again for your assistance.
Unfortunately, my code so far doesn't seem to be working. 

The error is "(Invalid filearea)"
Is it possibly because I've given the "filearea" section a name I just made up? Should I use something different here?
Do I have to define a file area? The plugin is an interface for the students that shows their course modules, it's not a forum.

Here's the code I'm using:

$myInterface = ' my interface text'; // The text used to generate the interface.

$coursecontext = context_course::instance($courseid); //$context is already being used for a $cm
$fs = get_file_storage();
$file_name = "context_" . $coursecontext->id . "_interface.txt";
$fileinfo = array(
    'component' => 'mod_myPlugin', // usually = table name
    'filearea' => 'myInterfaceTextFiles', // usually = table name
    'itemid' => 0, // usually = ID of row in table
    'contextid' => $coursecontext->id, // ID of context
    'filepath' => '/', // any path beginning and ending in /
    'filename' => $file_name); // any filename

$fs->create_file_from_string($fileinfo, $myInterface );


This is the error getting generated:
Unknown exception related to local files (Invalid filearea)

More information about this error  // Unfortunately, when I clicked the link, it says that "This page has been deleted."

×Debug info:
Error code: storedfileproblem
×Stack trace:
line 1402 of \lib\filestorage\file_storage.php: file_exception thrown
line 127 of \mod\myPlugin\view.php: call to file_storage->create_file_from_string()
×Output buffer:
context_course Object ( [_id:protected] => 25 [_contextlevel:protected] => 50 [_instanceid:protected] => 2 [_path:protected] => /1/3/25 [_depth:protected] => 3 [_locked:protected] => 0 ) 
25File Upload:
Array ( [component] => mod_myPlugin [filearea] => myInterfaceTextFiles [itemid] => 0 [contextid] => 25 [filepath] => / [filename] => course_25_interface.txt ) 
In reply to Kurt Armbruster

Re: Quesitons about File API

Davo Smith -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Fileareas are usually lowercase, letters only and no punctuation - I don't know if that is enforced, I've never tried anything different.

The component should also be lowercase.
In reply to Davo Smith

Re: Quesitons about File API

Kurt Armbruster -
In reply to Kurt Armbruster

Re: Quesitons about File API

Kurt Armbruster -
On to the next step. The original plugin uses javascript to create an upload button to get the file.
It then puts the text from the file into a JSON object at upload.txt

Can I use AJAX to transfer the raw string data from upload.txt in the JS to the $myinterface variable (from the example above) in a php file?
I've been looking for an "AJAX for beginners" breakdown to do this without much luck.
Or would there be a better way to either upload the file using php (and the existing uploadbutton div), or transfer the string from JS to php?
In reply to Kurt Armbruster

Re: Quesitons about File API

Kurt Armbruster -
I was finally successful and able to upload and save the files locally on the server in ..\moodledata\filedir\

These articles were helpful in understanding different AJAX methods and how to get JS and PHP to talk to each other:
How to use AJAX to send form data: https://code-boxx.com/call-php-file-from-javascript/
How to convert a JSON object into form data: https://stackoverflow.com/a/68487267/13914602
Getting the data out of $_POST once it's been moved from JS to PHP: https://stackoverflow.com/a/67828716/13914602

As for where it goes in ..\moodledata\filedir\ this moodle doc was helpful:
https://docs.moodle.org/dev/File_API_internals#:~:text=Files%20are%20stored%20in%20%24CFG,SHA1%20hash%20of%20their%20content
In reply to Kurt Armbruster

Re: Quesitons about File API

Davo Smith -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Glad to hear you've got it all working, but do be aware that those links do not describe ways that you should be writing code for a Moodle site.

For a Moodle site, AJAX calls should be via webservices: https://docs.moodle.org/dev/AJAX
You should never be touching $_POST directly in Moodle - always use the required_param() / optional_param() wrappers that make sure the data is cleaned before it is used (or required_param_array() / optional_param_array() if you need them).

Knowing how files are stored within moodledata is an interesting internal implementation detail (and it's a pretty cool storage mechanism) - but this should never be something you take into account when writing code to work with files in Moodle. Please make sure you're only accessing files by using the Moodle Files API, otherwise there's a good chance you'll manage to break your Moodle site at some point ...