第13章 Drupal文件

Drupal中,可以使用多种方式来上传和下载文件。在本章中,我们将讲述什么是公共和私有文件,以及如何提供它们,简要的介绍多媒体文件的处理,并学习一下Drupal的文件认证钩子。

 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal是如何提供文件的

Drupal提供了两种相互排斥的模式,用来管理文件下载的安全性:公共模式和私有模式。在私有模式下,在请求一个下载文件时将会检查用户的权限,如果用户不具有相应的访问权限,那么下载将被拒绝。在公共模式下,任何可以访问文件URL的用户都可以下载文件。这一设置将应用于整个站点,而不是应用于一个模块或者一个文件,所以通常在初始设立站点期间就做出选择,到底是使用私有模式还是使用公共模式,这一设置将会影响到使用了Drupal文件API的所有模块。

 
警告 由于公共和私有文件的存储方法会为文件下载生成不同的URL,所以在你开始上传文件以前,你需要为你的站点做出最佳选择,在以后要一直坚持使用你选的方法,这一点很重要。
 
    为了设立文件系统路径,并指定使用哪种下载方法,导航到“管理➤站点配置➤文件系统”。
 
    如图13-1所示,如果你指定的目录不存在,或者如果PHP对该目录没有写权限,那么Drupal将会给你警告。
 
图13-1 在Drupal中,用来指定文件相关设置的界面。在这里,Drupal警告了----指定的文件系统路径不具有合适的权限;文件系统路径指定的目录必须已经存在并且具有合适的权限。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal公共文件

