出于安全原因,WooCommerce checkout 页面本身并不支持将文件上传域添加为可能的域.我希望在 checkout 页面上添加一个文件上传域,这样客户可以给我们一个文档,然后可以附加到他们的订单,并在future 通过订单仪表板再次引用,如果我们需要的管理员.

我试过两种不同的方法,但都走进了死胡同.我试过的两种不同的解决方案是:

  1. 重力表格上传表格-try 通过挂钩在 checkout 页面上显示重力表格表格,文件字段数据永远不会显示在$_FILES或$_POST中.
  2. 使用AJAX上载域-try 创建上载域,然后使用AJAX将上载域的数据发送到WordPress的AJAX函数,但这种方法的问题是,如果文件已经上传,则无法验证文件大小.因此,用户可能会上传非常大的文件,或者他们可能会因为文件上传路径被添加到文件上传元素的HTML中而扰乱文件的存储位置,如下所示:
add_action( 'wp_ajax_mishaupload', 'misha_file_upload' );
add_action( 'wp_ajax_nopriv_mishaupload', 'misha_file_upload' );

function misha_file_upload(){

    $upload_dir = wp_upload_dir();

    if ( isset( $_FILES[ 'misha_file' ] ) ) {
        $path = $upload_dir[ 'path' ] . '/' . basename( $_FILES[ 'misha_file' ][ 'name' ] );

        if( move_uploaded_file( $_FILES[ 'misha_file' ][ 'tmp_name' ], $path ) ) {
            echo $upload_dir[ 'url' ] . '/' . basename( $_FILES[ 'misha_file' ][ 'name' ] );
        }
    }
    die;
}

其中,第echo $upload_dir[ 'url' ] . '/' . basename( $_FILES[ 'misha_file' ][ 'name' ] );行将文件目录添加到输入元素的值=部分,这在安全性方面并不理想.

然后,我就可以使用以下代码添加‘FILE’类型的字段了:

/**
 * Function for `woocommerce_form_field` filter-hook.
 *
 * @param  $field
 * @param  $key
 * @param  $args
 * @param  $value
 *
 * @return
 */
function wp_kama_woocommerce_form_field_filter( $field, $key, $args, $value ){

    // check if field is a file field
    if( $args['type'] == 'file' ){
            // add custom HTML to the field
            $field = '<div class="woocommerce-additional-fields__field-wrapper">';
            $field .= '<p class="form-row notes woocommerce-validated" id="certificate_file_upload_field" data-priority="">';
            $field .= '<label for="certificate_file_upload" class="">Upload Certificate</label>';
            $field .= '<span class="woocommerce-input-wrapper">';

            $field .= sprintf(
                '<input type="file" class="%s" name="%s" id="%s"/>',
                esc_attr( implode( ' ', $args['class'] ) ),
                esc_attr( $key ),
                esc_attr( $args['id'] ),
            );

            $field .= '</span>';
            $field .= '</p>';
            $field .= '</div>';
    }

    return $field;
}
add_filter( 'woocommerce_form_field', 'wp_kama_woocommerce_form_field_filter', 10, 4 );

然后,上面的代码允许我在另一个钩子中执行以下操作:

function add_custom_checkout_field($checkout) {
    woocommerce_form_field('certificate_file_upload', array(
        'type' => 'file',
        'class' => array('form-row-wide'),
        'label' => __('Upload Certificate'),
        'required' => false,
    ), $checkout->get_value('certificate_file_upload'));
}
add_action( 'woocommerce_after_order_notes', 'add_custom_checkout_field' );

然后,此代码会将文件字段添加到 checkout 页面.此时的问题是,$_FILES和$_POST都没有任何与密钥"CERTIFICATE_FILE_UPLOAD"相关的文件数据,这是try 处理实际文件数据本身时的问题.

