The WooCommerce checkout page doesn’t natively support adding a file upload field as a possible field due to security reasons. I’m looking to add a file upload field on the checkout page so that the customer can give us a document that can then be attached to their order and be referenced again by admins through the orders dashboard in the future if we need to.
I’ve tried 2 different methods but both have led to dead ends. The 2 different solutions I’ve tried were:
- Gravity Forms Upload Form – tried displaying a gravity forms form on the checkout page via a hook and the file field data would never show up in $_FILES or $_POST.
- Upload field with ajax – tried making an upload field and then using ajax to send the upload field’s data to a wordpress ajax function but the issue with this approach is that you cannot validate the file size / if the file’s been uploaded already. So a user could potentially upload very large files or they could mess with where the file is stored due to the file upload path being added in the HTML of the file upload element like so:
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;
}
where the line echo $upload_dir[ ‘url’ ] . ‘/’ . basename( $_FILES[ ‘misha_file’ ][ ‘name’ ] ); adds the file directory to the value= part of the input element which isn’t ideal in terms of security.
I then got to this point where I can now add ‘file’ type fields with the following code:
/**
* 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 );
The code above lets me then do the following in another hook:
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' );
which this code then adds a file field to the checkout page. The issue at this point is that neither $_FILES nor $_POST has any file data related to the key "certificate_file_upload" which is an issue when trying to do anything with the actual file data itself.
I’ve tried searching around for how WooCommerce deals with the default checkout fields to see how I could possibly add my file data to $_FILES/$_POST but all I’ve come up with is that they manage the data possibly through the WooCommerce plugin:
woocommerce->assets->js->frontend->checkout.js
but I don’t know how I could go about adding file support to the checkout page past this point without modifying their files (which will be overriden whenever they update the plugin) if this is even the correct file to be doing this in.
Is checkout.js the file that I should be looking at in the first place to add my file data to $_FILES or should I be looking somewhere else? If checkout.js is the correct file I should be looking at, is there a way around modifying their file to allow my file data to be added to $_FILES?
I would like to avoid having to download a plugin just to make file uploading on the checkout page possible as I’m trying to avoid bloat but if that’s the only solution I guess I’ll go with that if nothing is posssible to fix this.
2
Answers
The following ultra lightweight plugin uses Ajax in a very secure way, to allow uploads in WooCommerce checkout page.
When using Ajax, you can:
All uploaded files will go to a folder named "wc_checkout_uploads" located in WordPress main "uploads" directory. They will be included in a subfolder with the user ID as name (in 6 digits length).
For guests users, if checkout is enabled, the uploads have the same upload directory
000000
and a subdirectory based on the billing email.See the "Complete Usage Examples" section (below after the plugin code) to:
Here is the code of this lightweight plugin:
Main PHP file (add it to a folder named as you like): checkout_uploads.php
The Javascript file located in a "js" subfolder: checkout_upload.js
End of the plugin code.
Complete Usage Examples:
1) Adding an upload field:
A) After the order notes (accepting only text / pdf files and limitting the download size).
This code goes in functions.php file of your active child theme (or active theme) or using Code Snippets plugin (recommended by WooCommerce).
B) In the billing (or shipping) address section for a specific user role (accepting only text / pdf files and limitting the download size).
Note: Here the field has "required" option enabled.
For logged users only, you can use:
Important: When the field is required, and located in billing or shipping fields sections, add the following code, to avoid woocommerce stopping checkout (when the file has been uploaded):
This code goes in functions.php file of your active child theme (or active theme) or using Code Snippets plugin (recommended by WooCommerce).
2) Validation to be used when the file is required:
This code goes in functions.php file of your active child theme (or active theme) or using Code Snippets plugin (recommended by WooCommerce).
3) Save the uploaded file URL and name:
4) Display the uploaded file URL and name:
A) In the admin, on order edit pages, after billing address:
This code goes in functions.php file of your active child theme (or active theme) or using Code Snippets plugin (recommended by WooCommerce).
B) Everywhere needed with
$order
variable (the WC_Order object):First, if needed, you can get the WC_Order object from the order ID like:
Then you will get the data using:
Then, for a file, you can display it as a link like:
Or for an image, you can display the image like:
This is very nice, I tested it and it was really amazing however I discovered one problem while testing. I submitted two uploads with the same filename and they overwrote each other. Since people might use similar names for files such as "document.jpg" or such, I was wondering how to overcome it? Possible append random digits to the end of each filename after upload but before save? Or do a existing file check?