[ Index ]

PHP Cross Reference of WordPress

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 ( $.isFunction( this[ key ] ) ) {
  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.bind( '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.bind('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                  }
 362  
 363                  // Generate attributes for a new `Attachment` model.
 364                  attributes = _.extend({
 365                      file:      file,
 366                      uploading: true,
 367                      date:      new Date(),
 368                      filename:  file.name,
 369                      menuOrder: 0,
 370                      uploadedTo: wp.media.model.settings.post.id
 371                  }, _.pick( file, 'loaded', 'size', 'percent' ) );
 372  
 373                  // Handle early mime type scanning for images.
 374                  image = /(?:jpe?g|png|gif)$/i.exec( file.name );
 375  
 376                  // For images set the model's type and subtype attributes.
 377                  if ( image ) {
 378                      attributes.type = 'image';
 379  
 380                      // `jpeg`, `png` and `gif` are valid subtypes.
 381                      // `jpg` is not, so map it to `jpeg`.
 382                      attributes.subtype = ( 'jpg' === image[0] ) ? 'jpeg' : image[0];
 383                  }
 384  
 385                  // Create a model for the attachment, and add it to the Upload queue collection
 386                  // so listeners to the upload queue can track and display upload progress.
 387                  file.attachment = wp.media.model.Attachment.create( attributes );
 388                  Uploader.queue.add( file.attachment );
 389  
 390                  self.added( file.attachment );
 391              });
 392  
 393              up.refresh();
 394              up.start();
 395          });
 396  
 397          this.uploader.bind( 'UploadProgress', function( up, file ) {
 398              file.attachment.set( _.pick( file, 'loaded', 'percent' ) );
 399              self.progress( file.attachment );
 400          });
 401  
 402          /**
 403           * After a file is successfully uploaded, update its model.
 404           *
 405           * @param {plupload.Uploader} up       Uploader instance.
 406           * @param {plupload.File}     file     File that was uploaded.
 407           * @param {Object}            response Object with response properties.
 408           * @return {mixed}
 409           */
 410          this.uploader.bind( 'FileUploaded', function( up, file, response ) {
 411  
 412              try {
 413                  response = JSON.parse( response.response );
 414              } catch ( e ) {
 415                  return error( pluploadL10n.default_error, e, file );
 416              }
 417  
 418              if ( ! _.isObject( response ) || _.isUndefined( response.success ) ) {
 419                  return error( pluploadL10n.default_error, null, file );
 420              } else if ( ! response.success ) {
 421                  return error( response.data && response.data.message, response.data, file );
 422              }
 423  
 424              // Success. Update the UI with the new attachment.
 425              fileUploaded( up, file, response );
 426          });
 427  
 428          /**
 429           * When plupload surfaces an error, send it to the error handler.
 430           *
 431           * @param {plupload.Uploader} up            Uploader instance.
 432           * @param {Object}            pluploadError Contains code, message and sometimes file and other details.
 433           */
 434          this.uploader.bind( 'Error', function( up, pluploadError ) {
 435              var message = pluploadL10n.default_error,
 436                  key;
 437  
 438              // Check for plupload errors.
 439              for ( key in Uploader.errorMap ) {
 440                  if ( pluploadError.code === plupload[ key ] ) {
 441                      message = Uploader.errorMap[ key ];
 442  
 443                      if ( _.isFunction( message ) ) {
 444                          message = message( pluploadError.file, pluploadError );
 445                      }
 446  
 447                      break;
 448                  }
 449              }
 450  
 451              error( message, pluploadError, pluploadError.file );
 452              up.refresh();
 453          });
 454  
 455      };
 456  
 457      // Adds the 'defaults' and 'browser' properties.
 458      $.extend( Uploader, _wpPluploadSettings );
 459  
 460      Uploader.uuid = 0;
 461  
 462      // Map Plupload error codes to user friendly error messages.
 463      Uploader.errorMap = {
 464          'FAILED':                 pluploadL10n.upload_failed,
 465          'FILE_EXTENSION_ERROR':   pluploadL10n.invalid_filetype,
 466          'IMAGE_FORMAT_ERROR':     pluploadL10n.not_an_image,
 467          'IMAGE_MEMORY_ERROR':     pluploadL10n.image_memory_exceeded,
 468          'IMAGE_DIMENSIONS_ERROR': pluploadL10n.image_dimensions_exceeded,
 469          'GENERIC_ERROR':          pluploadL10n.upload_failed,
 470          'IO_ERROR':               pluploadL10n.io_error,
 471          'SECURITY_ERROR':         pluploadL10n.security_error,
 472  
 473          'FILE_SIZE_ERROR': function( file ) {
 474              return pluploadL10n.file_exceeds_size_limit.replace( '%s', file.name );
 475          },
 476  
 477          'HTTP_ERROR': function( file ) {
 478              if ( file.type && file.type.indexOf( 'image/' ) === 0 ) {
 479                  return pluploadL10n.http_error_image;
 480              }
 481  
 482              return pluploadL10n.http_error;
 483          },
 484      };
 485  
 486      $.extend( Uploader.prototype, /** @lends wp.Uploader.prototype */{
 487          /**
 488           * Acts as a shortcut to extending the uploader's multipart_params object.
 489           *
 490           * param( key )
 491           *    Returns the value of the key.
 492           *
 493           * param( key, value )
 494           *    Sets the value of a key.
 495           *
 496           * param( map )
 497           *    Sets values for a map of data.
 498           */
 499          param: function( key, value ) {
 500              if ( arguments.length === 1 && typeof key === 'string' ) {
 501                  return this.uploader.settings.multipart_params[ key ];
 502              }
 503  
 504              if ( arguments.length > 1 ) {
 505                  this.uploader.settings.multipart_params[ key ] = value;
 506              } else {
 507                  $.extend( this.uploader.settings.multipart_params, key );
 508              }
 509          },
 510  
 511          /**
 512           * Make a few internal event callbacks available on the wp.Uploader object
 513           * to change the Uploader internals if absolutely necessary.
 514           */
 515          init:     function() {},
 516          error:    function() {},
 517          success:  function() {},
 518          added:    function() {},
 519          progress: function() {},
 520          complete: function() {},
 521          refresh:  function() {
 522              var node, attached, container, id;
 523  
 524              if ( this.browser ) {
 525                  node = this.browser[0];
 526  
 527                  // Check if the browser node is in the DOM.
 528                  while ( node ) {
 529                      if ( node === document.body ) {
 530                          attached = true;
 531                          break;
 532                      }
 533                      node = node.parentNode;
 534                  }
 535  
 536                  /*
 537                   * If the browser node is not attached to the DOM,
 538                   * use a temporary container to house it, as the browser button shims
 539                   * require the button to exist in the DOM at all times.
 540                   */
 541                  if ( ! attached ) {
 542                      id = 'wp-uploader-browser-' + this.uploader.id;
 543  
 544                      container = $( '#' + id );
 545                      if ( ! container.length ) {
 546                          container = $('<div class="wp-uploader-browser" />').css({
 547                              position: 'fixed',
 548                              top: '-1000px',
 549                              left: '-1000px',
 550                              height: 0,
 551                              width: 0
 552                          }).attr( 'id', 'wp-uploader-browser-' + this.uploader.id ).appendTo('body');
 553                      }
 554  
 555                      container.append( this.browser );
 556                  }
 557              }
 558  
 559              this.uploader.refresh();
 560          }
 561      });
 562  
 563      // Create a collection of attachments in the upload queue,
 564      // so that other modules can track and display upload progress.
 565      Uploader.queue = new wp.media.model.Attachments( [], { query: false });
 566  
 567      // Create a collection to collect errors incurred while attempting upload.
 568      Uploader.errors = new Backbone.Collection();
 569  
 570      exports.Uploader = Uploader;
 571  })( wp, jQuery );


Generated: Thu Nov 26 01:00:03 2020 Cross-referenced by PHPXref 0.7.1