Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

File API internals

From MoodleDocs

Note: This page is a work-in-progress. Feedback and suggested improvements are welcome. Please join the discussion on moodle.org or use the page comments.

File API
Project state Implemented
Tracker issue MDL-14589
Discussion n/a
Assignee Petr Škoda (škoďák)

Moodle 2.0


Objectives

The goals of the new File API are:

  • allow files to be stored within Moodle, as part of the content (as we do now).
  • use a consistent and flexible approach for all file handling throughout Moodle.
  • give modules control over which users can access a file, using capabilities and other local rules.
  • make it easy to determine which parts of Moodle use which files, to simplify operations like backup and restore.
  • track where files originally came from.
  • avoid redundant storage, when the same file is used twice.
  • fully support Unicode file names, irrespective of the capabilities of the underlying file system.

Overview

The File API is a set of core interfaces to allow the rest of Moodle to store, serve and manage files. It applies only to files that are part of the Moodle site's content. It is not used for internal files, such as those in the following subdirectories of dataroot: temp, lang, cache, environment, filter, search, sessions, upgradelogs, ...

The API can be subdivided into the following parts:

File storage
Low level file storage without access control information. Stores the content of files on disc, with metadata in associated database tables.
File serving
Lets users accessing a Moodle site get the files (file.php, draftfile.php, pluginfile.php, userfile.php, etc.)
  • Serve the files on request
  • with appropriate security checks
File related user interfaces
Provides the interface for (lib/form/file.php, filemanager.php, filepicker.php and files/index.php, draftfiles.php)
  • Form elements allowing users to select a file using the Repository API, and have it stored within Moodle.
  • UI for users to manage their files, replacing the old course files UI
File browsing API
Allows code to browse and optionally manipulate the file areas
  • find information about available files in each area.
  • print links to files.
  • optionally move/rename/copy/delete/etc.

File API internals

File storage on disk

Files are stored in $CFG->dataroot (also known as moodledata) in the filedir subfolder.

Files are stored according to the SHA1 hash of their content. This means each file with particular contents is stored once, irrespective of how many times it is included in different places, even if it is referred to by different names. (This idea comes from the git version control system.) To relate a file on disc to a user-comprehensible path or filename, you need to use the files database table. See the next section.

Suppose a file has SHA1 hash 081371cb102fa559e81993fddc230c79205232ce. Then it will be stored in on disc as moodledata/filedir/08/13/71/081371cb102fa559e81993fddc230c79205232ce.

This means Moodle can not store two files with the same SHA1 hash, luckily it is extremely unlikely that this would ever happen. Technically it is also possible to implement reliable collision tests (with some performance cost), for now we just test file lengths in addition to SHA1 hash.

Files table

This table contains one entry for each usage of a file. Enough information is kept here so that the file can be fully identified and retrieved again if necessary. It is necessary because some databases have hard limit on index size.

If, for example, the same image is used in a user's profile, and a forum post, then there will be two rows in this table, one for each use of the file, and Moodle will treat the two as separate files, even though the file is only stored once on disc.

Field Type Default Info
id int(10) auto-incrementing The unique ID for this file.
contenthash varchar(40) The sha1 hash of content.
pathnamehash varchar(40) The sha1 hash of contextid+filearea+itemid+filepath+filename - prevents file duplicates and allows fast lookup. It is necessary because some databases have hard limit on index size.
contextid int(10) The context id defined in context table - identifies the instance of plugin owning the file.
filearea varchar(50) Like "submissions", "intro" and "content" (images and swf linked from summaries), etc.; "blogs" and "userfiles" are special case that live at the system context.
itemid int(10) Some plugin specific item id (eg. forum post, blog entry or assignment submission or user id for user files)
filepath text relative path to file from module content root, useful in Scorm and Resource mod - most of the mods do not need this
filename varchar(255) The full Unicode name of this file (case sensitive)
userid int(10) NULL Optional - general user id field - meaning depending on plugin
filesize int(10) size of file - bytes
mimetype varchar(100) NULL type of file
status int(10) general file status flag - will be used for lost or infected files
source text file source - usually url
author varchar(255) original author of file, used when importing from other systems
license varchar(255) license type, empty means site default
timecreated int(10) The time this file was created
timemodified int(10) The last time the file was last modified

Indexes:

  • non-unique index on (contextid, filearea, itemid)
  • non-unique index on (contenthash)
  • unique index on (pathnamehash).

The plugin type does not need to be specified because it can be derived from the context. Items like blog that do not have their own context will use their own file area inside a suitable context. In this case, the user context.

Entries with filename = '.' represent directories. Directory entries like this are created automatically when a file is added within them.