最简单的配置就是公共文件下载方法,此时Drupal不参与下载流程。在文件被上传时,Drupal简单的将它们保存到了你在“管理➤站点配置➤文件系统”中所指定的目录,并在数据库中追踪文件的URL(这样Drupal就知道有哪些文件可用,谁上传的,等等)。当一个文件被请求时,它将作为一个静态文件通过HTTP被Web服务器直接传递给用户, Drupal一点也没有参与这一流程。由于不需要执行PHP代码,所以这种方式的特点就是非常的快。然而,这里没有检查用户的权限。

 
    当指定文件系统路径时,该文件夹必须存在并且允许PHP对其可写。一般情况下运行Web服务器的用户(在操作系统上)也就是运行PHP的用户。因此,授予该用户对files文件夹的写权限,将允许Drupal上传文件。这些完成以后,一定要在“管理➤站点配置➤文件系统”中指定文件系统路径。一旦保存这些修改,Drupal将在你的files文件夹中自动的创建一个.htaccess文件。这一点是必要的,它可用来保护你的服务器,以避免一个已知Apache安全漏洞----用户可以上传文件并执行嵌入在上传文件中的脚本(参看http://drupal.org/node/66763)。检查以确保你的files.htaccess文件,里面包含以下信息:文件夹下面包含一个
 
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
Options None
Options +FollowSymLinks
 
提示 当在一个Web服务器集群上运行Drupal时,临时文件目录的位置需要被所有的web服务器所共享。由于Drupal可以使用一个请求来上传文件,使用第二个请求将它的状态从临时的改为持久的。许多负载均衡方案会把临时文件放在一个服务器上,而第二个请求则转到另一个服务器上。当出现这种情况时,文件在上传时看起来是正确的,但是它们将不会显示在它们要添加到的节点或内容中。确保你的所有web服务器共享一个temp目录,并使用一个基于会话的负载均衡器。你的文件目录,和你的数据库一样,对于你的web服务器来讲应该是全局的。
 

Drupal版本:

drupal私有文件

 

在私有下载模式下,files文件夹可以放在PHP可读可写的任何地方,而且不需要(大多数时候不应该)能被web服务器本身直接访问。
 
    drupal私有文件的安全性是有性能成本的。在这里,没有将提供文件服务的工作委托给web服务器,而是Drupal负责检查访问权限和分发文件,Drupal使用完整的引导指令来处理每个文件请求。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

PHP设置

老葛的Drupal培训班 Think in Drupal

php.ini中的一些设置常被忽略,但它们对于文件上传却很重要。第一个就是post_max_size,它位于php.ini中“Data Handling”部分的下面。由于文件的上传是通过一个HTTP POST请求完成的,如果尝试上传的文件的大小比post_max_size还大,这样要发送的POST数据的总大小就超过了post_max_size,因此上传将会失败。
 
; Maximum size of POST data that PHP will accept.
post_max_size = 8M
 
    php.ini里面的“File Uploads”部分中,包含了一些更重要的设置。在这里,你可以设置是否允许上传文件,上传文件的大小的上限:
 
;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;
 
; Whether to allow HTTP file uploads.
file_uploads = On
 
; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
;upload_tmp_dir =
 
; Maximum allowed size for uploaded files.
upload_max_filesize = 20M
 
    如果文件上传失败了,那么你需要检查一下是不是由于这些设置所引起的。还有,注意upload_max_filesize应该小于post_max_size,而post_max_size应该小于memory_limit:
 
upload_max_filesize < post_max_size < memory_limit
 
    你需要注意的最后两个设置是max_execution_time和max_input_time。在上传一个文件时,如果你的脚本执行时间超过了这些设置,那么PHP将终止你的脚本。在你的网络连接比较慢时,如果上传失败,那么你就需要检查一下这些设置。
 
;;;;;;;;;;;;;;;;;;;
; Resource Limits ;
;;;;;;;;;;;;;;;;;;;
 
max_execution_time = 60     ; Maximum execution time of each script, in seconds
                        ; xdebug uses this, so set it very high for debugging
max_input_time = 60         ; Maximum amount of time each script may spend
                        ; parsing request data
 
    在调试的时候,你可能想把max_execution_time的值设置的大一点(例如,1600),这样调试器就不会超时。记住,然而,如果你的服务器非常繁忙,那么文件上传的时间过长,就可能妨碍Apache的进程,从而产生潜在的可升级性问题。

Drupal版本:

drupal多媒体处理

 

文件API(位于includes/file.inc)没有为上传文件提供一个通用的用户界面。为了为大多数终端用户填补这一空白,在Drupal核心中带有了upload.module,而且有多个第3方模块提供了备选方案。
 
上传模块
    上传模块为你选择的节点类型添加了一个上传字段。上传字段如图13-2所示。
13-2.当启用了上传模块并且用户具有“上传文件”权限时,在节点表单中添加了一个“附件”字段。
 
    在节点编辑表单上,上传一个文件以后,upload.module将在节点正文下面为其添加下载链接。拥有“查看已上传文件”权限的用户可以看到这些链接,如图13-3所示。
13-3. 使用核心上传模块为一个节点上传文件后,得到的一个通用的列表视图。
 
    这一通用解决方案对于大多数用户来说可能并不健壮,所以在接下来的一节中,让我们看一些更特殊的例子。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

其它的通用文件处理模块

 

对于文件上传, upload.module的替代模块可参看http://drupal.org/project/Modules/category/62。文件上传的另一选择是,使用CCK模块加上一个第3方的文件处理字段模块,比如imagefield 或 filefield。关于CCK字段类型的更多详细,可参看http://drupal.org/taxonomy/term/88
 
图片和相册
    需要创建一个相册?那么图片模块(http://drupal.org/project/image)是个不错的选择。它能够处理图片的调整大小和创建相册。当使用CCK在节点内部展示图片时,此时也有一些不错的解决方案。Imagecache (http://drupal.org/project/imagecache)能够处理图片衍生物的创建(上传图片的附加的改进拷贝,比如一个缩略图),而imagefield (http://drupal.org/project/imagefield)可以在节点表单中创建一个图片上传字段。
 
视频和音频
   有许多模块,可用来管理多媒体文件,比如视频文件、Flash内容、幻灯放映、等等,大家可以参看http://drupal.org/project/Modules/category/67

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal文件API

老葛的Drupal培训班 Think in Drupal

文件API位于includes/file.inc中。在本节中,我们将介绍一些常用函数。更多的详细,感兴趣的读者可直接通过API文档学习当前的文件API http://api.drupal.org/api/6/group/file/6
 
数据库模式
    尽管Drupal将文件存放在磁盘上,但它仍然使用数据库来存储文件的一些合理的元数据。除了上传者、MIME类型、位置,它还为已上传文件维护了修订信息。files表的模式,如表13-1所示:
 
13-1. files表
字段*      类型           默认值 描述
fid        serial                  主键
uid         int             0       与文件相联的用户的ID
filename    varchar(255)    ''      文件的名字
filepath    varchar(255)    ''      文件的路径,这里相对于Drupal的根目录
filemime    varchar(255)    ''      文件的MIME类型
filesize   int             0       文件的大小,以字节为单位
status      int             0       一个标记,用来指示文件是临时的(1)或是持久的(0)
timestamp  int             0       一个Unix时间戳,用来指示文件的添加时间
*粗体指示一个主键,斜体指示一个索引字段
 
    启用文件管理的模块,使用它们自己的数据库表来保存它们自己的数据。例如,由于上传模块将文件与节点关联了起来,所以它在upload表中追踪了这一信息。核心上传模块的数据库表的模式,可参看表13-2。
 
13-2.上传模块使用的upload表
字段*         类型       默认值 描述
fid           int         0       主键(在files表中,文件的fid)
nid             int         0       与已上传文件相联的nid
vid           int         0       与已上传文件相联的节点修订本ID
description     varchar(255)''      已上传文件的描述
list            int         0       一个标记,用来指示文件在节点中是否列出,列出(1)                                   或不列出(0)
weight          int         0       这个上传文件的重量,相对于该节点的其它上传文件
*粗体指示一个主键,斜体指示一个索引字段
 

Drupal版本:

常用任务和函数

 

如果你想对一个文件做些处理的话,那么文件API已经提供了许多方便的函数供你使用。让我们看一些比较常用的函数。
 
查找文件系统路径
    文件系统路径,就是Drupal可以写文件的目录的路径,比如用来上传文件。这个目录在Drupal的管理界面“管理➤站点配置➤文件系统”中被称为“文件系统路径”,它对应于Drupal变量file_directory_path。
 
file_directory_path()
    这个函数实际就是对variable_get('file_directory_path', conf_path().'/files')作了简单包装。在新的Drupal安装中,它的返回值为sites/default/files。
 
向一个文件保存数据
    有时,你只想把数据保存在一个文件中。下面的这个函数可以实现这一点。
 
file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME)
    $data参数将变成文件的内容。$dest参数是目的文件的文件路径。$replace用来判定目的文件已存在时Drupal的行为。可能值如表13-3所示。
 
13-3.当目标文件已存在时,用来判定Drupal行为的常量
名字                 含义
FILE_EXISTS_REPLACE     使用当前文件替代已有文件
FILE_EXISTS_RENAME      添加一个下划线和一个整数来保证新文件名的唯一性
FILE_EXISTS_ERROR       中止并返回FALSE,
 
    下面是一个简单示例,它将一个简短的字符串保存在了一个文件中,而该文件位于Drupal的文件系统目录里面:
 
$filename = 'myfile.txt';
$dest= file_directory_path() .'/'. $filename;
file_save_data('My data', $dest);
 
    这个文件的位置将如同sites/default/files/myfile.txt一样,它里面包含字符串My data
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

复制和移动文件

 

下面的函数可以帮你处理文件系统中已有的文件。
 
file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME)
    file_copy()函数用来将文件复制到Drupal的文件系统路径下(一般为sites/default/files)。$source参数是一个字符串,用来指定原始文件的位置,可以在函数中还处理了一个文件对象,后者具有属性$source->filepath和可选属性$source->filename(例如上传模块使用了一个文件对象)。注意,由于$source参数是通过引用传递的,所以它必须是一个变量,而不是一个字面上的字符串。列表13-1和13-2显示了一个正被复制到Drupal的默认文件目录中的文件(也就是,没有提供目的文件$dest),前一个为错误的,后一个为正确的。
 
