I’ve a front-end form with a file input where anybody (no registered users) can upload an image that will be attached to a custom meta field in the back-end. To preview the image I’m using the old iframe
technique. My form looks like this:
<form id="upload-photo" method="post" target="preview-iframe" action="<?= get_template_directory_uri() ?>/inc/upload.php" enctype="multipart/form-data" >
<div id="preview"><img src="" alt="" /></div>
<iframe id="preview-iframe" name="preview-iframe" src=""></iframe>
<input type="file" name="author_photo" />
<input type="hidden" id="attachment" name="attachment" value=""/>
<button type="submit" id="upload">Upload</button>
</form>
Then I use WordPress built-in functions to handle the upload and move the file into the media gallery. I use the hidden field to store the WordPress id
of the attachment so if users decide to change the picture by uploading a new one then the old one would get removed. This is my PHP:
<?php
define('WP_USE_THEMES', false);
require_once '../../../../wp-load.php';
require_once(ABSPATH .'wp-admin/includes/image.php');
require_once(ABSPATH .'wp-admin/includes/file.php');
require_once(ABSPATH .'wp-admin/includes/media.php');
if (isset($_POST['attachment'])) {
wp_delete_attachment($_POST['attachment'], true);
}
foreach ($_FILES as $file => $data) {
if ($data['error'] === UPLOAD_ERR_OK) {
$attachment = media_handle_upload($file, null);
}
}
echo wp_get_attachment_image($attachment, 'author', 0, array('id' => $attachment));
?>
And finally the jQuery that glues it all together:
var $preview = $('#preview'),
$iframe = $('#preview-iframe'),
$attachment = $('#attachment');
$('#upload').click(function() {
$iframe.load(function() {
var img = $iframe.contents().find('img')[0];
$preview.find('img').attr('src', img.src);
$attachment.val(img.id);
});
});
Everything works perfect but there are few issues with this simple approach:
-
If JavaScript is disabled images don’t get removed
-
If the user uploads a file then refreshes the site and then uploads and other image, then the previous one wouldn’t get deleted because the previous attachment ID doesn’t exist due to the refresh.
-
A malicious user could edit the hidden
attachment
field with a different ID.
I though about uploading the files to a /temp
folder for previewing purposes only and then run a cron job every X time to empty it out. But how do I then make use of WordPress functions to move the image from /temp
to the gallery once the whole form has been submitted so I can get and attachment id to link to the post?
Notice that I’ve two forms, one for handling the image, and the global form with all the content that will be posted and that already works since I can post the new post as “draft” and admins have the power to decide. But how to do this for images securely? How to preview an image and put it in the gallery only if the form has been posted successfully?
I know about the FileReader API but I need compatibility for IE8+ so that won’t do. I’m also aware of all the Flash and Silverlight solutions but that’s not an option either. Also please don’t just link to WordPress plugins, I’m trying to learn here.
Ok, it seems I’m answering my own questions again. This is how I solved it. I found a WordPress function
media_handle_sideload
that lets you upload files from other locations and not only files from the$_FILES
array like the previous function.So I went with my initial approach now that I know about that function. I basically upload the file to a
/temp
folder for preview purposes and give it a unique id that I store into the hidden field. When the user submits the overall form and passes validation I take the ID that was stored and find out if the file exists and if so I move it to the gallery. This solves most of my concerns about security because even if a malicious user finds an existing unique ID (unlikely but possible) the file wouldn’t get removed like before, but just moved into the gallery (not a big deal).Finally I set-up a cron job to empty out the temp folder every X amount of time.