User_FTW
User_FTW

Reputation: 524

Uploading multiple files on WordPress front-end using Ajax

I have a custom theme with a front-end form which is used to create a post using Ajax, and it works except for the file uploads.

The form looks like this...

<form class="job-form" id="job-form" method="post" enctype="multipart/form-data">

    <input type="text" name="job_title" id="job_title" />
    <input type="text" name="job_description" id="job_description" />
    <input type="file" name="job_files[]" id="job_files" multiple="multiple"/>

    <button>Submit</button>
</form>

...and this JS...

$('#job-form').on('submit', function(e) {
    e.preventDefault();

    $.post( inputs.ajaxurl, {
        action : 'add_job',
        nonce : inputs.nonce,
        post : $(this).serialize()
    },
    function(response) {
        console.log(response);
        ResponseSuccess(response);
    }); 

    return false;

});

..and PHP to add everything to the database:

add_action( 'wp_ajax_add_job', 'wp_ajax_add_job' );
add_action( 'wp_ajax_nopriv_add_job', 'add_job' );
function add_job() {

    $params = array();
    parse_str($_POST["post"], $params);

    if(isset($_POST["job-form"])) {

        $job_title          = sanitize_text_field($params['job_title']);
        $job_description    = sanitize_text_field($params['job_description']);

        $args = array(
            'post_title'    => $job_title,
            'post_status'   => 'publish',
            'post_type'     => 'jobs',
            'post_author'   => 1
        );
        
        $result     = wp_insert_post( $args );
        $post_id    = $result;
    
        add_post_meta( $post_id, "job_title" , $job_title);
        add_post_meta( $post_id, "job_description" , $job_description);

        /* If attachments were attached, upload them */
        if($_FILES) {
            require_once(ABSPATH . "wp-admin" . '/includes/image.php');
            require_once(ABSPATH . "wp-admin" . '/includes/file.php');
            require_once(ABSPATH . "wp-admin" . '/includes/media.php');

            $files = $_FILES["job_files"];  
            foreach ($files['name'] as $key => $value) {            
                if ($files['name'][$key]) { 
                    $file = array( 
                        'name'      => $files['name'][$key],
                        'type'      => $files['type'][$key], 
                        'tmp_name'  => $files['tmp_name'][$key], 
                        'error'     => $files['error'][$key],
                        'size'      => $files['size'][$key]
                    ); 
                    $_FILES = array ("job_files" => $file); 
                    foreach ($_FILES as $file => $array) {              
                        $new_upload = media_handle_upload($file,$post_id); 
                    }
                } 
            }
        }

        ajaxStatus('job-created', __('Job created.', 'jobs-theme'), $post_id);
            
    }
}

When submitting the form, the post gets created along with the meta keys and meta values without any dramas, but the file attachments are not working. What am I doing wrong?

Upvotes: 2

Views: 993

Answers (2)

dumbass
dumbass

Reputation: 27214

As jQuery documentation for serialize explains,

Data from file select elements is not serialized.

The reason is that serialize encodes form elements into the application/x-www-form-urlencoded format, which can only accommodate plaintext string values, and not file uploads. To be able to upload files, you will have to submit the whole form in the multipart/form-data format. One way to accomplish it is to use the FormData object; jQuery doesn’t directly support it, but it can accomodate it easily.

Here’s a minimal modification of your code to make this work:

$('#job-form').on('submit', function(e) {
    e.preventDefault();

    // take the data from the form
    const formData = new FormData(this);
    // append further fields
    formData.append('action', 'add_job');
    formData.append('nonce', inputs.nonce);

    $.ajax( inputs.ajaxurl, {
        type: 'POST',
        data: formData,
        contentType: false, // prevent jQuery from using an incorrect Content-type header
        processData: false, // prevent jQuery from mangling the form data
        success: function(response) {
            console.log(response);
            ResponseSuccess(response);
        }
    }); 

    return false;
});

The contentType and processData properties need to be set to prevent jQuery from mangling the data and adding incorrect headers.

Because I had to modify the actually-submitted form structure to be able to handle file uploads, the form code and the receiver script will also have to be modified, for example like this:

    <form class="job-form" id="job-form" method="post" enctype="multipart/form-data">

        <input type="text" name="post[job_title]" id="job_title" />
        <input type="text" name="post[job_description]" id="job_description" />
        <input type="file" name="job_files[]" id="job_files" multiple="multiple"/>

        <button>Submit</button>
    </form>

And in the receiving script:

    # ...
    function add_job() {

        $params = $_POST['post'];

        if(isset($_POST["job-form"])) {
    # ...

Here I am taking advantage of PHP’s built-in structural form parsing, where fields with names of the form a[b] generate associative arrays that can be accessed as $_POST['a']['b'] on the PHP side.

Upvotes: 3

Tansukh Rathod
Tansukh Rathod

Reputation: 90

You need to rewrite your ajax request code just like below.


var $form = $("#yourFormSelector");
$.ajax({
    type: 'post',
    url: 'url',
    data: new FormData($form[0]),
    contentType: false,
    processData: false,
    success: function(response) {
        //Your response handler code...
    }
});

Upvotes: -1

Related Questions