列表 13-1.错误方式:将文件复制到Drupal的默认文件目录(一个字符串无法通过引用传递)
 
file_copy('/path/to/file.pdf');
 
列表 13-2.正确方式:将文件复制到Drupal的默认文件目录
$source = '/path/to/file.pdf';
file_copy($source);
 
    $dest参数是一个字符串,用来指定新复制的文件在Drupal的文件系统路径中目的地。如果没有指定$dest参数,那么将使用文件系统路径。如果$dest位于Drupal的文件系统路径以外(Drupal的临时目录除外),或者如果文件系统路径指定的目录不可写,那么复制将会失败。
    $replace参数用来判定目的文件已存在时Drupal的行为。表13-3总结了$replace参数可用的常量。
 
file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME)
    file_move()函数和file_copy()函数类似(实际上,它调用file_copy()),但是它还会调用file_delete()来删除原始文件。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

检查目录、路径、位置

 

当你使用文件时,你常常需要停下来判定一下一切是否就绪。例如,一个目录可能并不存在或者不可写。下面的函数将帮你解决这类问题。
 
file_create_path($dest = 0)
    这个函数用来获取Drupal的文件系统路径中项目的路径。例如,当启用CSS优化时,Drupal将创建一个css子目录用来存放聚合压缩的CSS文件,它是这样实现的:
 