我试着搜索WooCommerce如何处理默认的 checkout 字段,看看如何才能将我的文件数据添加到$_FILES/$_POST,但我得到的结果是,它们通过WooCommerce插件管理数据possibly: woocommerce->assets->js->frontend->checkout.js 但我不知道如何在不修改他们的文件的情况下向 checkout 页面添加文件支持(当他们更新插件时,这些文件将被覆盖),如果这就是做这件事的正确文件.

在将文件数据添加到$_FILES时,我首先应该查看的是check out.js文件,还是应该查找其他文件?如果check out.js是我应该查看的正确文件,有没有办法修改他们的文件以允许将我的文件数据添加到$_FILES?

我想避免不得不下载一个插件,只是为了让文件上传到 checkout 页面成为可能,因为我试图避免臃肿,但如果这是唯一的解决方案,我想我会go ,如果没有任何东西可以修复这个问题.

推荐答案

下面这个超轻量级插件以一种非常安全的方式使用了AJAX,允许在WooCommerce checkout 页面上传.

使用AJAX时,您可以:

  • 限制/判断文件大小,
  • 仅限于接受的文件类型,
  • 判断文件是否已上载(但这不是很有帮助,因为有人可以上载文件,然后重新上载同名的更新文件,而无需签出).
  • 完全隐藏所有敏感数据,如上传路径,使用WC会话安全存储.

所有上传的文件都会被放到WordPress主"上传"目录中名为"WC_CHECKOUT_UPLOADS"的文件夹中.它们将被包括在一个子文件夹中,以用户ID作为名称(长度为6位).
对于访客用户,如果启用 checkout ,则上传具有相同的上传目录000000和基于计费邮箱的子目录.

请参阅第"Complete Usage Examples"节第(below after the plugin code)节,以:

  • 显示上载域,
  • 在需要时验证该字段,
  • 将文件URL和名称保存为定制订单元数据并显示在管理员中,
  • 在您喜欢的任何地方使用该定制订单元数据.

以下是这个轻量级插件的代码:

Main PHP file (add it to a folder named as you like):101

<?php
/*
Plugin Name: WooCommerce Checkout upload
Plugin URI: https://stackoverflow.com/a/76691778/3730754
Description: Add a input field type "file" for checkout (Ajax securely powered), and save the downloaded file URL and name as custom order metadata.
Version: 1.0
Author: LoicTheAztec
Author URI: https://stackoverflow.com/users/3730754/loictheaztec
*/

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

register_activation_hook(__FILE__, 'wcu_plugin_activation');
function wcu_plugin_activation() {
    // Make sure that WooCommerce plugin is active
    if ( ! in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
        $message = 'Requires WooCommerce plugin activated.';
        echo $message;
        trigger_error($message, E_USER_NOTICE);
    }
}

// Enqueue JavaScript file and localize it
add_action( 'wp_enqueue_scripts', 'checkout_uploads_enqueue_scripts' );
function checkout_uploads_enqueue_scripts() {
   if ( is_checkout() && ! is_wc_endpoint_url() ) {
        wp_enqueue_script( 
            'checkout-uploads',  
            plugins_url( 'js/checkout_upload.js', __FILE__ ), 
            array('jquery'), false, true 
        );

        wp_localize_script(
            'checkout-uploads',
            'checkout_uploads_params',
            array(
                'ajax_url' => admin_url( 'admin-ajax.php?action=checkout_upload&security='.wp_create_nonce('checkout_upload') ),
            )
        );
    }
}

