diff --git a/wp-admin/admin-ajax.php b/wp-admin/admin-ajax.php index d9060ceb9..ad2fdae66 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 93d1b4a84..be7785784 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 710026c22..54b1eac7a 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 68576c501..1763a7f28 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 5787499bb..0b4baf1cc 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 000000000..8ad273860 --- /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 000000000..e69de29bb diff --git a/wp-includes/media.php b/wp-includes/media.php index e3b9008e3..e67563b87 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 14f86bcb3..f1b907334 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');