// Create the css/ within the files folder.
$csspath = file_create_path('css');
file_check_directory($csspath, FILE_CREATE_DIRECTORY);
 
    一些例子,如下所示:
 
$path = file_create_path('foo'); // returns 'sites/default/files/foo'
$path = file_create_path('foo.txt'); // returns 'sites/default/files/foo.txt'
$path = file_create_path('sites/default/files/bar/baz')
// returns 'sites/default/files/bar/baz'
 
$path = file_create_path('/usr/local/') // returns FALSE
 
file_check_directory(&$directory, $mode = 0, $form_item = NULL)
    这个函数检查给定目录的存在和可写。$directory参数是一个目录的路径,由于它是通过引用传递的,所以它必须作为一个变量传递过来。$mode参数是用来判定,当目录不存在或不可写的时候,Drupal应该做什么。表13-4给出了可用的模式。
 
13-4.file_check_directory()的$mode参数的可能值
                      含义
0                           如果目录不存在,不创建目录
FILE_CREATE_DIRECTORY       如果目录不存在,创建目录
FILE_MODIFY_PERMISSIONS     如果目录不存在,创建目录。如果目录已存在,尝试使它可写。
 
    $form_item参数是表单项目的名字,当目录创建失败时,可对其设置错误消息。$form_item参数是可选的。
    这个函数还会测试,正被检查的目录是不是文件系统路径或者临时目录,如果是的话,出于安全性向其添加一个.htaccess文件(参看第20章)。
 
file_check_path(&$path)
    如果你有一个文件路径,你想把它拆分成文件名和基名字,那么可以使用file_check_path()。$path参数必须是一个变量;该变量将被修改为仅包含基名字。这有一些例子:
 
$path = 'sites/default/files/foo.txt';
$filename = file_check_path($path);
 
现在$path为sites/default/files,而$filename为foo.txt。
 
$path = 'sites/default/files/css'; // Where Drupal stores optimized CSS files.
$filename = file_check_path($path);
 
现在$path为sites/default/files;如果css目录不存在,那么$filename为css,否则,$filename为空。
 
$path = '/etc/bar/baz.pdf';
$filename = file_check_path($path);
 
由于/etc/bar不存在或者不可写,所以$path现在为/etc/bar,而$filename现在为FALSE。
 
file_check_location($source, $directory = ‘’)
    有时候你有一个文件路径,但是你却不信任它。可能一个用户输入了它,想使用黑客技巧来获取站点的一些内部信息。(例如,提供了一个files/../../../etc/passwd,而不是一个有效的文件名)。“这个文件真的位于这个目录下面吗?”调用这个函数就可以回答这个问题。例如,如果文件的实际位置不在Drupal的文件系统路径下面,那么将会返回0:
 
$real_path = file_check_location($path, file_directory_path());
 
    如果文件位于Drupal的文件系统路径下面,那么将会返回文件的实际路径。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

上传文件

 

尽管上传模块提供了一个完整的实现,用来为节点上传文件;但是有时候,你不想把上传的文件与节点关联起来。下面的函数可帮你实现这一点。
 
file_save_upload($source, $validators = array(), $dest = FALSE,
$replace = FILE_EXISTS_RENAME)
    $source参数用来告诉函数哪个已上传的文件将被保存。$source对应于web表单中的文件输入字段的名字。例如,如果在“管理➤用户管理➤用户设置”中启用了头像图片支持,那么在“我的帐户”页面的表单上,就会有一个文件字段用来允许你上传自己的图片,该字段的名字就是picture_upload。显示在浏览器中的表单,如图13-4所示。当用户点击保存按钮时,将会得到$_FILES全局变量,如图13-5所示。注意,$_FILES中的信息是以表单的文件字段的名字为键的(这样,就可以在单个表单中支持多个文件字段了)。全局变量$_FILES是由PHP本身定义的,而不是由Drupal。
 