// ADd Input File type to WooCommerce form fields
add_filter( 'woocommerce_form_field', 'woocommerce_form_input_field_type_file', 10, 4 );
function woocommerce_form_input_field_type_file( $field, $key, $args, $value ){
    if( $args['type'] == 'file' ){
        if ( $args['required'] ) {
            $args['class'][] = 'validate-required';
            $required        = '&nbsp;<abbr class="required" title="' . esc_attr__( 'required', 'woocommerce' ) . '">*</abbr>';
        } else {
            $required = '&nbsp;<span class="optional">(' . esc_html__( 'optional', 'woocommerce' ) . ')</span>';
        }
        $field           = '';
        $label_id        = $args['id'];
        $sort            = $args['priority'] ? $args['priority'] : '';
        $field_container = '<p class="form-row %1$s" id="%2$s" data-priority="' . esc_attr( $sort ) . '">%3$s</p>';
        $max_size        = isset($args['max_size']) ? 'data-max_size="' . intval( $args['max_size'] ) . '" ' : '';
        $accept          = isset($args['accept']) ? 'accept="' . esc_attr( $args['accept'] ) . '" ' : '';
        
        $field .= sprintf( '<input type="%s" class="input-file %s" name="%s" id="%s" %s/>', esc_attr( $args['type'] ), 
            esc_attr( implode( ' ', $args['input_class'] ) ), esc_attr( $key ), esc_attr( $args['id'] ), $max_size . $accept );

        if ( ! empty( $field ) ) {
            $field_html = '<label for="' . esc_attr( $label_id ) . '" class="' . esc_attr( implode( ' ', $args['label_class'] ) ) . '">' . wp_kses_post( $args['label'] ) . $required . '</label>';
            $field_html .= '<span class="woocommerce-input-wrapper">' . $field;

            if ( $args['description'] ) {
                $field_html .= '<span class="description" id="' . esc_attr( $args['id'] ) . '-description" aria-hidden="true">' . wp_kses_post( $args['description'] ) . '</span>';
            }

            $field_html .= '<span class="upload-response" style="display:none"></span></span>';

            $container_class = esc_attr( implode( ' ', $args['class'] ) );
            $container_id    = esc_attr( $args['id'] ) . '_field';
            $field           = sprintf( $field_container, $container_class, $container_id, $field_html );
        }
    }
    if ( $args['return'] ) {
        return $field;
    } else {
        echo $field;
    }
}

// PHP Ajax responder
add_action( 'wp_ajax_checkout_upload', 'checkout_ajax_file_upload' );
add_action( 'wp_ajax_nopriv_checkout_upload', 'checkout_ajax_file_upload' );
function checkout_ajax_file_upload(){
    check_ajax_referer('checkout_upload', 'security'); 

    global $current_user;

    if ( isset($_FILES['uploads']) ) {
        if ( ! $current_user->ID && isset($_POST['email']) && ! empty($_POST['email']) ) {
            // Generating a sub / subfolder (path) from billing email in '000000' guest directory
            $user_path = '000000/'.substr(sanitize_title($_POST['email']), 0, 10); // For Guests
        } else {
            $user_path = str_pad($current_user->ID, 6, '0', STR_PAD_LEFT); // For logged in users
        }
        $upload_dir  = wp_upload_dir();
        $user_path   = '/wc_checkout_uploads/' . $user_path;
        $user_folder = $upload_dir['basedir']  . $user_path;
        $user_url    = $upload_dir['baseurl']  . $user_path;

        if ( ! is_dir( $user_folder ) ) {
            wp_mkdir_p( $user_folder );
            chmod( $user_folder, 0777 );
        }
        $file_path = $user_folder . '/' . basename($_FILES['uploads']['name']);
        $file_url  = $user_url . '/' . basename( $_FILES['uploads']['name']);

        if( move_uploaded_file($_FILES['uploads']['tmp_name'], $file_path)) {
            // Save the file URL and the file name to WC Session
            WC()->session->set('checkout_upload', array(
                'file_url'  => $file_url, 
                'file_name' => $_FILES['uploads']['name']
            ));
            
            echo '<span style="color:green">' . __('Upload completed', 'woocommerce') . '</span><br>';
        } else {
            echo '<span style="color:red">' . __('Upload failed.') . '</span>';
        }
    }
    wp_die();
}

The Javascript file位于101子文件夹中:102

