General developer forum

Fetch a file and display it using the File API

 
Picture of Mark Johnson
Re: Fetch a file and display it using the File API
Core developersParticularly helpful MoodlersPlugin developers

The File API documentation has various examples that cover taking a file uploaded by a user, storing it, and serving it again.

Generally, the process is something like the following, but it depends on your exact use case:

  1. The user uploads a file to a filemanager field on a form.  This stores in in the user's draftarea with a random itemid.  When the form is submitted, the value of this field is the itemid.
  2. You use file_save_draft_area_files() to move these to a permanent file area for your plugin (you'll need a context, such as your plugin instance context, and a unique itemid within that context, such as the id of a record relating to that user's submission).
  3. You get the stored_file object representing the file you want to serve (there are various API functions for this depending on your use case) then use moodle_url::make_pluginfile_url() to generate the URL.
 
Average of ratings: -
Raymond
Re: Fetch a file and display it using the File API
 

Hi Mark,

I think I've got myself into a bit of a pickle here. I have done everything done right in getting the file into the moodledata/filedir directory using the filemanager in a moodle form. I can also manage to display the "filenames", along with the URL. But when I click on the URL, I get an error below:

Sorry, the requested file could not be found
More information about this error
× Debug info: 
Error code: filenotfound
× Stack trace:
  • line 503 of /lib/setuplib.php: moodle_exception thrown
  • line 1817 of /lib/filelib.php: call to print_error()
  • line 4606 of /lib/filelib.php: call to send_file_not_found()
  • line 37 of /pluginfile.php: call to file_pluginfile()

Here is the code that I'm using to fetch the files:

echo 'FILES <br />';
$posts = $DB->get_records('groupwork_discussion_posts', ['discussion' => $discussion->id]);
foreach ($posts as $post) {
    $out = array();
    $fs = get_file_storage();
    //  $files = $fs->get_area_files($context->id, 'mod_groupwork', 'content', $post->id);
    $files = $fs->get_area_files($context->id, 'mod_groupwork', 'content', $post->id, 'sortorder DESC, id ASC', false); 
    foreach ($files as $file) {
        $filename = $file->get_filename();
        $path = '/' . $context->id . '/' . 'mod_groupwork' . '/' . 'content' . '/' . $post->id . '/' . $file->get_filename();
        $url = moodle_url::make_file_url('/pluginfile.php', $path, $displaytype == RESOURCELIB_DISPLAY_DOWNLOAD);
        $out[] = html_writer::link($url, $filename);  
    }
    $br = html_writer::empty_tag('br');
    echo implode($out).$br;
}

This is displaying whats in the image below, but when I click on it, it shows the error above. This is also the URL generated on one of the files I uploaded using the filemanager.

http://localhost/pluginfile.php/62805/mod_groupwork/content/7396/Things%20Fall%20Apart.pdf 







 
Average of ratings: -
Picture of sam marshall
Re: Fetch a file and display it using the File API
Core developersParticularly helpful MoodlersPlugin developersTesters

Hiya,

Did you implement the callback groupwork_pluginfile in your module's lib.php? You need to implement this because your plugin gets to control access to its own files - by default none of them are downloadable.

If you didn't implement it, that's the problem; implement it. smile If you did implement it then there may be a bug with it.

--sam

 
Average of ratings: -
Raymond
Re: Fetch a file and display it using the File API
 

Thanks Sam for the response. I do have that function in the lib.php file, and I've pasted it below. Perhaps I don't understand that function really well, since I copied the code from one of the plugins last year.


function groupwork_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options = array()) {
    global $CFG, $DB;
    require_once("$CFG->libdir/resourcelib.php");
    if ($context->contextlevel != CONTEXT_MODULE) {
        return false;
    }
    require_course_login($course, true, $cm);
    if (!has_capability('mod/groupwork:view', $context)) {
        return false;
    }
    if ($filearea !== 'content' && $filearea !== 'attachment') {
        // intro is handled automatically in pluginfile.php
        return false;
    }
    // $arg could be revision number or index.html
    $arg = array_shift($args);
    if ($arg == 'index.html' || $arg == 'index.htm') {
        // serve page content
        $filename = $arg;
        if (!$groupwork = $DB->get_record('groupwork', array('id' => $cm->instance), '*', MUST_EXIST)) {
            return false;
        }
        // remove https://moodle.org/pluginfile.php/114/mod_forum/post/1466119/
        $content = str_replace('https://moodle.org/pluginfile.php/114/mod_forum/post/1466119/', '', $groupwork->content);
        $formatoptions = new stdClass;
        $formatoptions->noclean = true;
        $formatoptions->overflowdiv = true;
        $formatoptions->context = $context;
        $content = format_text($content, $groupwork->contentformat, $formatoptions);
        send_file($content, $filename, 0, 0, true, true);
    } else {
        $fs = get_file_storage();
        $relativepath = implode('/', $args);
        $fullpath = "/$context->id/mod_groupwork/$filearea/0/$relativepath";
          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
            // $groupwork = $DB->get_record('groupwork', array('id' => $cm->instance), 'id, legacyfiles', MUST_EXIST);
            // if ($groupwork->legacyfiles != RESOURCELIB_LEGACYFILES_ACTIVE) {
            // return false;
            //}
            if (!$file = resourcelib_try_file_migration('/' . $relativepath, $cm->id, $cm->course, 'mod_groupwork', 'content', 0)) {
                return false;
            }
            //file migrate - update flag
            // $groupwork->legacyfileslast = time();
            $DB->update_record('groupwork', $groupwork);
        }
        // finally send the file
        send_stored_file($file, null, 0, $forcedownload, $options);
    }
 
Average of ratings: -
Picture of sam marshall
Re: Fetch a file and display it using the File API
Core developersParticularly helpful MoodlersPlugin developersTesters

I don't have time to look at it in detail, I only had a very quick glance, but you can check where it's failing - either use a debugger or if you don't have one set up (I usually never get it working) then put some debugging message like:

echo 'got here!'; exit;

at the top of the pluginfile function. If it does get there, then add similar messages just before it does 'return false' in each of the conditions where it might refuse access - that will tell you which thing is causing it to fail. Returning false anywhere here will cause it to send a 404 I think so that's most likely - of course it might be failing somewhere else but you can find that out this way too.

--sam


 
Average of ratings: -
Raymond
Re: Fetch a file and display it using the File API
 

I did manage to crack it. I had to make amendments to my module_pluginfile() method as well as the module_get_file_info() methods. After that, I can upload and fetch all files via the file manager.

Now I'm battling with the editor one. Somehow when I upload a file through the editor, only the uploader can view/download the file. Everyone else gets an error saying "Invalid user", and the file has a url with draftfile.php on it instead of the normal pluginfile.php...

How can I sort this out so that I can use the editor throughout my plugin and allow my teachers to use this to upload files?

 
Average of ratings: -
Davo
Re: Fetch a file and display it using the File API
Core developersParticularly helpful MoodlersPlugin developers

You need to make sure you're using the file_prepare_standard_editor() and file_postupdate_standard_editor() functions ( https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#editor ) to a) copy files in and out of the draft area when entering / exiting the form and b) rewrite all URLs embedded in the form text data to/from the draftfile.php versions.

Any of the core forms that allow file uploads in the editor do this, so there are lots of examples throughout the standard Moodle code.


 
Average of ratings: -