Note: 'files' plural is used even thought that goes against the coding guidelines because 'file' is a reserved word in some SQL dialects.

Implementation of basic operations

Each plugin may directly access only files in own context and areas!

Low level access API is defined in file_storage class which is obtained from get_file_storage().

Storing a file

  1. Calculate the SHA1 hash of the file contents.
  2. Check if a file with this SHA1 hash already exists on disc in file directory or file trash. If not, store the file there.
  3. Add the record for this file to the files table using the low level address

Reading a file

  1. Fetch the record (which includes the SHA1 hash) for the file you want from the files table. You can fetch either all area files or quickly get one file with a specific contenthash.
  2. Retrieve the contents using the SHA1 hash from the file directory.

Deleting a file

  1. Delete the record from the files table.
  2. Verify if some other file is still needing the content, if not move the content file into file trash
  3. Later, admin/cron.php deletes content files from trash directory

File serving

Deals with serving of files - browser requests file, Moodle sends it back. We have three main files. It is important to setup slasharguments on server properly (file.php/some/thing/xxx.jpg), any content that relies on relative links can not work without it (scorm, uploaded html pages, etc.).

legacy file.php

Serves legacy course files, the file name and parameter structure is critical for backwards compatibility of existing course content.

/file.php/courseid/dir/dir/filename.ext

Internally the files are stored in array('contextid'=>$coursecontextid, 'filearea'=>'course_content', 'itemid'=>0)

The legacy course files are completely disabled in all new courses created in 2.0. The major problem here is to how to educate our users that they can not make huge piles of files in each course any more.

pluginfile.php

All plugins should use this script to serve all files.

  • plugins decide about access control
  • optional XSS protection - student submitted files must not be served with normal headers, we have to force download instead; ideally there should be second wwwroot for serving of untrusted files
  • links to these files are constructed on the fly from the relative links stored in database, this means that plugin may link only own files

Absolute file links need to be rewritten if html editing allowed in plugin. The links are stored internally as relative links. Before editing or display the internal link representation is converted to absolute links using simple str_replace() @@thipluginlink/summary@@/image.jpg --> /pluginfile.php/assignmentcontextid/intro/image.jpg, it is converted back to internal links before saving.

Script parameters are virtual file names, in most cases the parameters match the low level file storage, but they do not have to:

/pluginfile.php/contextid/areaname/arbitrary/params/or/dirs/filename.ext

pluginfile.php detects the type of plugin from context table, fetches basic info (like $course or $cm if appropriate) and calls plugin function (or later method) which does the access control and finally sends the file to user. areaname separates files by type and divides the context into several subtrees - for example summary files (images used in module intros), post attachments, etc.

Assignment example

/pluginfile.php/assignmentcontextid/intro/someimage.jpg
/pluginfile.php/assignmentcontextid/submission/submissionid/attachmentname.ext
/pluginfile.php/assignmentcontextid/extra/allsubmissionfiles.zip
Uhm... all those files together? What's going to differentiate the "submission" path in the example above from the "summary" path? Is it supposed that the editor, or the filemanager won't allow , for example to pick-up one file from the "submission" area to be used in the summary of one assignment and only the "summary" area will be showed? That means multiple file managers by context and it's against the clean "one file manager per context" agreed below Eloy Lafuente (stronk7) 21:28, 26 June 2008 (CDT)
Yes Eloy, the different areas (summary, submission) etc. have different uses, different access control. There are two types of file manager - the two pane file manager which lists all contexts+areas user may access, and minimalistic manager in html editor which shows only subset of areas from current plugin (because you can not link anything else).

scorm example

/pluginfile.php/scormcontextid/intro/someimage.jpg
/pluginfile.php/scormcontextid/content/revisionnumber/dir/somescormfile.js

The revision counter is incremented when any file changes in order to prevent caching problems. The lifetime should be adjustable in module settings.

quiz example

pluginfile.php/quizcontextid/intro/niceimage.jpg
pluginfile.php/quizcontextid/report/type/export.ods

questions example

This section was out of date. See File_storage_conversion_Quiz_and_Questions for the latest thinking.

blog example

Blog entries or notes in general do not have context id (because they live in system context, SYSCONTEXTID below is the id of system context). The note attachments are always served with XSS protection on, ideally we should use separate wwwroot for this. Access control can be hardcoded.

/pluginfile.php/SYSCONTEXTID/blog_attachment/blogentryid/attachmentname.ext

Internally stored in array('contextid'=>SYSCONTEXTID, 'filearea'=>'blog_attachment', 'itemid'=>$blogentryid)

/pluginfile.php/SYSCONTEXTID/blog_post/blogentryid/embeddedimage.ext

