在WordPress中上传不同类型附件的验证和展示逻辑

首先我们需要了解Wordpress允许上传的文件类型,默认允许上传的文件包括视频、音频、图片甚至txt纯文本文件,我们可以通过设置来更改允许上传的文件类型,程序本身对此并没有限制。通过upload_mimes钩子我们可以限定Wordpress程序允许上传的附件类型。

// Respecify allowed extentions in wp-admin
function xcm_supported_mime_types($mime_types)
{
    if (is_admin() || defined('REST_REQUEST') && REST_REQUEST) { //admin or REST_REQUEST(block editor)
        $mime_types = array(
            'jpg|jpeg|jpe|jfif' => 'image/jpeg',
            'webp'         => 'image/webp',
            'gif'          => 'image/gif',
            'png'          => 'image/png',
            'bmp'          => 'image/bmp',
            'mp4'          => 'video/mp4',
            'mp3'          => 'audio/mp3',
            'mpg|mpeg'     => 'video/mpeg',
            'mov'          => 'video/quicktime',
            'avi'          => 'video/x-msvideo',
            'wmv'          => 'video/x-ms-wmv',
            'tif|tiff'     => 'image/tiff',
            'zip'          => 'application/zip',
            '7z'           => 'application/x-7z-compressed',
            'gzip'         => 'application/x-gzip',
            'rar'          => 'application/rar',//dosen't work?
            'rar'          => 'application/ocelet-stream',//dosen't work?
            'rar'          => 'application/x-rar-compressed',//dosen't work?
            'pdf'          => 'application/pdf',
            'doc'          => 'application/msword',
            'docx'         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'ppt'          => 'application/vnd.ms-powerpoint',
            'pptx'         => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'ppsx'         => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
            'xls'          => 'application/vnd.ms-excel',
            'xlsx'         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'txt'          => 'text/plain',
            'vtt'          => 'text/plain',
        );
    }
    return $mime_types;
}
add_filter('upload_mimes', 'xcm_supported_mime_types', 1, 1);

对于各种不同的附件类型,图片是比较好处理的,也是最为基础的

视频类型需要设置poster,对于体积较大的视频文件也需要生成预览视频。

压缩文件类型,比如zip, 7z 等格式,为了能够直观的建立对其中内容的印象,也需要配置缩略图,根据其内容的不同,也有可能需要配置预览视频。

为了快速了解非图片附件的内容,网站上常见的做法生成长拼图。有的长拼图需要手动制作,但是有些可以通过程序自动生成,比如视频附件就很适合通过程序自动生成。

验证是否重复上传

如果你是一个强迫症患者,这里探讨的问题将有助于减轻你的痛苦。Wordpress本身并不验证文件是否重复,但我们仍可以通过提供的钩子工具实现验证功能。

//校验是否重复上传
function xcm_repeat_upload_checker( $upload, $overrides ) {
    $md5 = md5_file($upload['file']);
    $args = array(
        'post_type'  => 'attachment',
        'post_status'=> 'inherit',
        'meta_query' => array(
            array(
                'key'     => 'fileMd5',
                'value'   => $md5,
                'compare' => '=',
            ),
        ),
    );
    $repeat_files = get_posts( $args );
    if(!empty($repeat_files)) {
        unlink($upload['file']); 
        $overrides = [
            'error' => '该文件已经存在,请勿重复上传!'.' 重复文件ID:'.$repeat_files[0]->ID.', 或搜索关键词查阅:'.$repeat_files[0]->post_title.', MD5:'.$md5,
        ];
        return $overrides;
    }
    return $upload;
}
add_filter( 'wp_handle_upload','xcm_repeat_upload_checker', 10, 2 );

上述的代码通过wp_handle_upload这个filter发生作用,其方法是校验文件的md5值。wp_handle_upload在文件信息被写入数据库之前发生作用,此时文件已传递到了服务器主机的临时文件夹,但是尚未转移到指定目录,也没有被写入wordpress数据库。我们使用php自带的md5_file()函数计算文件的md5值,然后在数据库中查询是否有包含这个值的附件,如果有则删除文件返回错误提示,如果没有则继续运行。附件在上传的时候会自动添加一个“fileMd5”的字段,用来存储每一个附件的md5值,这也是前述判断的先决条件。

思考:此时已经生成了文件的Md5值,是否可以将这个Md5值传递出去呢?

接下来我们设计一个对文件的处理流程,包含重复验证以及自动生成缩略图。