13-4.表单元素的文件字段,它显示在“我的帐户”页面
13-5.HTTP POST之后,得到的$_FILES全局变量
 
    $validators参数是一个数组,里面包含了成功文件上传后所要调用的函数的名字。例如,user_validate_picture()函数,当用户编辑了他/她的“我的帐户”页面以后将调用这个表单验证函数,这个函数在调用file_save_upload()以前添加了3个验证器。如果需要向验证器函数中传递参数,那么可将参数定义在后面的数组中。例如,在下面的代码中,当验证器运行时,对file_validate_image_resolution()的调用应该像file_validate_image_resolution('85x85')一样:
 
/**
 * Validates uploaded picture on user account page.
 */
function user_validate_picture(&$form, &$form_state) {
    $validators = array(
        'file_validate_is_image' => array(),
        'file_validate_image_resolution' =>
            array(variable_get('user_picture_dimensions', '85x85')),
        'file_validate_size' => array(variable_get('user_picture_file_size', '30')
            * 1024),
    );
    if ($file = file_save_upload('picture_upload', $validators)) {
        ...
    }
    ...
}
 
    file_save_upload()函数中的$dest参数是可选的,它包含的是文件将被复制到的目录。例如,在处理把文件附加在一个节点上时,上传模块使用file_directory_path()(默认为sites/default/files)作为$dest的值(参看图13-6)。如果没有提供$dest,那么将使用临时目录。
    $replace参数用来定义,在一个同名文件已存在时,Drupal应该做什么。可能值如表13-3所示。
 
13-6.文件对象已存在,当它传递给file_save_upload()的验证器时的情景
 
    file_save_upload()的返回值是一个包含了完整属性的文件对象(如图13-7所示);如果有地方出错的话,那么将返回0。
 
13-7.成功调用file_save_upload()以后,返回的文件对象
 
    在调用了file_save_upload()以后,在Drupal的临时目录中新增了一个文件,同时向files表中写入了一条新纪录。该纪录包含的值与如图13-7所示的文件对象相同。
 
    注意状态字段被设置为了0。这意味着到目前为止,在Drupal看来,这个仍然是一个临时文件。调用者需要负责将该文件改为持久的。继续使用我们的上传一个用户头像这个例子,我们看到用户模块负责将这个文件复制到了Drupal的user_picture_path变量所定义的目录中,并使用用户的ID对其重命名:
 
// The image was saved using file_save_upload() and was added to the
// files table as a temporary file. We'll make a copy and let the garbage
// collector delete the original upload.
$info = image_get_info($file->filepath);
$destination = variable_get('user_picture_path', 'pictures') .
'/picture-'. $form['#uid'] .'.'. $info['extension'];
file_copy($file, $destination, FILE_EXISTS_REPLACE));
...
 
    这将已上传的图片移到了sites/default/files/pictures/picture-2.jpg。
    在前面的代码注释中所提到的垃圾收集器,用来清理临时目录中的过期的临时文件。对于每个临时文件,在files表中都为其保存了一条状态字段为0的纪录,所以Drupal知道需要清理哪些文件。垃圾收集器位于modules/system/system.module的system_cron()函数中。它将删除那些过期文件,这里的过期指的是超过了常量DRUPAL_MAXIMUM_TEMP_FILE_AGE所指定的秒数。该常量的值为1440秒,也就是24分钟。

    如果提供了$dest参数,并且文件被移动到了它的最终位置,来代替原来的临时目录,那么调用者可以通过调用file_set_status(&$file, $status)将files表中纪录的状态修改为持久的,这里面$file被设置为一个完整的文件对象(如图13-7所示),$status被设置为FILE_STATUS_PERMANENT。依照includes/file.inc,如果你想在你的模块中使用额外的状态常量的话,那么你必须从256开始,因为0, 1, 2, 4, 8, 16, 32, 64, 和128是为核心保留的。

 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

file_save_upload()中可用的验证函数如下所示。

老葛的Drupal培训班 Think in Drupal

file_save_upload()中可用的验证函数如下所示。
 
file_validate_extensions($file, $extensions)
    $file参数是一个文件的名字。$extensions参数是一个字符串,里面包含了使用空格定界的文件扩展名。如果文件的扩展名被允许的话,那么函数将返回一个空数组;如果文件的扩展名不被允许的话,那么函数将返回一个包含错误消息的数组,错误消息通常为只允许使用以下扩展名的文件:jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp。这个函数是一个可用于file_save_upload()的验证器。
 
