| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * Routes media frame uploads through the client-side media pipeline. 3 * 4 * Every wp.media frame - the Media Library grid on wp-admin/upload.php and 5 * the media modal the block editor, the site editor and the block widgets 6 * screen open - uploads via wp.Uploader / plupload to async-upload.php. 7 * When the browser is cross-origin isolated and supports the client-side 8 * pipeline, this script intercepts the uploader's FilesAdded handler and 9 * hands files to wp.mediaUploadPipeline (media-upload-pipeline.js) 10 * instead: the original image is uploaded via the REST API and thumbnails 11 * are generated in the browser (wasm-vips), then sideloaded and finalized. 12 * 13 * The frame's own UI is reused: the same placeholder tiles, progress bars, 14 * "Uploading n/m" status, and error list that wp-plupload.js drives. 15 * 16 * When client-side support is unavailable the script cleanly no-ops and 17 * the classic plupload flow is left untouched. 18 * 19 * @output wp-admin/js/media-frame-upload.js 20 */ 21 22 /* global plupload */ 23 24 ( function () { 25 // Guard against double execution (e.g. duplicate enqueues). 26 if ( window.__wpMediaFrameUpload ) { 27 return; 28 } 29 30 const pipeline = window.wp && wp.mediaUploadPipeline; 31 32 // Bail unless the browser actually supports client-side processing. This 33 // is the clean no-op: when the isolation headers did not land, classic 34 // plupload keeps handling uploads. 35 if ( 36 ! pipeline || 37 typeof plupload === 'undefined' || 38 ! wp.Uploader || 39 ! wp.media || 40 ! pipeline.configure() 41 ) { 42 return; 43 } 44 45 window.__wpMediaFrameUpload = true; 46 47 /** 48 * Resets the upload queue once every attachment has finished uploading. 49 * 50 * Parity with wp-plupload.js so browse mode flips back when done. 51 */ 52 function maybeResetQueue() { 53 const complete = wp.Uploader.queue.all( function ( 54 /** @type {WPAttachment} */ attachment 55 ) { 56 return ! attachment.get( 'uploading' ); 57 } ); 58 59 if ( complete ) { 60 wp.Uploader.queue.reset(); 61 } 62 } 63 64 /** 65 * Handles a completed upload by syncing the grid tile with the server data. 66 * 67 * @param {WPUploader} wpUploader The wp.Uploader instance that queued the file. 68 * @param {WPAttachment} model The placeholder Attachment model. 69 * @param {PipelineAttachment} attachment The finalized attachment from the pipeline. 70 */ 71 function handleSuccess( wpUploader, model, attachment ) { 72 // Not silent: Attachments re-keys a model from its cid to its id when 73 // it sees the change event. Without that the collection cannot find the 74 // attachment by id, and refreshing the library duplicates its tile. 75 model.set( { id: attachment.id } ); 76 77 // Register the model in Attachments.all (parity with wp-plupload.js). 78 wp.media.model.Attachment.get( attachment.id, model ); 79 80 // Clear the uploading state before the refetch lands, not after. The 81 // attachment details view re-renders on the refetch's changes, so it has 82 // to see `uploading: false` by then or it keeps showing a progress bar. 83 // wp-plupload.js sets both in one call for the same reason. 84 [ 'file', 'loaded', 'size', 'percent' ].forEach( function ( key ) { 85 model.unset( key, { silent: true } ); 86 } ); 87 model.set( { uploading: false }, { silent: true } ); 88 89 model 90 .fetch() 91 .fail( function () { 92 // Fetch failed, but the upload succeeded: fill the tile with what 93 // the pipeline gave us so it is not left empty. 94 model.set( attachment ); 95 } ) 96 .always( function () { 97 maybeResetQueue(); 98 99 // Parity with wp-plupload.js, which exposes this callback so 100 // other code can react to a finished upload. 101 wpUploader.success( model ); 102 } ); 103 } 104 105 /** 106 * Handles an upload error by removing the tile and surfacing the message. 107 * 108 * The error goes into wp.Uploader.errors exactly like a classic upload 109 * error, so the grid's error sidebar renders it, announces it, and 110 * moves focus to its Dismiss button. 111 * 112 * @param {WPUploader} wpUploader The wp.Uploader instance that queued the file. 113 * @param {WPAttachment} model The placeholder Attachment model. 114 * @param {UploadError} error The upload error. 115 * @param {string} fileName Name of the file (for the error label). 116 */ 117 function handleError( wpUploader, model, error, fileName ) { 118 const message = pipeline.getErrorText( error, fileName ); 119 const file = { name: fileName }; 120 121 model.destroy(); 122 123 wp.Uploader.errors.unshift( { 124 message: message, 125 data: {}, 126 file: file, 127 } ); 128 129 maybeResetQueue(); 130 131 // Parity with wp-plupload.js, which exposes this callback so other 132 // code can react to a failed upload. 133 wpUploader.error( message, {}, file ); 134 } 135 136 /** 137 * Whether a plupload uploader posts attachments the way wp.Uploader does 138 * by default. 139 * 140 * The init wrapper below reaches every wp.Uploader built on the page, 141 * and a plugin can build one for its own purposes. Core's defaults tell 142 * an attachment upload apart: it posts to async-upload.php with the 143 * `upload-attachment` action. Anything a plugin pointed elsewhere, or 144 * gave another action, is uploading something else and is left alone. 145 * 146 * @param {plupload.Uploader} up The plupload uploader instance. 147 * @return {boolean} True when the uploader posts attachments. 148 */ 149 function isAttachmentUploader( up ) { 150 const settings = up.settings || {}; 151 const params = settings.multipart_params || {}; 152 const url = new URL( settings.url || '', location.href ); 153 154 return ( 155 'upload-attachment' === params.action && 156 url.pathname.endsWith( 'async-upload.php' ) 157 ); 158 } 159 160 /** 161 * Intercepts files added to a plupload uploader. 162 * 163 * Builds the same placeholder tiles as wp-plupload for the files the 164 * pipeline takes (see wp.mediaUploadPipeline.routeBatch()). Returns 165 * undefined to leave the whole batch to plupload's built-in handler. 166 * 167 * @param {WPUploader} wpUploader The wp.Uploader instance. 168 * @param {plupload.Uploader} up The plupload uploader instance. 169 * @param {plupload.File[]} files Files added to the queue. 170 * @return {boolean|undefined} False to suppress the built-in handler. 171 */ 172 function handleFilesAdded( wpUploader, up, files ) { 173 if ( ! isAttachmentUploader( up ) ) { 174 return; 175 } 176 177 // The classic flow attaches to `post_id` the same way, and rejects 178 // a post the user cannot edit just as the REST API does. 179 const params = ( up.settings && up.settings.multipart_params ) || {}; 180 181 return pipeline.routeBatch( up, files, { 182 postId: parseInt( params.post_id, 10 ) || 0, 183 onQueue: function ( 184 /** @type {plupload.File} */ file, 185 /** @type {File} */ nativeFile 186 ) { 187 // Build the same placeholder attributes as wp-plupload.js so 188 // the grid's progress tiles and "Uploading n/m" status work 189 // unchanged. 190 /** @type {PlaceholderAttributes} */ 191 const attributes = { 192 file: file, 193 uploading: true, 194 date: new Date(), 195 filename: file.name, 196 menuOrder: 0, 197 uploadedTo: wp.media.model.settings.post.id, 198 loaded: file.loaded, 199 size: file.size, 200 percent: file.percent, 201 }; 202 203 /* 204 * Early mime type scanning for images, as wp-plupload.js does, 205 * extended with the formats the client-side pipeline accepts. 206 */ 207 const image = /(?:jpe?g|png|gif|webp|avif|heic|heif)$/i.exec( 208 file.name 209 ); 210 if ( image ) { 211 attributes.type = 'image'; 212 // `jpg` is not a valid subtype, so map it to `jpeg`. 213 const subtype = image[ 0 ].toLowerCase(); 214 attributes.subtype = 'jpg' === subtype ? 'jpeg' : subtype; 215 } 216 217 const model = wp.media.model.Attachment.create( attributes ); 218 wp.Uploader.queue.add( model ); 219 wpUploader.added( model ); 220 221 return { 222 onSuccess: function ( 223 /** @type {PipelineAttachment} */ attachment 224 ) { 225 handleSuccess( wpUploader, model, attachment ); 226 }, 227 onError: function ( /** @type {UploadError} */ error ) { 228 handleError( wpUploader, model, error, nativeFile.name ); 229 }, 230 // plupload builds its own tile for the file it took over. 231 onHandOff: function () { 232 model.destroy(); 233 }, 234 onProgress: function ( /** @type {number} */ percent ) { 235 model.set( { percent: percent } ); 236 wpUploader.progress( model ); 237 }, 238 }; 239 }, 240 } ); 241 } 242 243 // Wrap wp.Uploader.prototype.init (an empty stub called once per instance 244 // after plupload is initialized) to bind a higher-priority FilesAdded 245 // handler on every uploader instance: the grid's, and the one each media 246 // modal creates when it opens. 247 const originalInit = wp.Uploader.prototype.init; 248 wp.Uploader.prototype.init = function () { 249 originalInit.apply( this, arguments ); 250 251 const wpUploader = /** @type {WPUploader} */ ( this ); 252 const up = /** @type {plupload.Uploader|undefined} */ ( this.uploader ); 253 254 if ( ! up || up.__wpMediaFrameUploadBound ) { 255 return; 256 } 257 up.__wpMediaFrameUploadBound = true; 258 259 // plupload sorts handlers by priority (descending) and a `false` 260 // return breaks the chain, so priority 100 runs before and suppresses 261 // the built-in FilesAdded handler. 262 up.bind( 263 'FilesAdded', 264 function ( uploader, files ) { 265 return handleFilesAdded( wpUploader, uploader, files ); 266 }, 267 this, 268 100 269 ); 270 }; 271 } )();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Tue Oct 20 08:20:34 2026 | Cross-referenced by PHPXref |