Upload: Fix the method used to create image sub-sizes when uploading fails with a PHP fatal error. Use a custom header to send the new attachment post ID even in HTTP 500 responses instead of an upload reference sent by the client. Also add another cap check and remove the action when deleting an attachment post during a failed upload cleanup.

Props timothyblynjacobs, clorith, azaozz.
Fixes #48200.
Built from https://develop.svn.wordpress.org/trunk@46382


git-svn-id: http://core.svn.wordpress.org/trunk@46181 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Andrew Ozz 2019-10-03 21:02:00 +00:00
parent efa9da7446
commit ff225c8bfc
9 changed files with 59 additions and 141 deletions

View File

@ -2422,39 +2422,19 @@ function wp_ajax_media_create_image_subsizes() {
wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to upload files.' ) ) );
}
if ( ! empty( $_POST['_wp_temp_upload_ref'] ) ) {
// Uploading of images usually fails while creating the sub-sizes, either because of a timeout or out of memory.
// At this point the file has been uploaded and an attachment post created, but because of the PHP fatal error
// the cliend doesn't know the attachment ID yet.
// To be able to find the new attachment_id in these cases we temporarily store an upload reference sent by the client
// in the original upload request. It is used to save a transient with the attachment_id as value.
// That reference currently is Plupload's `file.id` but can be any sufficiently random alpha-numeric string.
$attachment_id = _wp_get_upload_ref_attachment_id( $_POST['_wp_temp_upload_ref'] );
} else {
wp_send_json_error( array( 'message' => __( 'Invalid file reference.' ) ) );
}
if ( empty( $attachment_id ) ) {
if ( empty( $_POST['attachment_id'] ) ) {
wp_send_json_error( array( 'message' => __( 'Upload failed. Please reload and try again.' ) ) );
}
$attachment_id = (int) $_POST['attachment_id'];
if ( ! empty( $_POST['_wp_upload_failed_cleanup'] ) ) {
// Upload failed. Cleanup.
if ( wp_attachment_is_image( $attachment_id ) ) {
if ( wp_attachment_is_image( $attachment_id ) && current_user_can( 'delete_post', $attachment_id ) ) {
$attachment = get_post( $attachment_id );
// Posted at most 10 min ago.
// Created at most 10 min ago.
if ( $attachment && ( time() - strtotime( $attachment->post_date_gmt ) < 600 ) ) {
/**
* Runs when an image upload fails during the post-processing phase,
* and the newly created attachment post is about to be deleted.
*
* @since 5.3.0
*
* @param int $attachment_id The attachment post ID.
*/
do_action( 'wp_upload_failed_cleanup', $attachment_id );
wp_delete_attachment( $attachment_id, true );
wp_send_json_success();
}
@ -2465,7 +2445,7 @@ function wp_ajax_media_create_image_subsizes() {
// The js that handles the response would need to also handle HTTP 500 errors.
wp_update_image_subsizes( $attachment_id );
if ( ! empty( $_POST['_legasy_support'] ) ) {
if ( ! empty( $_POST['_legacy_support'] ) ) {
// The old (inline) uploader. Only needs the attachment_id.
$response = array( 'id' => $attachment_id );
} else {
@ -2478,8 +2458,6 @@ function wp_ajax_media_create_image_subsizes() {
}
// At this point the image has been uploaded successfully.
_wp_clear_upload_ref( $_POST['_wp_temp_upload_ref'] );
wp_send_json_success( $response );
}

View File

@ -981,64 +981,6 @@ function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
return _wp_handle_upload( $file, $overrides, $time, $action );
}
/**
* Temporarily stores the client upload reference in a transient.
*
* @since 5.3.0
* @access private
*
* @param string $upload_ref The upload reference sent by the client.
* @param int $attachment_id Attachment post ID.
* @return bool Whether the transient was set.
*/
function _wp_set_upload_ref( $upload_ref, $attachment_id ) {
$upload_ref = preg_replace( '/[^a-zA-Z0-9_]/', '', $upload_ref );
if ( ! empty( $upload_ref ) ) {
return set_transient( '_wp_temp_image_ref:' . $upload_ref, $attachment_id, HOUR_IN_SECONDS );
}
return false;
}
/**
* Get attachment post ID from an upload reference.
*
* @since 5.3.0
* @access private
*
* @param string $upload_ref The upload reference sent by the client.
* @return int The attachemtn post ID. Zero if the upload reference has expired or doesn't exist.
*/
function _wp_get_upload_ref_attachment_id( $upload_ref ) {
$upload_ref = preg_replace( '/[^a-zA-Z0-9_]/', '', $upload_ref );
if ( ! empty( $upload_ref ) ) {
return (int) get_transient( '_wp_temp_image_ref:' . $upload_ref );
}
return 0;
}
/**
* Remove the transient that stores a temporary upload reference.
*
* @since 5.3.0
* @access private
*
* @param string $upload_ref The upload reference sent by the client.
* @return bool Whether the transient was removed.
*/
function _wp_clear_upload_ref( $upload_ref ) {
$upload_ref = preg_replace( '/[^a-zA-Z0-9_]/', '', $upload_ref );
if ( ! empty( $upload_ref ) ) {
return delete_transient( '_wp_temp_image_ref:' . $upload_ref );
}
return false;
}
/**
* Downloads a URL to a local temporary file using the WordPress HTTP API.
*

View File

@ -251,6 +251,12 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
return $image_meta;
}
// Set a custom header with the attachment_id.
// Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
if ( ! headers_sent() ) {
header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
}
// Resize the image
$resized = $editor->resize( $threshold, $threshold );
$rotated = null;
@ -273,6 +279,8 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
$image_meta['image_meta']['orientation'] = 1;
}
wp_update_attachment_metadata( $attachment_id, $image_meta );
} else {
// TODO: handle errors.
}
@ -289,6 +297,10 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
return $image_meta;
}
if ( ! headers_sent() ) {
header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
}
// Rotate the image
$rotated = $editor->maybe_exif_rotate();
@ -303,6 +315,8 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
$image_meta['image_meta']['orientation'] = 1;
}
wp_update_attachment_metadata( $attachment_id, $image_meta );
} else {
// TODO: handle errors.
}
@ -386,6 +400,12 @@ function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
return $image_meta;
}
// Set a custom header with the attachment_id.
// Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
if ( ! headers_sent() ) {
header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
}
// If stored EXIF data exists, rotate the source image before creating sub-sizes.
if ( ! empty( $image_meta['image_meta'] ) ) {
$rotated = $editor->maybe_exif_rotate();

View File

@ -314,7 +314,6 @@ function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrid
$title = sanitize_text_field( $name );
$content = '';
$excerpt = '';
$_ref = false;
if ( preg_match( '#^audio#', $type ) ) {
$meta = wp_read_audio_metadata( $file );
@ -409,20 +408,9 @@ function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrid
$attachment_id = wp_insert_attachment( $attachment, $file, $post_id, true );
if ( ! is_wp_error( $attachment_id ) ) {
// If an image, keep the upload reference until all image sub-sizes are created.
if ( ! empty( $_POST['_wp_temp_upload_ref'] ) && wp_attachment_is_image( $attachment_id ) ) {
$_ref = _wp_set_upload_ref( $_POST['_wp_temp_upload_ref'], $attachment_id );
}
// The image sub-sizes are created during wp_generate_attachment_metadata().
// This is generally slow and may cause timeouts or out of memory errors.
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
// At this point the image is uploaded successfully even if there were specific errors or some sub-sizes were not created.
// The transient is not needed any more.
if ( $_ref ) {
_wp_clear_upload_ref( $_POST['_wp_temp_upload_ref'] );
}
}
return $attachment_id;

View File

@ -431,9 +431,19 @@ jQuery( document ).ready( function( $ ) {
tryAgain = function( up, error ) {
var file = error.file;
var times;
var id;
if ( ! file || ! file.id ) {
wpQueueError( error.message || pluploadL10n.default_error );
if ( ! error || ! error.responseHeaders ) {
wpQueueError( pluploadL10n.http_error_image );
return;
}
id = error.responseHeaders.match( /x-wp-upload-attachment-id:\s*(\d+)/i );
if ( id && id[1] ) {
id = id[1];
} else {
wpQueueError( pluploadL10n.http_error_image );
return;
}
@ -449,8 +459,8 @@ jQuery( document ).ready( function( $ ) {
dataType: 'json',
data: {
action: 'media-create-image-subsizes',
_wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce,
_wp_temp_upload_ref: file.id,
_wpnonce: wpUploaderInit.multipart_params._wpnonce,
attachment_id: id,
_wp_upload_failed_cleanup: true,
}
});
@ -478,8 +488,8 @@ jQuery( document ).ready( function( $ ) {
data: {
action: 'media-create-image-subsizes',
_wpnonce: wpUploaderInit.multipart_params._wpnonce,
_wp_temp_upload_ref: file.id,
_legasy_support: 'true',
attachment_id: id,
_legacy_support: 'true',
}
}).done( function( response ) {
var message;
@ -589,21 +599,6 @@ jQuery( document ).ready( function( $ ) {
uploader.bind( 'UploadComplete', function() {
uploadComplete();
});
/**
* When uploading images add a file reference used to retrieve the attachment_id
* if the uploading fails due to a server timeout of out of memoty (HTTP 500) error.
*
* @param {plupload.Uploader} up Uploader instance.
* @param {plupload.File} file File for uploading.
*/
uploader.bind( 'BeforeUpload', function( up, file ) {
if ( file.type && file.type.indexOf( 'image/' ) === 0 ) {
up.settings.multipart_params._wp_temp_upload_ref = file.id;
} else {
up.settings.multipart_params._wp_temp_upload_ref = '';
}
} );
};
if ( typeof( wpUploaderInit ) == 'object' ) {

File diff suppressed because one or more lines are too long

View File

@ -119,9 +119,19 @@ window.wp = window.wp || {};
*/
tryAgain = function( message, data, file ) {
var times;
var id;
if ( ! file || ! file.id ) {
error( pluploadL10n.upload_failed, data, file, 'no-retry' );
if ( ! data || ! data.responseHeaders ) {
error( pluploadL10n.http_error_image, data, file, 'no-retry' );
return;
}
id = data.responseHeaders.match( /x-wp-upload-attachment-id:\s*(\d+)/i );
if ( id && id[1] ) {
id = id[1];
} else {
error( pluploadL10n.http_error_image, data, file, 'no-retry' );
return;
}
@ -138,7 +148,7 @@ window.wp = window.wp || {};
data: {
action: 'media-create-image-subsizes',
_wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce,
_wp_temp_upload_ref: file.id,
attachment_id: id,
_wp_upload_failed_cleanup: true,
}
});
@ -161,7 +171,7 @@ window.wp = window.wp || {};
data: {
action: 'media-create-image-subsizes',
_wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce,
_wp_temp_upload_ref: file.id, // Used to find the new attachment_id.
attachment_id: id,
}
}).done( function( response ) {
if ( response.success ) {
@ -315,21 +325,6 @@ window.wp = window.wp || {};
$('#' + this.uploader.id + '_html5_container').hide();
}
/**
* When uploading images add a reference used to retrieve the attachment_id.
* Used if the uploading fails due to a server timeout of out of memoty error (HTTP 500).
*
* @param {plupload.Uploader} up Uploader instance.
* @param {plupload.File} file File for uploading.
*/
this.uploader.bind( 'BeforeUpload', function( up, file ) {
if ( file.type && file.type.indexOf( 'image/' ) === 0 ) {
up.settings.multipart_params._wp_temp_upload_ref = file.id;
} else {
up.settings.multipart_params._wp_temp_upload_ref = '';
}
} );
/**
* After files were filtered and added to the queue, create a model for each.
*

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,7 @@
*
* @global string $wp_version
*/
$wp_version = '5.3-beta2-46381';
$wp_version = '5.3-beta2-46382';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.