You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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