Internally stored in array('contextid'=>SYSCONTEXTID, 'filearea'=>'blog_post', 'itemid'=>$blogentryid)

backup example

It would be nice to have some special protection of backup files - new capabilities for backup file download, upload. Backups contain a lot of personal info, we could block restoring of backups from other sites too.

/pluginfile.php/coursecontextid/backup/backupfile.zip

Internally stored in array('contextid'=>$coursecontextid, 'filearea'=>'backup', 'itemid'=>0)

userfile.php

Personal file storage, intended as an online storage of work in progress like assignments before the submission.

  • read/write own files only for now
  • option to share with others later
  • personal "websites" will not be supported (security)
/userfile.php/userid/dir/dir/filename.ext

rssfile.php

Replaces rss/file.php which is kept only for backwards compatibility. RSS files should not require sessions/cookies, URLs should contain some sort of security token/key. Internally the files may be stored in database or together with other files. Performance improvements - we should support both Etag (cool) and Last-Modified (more used), when we receive If-None-Match/If-Modified-Since => 304

/rssfile.php/contextid/any/parameters/module/wants/rss.xml
/rssfile.php/SYSCONTEXTID/blog/userid/rss.xml

Again modules and plugins decide what gets sent to user.

Temporary files

Temporary files are usually used during the lifetime of one script only. uses:

  • exports
  • imports
  • zipping/unzipping
  • processing by executable files (latex, mimetex)

Ideally these files should never use utf-8 (which is a major problem for zipping at the moment). Proposed new sha1 based file storage is not suitable both for performance and technical reasons.

Legacy file storage and serving

Going to use good-old separate directories in $CFG->dataroot.