file_validate_is_image(&$file)
    这个函数获得一个文件对象,并尝试将$file->filepath传递给image_get_info()。如果image_get_info()可以从该文件提取信息的话,那么这个函数将返回一个空数组;如果处理失败的话,那么将返回一个包含错误消息的数组,错误消息通常为仅允许JPEG,PNG和GIF图片。这个函数是一个可用于file_save_upload()的验证器。
 
file_validate_image_resolution(&$file, $maximum_dimensions = 0,
$minimum_dimensions = 0)
    这个函数获得一个文件对象,并在多个操作中使用$file->filepath。如果文件是一个图片的话,那么这个函数将检查该图片是否超过了$maximum_dimensions,如果可能的话将尝试调整它的大小。如果一切正常,那么将返回一个空数组;而$file对象,由于它是通过引用传递的,如果图片的大小被调整了,那么它的$file->filesize将被设置为新的大小。否则,数组将包含一个错误消息,比如该图片太小了;最小尺寸为320x240像素。$maximum_dimensions和$minimum_dimensions就是由“宽”+ “x”+ “高”构成的字符串,(例如,640x480或85x85)这里的“宽”“高”都是像素数。默认值0指示在大小上没有限制。这个函数是一个可用于file_save_upload()的验证器。
 
file_validate_name_length($file)
    $file参数是一个文件对象。如果$file->filename没有超过255字符,那么它返回一个空数组。否则它返回一个包含错误消息的数组,来指示用户使用一个短一点的名字。这个函数是一个可用于file_save_upload()的验证器。
 
file_validate_size($file, $file_limit = 0, $user_limit = 0)
    这个函数检查一个文件的大小低于文件的上限,或者一个用户的累积上限。$file参数是一个文件对象,它必须包含$file->filesize,它是以字节为单位的文件大小。$file_limit参数是一个整数,表示单位的最大字节数。$user_limit参数是一个整数,表示当前用户允许使用的最大字节数。0意味着“没有限制”。如果验证通过,那么将返回一个空数组;否则将返回一个包含错误消息的数组。这个函数是一个可用于file_save_upload()的验证器。

Drupal版本:

为一个文件获取URL

 

如果你知道一个已上传的文件的名字,并想告诉客户该文件的URL,下面的函数将会有用。
 
file_create_url($path)
    不管Drupal是运行在公共下载模式,还是运行在私有下载模式,这个函数都将为一个文件返回正确的URL。$path参数是指向文件的路径(例如,sites/default/files/pictures/picture-1.jpg 或pictures/picture-1.jpg)。生成的URL将会是http://example.com/sites/default/files/pictures/picture-1.jpg。注意这里没有使用文件的绝对路径名字。这样在不同位置(服务器)之间迁移Drupal站点时,会方便一些。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

在一个目录下查找文件

 

Drupal提供了一个功能强大的函数file_scan_directory()。它浏览一个目录,从中查找匹配给定模式的文件。
 
file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0)
 
让我们简要的学习一下这个函数签名:
 
• $dir是进行搜索的目录的路径。不要在结尾处包含符号“/”。
 
• $mask是一个模式,用来应用于目录中所包含的文件。它是一个正则表达式。
 
• $nomask是一个正则表达式数组。任何匹配$nomask模式的东西都将被忽略。默认数组包含.(当前目录), .. (父目录), 和CVS。
 
• $callback为每个匹配所调用的函数的名字。将向回调函数传递一个参数:文件的路径。
 
• $recurse是一个布尔值,用来指示搜索是否递归到子目录中去。
 
• $key用来决定为file_scan_directory()返回的数组使用什么键。可能的值有filename (匹配的文件的完整路径), basename (filename without path 不带路径的文件名字), 和name (filename without path and without file suffix不带文件路径和后缀的文件名字)。
 
• $min_depth是能够从中返回文件的目录的最小深度。
 
    返回值是一个包含对象的关联数组。数组的键取决于$key参数的值,默认为filename。下面是一些例子。
    扫描themes/bluemarine目录,查找以.css结尾的任意文件:
 
$found = file_scan_directory('themes/bluemarine', '\.css$');
 
    生成的包含对象的数组如图13-8所示。
 
13-8. file_scan_directory()返回的默认结果是一个包含对象的数组,其中以完整的文件名为键
 
    将$key参数修改为basename将改变结果数组的键,如下面的代码和图13-9所示。
 
$found = file_scan_directory('themes/bluemarine', '\.css$', array('.', '..', 'CVS'),
0, TRUE, 'basename');
 