jQuery( function($) {
    if (typeof checkout_uploads_params === 'undefined') {
        return false;
    }

    $('form.checkout').on( 'change', 'input[type=file]', function() {
        const files = $(this).prop('files');
        const email = $('input#billing_email').val();

        if ( files.length ) {
            const file = files[0];
            const maxSize = $(this).data('max_size');
            const formData = new FormData();
            formData.append( 'uploads', file );
            formData.append( 'email', email );

            if ( maxSize > 0 && file.size > ( maxSize * 1024 ) ) {
                const maxSizeText = 'This file is to heavy (' + parseInt(file.size / 1024) + ' ko)';
                $( '.upload-response' ).html( maxSizeText ).css('color','red').fadeIn().delay(2000).fadeOut();
                return;
            }
            $('form.checkout').block({message: null, overlayCSS:{background:"#fff",opacity: .6}});

            $.ajax({
                url: checkout_uploads_params.ajax_url,
                type: 'POST',
                data: formData,
                contentType: false,
                enctype: 'multipart/form-data',
                processData: false,
                success: function ( response ) {
                    $('form.checkout').unblock();
                    $( '.upload-response' ).html( response ).fadeIn().delay(2000).fadeOut();
                },
                error: function ( error ) {
                    $('form.checkout').unblock();
                    $( '.upload-response' ).html( error ).css('color','red').fadeIn().delay(2000).fadeOut();
                }
            });
        }
    });
});

插件代码的结尾.

A new input field type "file" is now available for woocommerce form fields and has 2 additional optional arguments:


完整的用法示例:

1) Adding an upload field:

(accepting only text / pdf files and limitting the download size).

add_action( 'woocommerce_after_order_notes', 'add_custom_checkout_field' );
function add_custom_checkout_field($checkout) {

    echo '<div class="woocommerce-additional-fields__field-wrapper">';

    woocommerce_form_field('certificate', array(
        'type'      => 'file',
        'class'     => array('form-row-wide'),
        'label'     => __('Upload Certificate', 'woocommerce'),
        'required'  => false,
        'max_size'  => '3072', // in ko (here 3 Mo size limit)
        'accept'    => '.img,.doc,.docx,.rtf,.txt', // text documents and pdf
    ), '');

    echo '</div>';
}

这段代码放在活动子主题(或活动主题)或使用Code Snippets plugin (recommended by WooCommerce)的函数.php文件中.

(accepting only text / pdf files and limitting the download size).

Note:此处,该字段启用了"必填"选项.

add_filter( 'woocommerce_checkout_fields', 'add_custom_billing_field' );
function add_custom_billing_field( $fields ) {
    // Only for 'wholesale_customer' user role
    if( ! current_user_can( 'wholesale_customer' ) ) return $fields;

    $fields['billing']['billing_image'] = array(
        'type' => 'file',
        'label' => __('Upload your image', 'woocommerce'),
        'class' => array('form-row-wide'),
        'required' => true,
        'max_size'  => '5120', // in ko (here 5 Mo size limit)
        'accept'    => 'image/*', // Image files only
        'priority' => 200,
    );    
    
    return $fields;
}

仅对于已登录的用户,您可以使用:

    // Only for logged in users
    if( ! is_user_logged_in() ) return $fields;

Important:如果该字段是必填的,并且位于帐单或发货字段部分,请添加以下代码,以避免WooCommerce停止 checkout (当文件已上传时):

// On billing or shipping section, when "upload" field is required
add_action( 'woocommerce_after_checkout_validation', 'custom_after_checkout_validation', 20, 2 );
function custom_after_checkout_validation($data, $errors) {
    $field_key = 'billing_image'; // Here define the field key (or field ID)

    $errors->remove($field_key.'_required'); // Remove unwanted error for input file
}

这段代码放在活动子主题(或活动主题)或使用Code Snippets plugin (recommended by WooCommerce)的函数.php文件中.

2) Validation to be used when the file is required:

