1

So I'm making something for which users need to upload images from a canvas element (on the front end) using Wordpress's in-built Media Upload API.

I've successfully uploaded images with the API from a file input in a form:

<input type="file" name="async-upload" id="async-upload" size="40" />
...
<?php $new_attach_id = media_handle_upload( 'async-upload', $new_id ); ?>

and I've saved images from the canvas using my own script:

<script type="text/javascript">
...
var img = canvas.toDataURL("image/png");
...
ajax.send(img );
</script>
...
<?php 
$imageData=$GLOBALS['HTTP_RAW_POST_DATA'];
$filteredData=substr($imageData, strpos($imageData, ",")+1);
$unencodedData=base64_decode($filteredData);
$fp = fopen( 'saved_images/canv_save_test.png', 'wb' );
fwrite( $fp, $unencodedData);
...
?>

Problem is, Wordpress's media_handle_upload() only accepts an index to the $_FILES array for the upload.

So the question is: How can I pass the image data from HTTP_RAW_POST_DATA to it? Could I make the $_FILES['tmp-name'] point to it somehow? Could I use another script as an intermediate step somehow? Could I unleash the monkeys on the typewriter until they come up with the answer?

Any help very very much appreciated!

Thanks, Kane

EDIT: Solved

Thanks to @alex for his file_put_contents suggestion, and answers to another related question I posted here I now have this working. Solution: Send the unaltered base64 image data in the HTTP request, overwrite the tmp_file PHP generates and turn some security checks off for WP. Don't know how safe that is but here's the code for anyone who has the same problem:

<script type="text/javascript">
$('#save').click(function() {

        // get image data from cavas
        var img = canvas.toDataURL("image/png");

        // open ajax request
        var ajax = new XMLHttpRequest();
        ajax.open("POST",script_url,false); 
        //ajax.open("POST","/dumpvars.php",false); // testing

        // set headers
        boundary_str = "AJAX-------------" + (new Date).getTime();
        ajax.setRequestHeader('Content-Type', "multipart/form-data; boundary=" + boundary_str);

        // callback for completed request
        ajax.onreadystatechange=function()
        {
            if (ajax.readyState == 4)
            { 
                // Write out the filename.
                $("#ajax_out").html("Response: "+ajax.responseText+" End Response");
            }
        }

        // BUILD REQUEST
        var boundary = '--' + boundary_str; 
        var request_body = boundary + '\n'
        // print all html form fields
        $('#save_form input').each(function(){ 
            request_body += 'Content-Disposition: form-data; name="' 
            + $(this).attr('name') + '"' + '\n' 
            + '\n' 
            + $(this).val() + '\n' 
            + '\n' 
            + boundary + '\n';
        });
        // make filename
        var filename = $('#save_form input[name="title"]').val();
        filename = filename.replace(/\s+/g, '-').toLowerCase(); // hyphenate + lowercase
        filename = encodeURIComponent(filename) + (new Date).getTime() + ".png";
        // add image
        request_body += 'Content-Disposition: form-data; name="async-upload"; filename="' 
            + filename + '"' + '\n'
        + 'Content-Type: image/png' + '\n' 
        + '\n' 
        + img
        + '\n' 
        + boundary;

        // Send request
        ajax.send(request_body);

    });
</script>

...


<?php
    // Get transmitted image data 
    $loc = $_FILES['async-upload']['tmp_name'];
    $file = fopen($loc, 'rb');
    $contents = fread($file, filesize($loc));
    fclose($file);
    // Decode image data
    $filteredData=substr($contents, strpos($contents, ",")+1);
    $unencodedData=base64_decode($filteredData);
    // Overwrite temp file
    file_put_contents($loc, $unencodedData);

    // Pass image data to WP Upload (attach to post)
    $override['test_upload'] = false; // Override WP's upload security test (rewriting tmp_name made it fail)
    $override['test_form'] = false;
    $new_attach_id = media_handle_upload( 'async-upload', $new_id, array() , $override );

?>
Community
  • 1
  • 1
BaronVonKaneHoffen
  • 1,902
  • 1
  • 21
  • 29
  • Could you `file_put_contents()` the data somewhere, and then make your own faux `$_FILES` array to trick the function? – alex Mar 12 '11 at 16:27
  • Hmm... just tried that but it falls foul of is_uploaded_file() which checks if $_FILES['whateverfile']['tmp_name'] is actually uploaded via HTTP POST, which of course it isn't any more as it now points to a new file :( Thanks for the reply anyway good sir! – BaronVonKaneHoffen Mar 13 '11 at 21:15
  • There's actually another way round I thought of doing it - making the ajax request look like the request that gets generated from the standard form (that actually works). Think I'll make another question for that one though as it's a slightly different topic. Thanks again! – BaronVonKaneHoffen Mar 13 '11 at 21:19
  • Hmm... That didn't work but I've just managed to get your original suggestion to! I'll edit the original question and put the code in for the the benefit of anyone else struggling with this :) – BaronVonKaneHoffen Mar 15 '11 at 17:07
  • I'll post my comment as an answer :) – alex Mar 15 '11 at 22:56

2 Answers2

1

Im going to update this post with how I went about doing this just in case somebody else is struggling with saving Canvas data to Wordpress as a post attachment.

Instead of using media_handle_upload where you have to use the $_FILES array I used the media_sideload_image function. In order to do that all you have to do is just save your canvas image to a folder on your server then pass that URL to media_sideload_image and the post ID and that is it. It will attach that file to your post and copy the image to your uploads folder. You can create a TEMP_UPLOADS folder on your server to store your uploaded file then upon success of attaching it to a post deleting it manually.

define('UPLOAD_DIR', 'path/to/termp-folder/');
$img = $_POST['img'];
$post_id = $_POST['postId'];
$post_id = (int) $post_id;
$img = str_replace('data:image/jpeg;base64,', '', $img);
$img = str_replace(' ', '+', $img);
$data = base64_decode($img);
$file = UPLOAD_DIR . uniqid() . '.jpg';
$success = file_put_contents($file, $data);
$fullURL = $file;
$val = media_sideload_image($fullURL, $post_id, 'none');
echo $val;

the above sample does not delete the old temp file but you can easily figure out how to do that. I feel this solution is a lot simpler and works perfectly.

RamirezDev
  • 11
  • 1
1

You could file_put_contents() the data somewhere, and then make your own faux $_FILES array to trick the WordPress function.

alex
  • 479,566
  • 201
  • 878
  • 984