1

I'm trying to pass a FormData inside a JSON array to an AJAX script:

$('#form').submit(function(e)
{
    e.preventDefault();

    let formData = new FormData(this),
        data = {'action': 'insert', 'data': formData};

    $.ajax({
        data: data,
        url: '/wp-content/plugins/eng-dealer-map/bin/Admin.php',
        type: 'post',
        cache: false,
        contentType: false,
        processData: false,
        success: function(res) {console.log(res)},
        error: function(res) {console.log(res)}
    })
});

Then /bin/Admin.php

<?php
    ini_set('display_errors',1);
    ini_set('display_startup_errors',1);
    error_reporting(-1);

    $ds = DIRECTORY_SEPARATOR;

    require_once $_SERVER['DOCUMENT_ROOT']. $ds. 'wp-content'. $ds. 'plugins'. $ds. 'eng-dealer-map'. $ds. 'vendor'. $ds. 'autoload.php';

    var_dump($_POST);
    $admin = new \App\Endpoint\Admin(['action' => $_POST['action'], 'data' => $_POST['data']]);
    echo $admin->execute();

Which, in turn, finally goes to this (minified) class:

<?php
    namespace App\Endpoint;

    class Admin
    {
        protected $db;
        protected $table;

        protected $sql;
        protected $data;

        public function __construct($foo)
        {
            require_once $_SERVER['DOCUMENT_ROOT']. DIRECTORY_SEPARATOR. 'wp-config.php';
            global $wpdb;

            $this->db = $wpdb;
            $this->table = '`'. $this->db->prefix .'trey_is_the_best`';

            $this->sql = $this->build($foo['action']);
            $this->data = $foo['data'];
        }

        public function build(string $action)
        {
            switch($action)
            {
                case 'insert': return $this->insertSql(); break;
                case 'update': return $this->updateSql(); break;
                case 'remove': return $this->removeSql(); break;
                default: throw new \Exception('trey'); break;
            }
        }

        public function execute()
        {
            if (!empty($this->sql)) {
                try {
                    if ($this->db->query($this->db->prepare($this->sql, $this->data))) {
                        return true;
                    } else {
                        throw new \Exception($this->db->last_error);
                    }
                } catch (\Exception $e) {
                    throw new \Exception($e->getMessage());
                }
            } else {
                return 'SQL empty!';
            }
        }
    }

Unfortunately, it seems $_POST gets lost along the way. Adding this in my JS:

for (let pair of formData.entries())
{
    console.log(pair[0]+ ', ' + pair[1]);
}

which shows my data correctly. I then var_dump($_POST) in bin/Admin.php which shows an empty array.

I changed 'data': formData to 'data': 'hello, world' so I have a feeling FormData doesn't like being inside a JSON array with other elements?

So how do I send FormData with other elements to my AJAX script? I know I could use:

formData.append('action', 'action-value')

but that feels like it's an extra step when it could just been sent as one object to my script.

I also tried using JSON.stringify on formData but again, nothing. Is there a way I can send formData alongside other data as one object without using .append()? Or is that my only option?

treyBake
  • 6,440
  • 6
  • 26
  • 57
  • What's in `file_get_contents('php://input')` ? – Felippe Duarte Dec 16 '19 at 16:58
  • @FelippeDuarte ah interesting.. [object Object] – treyBake Dec 16 '19 at 16:59
  • take a look https://stackoverflow.com/questions/8893574/php-php-input-vs-post – Felippe Duarte Dec 16 '19 at 16:59
  • Composing `data` like that is a bad idea, but you can get this to work if you use `let data = {'action': 'insert', 'data': $(this).serialize()};`. I'd append the `action` to the formdata instead. –  Dec 16 '19 at 17:03
  • @FelippeDuarte ahh interesting read! So using the `php://input` I can get `[object Object]` - so how do I parse that? I have a feeling that formData doesn't like being a part of an object, feels like it expects everything to be a part of it rather than it coexisting – treyBake Dec 16 '19 at 17:04
  • is it a multipart form? If not you can use `data: $(this).serialize() + "&action=insert",` – Moob Dec 16 '19 at 17:04
  • @ChrisG may I ask why? (I prefer the PHP side of life to JS haha) I can try the serialize idea though :) – treyBake Dec 16 '19 at 17:04
  • Not sure what JS vs. PHP has to do with that, it's a bad idea because jQuery can't deal with it. You need to compose a proper POST request, and while my suggestion allows that, it inserts a query string as parameter in a query string, which should send shivers up and down everybody's spine. If you add the `action=insert` to the form data instead, the problem instantly dissolves into neatness. –  Dec 16 '19 at 17:07
  • @ChrisG because I don't know enough js/jQuery to know what's good and bad haha :p I've opted for `formData.append()` for some reason, it just felt weird that I couldn't append one object to another, but I guess that's the joys of JS dev haha – treyBake Dec 16 '19 at 17:08
  • Putting the FormData object inside another object is no problem at all, but in order to send it to the server it has to be serialized in some way. JS/jQuery easily supports doing that for you, but it expects either a FormData object or a regular object, not a nested combination of the two. My suggestion turns the form data into a string, so that works fine, but it creates an ugly mess like `action=insert&data=username%30admin` because the form data querystring now becomes a value in the outer querystring. –  Dec 16 '19 at 17:36

1 Answers1

2

I have a feeling FormData doesn't like being inside a JSON array with other elements?

Correct. You can't convert FormData to JSON.

So how do I send FormData with other elements to my AJAX script?

Add the extra data to the FormData.

I know I could use:

formData.append('action', 'action-value')

Yes, do that.

but that feels like it's an extra step when it could just been sent as one object to my script.

The FormData object is one object. You just need to add the data to it instead of adding the data to a different object and then trying to add the data from FormData to it.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335