13-9.现在的结果是以文件名为键,原有的完整文件路径被省略了
 
    $callback参数的使用,可使得Drupal方便的清空最优化CSS文件缓存,后者通常位于sites/default/files/css。drupal_clear_css_cache()函数使用file_delete作为回调:
 
file_scan_directory(file_create_path('css'), '.*', array('.', '..', 'CVS'),
'file_delete', TRUE);

老葛的Drupal培训班 Think in Drupal

Drupal版本:

查找临时目录

 

下面的函数用于报告临时目录的位置,通常称为“temp”目录。
 
file_directory_temp()
    这个函数首先检查Drupal变量file_directory_temp。如果该变量没有设置,那么对于Unix,它将查找/tmp目录;而对于Windows,它将查找c:\\windows\temp和c:\\winnt\temp目录。如果这些都不成功,那么它将把临时目录设置为文件系统路径内部的名为tmp的目录(例如,sites/default/files/tmp)。它将返回临时目录的最终位置,并将file_directory_temp变量设为该值。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

中和危险文件

 

假定你使用的是公共文件下载方法,并且你启用了文件上传。那么当有人上传一个名为bad_exploit.php的文件时,会发生什么呢?当攻击者访问http://example.com/sites/default/files/bad_exploit.php时,它会不会执行?但愿不会,有3个原因。首先,在上传的文件设置的允许的扩展名列表中永远不会出现.php。其次,.htaccess文件应该放在sites/default/files/.htaccess中(参看第20章)。然而,在几个常见的Apache配置中,上传文件exploit.php.txt也可能导致把该文件中的代码作为PHP代码进行执行(参看http://drupal.org/files/sa-2006-007/advisory.txt)。这样就给我们带来了第3个原因:修改文件名字从而无害的呈现文件。作为防御上传可执行文件的一个手段,可以使用下面的函数。
 
file_munge_filename($filename, $extensions, $alerts = TRUE)
    $filename参数是要被修改的文件名字。$extensions是一个字符串,包含了使用空格定界的文件扩展名。$alerts参数是一个布尔值,默认为TRUE,通过使用drupal_set_message()来警告用户,该文件的名字已被修改。返回的是修改后的文件名,向里面插入了下划线来禁止潜在的执行。
 