// Required upload field validation
add_action( 'woocommerce_checkout_process', 'checkout_required_upload_validation' );
function checkout_required_upload_validation() {
    $checkout_upload = WC()->session->get('checkout_upload');
    if( empty( $checkout_upload ) ) {
        wc_add_notice( __('Uploading your file is required in order to checkout.', 'woocommerce'), 'error' ); // Displays an error notice
    }
}

这段代码放在活动子主题(或活动主题)或使用Code Snippets plugin (recommended by WooCommerce)的函数.php文件中.

3) Save the uploaded file URL and name:

// Save the uploaded file URL and name (array
add_action( 'woocommerce_checkout_create_order', 'save_checkout_uploaded_file', 10, 2 );
function save_checkout_uploaded_file( $order, $data ){
    if( $checkout_upload = WC()->session->get('checkout_upload') ) {
        $order->update_meta_data( '_checkout_upload', $checkout_upload ); // Save 
    }
    WC()->session->__unset('checkout_upload'); // Remove session variable
}

4) Display the uploaded file URL and name:

A) In the admin,在订单编辑页面上,付款后地址:

// Display the uploaded file in admin orders
add_action('woocommerce_admin_order_data_after_billing_address', 'display_uploaded_file_in_admin_orders');
function display_uploaded_file_in_admin_orders( $order ){
    if( $checkout_upload = $order->get_meta( '_checkout_upload' ) ) {
        printf( '<p>%s <br><a href="%s">%s</a></p>', 
            __("File Upload:", 'woocommerce'), 
            $checkout_upload['file_url'], 
            $checkout_upload['file_name'] 
        );
    }
}

这段代码放在活动子主题(或活动主题)或使用Code Snippets plugin (recommended by WooCommerce)的函数.php文件中.

B) Everywhere needed,变量为$order(WC_ORDER对象):

首先,如果需要,您可以从订单ID获取WC_ORDER对象,如下所示:

$order = wc_get_order( $order_id );

然后,您将使用以下方式获取数据:

$upload = $order->get_meta('_checkout_upload');

然后,对于文件,您可以将其显示为如下所示的链接:

$upload = $order->get_meta('_checkout_upload');

printf( '<p>%s <br><a href="%s">%s</a></p>', 
    __("File Upload:", 'woocommerce'), 
    $upload['file_url'], 
    $upload['file_name'] 
);

Or for an image,则可以显示如下图像:

$upload = $order->get_meta('_checkout_upload');

printf( '<p>%s <br><img src="%s" alt="%s" /><br><a href="%s">%s</a></p>', 
    __("Image Uploaded:", 'woocommerce'), 
    $upload['file_url'], 
    $upload['file_name'], 
    $upload['file_url'], 
    $upload['file_name'] 
);

Php相关问答推荐

在不刷新页面的情况下无法使用JQuery更新id

PHP Perl正则表达式—URL前面没有等号和可能的单引号或双引号

PHP Array to Powershell exec命令Args

Yahoo Finance API文件_GET_CONTENTS 429请求太多

如何更改数据表行背景 colored颜色

LaravelEloquent 地根据表中的值提取记录

WooCommerce在购买后多次重定向

模式 bootstrap 未显示

在WooCommerce管理中为订单项目添加自定义字段价格值

PHP按另一个二维数组的排序顺序对二维数组进行排序

更改WooCommerce checkout 中的错误消息以获得不可用的送货方式

PHP Match如何准确判断条件?

在 WooCommerce 订阅续订订单中设置送货方式

如何处理 Null 上的 array_shift() ?

产品页面自定义复选框可对 WooCommerce 购物车小计启用百分比折扣

如何搜索和替换 XML 文件中的一段文本?

使用其ID查询和捕获与订单相关的其他详细信息的shopware 6方法

Laravel 8:会话不适用

Eloquent 的模型更新了它应该 laravel php 的更多行

Google Drive - 仅使用 FileID 获取文件的 FileSize