Нет описания

videopress-plupload.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /* global pluploadL10n, plupload, _wpPluploadSettings, JSON */
  2. window.wp = window.wp || {};
  3. ( function ( exports, $ ) {
  4. var Uploader, vp;
  5. if ( typeof _wpPluploadSettings === 'undefined' ) {
  6. return;
  7. }
  8. /**
  9. * A WordPress uploader.
  10. *
  11. * The Plupload library provides cross-browser uploader UI integration.
  12. * This object bridges the Plupload API to integrate uploads into the
  13. * WordPress back end and the WordPress media experience.
  14. *
  15. * @param {object} options The options passed to the new plupload instance.
  16. * @param {object} options.container The id of uploader container.
  17. * @param {object} options.browser The id of button to trigger the file select.
  18. * @param {object} options.dropzone The id of file drop target.
  19. * @param {object} options.plupload An object of parameters to pass to the plupload instance.
  20. * @param {object} options.params An object of parameters to pass to $_POST when uploading the file.
  21. * Extends this.plupload.multipart_params under the hood.
  22. */
  23. Uploader = function ( options ) {
  24. var self = this,
  25. isIE =
  26. navigator.userAgent.indexOf( 'Trident/' ) !== -1 ||
  27. navigator.userAgent.indexOf( 'MSIE ' ) !== -1,
  28. elements = {
  29. container: 'container',
  30. browser: 'browse_button',
  31. dropzone: 'drop_element',
  32. },
  33. key,
  34. error;
  35. this.supports = {
  36. upload: Uploader.browser.supported,
  37. };
  38. this.supported = this.supports.upload;
  39. if ( ! this.supported ) {
  40. return;
  41. }
  42. // Arguments to send to pluplad.Uploader().
  43. // Use deep extend to ensure that multipart_params and other objects are cloned.
  44. this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults );
  45. this.container = document.body; // Set default container.
  46. // Extend the instance with options.
  47. //
  48. // Use deep extend to allow options.plupload to override individual
  49. // default plupload keys.
  50. $.extend( true, this, options );
  51. // Proxy all methods so this always refers to the current instance.
  52. for ( key in this ) {
  53. if ( $.isFunction( this[ key ] ) ) {
  54. this[ key ] = $.proxy( this[ key ], this );
  55. }
  56. }
  57. // Ensure all elements are jQuery elements and have id attributes,
  58. // then set the proper plupload arguments to the ids.
  59. for ( key in elements ) {
  60. if ( ! this[ key ] ) {
  61. continue;
  62. }
  63. this[ key ] = $( this[ key ] ).first();
  64. if ( ! this[ key ].length ) {
  65. delete this[ key ];
  66. continue;
  67. }
  68. if ( ! this[ key ].prop( 'id' ) ) {
  69. this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ );
  70. }
  71. this.plupload[ elements[ key ] ] = this[ key ].prop( 'id' );
  72. }
  73. // If the uploader has neither a browse button nor a dropzone, bail.
  74. if (
  75. ! ( this.browser && this.browser.length ) &&
  76. ! ( this.dropzone && this.dropzone.length )
  77. ) {
  78. return;
  79. }
  80. // Make sure flash sends cookies (seems in IE it does without switching to urlstream mode)
  81. if (
  82. ! isIE &&
  83. 'flash' === plupload.predictRuntime( this.plupload ) &&
  84. ( ! this.plupload.required_features ||
  85. ! this.plupload.required_features.hasOwnProperty( 'send_binary_string' ) )
  86. ) {
  87. this.plupload.required_features = this.plupload.required_features || {};
  88. this.plupload.required_features.send_binary_string = true;
  89. }
  90. // Initialize the plupload instance.
  91. this.uploader = new plupload.Uploader( this.plupload );
  92. delete this.plupload;
  93. // Set default params and remove this.params alias.
  94. this.param( this.params || {} );
  95. delete this.params;
  96. // Make sure that the VideoPress object is available
  97. if ( typeof exports.VideoPress !== 'undefined' ) {
  98. vp = exports.VideoPress;
  99. } else {
  100. window.console &&
  101. window.console.error( 'The VideoPress object was not loaded. Errors may occur.' );
  102. }
  103. /**
  104. * Custom error callback.
  105. *
  106. * Add a new error to the errors collection, so other modules can track
  107. * and display errors. @see wp.Uploader.errors.
  108. *
  109. * @param {string} message
  110. * @param {object} data
  111. * @param {plupload.File} file File that was uploaded.
  112. */
  113. error = function ( message, data, file ) {
  114. if ( file.attachment ) {
  115. file.attachment.destroy();
  116. }
  117. Uploader.errors.unshift( {
  118. message: message || pluploadL10n.default_error,
  119. data: data,
  120. file: file,
  121. } );
  122. self.error( message, data, file );
  123. };
  124. /**
  125. * After the Uploader has been initialized, initialize some behaviors for the dropzone.
  126. *
  127. * @param {plupload.Uploader} uploader Uploader instance.
  128. */
  129. this.uploader.bind( 'init', function ( uploader ) {
  130. var timer,
  131. active,
  132. dragdrop,
  133. dropzone = self.dropzone;
  134. dragdrop = self.supports.dragdrop = uploader.features.dragdrop && ! Uploader.browser.mobile;
  135. // Generate drag/drop helper classes.
  136. if ( ! dropzone ) {
  137. return;
  138. }
  139. dropzone.toggleClass( 'supports-drag-drop', !! dragdrop );
  140. if ( ! dragdrop ) {
  141. return dropzone.unbind( '.wp-uploader' );
  142. }
  143. // 'dragenter' doesn't fire correctly, simulate it with a limited 'dragover'.
  144. dropzone.bind( 'dragover.wp-uploader', function () {
  145. if ( timer ) {
  146. clearTimeout( timer );
  147. }
  148. if ( active ) {
  149. return;
  150. }
  151. dropzone.trigger( 'dropzone:enter' ).addClass( 'drag-over' );
  152. active = true;
  153. } );
  154. dropzone.bind( 'dragleave.wp-uploader, drop.wp-uploader', function () {
  155. // Using an instant timer prevents the drag-over class from
  156. // being quickly removed and re-added when elements inside the
  157. // dropzone are repositioned.
  158. //
  159. // @see https://core.trac.wordpress.org/ticket/21705
  160. timer = setTimeout( function () {
  161. active = false;
  162. dropzone.trigger( 'dropzone:leave' ).removeClass( 'drag-over' );
  163. }, 0 );
  164. } );
  165. self.ready = true;
  166. $( self ).trigger( 'uploader:ready' );
  167. } );
  168. this.uploader.bind( 'postinit', function ( up ) {
  169. up.refresh();
  170. self.init();
  171. } );
  172. this.uploader.init();
  173. if ( this.browser ) {
  174. this.browser.on( 'mouseenter', this.refresh );
  175. } else {
  176. this.uploader.disableBrowse( true );
  177. // If HTML5 mode, hide the auto-created file container.
  178. $( '#' + this.uploader.id + '_html5_container' ).hide();
  179. }
  180. /**
  181. * After files were filtered and added to the queue, create a model for each.
  182. *
  183. * @event FilesAdded
  184. * @param {plupload.Uploader} uploader Uploader instance.
  185. * @param {Array} files Array of file objects that were added to queue by the user.
  186. */
  187. this.uploader.bind( 'FilesAdded', function ( up, files ) {
  188. _.each( files, function ( file ) {
  189. var attributes, image;
  190. // Ignore failed uploads.
  191. if ( plupload.FAILED === file.status ) {
  192. return;
  193. }
  194. // Generate attributes for a new `Attachment` model.
  195. attributes = _.extend(
  196. {
  197. file: file,
  198. uploading: true,
  199. date: new Date(),
  200. filename: file.name,
  201. menuOrder: 0,
  202. uploadedTo: wp.media.model.settings.post.id,
  203. },
  204. _.pick( file, 'loaded', 'size', 'percent' )
  205. );
  206. // Handle early mime type scanning for images.
  207. image = /(?:jpe?g|png|gif|webp)$/i.exec( file.name );
  208. // For images set the model's type and subtype attributes.
  209. if ( image ) {
  210. attributes.type = 'image';
  211. // `jpeg`, `png` and `gif` are valid subtypes.
  212. // `jpg` is not, so map it to `jpeg`.
  213. attributes.subtype = 'jpg' === image[ 0 ] ? 'jpeg' : image[ 0 ];
  214. }
  215. // Create a model for the attachment, and add it to the Upload queue collection
  216. // so listeners to the upload queue can track and display upload progress.
  217. file.attachment = wp.media.model.Attachment.create( attributes );
  218. Uploader.queue.add( file.attachment );
  219. self.added( file.attachment );
  220. } );
  221. up.refresh();
  222. up.start();
  223. } );
  224. this.uploader.bind( 'UploadProgress', function ( up, file ) {
  225. file.attachment.set( _.pick( file, 'loaded', 'percent' ) );
  226. self.progress( file.attachment );
  227. } );
  228. /**
  229. * After a file is successfully uploaded, update its model.
  230. *
  231. * @param {plupload.Uploader} uploader Uploader instance.
  232. * @param {plupload.File} file File that was uploaded.
  233. * @param {Object} response Object with response properties.
  234. * @return {mixed}
  235. */
  236. this.uploader.bind( 'FileUploaded', function ( up, file, response ) {
  237. var complete;
  238. try {
  239. response = JSON.parse( response.response );
  240. } catch ( e ) {
  241. return error( pluploadL10n.default_error, e, file );
  242. }
  243. if ( typeof response.media !== 'undefined' ) {
  244. response = vp.handleRestApiResponse( response, file );
  245. } else {
  246. response = vp.handleStandardResponse( response, file );
  247. }
  248. _.each( [ 'file', 'loaded', 'size', 'percent' ], function ( key ) {
  249. file.attachment.unset( key );
  250. } );
  251. file.attachment.set( _.extend( response.data, { uploading: false } ) );
  252. wp.media.model.Attachment.get( response.data.id, file.attachment );
  253. complete = Uploader.queue.all( function ( attachment ) {
  254. return ! attachment.get( 'uploading' );
  255. } );
  256. if ( complete ) {
  257. vp && vp.resetToOriginalOptions( up );
  258. Uploader.queue.reset();
  259. }
  260. self.success( file.attachment );
  261. } );
  262. /**
  263. * When plupload surfaces an error, send it to the error handler.
  264. *
  265. * @param {plupload.Uploader} uploader Uploader instance.
  266. * @param {Object} error Contains code, message and sometimes file and other details.
  267. */
  268. this.uploader.bind( 'Error', function ( up, pluploadError ) {
  269. var message = pluploadL10n.default_error,
  270. key;
  271. // Check for plupload errors.
  272. for ( key in Uploader.errorMap ) {
  273. if ( pluploadError.code === plupload[ key ] ) {
  274. message = Uploader.errorMap[ key ];
  275. if ( _.isFunction( message ) ) {
  276. message = message( pluploadError.file, pluploadError );
  277. }
  278. break;
  279. }
  280. }
  281. error( message, pluploadError, pluploadError.file );
  282. vp && vp.resetToOriginalOptions( up );
  283. up.refresh();
  284. } );
  285. /**
  286. * Add in a way for the uploader to reset itself when uploads are complete.
  287. */
  288. this.uploader.bind( 'UploadComplete', function ( up ) {
  289. vp && vp.resetToOriginalOptions( up );
  290. } );
  291. /**
  292. * Before we upload, check to see if this file is a videopress upload, if so, set new options and save the old ones.
  293. */
  294. this.uploader.bind( 'BeforeUpload', function ( up, file ) {
  295. if ( typeof file.videopress !== 'undefined' ) {
  296. vp.originalOptions.url = up.getOption( 'url' );
  297. vp.originalOptions.multipart_params = up.getOption( 'multipart_params' );
  298. vp.originalOptions.file_data_name = up.getOption( 'file_data_name' );
  299. up.setOption( 'file_data_name', 'media[]' );
  300. up.setOption( 'url', file.videopress.upload_action_url );
  301. up.setOption( 'headers', {
  302. Authorization:
  303. 'X_UPLOAD_TOKEN token="' +
  304. file.videopress.upload_token +
  305. '" blog_id="' +
  306. file.videopress.upload_blog_id +
  307. '"',
  308. } );
  309. }
  310. } );
  311. };
  312. // Adds the 'defaults' and 'browser' properties.
  313. $.extend( Uploader, _wpPluploadSettings );
  314. Uploader.uuid = 0;
  315. // Map Plupload error codes to user friendly error messages.
  316. Uploader.errorMap = {
  317. FAILED: pluploadL10n.upload_failed,
  318. FILE_EXTENSION_ERROR: pluploadL10n.invalid_filetype,
  319. IMAGE_FORMAT_ERROR: pluploadL10n.not_an_image,
  320. IMAGE_MEMORY_ERROR: pluploadL10n.image_memory_exceeded,
  321. IMAGE_DIMENSIONS_ERROR: pluploadL10n.image_dimensions_exceeded,
  322. GENERIC_ERROR: pluploadL10n.upload_failed,
  323. IO_ERROR: pluploadL10n.io_error,
  324. HTTP_ERROR: pluploadL10n.http_error,
  325. SECURITY_ERROR: pluploadL10n.security_error,
  326. FILE_SIZE_ERROR: function ( file ) {
  327. return pluploadL10n.file_exceeds_size_limit.replace( '%s', file.name );
  328. },
  329. };
  330. $.extend( Uploader.prototype, {
  331. /**
  332. * Acts as a shortcut to extending the uploader's multipart_params object.
  333. *
  334. * param( key )
  335. * Returns the value of the key.
  336. *
  337. * param( key, value )
  338. * Sets the value of a key.
  339. *
  340. * param( map )
  341. * Sets values for a map of data.
  342. */
  343. param: function ( key, value ) {
  344. if ( arguments.length === 1 && typeof key === 'string' ) {
  345. return this.uploader.settings.multipart_params[ key ];
  346. }
  347. if ( arguments.length > 1 ) {
  348. this.uploader.settings.multipart_params[ key ] = value;
  349. } else {
  350. $.extend( this.uploader.settings.multipart_params, key );
  351. }
  352. },
  353. /**
  354. * Make a few internal event callbacks available on the wp.Uploader object
  355. * to change the Uploader internals if absolutely necessary.
  356. */
  357. init: function () {},
  358. error: function () {},
  359. success: function () {},
  360. added: function () {},
  361. progress: function () {},
  362. complete: function () {},
  363. refresh: function () {
  364. var node, attached, container, id;
  365. if ( this.browser ) {
  366. node = this.browser[ 0 ];
  367. // Check if the browser node is in the DOM.
  368. while ( node ) {
  369. if ( node === document.body ) {
  370. attached = true;
  371. break;
  372. }
  373. node = node.parentNode;
  374. }
  375. // If the browser node is not attached to the DOM, use a
  376. // temporary container to house it, as the browser button
  377. // shims require the button to exist in the DOM at all times.
  378. if ( ! attached ) {
  379. id = 'wp-uploader-browser-' + this.uploader.id;
  380. container = $( '#' + id );
  381. if ( ! container.length ) {
  382. container = $( '<div class="wp-uploader-browser" />' )
  383. .css( {
  384. position: 'fixed',
  385. top: '-1000px',
  386. left: '-1000px',
  387. height: 0,
  388. width: 0,
  389. } )
  390. .attr( 'id', 'wp-uploader-browser-' + this.uploader.id )
  391. .appendTo( 'body' );
  392. }
  393. container.append( this.browser );
  394. }
  395. }
  396. this.uploader.refresh();
  397. },
  398. } );
  399. // Create a collection of attachments in the upload queue,
  400. // so that other modules can track and display upload progress.
  401. Uploader.queue = new wp.media.model.Attachments( [], { query: false } );
  402. // Create a collection to collect errors incurred while attempting upload.
  403. Uploader.errors = new Backbone.Collection();
  404. exports.Uploader = Uploader;
  405. } )( wp, jQuery );