Moodle API文档 —— File API

概述

Moodle File API中介绍了Moodle管理文件的相关内容。如果你对该API的内部运行机制感兴趣,我们会在接下来的文档中介绍。本文档仅介绍File API的使用。和本API相关的还有Repository API,它介绍了用户如何从moodle中获取文件。

文件区

文件被存储在一个被称为文件区(File Areas)的结构中。一个文件由以下内容唯一标识:

  • 上下文id(context id
  • 完整的组件名(component name)(使用Frankenstyle,在之后的文档中会介绍),例如’course’, ‘mod_forum’, ‘mod_glossary’, ‘block_html’。
  • 文件区类型(file area type),例如’intro’,’post’。
  • 一个唯一的项目id(itemid),通常项目id和一些依赖于文件区类型的东西相关。

Moodle没有单独为文件区开辟空间来进行存储,它被隐式的存储在数据库的files表中。每一个子系统都只能访问它自己的文件区。例如,只有位于/mod/assignment/*目录下的文件可以访问组件名为mod_assignment的文件。

文件区的命名

Moodle对于文件区的命名规范并没有严格的定义,但是我们强烈推荐来使用常见的单数名词来为文件区命名。(intro, post, attachment, description, …)

向用户提供文件

在使用文件时,你必须提供包括文件服务脚本(file serving script)在内的文件url,文件服务脚本通常为pluginfile.php。格式如下:

1
$url = $CFG->wwwroot/pluginfile.php/$contextid/$component/$filearea/arbitrary/extra/infomation.ext

例如:

1
$url = $CFG->wwwroot/pluginfile.php/$forumcontextid/mod_forum/post/$postid/image.jpg

不过,一般来说你没有必要使用这种直接写出url的方式。Moodle提供了名为moodle_url::make_pluginfile_url()的函数来获取文件的url。

1
$url = moodle_url::make_pluginfile_url($file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename());

注意:如果你不需要’itemid’,那么你需要给该参数传入null,此时在url中会忽略itemid的信息。在下面提到的回调函数中,你也需要注意这个问题。

文件服务脚本会根据相应的安全规范检查上下文id,组件名,文件区类型,并基于此提供文件服务。

注意:在多数情况下,开发者开发第三方你插件时,pluginfile.php会在合适的插件中寻找回调函数。这些函数定义在lib.php中,并且以component_name_pluginfile()的形式命名。arbitrary/extra/infomation.ext会被传给回调函数。例如,位于mod_forum+post文件区的文件通过调用位于mod/forum/lib.php文件下的mod_forum_pluginfile()函数来提供文件服务。开发者的第三方插件中,MYPLUGIN/lib.php文件下的相应函数应参照如下模式书写。当然,细节方面会由于相应插件的文件访问权限不同而有所区别。(例如,作业附件只能被教师访问,而学生只能提交文件。讨论区中的附件只有在打开相应的帖子时才能被访问)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function MYPLUGIN_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
// Check the contextlevel is as expected - if your plugin is a block, this becomes CONTEXT_BLOCK, etc.
if ($context->contextlevel != CONTEXT_MODULE) {
return false;
}
// Make sure the filearea is one of those used by the plugin.
if ($filearea !== 'expectedfilearea' && $filearea !== 'anotherexpectedfilearea') {
return false;
}
// Make sure the user is logged in and has access to the module (plugins that are not course modules should leave out the 'cm' part).
require_login($course, true, $cm);
// Check the relevant capabilities - these may vary depending on the filearea being accessed.
if (!has_capability('mod/MYPLUGIN:view', $context)) {
return false;
}
// Leave this line out if you set the itemid to null in make_pluginfile_url (set $itemid to 0 instead).
$itemid = array_shift($args); // The first item in the $args array.
// Use the itemid to retrieve any relevant data records and perform any security checks to see if the
// user really does have access to the file in question.
// Extract the filename / filepath from the $args array.
$filename = array_pop($args); // The last item in the $args array.
if (!$args) {
$filepath = '/'; // $args is empty => the path is '/'
} else {
$filepath = '/'.implode('/', $args).'/'; // $args contains elements of the filepath
}
// Retrieve the file from the Files API.
$fs = get_file_storage();
$file = $fs->get_file($context->id, 'mod_MYPLUGIN', $filearea, $itemid, $filepath, $filename);
if (!$file) {
return false; // The file does not exist.
}
// We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
// From Moodle 2.3, use send_stored_file instead.
send_file($file, 86400, 0, $forcedownload, $options);
}

你可以使用一个API函数来自动生成这些url,通常为file_rewrite_pluginfile_urls()函数。

获取用户提交的文件

在之后的文档中会有介绍

代码范例

需要注意的是,开发者们在不触及到moodle核心的情况下,通常不会直接使用文件API,表单元素(formslib elements)会自动帮助开发者解决这些问题。

浏览文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$browser = get_file_browser();
$context = get_system_context();
$filearea = null;
$itemid = null;
$filename = null;
if ($fileinfo = $browser->get_file_info($context, $component, $filearea, $itemid, '/', $filename)) {
// build a Breadcrumb trail
$level = $fileinfo->get_parent();
while ($level) {
$path[] = array('name'=>$level->get_visible_name());
$level = $level->get_parent();
}
$path = array_reverse($path);
$children = $fileinfo->get_children();
foreach ($children as $child) {
if ($child->is_directory()) {
echo $child->get_visible_name();
// display contextid, itemid, component, filepath and filename
var_dump($child->get_params());
}
}
}

移动文件

例如,如果你刚刚用如下路径创建了一个文件

1
2
$from_zip_file = $CFG->dataroot . '/temp/backup/' . $preferences->backup_unique_code .
'/' . $preferences->backup_name;

而你希望将它移动到course_backup文件区中,那么只需要

1
2
3
4
5
6
$context = get_context_instance(CONTEXT_COURSE, $preferences->backup_course);
$fs = get_file_storage();
$file_record = array('contextid'=>$context->id, 'component'=>'course', 'filearea'=>'backup',
'itemid'=>0, 'filepath'=>'/', 'filename'=>$preferences->backup_name,
'timecreated'=>time(), 'timemodified'=>time());
$fs->create_file_from_pathname($file_record, $from_zip_file);

列出文件区的文件

1
2
3
4
5
6
$fs = get_file_storage();
$files = $fs->get_area_files($contextid, 'mod_assignment', 'submission', $submission->id);
foreach ($files as $f) {
// $f is an instance of stored_file
echo $f->get_filename();
}

或以链接的方式列出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$out = array();
$fs = get_file_storage();
$files = $fs->get_area_files($contextid, 'mod_assignment', 'submission', $submission->id);
foreach ($files as $file) {
$filename = $file->get_filename();
$url = moodle_url::make_file_url('/pluginfile.php', array($file->get_contextid(), 'mod_assignment', 'submission',
$file->get_itemid(), $file->get_filepath(), $filename));
$out[] = html_writer::link($url, $filename);
}
$br = html_writer::empty_tag('br');
return implode($br, $out);

创建文件

下面给出了创建包含制定文本的文件的方式。这种方式和使用PHP函数file_put_contents是等价的。

1
2
3
4
5
6
7
8
9
10
11
12
13
$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');

如果你想基于一个真实的文件在moodle系统中创建文件,你可以使用create_file_from_pathname()函数。同样的,你也可以使用create_file_from_storedfile()函数来创建已经存在于moodle本地文件中的文件。一些细节可以浏览lib/filestorage/file_storage.php

和普通方法不同,这种方法不会覆盖掉已经存在的文件,如果你希望覆盖掉它,你只有先将其删除(如果存在的话)再重新创建该文件。

读取文件

下面给出了一种读取文件的方式,这和file_get_contents()函数是等价的。

注意:你只能在位于mod/mymodule/*目录下的代码中使用这种方法,其他代码中应使用file_browser来读取文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$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
}

你不能直接访问位于磁盘中的文件,而应在临时文件区中创建该文件的备份,之后再访问该文件。创建备份可以使用$file->copy_content_to($pathname)

删除文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$fs = get_file_storage();
// Prepare file record object
$fileinfo = array(
'component' => 'mod_mymodule',
'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']);
// Delete it if it exists
if ($file) {
$file->delete();
}