diff --git a/wp-admin/admin-ajax.php b/wp-admin/admin-ajax.php
index d9060ceb94..ad2fdae669 100644
--- a/wp-admin/admin-ajax.php
+++ b/wp-admin/admin-ajax.php
@@ -45,7 +45,7 @@ $core_actions_post = array(
'menu-locations-save', 'menu-quick-search', 'meta-box-order', 'get-permalink',
'sample-permalink', 'inline-save', 'inline-save-tax', 'find_posts', 'widgets-order',
'save-widget', 'set-post-thumbnail', 'date_format', 'time_format', 'wp-fullscreen-save-post',
- 'wp-remove-post-lock', 'dismiss-wp-pointer',
+ 'wp-remove-post-lock', 'dismiss-wp-pointer', 'upload-attachment',
);
// Register core Ajax calls.
diff --git a/wp-admin/includes/ajax-actions.php b/wp-admin/includes/ajax-actions.php
index 93d1b4a848..be77857844 100644
--- a/wp-admin/includes/ajax-actions.php
+++ b/wp-admin/includes/ajax-actions.php
@@ -1544,6 +1544,50 @@ function wp_ajax_save_widget() {
wp_die();
}
+function wp_ajax_upload_attachment() {
+ check_ajax_referer( 'media-form' );
+
+ if ( ! current_user_can( 'upload_files' ) )
+ wp_die( -1 );
+
+ if ( isset( $_REQUEST['post_id'] ) ) {
+ $post_id = $_REQUEST['post_id'];
+ if ( ! current_user_can( 'edit_post', $post_id ) )
+ wp_die( -1 );
+ } else {
+ $post_id = null;
+ }
+
+ $post_data = is_array( $_REQUEST['post_data'] ) ? $_REQUEST['post_data'] : array();
+
+ $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
+
+ if ( is_wp_error( $attachment_id ) ) {
+ echo json_encode( array(
+ 'type' => 'error',
+ 'data' => array(
+ 'message' => $attachment_id->get_error_message(),
+ 'filename' => $_FILES['async-upload']['name'],
+ ),
+ ) );
+ wp_die();
+ }
+
+ $post = get_post( $attachment_id );
+
+ echo json_encode( array(
+ 'type' => 'success',
+ 'data' => array(
+ 'id' => $attachment_id,
+ 'title' => esc_attr( $post->post_title ),
+ 'filename' => esc_html( basename( $post->guid ) ),
+ 'url' => wp_get_attachment_url( $attachment_id ),
+ 'meta' => wp_get_attachment_metadata( $attachment_id ),
+ ),
+ ) );
+ wp_die();
+}
+
function wp_ajax_image_editor() {
$attachment_id = intval($_POST['postid']);
if ( empty($attachment_id) || !current_user_can('edit_post', $attachment_id) )
diff --git a/wp-includes/class-wp-customize-setting.php b/wp-includes/class-wp-customize-setting.php
index 710026c221..54b1eac7a7 100644
--- a/wp-includes/class-wp-customize-setting.php
+++ b/wp-includes/class-wp-customize-setting.php
@@ -70,6 +70,9 @@ class WP_Customize_Setting {
wp_enqueue_script( 'farbtastic' );
wp_enqueue_style( 'farbtastic' );
break;
+ case 'upload':
+ wp_enqueue_script( 'wp-plupload' );
+ break;
}
}
@@ -394,6 +397,15 @@ class WP_Customize_Setting {
+
+ control, $this );
diff --git a/wp-includes/class-wp-customize.php b/wp-includes/class-wp-customize.php
index 68576c5011..1763a7f282 100644
--- a/wp-includes/class-wp-customize.php
+++ b/wp-includes/class-wp-customize.php
@@ -523,7 +523,13 @@ final class WP_Customize {
'section' => 'background',
'control' => 'color',
'default' => defined( 'BACKGROUND_COLOR' ) ? BACKGROUND_COLOR : '',
- 'sanitize_callback' => 'sanitize_hexcolor'
+ 'sanitize_callback' => 'sanitize_hexcolor',
+ ) );
+
+ $this->add_setting( 'background_image', array(
+ 'label' => 'Background Image',
+ 'section' => 'background',
+ 'control' => 'upload',
) );
/* Nav Menus */
diff --git a/wp-includes/js/customize-controls.dev.js b/wp-includes/js/customize-controls.dev.js
index 5787499bb9..0b4baf1ccb 100644
--- a/wp-includes/js/customize-controls.dev.js
+++ b/wp-includes/js/customize-controls.dev.js
@@ -70,6 +70,26 @@
}
});
+ api.UploadControl = api.Control.extend({
+ initialize: function( id, value, options ) {
+ var control = this;
+
+ api.Control.prototype.initialize.call( this, id, value, options );
+
+ this.uploader = new wp.Uploader({
+ browser: this.container.find('.upload'),
+ success: function( attachment ) {
+ control.set( attachment.url );
+ }
+ });
+
+ this.container.on( 'click', '.remove', function( event ) {
+ control.set('');
+ event.preventDefault();
+ });
+ }
+ });
+
// Change objects contained within the main customize object to Settings.
api.defaultConstructor = api.Setting;
@@ -194,7 +214,8 @@
* ===================================================================== */
api.controls = {
- color: api.ColorControl
+ color: api.ColorControl,
+ upload: api.UploadControl
};
$( function() {
@@ -229,6 +250,9 @@
// Background color uses postMessage by default
api('background_color').method = 'postMessage';
+
+ // api('background_image').method = 'postMessage';
+ api('background_image').uploader.param( 'post_data', { context: 'custom-background' });
});
})( wp, jQuery );
\ No newline at end of file
diff --git a/wp-includes/js/plupload/wp-plupload.dev.js b/wp-includes/js/plupload/wp-plupload.dev.js
new file mode 100644
index 0000000000..8ad2738606
--- /dev/null
+++ b/wp-includes/js/plupload/wp-plupload.dev.js
@@ -0,0 +1,156 @@
+if ( typeof wp === 'undefined' )
+ var wp = {};
+
+(function( exports, $ ) {
+ /*
+ * An object that helps create a WordPress uploader using plupload.
+ *
+ * @param options - object - The options passed to the new plupload instance.
+ * Requires the following parameters:
+ * - container - The id of uploader container.
+ * - browser - The id of button to trigger the file select.
+ * - dropzone - The id of file drop target.
+ * - plupload - An object of parameters to pass to the plupload instance.
+ * - params - An object of parameters to pass to $_POST when uploading the file.
+ * Extends this.plupload.multipart_params under the hood.
+ *
+ * @param attributes - object - Attributes and methods for this specific instance.
+ */
+ var Uploader = function( options ) {
+ var self = this,
+ elements = {
+ container: 'container',
+ browser: 'browse_button',
+ dropzone: 'drop_element'
+ },
+ key;
+
+ this.plupload = $.extend( { multipart_params: {} }, wpPluploadDefaults );
+ this.container = document.body; // Set default container.
+
+ // Extend the instance with options
+ //
+ // Use deep extend to allow options.plupload to override individual
+ // default plupload keys.
+ $.extend( true, this, options );
+
+ // Proxy all methods so this always refers to the current instance.
+ for ( key in this ) {
+ if ( $.isFunction( this[ key ] ) )
+ this[ key ] = $.proxy( this[ key ], this );
+ }
+
+ // Ensure all elements are jQuery elements and have id attributes
+ // Then set the proper plupload arguments to the ids.
+ for ( key in elements ) {
+ if ( ! this[ key ] )
+ continue;
+
+ this[ key ] = $( this[ key ] ).first();
+ if ( ! this[ key ].prop('id') )
+ this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ );
+ this.plupload[ elements[ key ] ] = this[ key ].prop('id');
+ }
+
+ this.uploader = new plupload.Uploader( this.plupload );
+ delete this.plupload;
+
+ // Set default params and remove this.params alias.
+ this.param( this.params || {} );
+ delete this.params;
+
+ this.uploader.bind( 'Init', this.init );
+
+ this.uploader.init();
+
+ this.uploader.bind( 'UploadProgress', this.progress );
+
+ this.uploader.bind( 'FileUploaded', function( up, file, response ) {
+ response = JSON.parse( response.response );
+
+ if ( ! response || ! response.type || ! response.data )
+ return self.error( pluploadL10n.default_error );
+
+ if ( 'error' === response.type )
+ return self.error( response.data.message, response.data );
+
+ if ( 'success' === response.type )
+ return self.success( response.data );
+
+ });
+
+ this.uploader.bind( 'Error', function( up, error ) {
+ var message = pluploadL10n.default_error,
+ key;
+
+ // Check for plupload errors.
+ for ( key in Uploader.errorMap ) {
+ if ( error.code === plupload[ key ] ) {
+ message = Uploader.errorMap[ key ];
+ break;
+ }
+ }
+
+ self.error( message, error );
+ up.refresh();
+ });
+
+ this.uploader.bind( 'FilesAdded', function( up, files ) {
+ $.each( files, function() {
+ self.added( this );
+ });
+
+ up.refresh();
+ up.start();
+ });
+ };
+
+ Uploader.uuid = 0;
+
+ Uploader.errorMap = {
+ 'FAILED': pluploadL10n.upload_failed,
+ 'FILE_EXTENSION_ERROR': pluploadL10n.invalid_filetype,
+ // 'FILE_SIZE_ERROR': '',
+ 'IMAGE_FORMAT_ERROR': pluploadL10n.not_an_image,
+ 'IMAGE_MEMORY_ERROR': pluploadL10n.image_memory_exceeded,
+ 'IMAGE_DIMENSIONS_ERROR': pluploadL10n.image_dimensions_exceeded,
+ 'GENERIC_ERROR': pluploadL10n.upload_failed,
+ 'IO_ERROR': pluploadL10n.io_error,
+ 'HTTP_ERROR': pluploadL10n.http_error,
+ 'SECURITY_ERROR': pluploadL10n.security_error
+ };
+
+ $.extend( Uploader.prototype, {
+ /**
+ * Acts as a shortcut to extending the uploader's multipart_params object.
+ *
+ * param( key )
+ * Returns the value of the key.
+ *
+ * param( key, value )
+ * Sets the value of a key.
+ *
+ * param( map )
+ * Sets values for a map of data.
+ */
+ param: function( key, value ) {
+ if ( arguments.length === 1 && typeof key === 'string' )
+ return this.uploader.settings.multipart_params[ key ];
+
+ if ( arguments.length > 1 ) {
+ this.uploader.settings.multipart_params[ key ] = value;
+ } else {
+ $.extend( this.uploader.settings.multipart_params, key );
+ }
+ },
+
+ init: function() {},
+ error: function() {},
+ success: function() {},
+ added: function() {},
+ progress: function() {},
+ complete: function() {}
+ });
+
+ exports.Uploader = Uploader;
+})( wp, jQuery );
\ No newline at end of file
diff --git a/wp-includes/js/plupload/wp-plupload.js b/wp-includes/js/plupload/wp-plupload.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/wp-includes/media.php b/wp-includes/media.php
index e3b9008e34..e67563b87f 100644
--- a/wp-includes/media.php
+++ b/wp-includes/media.php
@@ -1441,3 +1441,46 @@ function wp_embed_handler_googlevideo( $matches, $attr, $url, $rawattr ) {
return apply_filters( 'embed_googlevideo', '', $matches, $attr, $url, $rawattr );
}
+
+/**
+ * Prints default plupload arguments.
+ *
+ * @since 3.4.0
+ */
+function wp_plupload_default_settings() {
+ global $wp_scripts;
+
+ $max_upload_size = wp_max_upload_size();
+
+ $params = array(
+ 'action' => 'upload-attachment',
+ );
+ $params = apply_filters( 'plupload_default_params', $params );
+
+ $params['_wpnonce'] = wp_create_nonce( 'media-form' );
+
+ $settings = array(
+ 'runtimes' => 'html5,silverlight,flash,html4',
+ 'file_data_name' => 'async-upload', // key passed to $_FILE.
+ 'multiple_queues' => true,
+ 'max_file_size' => $max_upload_size . 'b',
+ 'url' => admin_url( 'admin-ajax.php' ),
+ 'flash_swf_url' => includes_url( 'js/plupload/plupload.flash.swf' ),
+ 'silverlight_xap_url' => includes_url( 'js/plupload/plupload.silverlight.xap' ),
+ 'filters' => array( array( 'title' => __( 'Allowed Files' ), 'extensions' => '*') ),
+ 'multipart' => true,
+ 'urlstream_upload' => true,
+ 'multipart_params' => $params,
+ );
+
+ $settings = apply_filters( 'plupload_default_settings', $settings );
+
+ $script = 'var wpPluploadDefaults = ' . json_encode( $settings ) . ';';
+
+ $data = $wp_scripts->get_data( 'wp-plupload', 'data' );
+ if ( $data )
+ $script = "$data\n$script";
+
+ $wp_scripts->add_data( 'wp-plupload', 'data', $script );
+}
+add_action( 'admin_init', 'wp_plupload_default_settings' );
diff --git a/wp-includes/script-loader.php b/wp-includes/script-loader.php
index 14f86bcb38..f1b9073345 100644
--- a/wp-includes/script-loader.php
+++ b/wp-includes/script-loader.php
@@ -228,6 +228,9 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'plupload-handlers', "/wp-includes/js/plupload/handlers$suffix.js", array('plupload-all', 'jquery') );
$scripts->localize( 'plupload-handlers', 'pluploadL10n', $uploader_l10n );
+ $scripts->add( 'wp-plupload', "/wp-includes/js/plupload/wp-plupload$suffix.js", array('plupload-all', 'jquery', 'json2') );
+ $scripts->localize( 'wp-plupload', 'pluploadL10n', $uploader_l10n );
+
// keep 'swfupload' for back-compat.
$scripts->add( 'swfupload', '/wp-includes/js/swfupload/swfupload.js', array(), '2201-20110113');
$scripts->add( 'swfupload-swfobject', '/wp-includes/js/swfupload/plugins/swfupload.swfobject.js', array('swfupload', 'swfobject'), '2201a');