jquery 如何添加文件上传到WooCommerce结帐?

gudnpqoy  于 2023-08-04  发布在  jQuery
关注(0)|答案(1)|浏览(104)

由于安全原因,WooCommerce结账页面不支持将文件上传字段添加为可能的字段。我期待添加一个文件上传结帐页面上的字段,以便客户可以给予我们一个文件,然后可以附加到他们的订单,并通过在未来的订单 Jmeter 板管理员再次引用,如果我们需要。
我试过两种不同的方法,但都是死路一条。我尝试过的两种不同的解决方案是:

  1. Gravity Forms Upload Form -尝试通过挂钩在结帐页面上显示Gravity Forms表单,但文件字段数据永远不会显示在$_FILES或$_POST中。
    1.使用 AJAX 上传字段-尝试创建一个上传字段,然后使用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' ]);**将文件目录添加到input元素的value=部分,这在安全性方面并不理想。
然后,我到了这一点,我现在可以用下面的代码添加'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”相关的文件数据,这在尝试使用实际文件数据本身执行任何操作时都是一个问题。
我试着搜索WooCommerce如何处理默认的结帐字段,看看我如何将我的文件数据添加到$_FILES/$_POST,但我想到的是他们通过WooCommerce插件管理数据可能woocommerce->assets->js->frontend->checkout.js但我不知道如何在不修改文件的情况下将文件支持添加到结帐页面(每当他们更新插件时都会被覆盖),如果这是正确的文件。
如果要将文件数据添加到$_FILES中,首先应该查看checkout.js文件,还是应该查看其他文件?如果checkout.js是我应该查看的正确文件,有没有办法修改他们的文件,以允许我的文件数据添加到$_FILES?
我想避免下载一个插件只是为了使文件上传的结帐页面可能,因为我试图避免臃肿,但如果这是唯一的解决方案,我想我会去,如果没有什么是可能的,以解决这个问题。

dgjrabp2

dgjrabp21#

下面的超轻量级插件以非常安全的方式使用 AJAX ,允许在WooCommerce结帐页面上传。
使用 AJAX 时,您可以:

  • 限制/检查文件大小,
  • 仅限于接受的文件类型,
  • 检查文件是否已经上传(但这并没有多大帮助,因为有人可以上传文件,然后重新上传同名的更新文件,而无需 checkout )。
  • 完全隐藏所有敏感数据,如上传路径,使用WC会话安全地存储它。

所有上传的文件将转到名为“wc_checkout_uploads”的文件夹中,该文件夹位于WordPress主“uploads”目录中。它们将包含在一个子文件夹中,用户ID作为名称(6位数长度)。
对于访客用户,如果启用了 checkout ,则上传具有相同的上传目录000000和基于计费电子邮件的子目录。
参见**“完整用法示例”**部分 (在插件代码之后)

  • 显示上载字段,
  • 在需要时验证字段,
  • 将文件URL和名称保存为自定义订单Meta数据并显示在管理中,
  • 在任何您喜欢地方使用自定义订单Meta数据。

这个轻量级插件代码如下:

**PHP主文件 (添加到您喜欢的文件夹中)checkout_uploads.php

<?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();
}

字符串

JavaScript文件位于 “js” 子文件夹:checkout_upload.js

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();
                }
            });
        }
    });
});


插件代码结束。

新的输入字段类型“file”现在可用于woocommerce表单字段,并具有2个额外的可选参数:

完整使用示例:

1)新增上传字段:
**A)订单备注后 (仅接受文本/ pdf文件并限制下载大小)

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>';
}


此代码将在您的活动子主题(或活动主题)的functions.php文件中或使用Code Snippets plugin(WooCommerce推荐)

B)在特定用户角色的账单 (或发货) 地址部分*(仅接受文本/ pdf文件并限制下载大小)*。
**注意:**此处字段已启用必填选项。

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;

**重要提示:**当该字段是必填字段,并且位于账单或运输字段部分时,添加以下代码,以避免woocommerce停止结帐(当文件已上传时):

// 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
}


此代码将在您的活动子主题(或活动主题)的functions.php文件中或使用Code Snippets plugin(WooCommerce推荐)

2)需要文件时使用的校验:

// 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
    }
}


此代码将在您的活动子主题(或活动主题)的functions.php文件中或使用Code Snippets plugin(WooCommerce推荐)

3)保存上传文件的URL和名称:

// 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)显示上传文件的URL和名称:
A)在管理界面,订单编辑页面,账单地址后:

// 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'] 
        );
    }
}


此代码将在您的活动子主题(或活动主题)的functions.php文件中或使用Code Snippets plugin(WooCommerce推荐)

B)Everywhere neededwith $order variable(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'] 
);

或者对于图片,可以这样显示图片:

$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'] 
);

相关问题