file serving and storage:

  1. user avatars - user/pix.php
  2. group avatars - user/pixgroup.php
  3. tex, algebra - filter/tex/* and filter/algebra/*
  4. rss cache (?full rss rewrite soon?) - backwards compatibility only rss/file.php

only storage:

  1. sessions

File browsing and management API

This is what other parts of Moodle use to access and manage files. The code is in lib/file/.

These section documents both the public facing parts of this code, and also the inner workings. Hopefully it makes it sufficiently clear which is which. For more information, see the API documentation in the code, and Using_the_file_API.

TODO write the rest of this section.

lib/filelib.php

Including this file includes all of the other library files.

Class: file_browser

Class: file_info and subclasses

Class: file_storage

Class: stored_file

File exceptions

File management user-interface

File manager

All the contexts, file areas and files now form a single huge tree structure, although each user only has access to certain parts of that tree. The file manager (files/index.php) allow users to brows this tree, and manage files within it, according to the level of permissions they have.

Single pane file manager is hard to implement without drag & drop which is notoriously problematic in web based applications. I propose to implement a two pane commander-style file manager. Two pane manager allows you to easily copy/move files between two different contexts (ex: courses).

File manager must not interact directly with filesystem API, instead each module should return traversable tree of files and directories with both real and localised names (localised names are needed for dirs like backupdata).

This code will be in file/.

Formslib field types

This code will be in lib/form/

  • Upload single files.
  • Upload multiple files to a files area.
  • HTML editor (see next)

Integration with the HTML editor

Each instance of the HTML editor will be told to store related files in a particular file area.

During editing, files will be stored in a draft files area. Then when the form is submitted they will be (automatically) moved into the real file area.

Files will be selected using the repository file picker.

Local files repository plugin

Allows users to see an browse files they already have access to within this Moodle, for inclusion in the page currently being edited.

This code will be in repository/local/

Backwards compatibility

Content backwards compatibility

This should be preserved as much as possible. This will involve rewriting links in content during the upgrade to 2.0.

Some new features (like resource sharing - if implemented) may not work with existing data that still uses files from course files area.

There might be a breakage of links due to special characters stripping in uploaded files which will not match the links in uploaded html files any more. This should not be very common I hope.

Code backwards compatibility

Other Moodle code (for example plugins) will have to be converted to the new APIs. See Using_the_file_API for guidance.

It is not possible to provide backwards-compatibility here. For example, the old $CFG->dataroot/$courseid/ will no longer exist, and there is no way to emulate that, so we won't try.


Upgrade and migration

When a site is upgraded to Moodle 2.0, all the files in moodledata will have to be migrated. This is going to be a pain, like DML/DDL was :-(

The upgrade process should be interruptible (like the Unicode upgrade was) so it can be stopped/restarted any time.

Migration of content

  • resources - move files to new resource content file area; can be done automatically for pdf, image resources; definitely not accurate for uploaded web pages
  • questions - image file moved to new area, image tag appended to questions
  • moddata files - the easiest part, just move to new storage
  • coursefiles - there might be many outdated files :-( :-(
  • rss feeds links in readers - will be broken, the new security related code would break it anyway

Moving files to files table and file pool

The migration process must be interruptable because it might take a very long time. The files would be moved from old location, the restarting would be straightforward.

Proposed stages:

  1. migration of all course files except moddata - finish marked by some $CFG->files_migrated=true; - this step breaks the old file manager and html editor integration
  2. migration of blog attachments
  3. migration of question files
  4. migration of moddata files - each module is responsible to copy data from converted coursefiles or directly from moddata which is not converted automatically

Some people use symbolic links in coursefiles - we must make sure that those will be copied to new storage in both places, though they can not be linked any more - anybody wanting to have content synced will need to move the files to some repository and set up the sync again.

Talked about a double task here, when migrating course files to module areas:
  1. Parse html files to detect all the dependencies and move them together.
  2. Fallback in pluginfile.php so, if something isn't found in module filearea, search for it in course filearea, copying it and finally, serving it.
Also we talked about the possibility of add a new setting to resource in order to define if it should work against old coursefiles or new autocontained file areas. Migrated resources will point to old coursefiles while new ones will enforce autocontained file areas.
it seems that only resource files will be really complex (because allow arbitrary HTML inclusion). The rest (labels, intros... doesn't) and should be easier to parse.
Eloy Lafuente (stronk7) 19:00, 29 June 2008 (CDT)


Parts of Moodle that need to be modified

Of course, all parts of Moodle that use files need to be changed. See MDL-14589 for a more detailed list.

Any form where the user can upload files

Any from containing a HTML editor

Since users can embed images etc. there.

Backup/restore

File handling in backups needs to be fully rewritten - list of files in xml + pool of sha1 named files with contents. This solves the utf-8 trouble here, yay!!

Antivirus scanning

Issues that need to be resolved

Unicode support in zip format

This has now been solved by using the built in zip in PHP 5.2.8.

Zip format is an old standard for compressing files. It was created long before Unicode existed, and Unicode support was only recently added. There are several ways used for encoding of non-ASCII characters in path names, but unfortunately it is not very standardised. Most Windows packers use DOS encoding.

Client software:

  • Windows built-in compression - bundled with Windows, non-standard DOS encoding only
  • WinZip - shareware, Unicode option (since v11.2)
  • TotalCommander - shareware, single byte(DOS) encoding only
  • 7-Zip - free, Unicode or DOS encoding depending on characters used in file name (since v4.58beta)
  • Info-ZIP - free, uses some weird character set conversions

PHP extraction:

  • Info-ZIP binary execution - no Unicode support at all, mangles character sets in file names (depends on OS, see docs), files must be copied to temp directory before compression and after extraction
  • PclZip PHP library - reads single byte encoded names only, problems with random problems and higher memory usage.
  • Zip PHP extension - reads single byte encoded names only, 64bit operating system can not open/create archives with more than 500 files (depends on sum of lengths of all filenames and directories, to be fixed in PHP 5.3 and external PECL library, no PHP 5.2.x backport planned!), adding of files is limited by number of free file handles (around 1000 - depends on OS and other PHP code, workaround is to close and reopen archive)

Large file support: PHP running under 32bit operating systems does not support files >2GB (do not expect fix before PHP 6). This might be a potential problem for larger backups.

Tar Alternative:

  • tar with gzip compression - easy to implement in PHP + zlib extension (PclTar, Tar from PEAR or custom code)
  • no problem with unicode in *nix, Windows again expects DOS encoding :-(
  • seems suitable for backup/restore - yay!

Roadmap:

  1. add zip processing class that fully hides the underlying library
  2. use single byte encoding "garbage in/garbage out" approach for encoding of files in zip archives; add new 'zipencoding' string into lang packs (ex: cp852 DOS charset for Czech locale) and use it during extraction, we might support true unicode later when PHP Zip extension does that


Possible future ideas

Files maintenance report

This would do deep validation of the files on disc. It would:

  • Report when there is a row in the files table, but the corresponding file is missing from the file system.
  • Report files that still exist on disc, even though no references remain.
  • Report orphaned files.
  • Report total disc space usage by context (as much as is possible with content-addressible file storage).
  • Verify that the SHA1 hash of the contents of each file matches the file name.

In addition, it might offer options to fix these problems, where possible.

Support quotas per user, course, etc.

People want this.

If we have implemented the above report, it would then just be a matter of adding the interface hooks to stop people uploading more files once the quota has been reached.

(We could also divide the file size by number of instances that are using it, this might be considered more accurate in some scenarios.)


See also

Template:CategoryDeveloper ru:Development:File_API