[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/plupload/ -> wp-plupload.js (source)

   1  /* global pluploadL10n, plupload, _wpPluploadSettings */
   2  
   3  /**
   4   * @namespace wp
   5   */
   6  window.wp = window.wp || {};
   7  
   8  ( function( exports, $ ) {
   9      var Uploader;
  10  
  11      if ( typeof _wpPluploadSettings === 'undefined' ) {
  12          return;
  13      }
  14  
  15      /**
  16       * A WordPress uploader.
  17       *
  18       * The Plupload library provides cross-browser uploader UI integration.
  19       * This object bridges the Plupload API to integrate uploads into the
  20       * WordPress back end and the WordPress media experience.
  21       *
  22       * @class
  23       * @memberOf wp
  24       * @alias wp.Uploader
  25       *
  26       * @param {object} options           The options passed to the new plupload instance.
  27       * @param {object} options.container The id of uploader container.
  28       * @param {object} options.browser   The id of button to trigger the file select.
  29       * @param {object} options.dropzone  The id of file drop target.
  30       * @param {object} options.plupload  An object of parameters to pass to the plupload instance.
  31       * @param {object} options.params    An object of parameters to pass to $_POST when uploading the file.
  32       *                                   Extends this.plupload.multipart_params under the hood.
  33       */
  34      Uploader = function( options ) {
  35          var self = this,
  36              isIE, // Not used, back-compat.
  37              elements = {
  38                  container: 'container',
  39                  browser:   'browse_button',
  40                  dropzone:  'drop_element'
  41              },
  42              tryAgainCount = {},
  43              tryAgain,
  44              key,
  45              error,
  46              fileUploaded;
  47  
  48          this.supports = {
  49              upload: Uploader.browser.supported
  50          };
  51  
  52          this.supported = this.supports.upload;
  53  
  54          if ( ! this.supported ) {
  55              return;
  56          }
  57  
  58          // Arguments to send to pluplad.Uploader().
  59          // Use deep extend to ensure that multipart_params and other objects are cloned.
  60          this.plupload = $.extend( true, { multipart_params: {} }, Uploader.defaults );
  61          this.container = document.body; // Set default container.
  62  
  63          /*
  64           * Extend the instance with options.
  65           *
  66           * Use deep extend to allow options.plupload to override individual
  67           * default plupload keys.
  68           */
  69          $.extend( true, this, options );
  70  
  71          // Proxy all methods so this always refers to the current instance.
  72          for ( key in this ) {
  73              if ( typeof this[ key ] === 'function' ) {
  74                  this[ key ] = $.proxy( this[ key ], this );
  75              }
  76          }
  77  
  78          // Ensure all elements are jQuery elements and have id attributes,
  79          // then set the proper plupload arguments to the ids.
  80          for ( key in elements ) {
  81              if ( ! this[ key ] ) {
  82                  continue;
  83              }
  84  
  85              this[ key ] = $( this[ key ] ).first();
  86  
  87              if ( ! this[ key ].length ) {
  88                  delete this[ key ];
  89                  continue;
  90              }
  91  
  92              if ( ! this[ key ].prop('id') ) {
  93                  this[ key ].prop( 'id', '__wp-uploader-id-' + Uploader.uuid++ );
  94              }
  95  
  96              this.plupload[ elements[ key ] ] = this[ key ].prop('id');
  97          }
  98  
  99          // If the uploader has neither a browse button nor a dropzone, bail.
 100          if ( ! ( this.browser && this.browser.length ) && ! ( this.dropzone && this.dropzone.length ) ) {
 101              return;
 102          }
 103  
 104          // Initialize the plupload instance.
 105          this.uploader = new plupload.Uploader( this.plupload );
 106          delete this.plupload;
 107  
 108          // Set default params and remove this.params alias.
 109          this.param( this.params || {} );
 110          delete this.params;
 111  
 112          /**
 113           * Attempt to create image sub-sizes when an image was uploaded successfully
 114           * but the server responded with HTTP 5xx error.
 115           *
 116           * @since 5.3.0
 117           *
 118           * @param {string}        message Error message.
 119           * @param {object}        data    Error data from Plupload.
 120           * @param {plupload.File} file    File that was uploaded.
 121           */
 122          tryAgain = function( message, data, file ) {
 123              var times, id;
 124  
 125              if ( ! data || ! data.responseHeaders ) {
 126                  error( pluploadL10n.http_error_image, data, file, 'no-retry' );
 127                  return;
 128              }
 129  
 130              id = data.responseHeaders.match( /x-wp-upload-attachment-id:\s*(\d+)/i );
 131  
 132              if ( id && id[1] ) {
 133                  id = id[1];
 134              } else {
 135                  error( pluploadL10n.http_error_image, data, file, 'no-retry' );
 136                  return;
 137              }
 138  
 139              times = tryAgainCount[ file.id ];
 140  
 141              if ( times && times > 4 ) {
 142                  /*
 143                   * The file may have been uploaded and attachment post created,
 144                   * but post-processing and resizing failed...
 145                   * Do a cleanup then tell the user to scale down the image and upload it again.
 146                   */
 147                  $.ajax({
 148                      type: 'post',
 149                      url: ajaxurl,
 150                      dataType: 'json',
 151                      data: {
 152                          action: 'media-create-image-subsizes',
 153                          _wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce,
 154                          attachment_id: id,
 155                          _wp_upload_failed_cleanup: true,
 156                      }
 157                  });
 158  
 159                  error( message, data, file, 'no-retry' );
 160                  return;
 161              }
 162  
 163              if ( ! times ) {
 164                  tryAgainCount[ file.id ] = 1;
 165              } else {
 166                  tryAgainCount[ file.id ] = ++times;
 167              }
 168  
 169              // Another request to try to create the missing image sub-sizes.
 170              $.ajax({
 171                  type: 'post',
 172                  url: ajaxurl,
 173                  dataType: 'json',
 174                  data: {
 175                      action: 'media-create-image-subsizes',
 176                      _wpnonce: _wpPluploadSettings.defaults.multipart_params._wpnonce,
 177                      attachment_id: id,
 178                  }
 179              }).done( function( response ) {
 180                  if ( response.success ) {
 181                      fileUploaded( self.uploader, file, response );
 182                  } else {
 183                      if ( response.data && response.data.message ) {
 184                          message = response.data.message;
 185                      }
 186  
 187                      error( message, data, file, 'no-retry' );
 188                  }
 189              }).fail( function( jqXHR ) {
 190                  // If another HTTP 5xx error, try try again...
 191                  if ( jqXHR.status >= 500 && jqXHR.status < 600 ) {
 192                      tryAgain( message, data, file );
 193                      return;
 194                  }
 195  
 196                  error( message, data, file, 'no-retry' );
 197              });
 198          }
 199  
 200          /**
 201           * Custom error callback.
 202           *
 203           * Add a new error to the errors collection, so other modules can track
 204           * and display errors. @see wp.Uploader.errors.
 205           *
 206           * @param {string}        message Error message.
 207           * @param {object}        data    Error data from Plupload.
 208           * @param {plupload.File} file    File that was uploaded.
 209           * @param {string}        retry   Whether to try again to create image sub-sizes. Passing 'no-retry' will prevent it.
 210           */
 211          error = function( message, data, file, retry ) {
 212              var isImage = file.type && file.type.indexOf( 'image/' ) === 0,
 213                  status = data && data.status;
 214  
 215              // If the file is an image and the error is HTTP 5xx try to create sub-sizes again.
 216              if ( retry !== 'no-retry' && isImage && status >= 500 && status < 600 ) {
 217                  tryAgain( message, data, file );
 218                  return;
 219              }
 220  
 221              if ( file.attachment ) {
 222                  file.attachment.destroy();
 223              }
 224  
 225              Uploader.errors.unshift({
 226                  message: message || pluploadL10n.default_error,
 227                  data:    data,
 228                  file:    file
 229              });
 230  
 231              self.error( message, data, file );
 232          };
 233  
 234          /**
 235           * After a file is successfully uploaded, update its model.
 236           *
 237           * @param {plupload.Uploader} up       Uploader instance.
 238           * @param {plupload.File}     file     File that was uploaded.
 239           * @param {Object}            response Object with response properties.
 240           */
 241          fileUploaded = function( up, file, response ) {
 242              var complete;
 243  
 244              // Remove the "uploading" UI elements.
 245              _.each( ['file','loaded','size','percent'], function( key ) {
 246                  file.attachment.unset( key );
 247              } );
 248  
 249              file.attachment.set( _.extend( response.data, { uploading: false } ) );
 250  
 251              wp.media.model.Attachment.get( response.data.id, file.attachment );
 252  
 253              complete = Uploader.queue.all( function( attachment ) {
 254                  return ! attachment.get( 'uploading' );
 255              });
 256  
 257              if ( complete ) {
 258                  Uploader.queue.reset();
 259              }
 260  
 261              self.success( file.attachment );
 262          }
 263  
 264          /**
 265           * After the Uploader has been initialized, initialize some behaviors for the dropzone.
 266           *
 267           * @param {plupload.Uploader} uploader Uploader instance.
 268           */
 269          this.uploader.bind( 'init', function( uploader ) {
 270              var timer, active, dragdrop,
 271                  dropzone = self.dropzone;
 272  
 273              dragdrop = self.supports.dragdrop = uploader.features.dragdrop && ! Uploader.browser.mobile;
 274  
 275              // Generate drag/drop helper classes.
 276              if ( ! dropzone ) {
 277                  return;
 278              }
 279  
 280              dropzone.toggleClass( 'supports-drag-drop', !! dragdrop );
 281  
 282              if ( ! dragdrop ) {
 283                  return dropzone.unbind('.wp-uploader');
 284              }
 285  
 286              // 'dragenter' doesn't fire correctly, simulate it with a limited 'dragover'.
 287              dropzone.on( 'dragover.wp-uploader', function() {
 288                  if ( timer ) {
 289                      clearTimeout( timer );
 290                  }
 291  
 292                  if ( active ) {
 293                      return;
 294                  }
 295  
 296                  dropzone.trigger('dropzone:enter').addClass('drag-over');
 297                  active = true;
 298              });
 299  
 300              dropzone.on('dragleave.wp-uploader, drop.wp-uploader', function() {
 301                  /*
 302                   * Using an instant timer prevents the drag-over class
 303                   * from being quickly removed and re-added when elements
 304                   * inside the dropzone are repositioned.
 305                   *
 306                   * @see https://core.trac.wordpress.org/ticket/21705
 307                   */
 308                  timer = setTimeout( function() {
 309                      active = false;
 310                      dropzone.trigger('dropzone:leave').removeClass('drag-over');
 311                  }, 0 );
 312              });
 313  
 314              self.ready = true;
 315              $(self).trigger( 'uploader:ready' );
 316          });
 317  
 318          this.uploader.bind( 'postinit', function( up ) {
 319              up.refresh();
 320              self.init();
 321          });
 322  
 323          this.uploader.init();
 324  
 325          if ( this.browser ) {
 326              this.browser.on( 'mouseenter', this.refresh );
 327          } else {
 328              this.uploader.disableBrowse( true );
 329          }
 330  
 331          $( self ).on( 'uploader:ready', function() {
 332              $( '.moxie-shim-html5 input[type="file"]' )
 333                  .attr( {
 334                      tabIndex:      '-1',
 335                      'aria-hidden': 'true'
 336                  } );
 337          } );
 338  
 339          /**
 340           * After files were filtered and added to the queue, create a model for each.
 341           *
 342           * @param {plupload.Uploader} up    Uploader instance.
 343           * @param {Array}             files Array of file objects that were added to queue by the user.
 344           */
 345          this.uploader.bind( 'FilesAdded', function( up, files ) {
 346              _.each( files, function( file ) {
 347                  var attributes, image;
 348  
 349                  // Ignore failed uploads.
 350                  if ( plupload.FAILED === file.status ) {
 351                      return;
 352                  }
 353  
 354                  if ( file.type === 'image/heic' && up.settings.heic_upload_error ) {
 355                      // Show error but do not block uploading.
 356                      Uploader.errors.unshift({
 357                          message: pluploadL10n.unsupported_image,
 358                          data:    {},
 359                          file:    file
 360                      });
 361                  } else if ( file.type === 'image/webp' && up.settings.webp_upload_error ) {
 362                      // Disallow uploading of WebP images if the server cannot edit them.
 363                      error( pluploadL10n.noneditable_image, {}, file, 'no-retry' );
 364                      up.removeFile( file );
 365                      return;
 366                  } else if ( file.type === 'image/avif' && up.settings.avif_upload_error ) {
 367                      // Disallow uploading of AVIF images if the server cannot edit them.
 368                      error( pluploadL10n.noneditable_image, {}, file, 'no-retry' );
 369                      up.removeFile( file );
 370                      return;
 371                  }
 372  
 373                  // Generate attributes for a new `Attachment` model.
 374                  attributes = _.extend({
 375                      file:      file,
 376                      uploading: true,
 377                      date:      new Date(),
 378                      filename:  file.name,
 379                      menuOrder: 0,
 380                      uploadedTo: wp.media.model.settings.post.id
 381                  }, _.pick( file, 'loaded', 'size', 'percent' ) );
 382  
 383                  // Handle early mime type scanning for images.
 384                  image = /(?:jpe?g|png|gif)$/i.exec( file.name );
 385  
 386                  // For images set the model's type and subtype attributes.
 387                  if ( image ) {
 388                      attributes.type = 'image';
 389  
 390                      // `jpeg`, `png` and `gif` are valid subtypes.
 391                      // `jpg` is not, so map it to `jpeg`.
 392                      attributes.subtype = ( 'jpg' === image[0] ) ? 'jpeg' : image[0];
 393                  }
 394  
 395                  // Create a model for the attachment, and add it to the Upload queue collection
 396                  // so listeners to the upload queue can track and display upload progress.
 397                  file.attachment = wp.media.model.Attachment.create( attributes );
 398                  Uploader.queue.add( file.attachment );
 399  
 400                  self.added( file.attachment );
 401              });
 402  
 403              up.refresh();
 404              up.start();
 405          });
 406  
 407          this.uploader.bind( 'UploadProgress', function( up, file ) {
 408              file.attachment.set( _.pick( file, 'loaded', 'percent' ) );
 409              self.progress( file.attachment );
 410          });
 411  
 412          /**
 413           * After a file is successfully uploaded, update its model.
 414           *
 415           * @param {plupload.Uploader} up       Uploader instance.
 416           * @param {plupload.File}     file     File that was uploaded.
 417           * @param {Object}            response Object with response properties.
 418           * @return {mixed}
 419           */
 420          this.uploader.bind( 'FileUploaded', function( up, file, response ) {
 421  
 422              try {
 423                  response = JSON.parse( response.response );
 424              } catch ( e ) {
 425                  return error( pluploadL10n.default_error, e, file );
 426              }
 427  
 428              if ( ! _.isObject( response ) || _.isUndefined( response.success ) ) {
 429                  return error( pluploadL10n.default_error, null, file );
 430              } else if ( ! response.success ) {
 431                  return error( response.data && response.data.message, response.data, file );
 432              }
 433  
 434              // Success. Update the UI with the new attachment.
 435              fileUploaded( up, file, response );
 436          });
 437  
 438          /**
 439           * When plupload surfaces an error, send it to the error handler.
 440           *
 441           * @param {plupload.Uploader} up            Uploader instance.
 442           * @param {Object}            pluploadError Contains code, message and sometimes file and other details.
 443           */
 444          this.uploader.bind( 'Error', function( up, pluploadError ) {
 445              var message = pluploadL10n.default_error,
 446                  key;
 447  
 448              // Check for plupload errors.
 449              for ( key in Uploader.errorMap ) {
 450                  if ( pluploadError.code === plupload[ key ] ) {
 451                      message = Uploader.errorMap[ key ];
 452  
 453                      if ( typeof message === 'function' ) {
 454                          message = message( pluploadError.file, pluploadError );
 455                      }
 456  
 457                      break;
 458                  }
 459              }
 460  
 461              error( message, pluploadError, pluploadError.file );
 462              up.refresh();
 463          });
 464  
 465      };
 466  
 467      // Adds the 'defaults' and 'browser' properties.
 468      $.extend( Uploader, _wpPluploadSettings );
 469  
 470      Uploader.uuid = 0;
 471  
 472      // Map Plupload error codes to user friendly error messages.
 473      Uploader.errorMap = {
 474          'FAILED':                 pluploadL10n.upload_failed,
 475          'FILE_EXTENSION_ERROR':   pluploadL10n.invalid_filetype,
 476          'IMAGE_FORMAT_ERROR':     pluploadL10n.not_an_image,
 477          'IMAGE_MEMORY_ERROR':     pluploadL10n.image_memory_exceeded,
 478          'IMAGE_DIMENSIONS_ERROR': pluploadL10n.image_dimensions_exceeded,
 479          'GENERIC_ERROR':          pluploadL10n.upload_failed,
 480          'IO_ERROR':               pluploadL10n.io_error,
 481          'SECURITY_ERROR':         pluploadL10n.security_error,
 482  
 483          'FILE_SIZE_ERROR': function( file ) {
 484              return pluploadL10n.file_exceeds_size_limit.replace( '%s', file.name );
 485          },
 486  
 487          'HTTP_ERROR': function( file ) {
 488              if ( file.type && file.type.indexOf( 'image/' ) === 0 ) {
 489                  return pluploadL10n.http_error_image;
 490              }
 491  
 492              return pluploadL10n.http_error;
 493          },
 494      };
 495  
 496      $.extend( Uploader.prototype, /** @lends wp.Uploader.prototype */{
 497          /**
 498           * Acts as a shortcut to extending the uploader's multipart_params object.
 499           *
 500           * param( key )
 501           *    Returns the value of the key.
 502           *
 503           * param( key, value )
 504           *    Sets the value of a key.
 505           *
 506           * param( map )
 507           *    Sets values for a map of data.
 508           */
 509          param: function( key, value ) {
 510              if ( arguments.length === 1 && typeof key === 'string' ) {
 511                  return this.uploader.settings.multipart_params[ key ];
 512              }
 513  
 514              if ( arguments.length > 1 ) {
 515                  this.uploader.settings.multipart_params[ key ] = value;
 516              } else {
 517                  $.extend( this.uploader.settings.multipart_params, key );
 518              }
 519          },
 520  
 521          /**
 522           * Make a few internal event callbacks available on the wp.Uploader object
 523           * to change the Uploader internals if absolutely necessary.
 524           */
 525          init:     function() {},
 526          error:    function() {},
 527          success:  function() {},
 528          added:    function() {},
 529          progress: function() {},
 530          complete: function() {},
 531          refresh:  function() {
 532              var node, attached, container, id;
 533  
 534              if ( this.browser ) {
 535                  node = this.browser[0];
 536  
 537                  // Check if the browser node is in the DOM.
 538                  while ( node ) {
 539                      if ( node === document.body ) {
 540                          attached = true;
 541                          break;
 542                      }
 543                      node = node.parentNode;
 544                  }
 545  
 546                  /*
 547                   * If the browser node is not attached to the DOM,
 548                   * use a temporary container to house it, as the browser button shims
 549                   * require the button to exist in the DOM at all times.
 550                   */
 551                  if ( ! attached ) {
 552                      id = 'wp-uploader-browser-' + this.uploader.id;
 553  
 554                      container = $( '#' + id );
 555                      if ( ! container.length ) {
 556                          container = $('<div class="wp-uploader-browser" />').css({
 557                              position: 'fixed',
 558                              top: '-1000px',
 559                              left: '-1000px',
 560                              height: 0,
 561                              width: 0
 562                          }).attr( 'id', 'wp-uploader-browser-' + this.uploader.id ).appendTo('body');
 563                      }
 564  
 565                      container.append( this.browser );
 566                  }
 567              }
 568  
 569              this.uploader.refresh();
 570          }
 571      });
 572  
 573      // Create a collection of attachments in the upload queue,
 574      // so that other modules can track and display upload progress.
 575      Uploader.queue = new wp.media.model.Attachments( [], { query: false });
 576  
 577      // Create a collection to collect errors incurred while attempting upload.
 578      Uploader.errors = new Backbone.Collection();
 579  
 580      exports.Uploader = Uploader;
 581  })( wp, jQuery );


Generated : Tue Mar 19 08:20:01 2024 Cross-referenced by PHPXref