$extensions = variable_get('upload_extensions_default', 'jpg jpeg gif png txt
doc xls pdf ppt pps odt ods odp');
$filename = file_munge_filename($filename, $extensions, FALSE);
 
$filename 现在为 exploit.php_.txt.
 
    通过在settings.php中将Drupal变量allow_insecure_uploads定义为1,你就可以阻止修改文件名了。但这通常是一个坏点子,因为它带来了安全隐患。
 
file_unmunge_filename($filename)
    这个函数尝试撤销file_munge_filename()的影响,它将“_.”替换为了“.”:
 
$original = file_unmunge_filename('exploit.php_.txt);
 
$original 现在为 exploit.php.txt.
 
    注意,如果在原始文件中,故意使用了“_.”,那么它也将被替换掉。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

检查磁盘空间

 

下面的函数用来报告文件所用的空间。
 
file_space_used($uid = NULL)
    这个函数用来返回文件使用的总的磁盘空间。它没有实际的去检查文件系统,而是使用数据库中files表的filesize字段的总计作为返回值。如果向这个函数传递了一个用户ID,那么对files表的查询将限定在匹配该用户ID的文件上。上传模块使用upload_space_used()对此函数作了简单的包装。由于只有当启用了上传模块时,才可以使用upload_space_used(),所以一般直接调用file_space_used()。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

用于下载的认证钩子

 

模块开发者可以通过实现hook_file_download(),来设置私有文件下载的访问权限。该钩子用于判定在什么条件下才把文件发送给浏览器,并为Drupal返回附加头部以追加到文件HTTP请求上。注意,如果你的Drupal安装使用的是公共文件下载设置,那么该钩子将不起任何作用。图13-10显示了下载流程的概览,这里以用户模块里面的hook_file_download()实现为例。
    由于对于每次下载,Drupal将触发所有实现了hook_file_download()钩子的模块,所以指定你钩子的范围就非常重要了。例如,以user_file_download()为例,只有当要下载的文件位于pictures目录时才响应文件下载。如果为真的话,它把头部信息添加到请求中。
 
function user_file_download($file) {
    $picture_path = variable_get('user_picture_path', 'pictures');
    if (strpos($file, $picture_path .'/picture-') === 0) {
        $info = image_get_info(file_create_path($file));
        return array('Content-type: '. $info['mime_type']);
    }
}
 
13-10.私有文件下载请求的生命周期
 
    如果请求被许可了,那么hook_file_download()实现就应该返回一个包含头部信息的数组;否则,返回-1表示拒绝了文件下载。如果没有模块响应这个钩子,那么Drupal将向浏览器返回一个404未找到错误信息。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

总结

 

在本章中,你学到了
• drupal公共和私有文件之间的区别
• 用于处理图片、视频、音频文件的drupal第3方模块
• 用于文件存储的数据库模式
• 操作文件的drupal常用函数
• 用于私有文件下载的认证钩子

老葛的Drupal培训班

Drupal版本:

中英文对照

 

munge:修改,混合,(译者注,这个词在我的金山词霸上没有,我在这里把它翻译为了修改,munge filename,修改文件名,这里的修改指的是把文件名中后缀部分的”.”号替换为“_.”。另外还有munge forms的用法,在本书中,指的是将两个表单定义数组混合(合并)在一起)
 
Public file的翻译
       我最初是翻译为公共文件,在重新翻译drupal6版的时候,使用简体中文包确认了一下,里面翻译为了“公开文件”,我觉得这两种翻译都挺好的,尽管我觉得我自己的翻译好一点,还是采用了“公开文件”。我查了金山词霸,发现里面是“公开的”意思,尽管也隐含了公开的意思。
   最终决定把它翻译为“公开”,把里面的替换了一遍,但是在翻译“public file download method”时,遇到了问题,如果翻译为“公开文件下载方法”的话,与原文出入太大,而使用“公共文件下载方法”则比较贴切。
   所以我又把“公开”改为“公共”。
   另外,我们都知道,有公共财产,私有财产之分,这里的公共财产的公共,英文就是public,而这里的私有就是private。两者是对应的。所以最后,尽管简体中文包里面使用了“公开”,我还是坚持使用了“公共”。
   我在翻译的时候,当一个词语的译法不确定的时候,尽可能使用金山词霸中现有的翻译,尽可能的采用简体中文包中的习惯译法,但是在确定简体中文包中的译文有问题时,或者说有不贴切的地方时,我坚定地把它修改了过来。
   比如vocabulary,这个词,在简体中文包中,有的地方被翻译为了“词汇表”,有的地方被翻译成了“术语表”,由于term一词被翻译成了术语,这里把vocabulary翻译成“术语表”也是很贴切的,但是我还是坚持把它翻译成“词汇表”,因为金山词霸中就是这么翻译的,词汇表下面也是可以放置术语的,词汇表这个概念比术语表更宽泛一些,最重要的一点,有个第3方模块,就是术语表模块(glossary module),如果我们这里把vocabulary翻译成了术语表,假定有一天,有人翻译“glossary module”的话,就会出现冲突。所以,这个也最终决定采用“词汇表”。
 还有“workflow settings”,被我翻译成了“工作流设置”,现有的译文是“流程设定”,现有译文不贴切,设定对应的英文为set up,这里没有“定”的意思,所以翻译的不贴切,另外workflow翻译成“流程”,也不大合适,这里讲的就是Drupal中的工作流,而不是Drupal的流程,这里面涉及的概念就是“工作流”,“触发器”,“动作”,在现有的计算机用语中都有对应的译法,所以我翻译成了“工作流设置”。
 另外,configuration,对应于“配置”, setting对应于“设置”。“配置”和“设置”好像是近义词,我建议将所有出现configuration的地方都统一的翻译成“配置”,所有的setting都翻译成“设置”。所以我大胆的将“站点设置”改为了“站点配置”。
       另外还有很多地方的译文,在坚持原有译法的同时,对现有的简体中文包作了批判。尽可能采用金山词霸中现有的翻译,尽可能的采用简体中文包中的习惯译法,这是我翻译时的准则,所以当你看到与现有简体中文包中有不一致的译文时,不要惊讶。
    另外,我发现简体中文包的许多地方,不是翻译的不贴切,而是翻译错了,由于时间的关系,无力去一一的修正里面的错误。
    另外,我自己的译文中,在本书中,也有个别地方不一致,限于译者的水平有限,以及可能存在的其它疏漏,望请批评指正。

老葛的Drupal培训班 Think in Drupal

Drupal版本: