diff --git a/core-blocks/file/edit.js b/core-blocks/file/edit.js index 0f9bf176675b9d..46ffb595520d85 100644 --- a/core-blocks/file/edit.js +++ b/core-blocks/file/edit.js @@ -21,7 +21,7 @@ import { MediaPlaceholder, BlockControls, RichText, - editorMediaUpload, + mediaUpload, } from '@wordpress/editor'; import { compose } from '@wordpress/compose'; @@ -55,7 +55,7 @@ class FileEdit extends Component { if ( this.isBlobURL( href ) ) { const file = getBlobByURL( href ); - editorMediaUpload( { + mediaUpload( { allowedType: '*', filesList: [ file ], onFileChange: ( [ media ] ) => this.onSelectFile( media ), diff --git a/core-blocks/gallery/edit.js b/core-blocks/gallery/edit.js index 14267a27cd459b..c706ea74817d11 100644 --- a/core-blocks/gallery/edit.js +++ b/core-blocks/gallery/edit.js @@ -25,7 +25,7 @@ import { MediaUpload, MediaPlaceholder, InspectorControls, - editorMediaUpload, + mediaUpload, } from '@wordpress/editor'; /** @@ -137,7 +137,7 @@ class GalleryEdit extends Component { addFiles( files ) { const currentImages = this.props.attributes.images || []; const { noticeOperations, setAttributes } = this.props; - editorMediaUpload( { + mediaUpload( { allowedType: 'image', filesList: files, onFileChange: ( images ) => { diff --git a/core-blocks/gallery/index.js b/core-blocks/gallery/index.js index b12ae034d5e3a7..333dedfea1c1bd 100644 --- a/core-blocks/gallery/index.js +++ b/core-blocks/gallery/index.js @@ -8,7 +8,7 @@ import { filter, every } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { createBlock } from '@wordpress/blocks'; -import { RichText, editorMediaUpload } from '@wordpress/editor'; +import { RichText, mediaUpload } from '@wordpress/editor'; import { createBlobURL } from '@wordpress/blob'; /** @@ -135,7 +135,7 @@ export const settings = { const block = createBlock( 'core/gallery', { images: files.map( ( file ) => ( { url: createBlobURL( file ) } ) ), } ); - editorMediaUpload( { + mediaUpload( { filesList: files, onFileChange: ( images ) => onChange( block.clientId, { images } ), allowedType: 'image', diff --git a/core-blocks/image/edit.js b/core-blocks/image/edit.js index 4453072293d84b..15b4e2a5a6c0eb 100644 --- a/core-blocks/image/edit.js +++ b/core-blocks/image/edit.js @@ -36,7 +36,7 @@ import { MediaPlaceholder, MediaUpload, BlockAlignmentToolbar, - editorMediaUpload, + mediaUpload, } from '@wordpress/editor'; import { withViewportMatch } from '@wordpress/viewport'; import { compose } from '@wordpress/compose'; @@ -84,7 +84,7 @@ class ImageEdit extends Component { const file = getBlobByURL( url ); if ( file ) { - editorMediaUpload( { + mediaUpload( { filesList: [ file ], onFileChange: ( [ image ] ) => { setAttributes( { ...image } ); diff --git a/docs/reference/deprecated.md b/docs/reference/deprecated.md index 699cf8ad1ac224..4be6209416cada 100644 --- a/docs/reference/deprecated.md +++ b/docs/reference/deprecated.md @@ -1,5 +1,12 @@ Gutenberg's deprecation policy is intended to support backwards-compatibility for two minor releases, when possible. The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +## 3.6.0 + + - `wp.editor.editorMediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. + - `wp.utils.getMimeTypesArray` has been removed. + - `wp.utils.mediaUpload` has been removed. Please use `wp.editor.mediaUpload` instead. + - `wp.utils.preloadImage` has been removed. + ## 3.5.0 - `wp.components.ifCondition` has been removed. Please use `wp.compose.ifCondition` instead. diff --git a/editor/components/media-placeholder/index.js b/editor/components/media-placeholder/index.js index 0fb751644af70d..30ecc9c50e2639 100644 --- a/editor/components/media-placeholder/index.js +++ b/editor/components/media-placeholder/index.js @@ -22,7 +22,7 @@ import deprecated from '@wordpress/deprecated'; */ import './style.scss'; import MediaUpload from '../media-upload'; -import editorMediaUpload from '../../utils/editor-media-upload'; +import { mediaUpload } from '../../utils/'; class MediaPlaceholder extends Component { constructor() { @@ -84,7 +84,7 @@ class MediaPlaceholder extends Component { onFilesUpload( files ) { const { onSelect, type, multiple, onError } = this.props; const setMedia = multiple ? onSelect : ( [ media ] ) => onSelect( media ); - editorMediaUpload( { + mediaUpload( { allowedType: type, filesList: files, onFileChange: setMedia, diff --git a/editor/store/defaults.js b/editor/store/defaults.js index 7fe4ffe517011a..b0b597a9d96d67 100644 --- a/editor/store/defaults.js +++ b/editor/store/defaults.js @@ -81,4 +81,10 @@ export const EDITOR_SETTINGS_DEFAULTS = { // Allowed block types for the editor, defaulting to true (all supported). allowedBlockTypes: true, + + // Maximum upload size in bytes allowed for the site. + maxUploadFileSize: 0, + + // List of allowed mime types and file extensions. + allowedMimeTypes: null, }; diff --git a/editor/utils/index.js b/editor/utils/index.js index b1f9d6a838063e..be415d33c3ab08 100644 --- a/editor/utils/index.js +++ b/editor/utils/index.js @@ -1 +1,20 @@ -export { default as editorMediaUpload } from './editor-media-upload'; +/** + * WordPress dependencies + */ +import deprecated from '@wordpress/deprecated'; + +/** + * Internal dependencies + */ +import mediaUpload from './media-upload'; + +export { mediaUpload }; + +export function editorMediaUpload( ...params ) { + deprecated( 'wp.editor.editorMediaUpload', { + version: '3.6', + alternative: 'wp.editor.mediaUpload', + plugin: 'Gutenberg', + } ); + mediaUpload( ...params ); +} diff --git a/editor/utils/editor-media-upload/index.js b/editor/utils/media-upload/index.js similarity index 74% rename from editor/utils/editor-media-upload/index.js rename to editor/utils/media-upload/index.js index bd6e08683a96da..5a8e39d7159e06 100644 --- a/editor/utils/editor-media-upload/index.js +++ b/editor/utils/media-upload/index.js @@ -7,7 +7,11 @@ import { noop } from 'lodash'; * WordPress dependencies */ import { select } from '@wordpress/data'; -import { mediaUpload } from '@wordpress/utils'; + +/** + * Internal dependencies + */ +import { mediaUpload } from './media-upload'; /** * Upload a media file when the file upload button is activated. @@ -15,29 +19,34 @@ import { mediaUpload } from '@wordpress/utils'; * * @param {Object} $0 Parameters object passed to the function. * @param {string} $0.allowedType The type of media that can be uploaded, or '*' to allow all. - * @param {?Object} $0.additionalData Additional data to include in the request. * @param {Array} $0.filesList List of files. * @param {?number} $0.maxUploadFileSize Maximum upload size in bytes allowed for the site. * @param {Function} $0.onError Function called when an error happens. * @param {Function} $0.onFileChange Function called each time a file or a temporary representation of the file is available. */ -export default function editorMediaUpload( { +export default function( { allowedType, filesList, maxUploadFileSize, onError = noop, onFileChange, } ) { - const postId = select( 'core/editor' ).getCurrentPostId(); + const { + getCurrentPostId, + getEditorSettings, + } = select( 'core/editor' ); + const allowedMimeTypes = getEditorSettings().allowedMimeTypes; + maxUploadFileSize = maxUploadFileSize || getEditorSettings().maxUploadFileSize; mediaUpload( { allowedType, filesList, onFileChange, additionalData: { - post: postId, + post: getCurrentPostId(), }, maxUploadFileSize, onError: ( { message } ) => onError( message ), + allowedMimeTypes, } ); } diff --git a/editor/utils/media-upload/media-upload.js b/editor/utils/media-upload/media-upload.js new file mode 100644 index 00000000000000..b925b7032bf8d7 --- /dev/null +++ b/editor/utils/media-upload/media-upload.js @@ -0,0 +1,168 @@ +/** + * External Dependencies + */ +import { compact, flatMap, forEach, get, has, includes, map, noop, startsWith } from 'lodash'; + +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Browsers may use unexpected mime types, and they differ from browser to browser. + * This function computes a flexible array of mime types from the mime type structured provided by the server. + * Converts { jpg|jpeg|jpe: "image/jpeg" } into [ "image/jpeg", "image/jpg", "image/jpeg", "image/jpe" ] + * The computation of this array instead of directly using the object, + * solves the problem in chrome where mp3 files have audio/mp3 as mime type instead of audio/mpeg. + * https://bugs.chromium.org/p/chromium/issues/detail?id=227004 + * + * @param {?Object} wpMimeTypesObject Mime type object received from the server. + * Extensions are keys separated by '|' and values are mime types associated with an extension. + * + * @return {?Array} An array of mime types or the parameter passed if it was "falsy". + */ +export function getMimeTypesArray( wpMimeTypesObject ) { + if ( ! wpMimeTypesObject ) { + return wpMimeTypesObject; + } + return flatMap( wpMimeTypesObject, ( mime, extensionsString ) => { + const [ type ] = mime.split( '/' ); + const extensions = extensionsString.split( '|' ); + return [ mime, ...map( extensions, ( extension ) => `${ type }/${ extension }` ) ]; + } ); +} + +/** + * Media Upload is used by audio, image, gallery, video, and file blocks to + * handle uploading a media file when a file upload button is activated. + * + * TODO: future enhancement to add an upload indicator. + * + * @param {Object} $0 Parameters object passed to the function. + * @param {string} $0.allowedType The type of media that can be uploaded, or '*' to allow all. + * @param {?Object} $0.additionalData Additional data to include in the request. + * @param {Array} $0.filesList List of files. + * @param {?number} $0.maxUploadFileSize Maximum upload size in bytes allowed for the site. + * @param {Function} $0.onError Function called when an error happens. + * @param {Function} $0.onFileChange Function called each time a file or a temporary representation of the file is available. + * @param {?Object} $0.allowedMimeTypes List of allowed mime types and file extensions. + */ +export function mediaUpload( { + allowedType, + additionalData = {}, + filesList, + maxUploadFileSize, + onError = noop, + onFileChange, + allowedMimeTypes = null, +} ) { + // Cast filesList to array + const files = [ ...filesList ]; + + const filesSet = []; + const setAndUpdateFiles = ( idx, value ) => { + filesSet[ idx ] = value; + onFileChange( compact( filesSet ) ); + }; + + // Allowed type specified by consumer + const isAllowedType = ( fileType ) => { + return ( allowedType === '*' ) || startsWith( fileType, `${ allowedType }/` ); + }; + + // Allowed types for the current WP_User + const allowedMimeTypesForUser = getMimeTypesArray( allowedMimeTypes ); + const isAllowedMimeTypeForUser = ( fileType ) => { + return includes( allowedMimeTypesForUser, fileType ); + }; + + files.forEach( ( mediaFile, idx ) => { + if ( ! isAllowedType( mediaFile.type ) ) { + return; + } + + // verify if user is allowed to upload this mime type + if ( allowedMimeTypesForUser && ! isAllowedMimeTypeForUser( mediaFile.type ) ) { + onError( { + code: 'MIME_TYPE_NOT_ALLOWED_FOR_USER', + message: __( 'Sorry, this file type is not permitted for security reasons.' ), + file: mediaFile, + } ); + return; + } + + // verify if file is greater than the maximum file upload size allowed for the site. + if ( maxUploadFileSize && mediaFile.size > maxUploadFileSize ) { + onError( { + code: 'SIZE_ABOVE_LIMIT', + message: sprintf( + // translators: %s: file name + __( '%s exceeds the maximum upload size for this site.' ), + mediaFile.name + ), + file: mediaFile, + } ); + return; + } + + // Set temporary URL to create placeholder media file, this is replaced + // with final file from media gallery when upload is `done` below + filesSet.push( { url: window.URL.createObjectURL( mediaFile ) } ); + onFileChange( filesSet ); + + return createMediaFromFile( mediaFile, additionalData ) + .then( ( savedMedia ) => { + const mediaObject = { + alt: savedMedia.alt_text, + caption: get( savedMedia, [ 'caption', 'raw' ], '' ), + id: savedMedia.id, + link: savedMedia.link, + title: savedMedia.title.raw, + url: savedMedia.source_url, + mediaDetails: {}, + }; + if ( has( savedMedia, [ 'media_details', 'sizes' ] ) ) { + mediaObject.mediaDetails.sizes = get( savedMedia, [ 'media_details', 'sizes' ], {} ); + } + setAndUpdateFiles( idx, mediaObject ); + } ) + .catch( ( error ) => { + // Reset to empty on failure. + setAndUpdateFiles( idx, null ); + let message; + if ( has( error, [ 'message' ] ) ) { + message = get( error, [ 'message' ] ); + } else { + message = sprintf( + // translators: %s: file name + __( 'Error while uploading file %s to the media library.' ), + mediaFile.name + ); + } + onError( { + code: 'GENERAL', + message, + file: mediaFile, + } ); + } ); + } ); +} + +/** + * @param {File} file Media File to Save. + * @param {?Object} additionalData Additional data to include in the request. + * + * @return {Promise} Media Object Promise. + */ +function createMediaFromFile( file, additionalData ) { + // Create upload payload + const data = new window.FormData(); + data.append( 'file', file, file.name || file.type.replace( '/', '.' ) ); + forEach( additionalData, ( ( value, key ) => data.append( key, value ) ) ); + return apiFetch( { + path: '/wp/v2/media', + body: data, + method: 'POST', + } ); +} diff --git a/utils/test/mediaupload.js b/editor/utils/media-upload/test/media-upload.js similarity index 73% rename from utils/test/mediaupload.js rename to editor/utils/media-upload/test/media-upload.js index 35dca2906e2499..9090bad7467017 100644 --- a/utils/test/mediaupload.js +++ b/editor/utils/media-upload/test/media-upload.js @@ -1,14 +1,7 @@ -/* eslint-disable no-console */ - /** * Internal dependencies */ -import { mediaUpload, getMimeTypesArray } from '../mediaupload'; - -// mediaUpload is passed the onImagesChange function -// so we can stub that out have it pass the data to -// console.error to check if proper thing is called -const onFileChange = ( obj ) => console.error( obj ); +import { mediaUpload, getMimeTypesArray } from '../media-upload'; const invalidMediaObj = { url: 'https://cldup.com/uuUqE_dXzy.jpg', @@ -23,34 +16,25 @@ const validMediaObj = { }; describe( 'mediaUpload', () => { - const originalConsoleError = console.error; - const originalGetUserSetting = window.getUserSetting; - - beforeEach( () => { - console.error = jest.fn(); - } ); - - afterEach( () => { - console.error = originalConsoleError; - window.getUserSetting = originalGetUserSetting; - } ); + const onFileChangeSpy = jest.fn(); it( 'should do nothing on no files', () => { - mediaUpload( { filesList: [ ], onFileChange, allowedType: 'image' } ); - expect( console.error ).not.toHaveBeenCalled(); + mediaUpload( { filesList: [ ], onFileChange: onFileChangeSpy, allowedType: 'image' } ); + expect( onFileChangeSpy ).not.toHaveBeenCalled(); } ); it( 'should do nothing on invalid image type', () => { - mediaUpload( { filesList: [ invalidMediaObj ], onFileChange, allowedType: 'image' } ); - expect( console.error ).not.toHaveBeenCalled(); + mediaUpload( { filesList: [ invalidMediaObj ], onFileChange: onFileChangeSpy, allowedType: 'image' } ); + expect( onFileChangeSpy ).not.toHaveBeenCalled(); } ); it( 'should call error handler with the correct error object if file size is greater than the maximum', () => { const onError = jest.fn(); + mediaUpload( { allowedType: 'image', filesList: [ validMediaObj ], - onFileChange, + onFileChange: onFileChangeSpy, maxUploadFileSize: 512, onError, } ); @@ -63,14 +47,14 @@ describe( 'mediaUpload', () => { it( 'should call error handler with the correct error object if file type is not allowed for user', () => { const onError = jest.fn(); - global._wpMediaSettings = { - allowedMimeTypes: { aac: 'audio/aac' }, - }; + const allowedMimeTypes = { aac: 'audio/aac' }; + mediaUpload( { allowedType: 'image', filesList: [ validMediaObj ], - onFileChange, + onFileChange: onFileChangeSpy, onError, + allowedMimeTypes, } ); expect( onError ).toBeCalledWith( { code: 'MIME_TYPE_NOT_ALLOWED_FOR_USER', diff --git a/lib/client-assets.php b/lib/client-assets.php index 8b93863e4f89dc..dba04765eb2229 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -227,7 +227,7 @@ function gutenberg_register_scripts_and_styles() { wp_register_script( 'wp-utils', gutenberg_url( 'build/utils/index.js' ), - array( 'lodash', 'wp-api-fetch', 'wp-deprecated', 'wp-html-entities', 'wp-i18n', 'wp-keycodes' ), + array( 'lodash', 'wp-api-fetch', 'wp-deprecated', 'wp-html-entities', 'wp-i18n', 'wp-keycodes', 'wp-editor' ), filemtime( gutenberg_dir_path() . 'build/utils/index.js' ), true ); @@ -458,7 +458,6 @@ function gutenberg_register_scripts_and_styles() { 'wp-keycodes', 'wp-element', 'wp-plugins', - 'wp-utils', 'wp-viewport', 'wp-tinymce', 'tinymce-latest-lists', @@ -1093,6 +1092,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { // https://github.com/WordPress/gutenberg/issues/5667. add_filter( 'user_can_richedit', '__return_true' ); + wp_enqueue_script( 'wp-utils' ); wp_enqueue_script( 'wp-edit-post' ); global $post; @@ -1160,16 +1160,6 @@ function gutenberg_editor_scripts_and_styles( $hook ) { ); } - $max_upload_size = wp_max_upload_size(); - if ( ! $max_upload_size ) { - $max_upload_size = 0; - } - // Initialize media settings. - wp_add_inline_script( 'wp-editor', 'window._wpMediaSettings = ' . wp_json_encode( array( - 'maxUploadSize' => $max_upload_size, - 'allowedMimeTypes' => get_allowed_mime_types(), - ) ), 'before' ); - // Prepare Jed locale data. $locale_data = gutenberg_get_jed_locale_data( 'gutenberg' ); wp_add_inline_script( @@ -1251,6 +1241,12 @@ function gutenberg_editor_scripts_and_styles( $hook ) { '' => apply_filters( 'default_page_template_title', __( 'Default template', 'gutenberg' ), 'rest-api' ), ), $available_templates ) : $available_templates; + // Media settings. + $max_upload_size = wp_max_upload_size(); + if ( ! $max_upload_size ) { + $max_upload_size = 0; + } + $editor_settings = array( 'alignWide' => $align_wide || ! empty( $gutenberg_theme_support[0]['wide-images'] ), // Backcompat. Use `align-wide` outside of `gutenberg` array. 'availableTemplates' => $available_templates, @@ -1261,6 +1257,8 @@ function gutenberg_editor_scripts_and_styles( $hook ) { 'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Write your story', 'gutenberg' ), $post ), 'isRTL' => is_rtl(), 'autosaveInterval' => 10, + 'maxUploadFileSize' => $max_upload_size, + 'allowedMimeTypes' => get_allowed_mime_types(), ); $post_autosave = get_autosave_newer_than_post_save( $post ); diff --git a/utils/index.js b/utils/index.js index c96efed8801f9a..d206dc2362eea7 100644 --- a/utils/index.js +++ b/utils/index.js @@ -1,4 +1,3 @@ -export * from './mediaupload'; - // Deprecations export * from './deprecated'; +export * from './mediaupload'; diff --git a/utils/mediaupload.js b/utils/mediaupload.js index 20e38427ab8e2d..af592a2e8beb67 100644 --- a/utils/mediaupload.js +++ b/utils/mediaupload.js @@ -6,12 +6,10 @@ import { compact, flatMap, forEach, get, has, includes, map, noop, startsWith } /** * WordPress dependencies */ +import deprecated from '@wordpress/deprecated'; import { __, sprintf } from '@wordpress/i18n'; - -/** - * WordPress dependencies - */ import apiFetch from '@wordpress/api-fetch'; +import { select } from '@wordpress/data'; /** * Browsers may use unexpected mime types, and they differ from browser to browser. @@ -27,6 +25,11 @@ import apiFetch from '@wordpress/api-fetch'; * @return {?Array} An array of mime types or the parameter passed if it was "falsy". */ export function getMimeTypesArray( wpMimeTypesObject ) { + deprecated( 'wp.utils.getMimeTypesArray', { + version: '3.6', + plugin: 'Gutenberg', + } ); + if ( ! wpMimeTypesObject ) { return wpMimeTypesObject; } @@ -55,10 +58,19 @@ export function mediaUpload( { allowedType, additionalData = {}, filesList, - maxUploadFileSize = get( window, [ '_wpMediaSettings', 'maxUploadSize' ], 0 ), + maxUploadFileSize, onError = noop, onFileChange, } ) { + deprecated( 'wp.utils.mediaUpload', { + version: '3.6', + alternative: 'wp.editor.mediaUpload', + plugin: 'Gutenberg', + } ); + + const editorSettings = select( 'core/editor' ).getSettings(); + maxUploadFileSize = maxUploadFileSize || editorSettings.maxUploadFileSize; + // Cast filesList to array const files = [ ...filesList ]; @@ -74,7 +86,7 @@ export function mediaUpload( { }; // Allowed types for the current WP_User - const allowedMimeTypesForUser = getMimeTypesArray( get( window, [ '_wpMediaSettings', 'allowedMimeTypes' ] ) ); + const allowedMimeTypesForUser = getMimeTypesArray( editorSettings.allowedMimeTypes ); const isAllowedMimeTypeForUser = ( fileType ) => { return includes( allowedMimeTypesForUser, fileType ); }; @@ -176,6 +188,11 @@ function createMediaFromFile( file, additionalData ) { * @return {Promise} Promise resolved once the image is preloaded. */ export function preloadImage( url ) { + deprecated( 'wp.utils.preloadImage', { + version: '3.6', + plugin: 'Gutenberg', + } ); + return new Promise( ( resolve ) => { const newImg = new window.Image(); newImg.onload = function() {