//create thumbnail for video 给上传的视频添加缩略图
//需要安装ffmpeg,在debian上的安装命令:apt-get install ffmpeg
//配置文件php.ini把 disable_functions 里面禁用 exec 函数去掉
function xcm_create_video_thumbnail($attachment_id){
    //step 1:获取当前文件id和路径
	$vrio = wp_get_attachment_url($attachment_id);
    $vri = $_SERVER['DOCUMENT_ROOT'].''.substr($vrio,strpos($vrio,'/wp-content'));

    //step 2:为当前文件生成'fileMd5'字段,用于验证以后的重复文件
    update_post_meta($attachment_id,'fileMd5',md5_file($vri));

    //step 3:验证文件类型为视频则继续向下执政
    $ext = strtolower(end(explode('.',$vrio)));
    if (!in_array($ext,['mp4','webm','mov','mpg','avi','wmv'])) //也可以用if (wp_attachment_is( 'video', $post_ID ) ),未测试
        return false;
    $imgd = str_replace($ext,'jpg',$vri);

    //step 4:生成视频缩略图,以下取时间轴的中间值,$output返回命令,$return_val===0表示执行成功
    exec("ffmpeg -ss `ffmpeg -i $vri 2>&1 | grep Duration | awk '{print $2}' | tr -d , | awk -F ':' '{print ($3+$2*60+$1*3600)/2}'` -i $vri -f image2 -y $imgd",$output,$return_val);

    //step 5:将缩略图插入数据库并关联到视频文件
    $attachment_imgd = array(
        'guid'           => $imgd, 
        'post_mime_type' => 'image/jpeg',
        'post_title'     => get_the_title($attachment_id),
        'post_status'    => 'inherit',
    );
    $attach_imgd_id = wp_insert_attachment( $attachment_imgd, $imgd, $attachment_id);
    require_once(ABSPATH . 'wp-admin/includes/image.php');
    require_once(ABSPATH . 'wp-admin/includes/file.php');
    require_once(ABSPATH . 'wp-admin/includes/media.php');
    $attach_imgd_data = wp_generate_attachment_metadata( $attach_imgd_id, $imgd );
    wp_update_attachment_metadata( $attach_imgd_id, $attach_imgd_data );
    wp_update_post(array( 'ID' => $attachment_id,'_thumbnail_id'=> $attach_imgd_id ));
    if( ! wp_next_scheduled( 'sck_generate_privew_event' ) ){
        wp_schedule_single_event( time()+60 , 'sck_generate_privew_event',[$ext,$vri]);//crontab最小间隔是1分钟
    }
}
add_action('add_attachment', 'xcm_create_video_thumbnail', 10, 1);// hook to generate thumbs and previews

function sck_generate_privew_in_60s($ext,$vri){
    //step 6:生成预览视频,注意预览文件名是以-preview.mp4为结尾
    $videod = str_replace('.'.$ext,'-preview.mp4',$vri); //预览视频名称
    exec("ffmpeg -i $vri -b 1200k -vcodec h264 -vf scale=640:-2 $videod"); 
}
add_action('sck_generate_privew_event', 'sck_generate_privew_in_60s', 10, 2);


看起来上述代码提供了较为详细的注释,但是我们还有必要继续厘清其逻辑关系。上边的代码里出现两个action钩子,add_attachment以及sck_generate_privew_event。前者用来处理附件上传数据的,后者是一个自定义钩子,其作用是注册一个事件进程用于定时任务。不要着急,下面我们将依次来分析这两个钩子的含义。

钩子:add_attachment

官方对add_attachment发生节点的描述:Fires once an attachment has been added. 这表明他将在附件上传(创建)成功之后发挥作用, 调用它的是wp_insert_attachment()函数。思考:这是否意味着我们在add_attachment钩子的回调函数中不能再使用wp_insert_attachment()?否者会产生无限循环?

从上面的代码中我们可以看出add_attachment钩子只接收一个参数——附件的ID:$attachment_id。当我们有了附件的ID,那么附件的其他相关数据信息也就唾手可得了。

Step 1:我们首先得要到该附件存储的路径。如果这个文件是视频的话,我们后续的缩略图文件以及视频预览文件会跟这个文件存储在相同的父目录下。这里用了看起来比较复杂的拼接,事实上直接使用get_attached_file($attachment_id)会更合适一点儿。

Step 2:我们在这个阶段存储前述的自定义字段fileMd5,使用update_post_meta()毋庸置疑。这个是所有类型的附件,视频、图片、音频等等都需要存储的内容,所以是否合适放在第二个步骤来执行?

Step 3:验证是否是视频文件,wp_is_attachment(‘video’,$attachment_id)是更好的选择,毋庸置疑。

Step 4:使用ffmpeg截取视频缩略图,这里使用了exec()命令,需要注意的是,该命令因其可能引发的风险在php中默认是禁止的。