wordpress 实现文件下载功能

本想这个功能应该不难,但却费了好些工夫。

在wordpress中想要实现点击下载,有两种方法

1、在下载链接上增加 `download=””` 属性

<a href="<?php echo $download_url; ?>" download="">下载</a>

但是这种方法查资料发现,只支持chrome和firefox,兼容性不好。

2、第二种方法也是费了好大劲才查找到的资料,直接上代码

<?php

// add the download ID query var which we'll set in the rewrite rule
function wpd_query_var($query_vars) {
    $query_vars[] = 'download_id';
    return $query_vars;
}
add_filter('query_vars', 'wpd_query_var');

// add rewrite rule
function wpd_rewrite() {
    add_rewrite_rule(
        'download/([0-9]+)/?$',
        'index.php?download_id=$matches[1]',
        'top'
    );
}
add_action('init', 'wpd_rewrite');

// check for download_id and output the file
function wpd_request($wp) {
    if (array_key_exists('download_id', $wp->query_vars)) {
        // 在此实现下载代码的书写
        // output your headers and file here
        // ID is in $wp->query_vars['download_id']
        // then exit to halt any further execution
        $download_id = $wp->query_vars['download_id'];
        $filename =  get_attached_file($download_id);
        if (file_exists($filename)) {
            include get_template_directory() . '/inc/download.php';
            downloadFile($filename);
        } else {
            wp_die(__('要下载的文件不存在!'));
        }
    }
}
add_action('parse_query', 'wpd_request');

下载时,引用了一个inc目录下的download.php,代码如下:

<?php

/**
 * @param $filePath //下载文件的路径
 * @param int $readBuffer //分段下载 每次下载的字节数 默认1024bytes
 * @param array $allowExt //允许下载的文件类型
 * @return void
 */
function downloadFile($filePath, $readBuffer = 1024, $allowExt = ['jpeg', 'jpg', 'peg', 'gif', 'zip', 'rar', 'txt', 'pdf']) {
    //检测下载文件是否存在 并且可读
    if (!is_file($filePath) && !is_readable($filePath)) {
        return false;
    }

    //检测文件类型是否允许下载
    $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    if (!in_array($ext, $allowExt)) {
        return false;
    }

    //设置头信息
    //声明浏览器输出的是字节流
    header('Content-Type: application/octet-stream');

    //声明浏览器返回大小是按字节进行计算
    header('Accept-Ranges:bytes');

    //告诉浏览器文件的总大小
    $fileSize = filesize($filePath); //坑 filesize 如果超过2G 低版本php会返回负数
    header('Content-Length:' . $fileSize); //注意是'Content-Length:' 非Accept-Length

    //声明下载文件的名称
    //   $useragent = $_SERVER['HTTP_USER_AGENT'];
    //     if (strpos($useragent, 'baiduboxapp')) {
    //           $encoded_filename = $filePath;
    //     } else {
    //           $encoded_filename = urlencode($filePath);
    //           $encoded_filename = str_replace("+", "%20", $encoded_filename);
    //     }

    header('Content-Disposition:attachment;filename=' . basename($filePath)); //声明作为附件处理和下载后文件的名称

    //获取文件内容
    $handle = fopen($filePath, 'rb'); //二进制文件用‘rb’模式读取
    while (!feof($handle)) { //循环到文件末尾 规定每次读取(向浏览器输出为$readBuffer设置的字节数)
        echo fread($handle, $readBuffer);
    }
    fclose($handle); //关闭文件句柄
    exit;
}

这样,下载链接只要按如下写法即可实现下载:

<a href="/download/<?php echo $ids[$i]; ?>">点击下载</a>

$ids[$i]表示wordpress附件的id

3、注意到还有一种方式似乎代码更少一些,代码如下:

<?php
add_action('init', function () {
    add_rewrite_endpoint('download', EP_ROOT);
    add_action('template_redirect', function () {
        if ($pid = get_query_var('download')) {
            $found = false;
            if ($file = wp_get_attachment_image_src(absint($pid), 'full')) {
                $found = true;
                header("Content-Disposition: attachment; filename=" . basename($file[0]));
                readfile($file[0]);
                exit();
            }
            if (!$found)
                wp_die(__('Image file not found!'));
        }
    });
});

但是这种方式只适合下载图片文件,可以把它改造一下,用来下载所有文件,改造后的代码如下:

<?php
add_action('init', function () {
    add_rewrite_endpoint('download', EP_ROOT);
    add_action('template_redirect', function () {
        if ($pid = get_query_var('download')) {
            $found = false;

            // 根据附件ID查询附件的真实路径
            $filename = get_attached_file($pid);
            if (file_exists($filename)) {
                $found = true;
                header("Content-Disposition: attachment; filename=" . basename($filename));
                readfile($filename);
                exit();
            }

            if (!$found)
                wp_die(__('Image file not found!'));
        }
    });
});

然后使用这种链接即可实现下载http://localhost/download/123/123表示附件的ID

和第二种方法,思路是一样的,都是添加通过URL重写规则,然后拦截该URL以实现下载功能。但是第三种方法代码更简练一些。