| 1 |
/* global plupload, pluploadL10n, ajaxurl, wpUploaderInit, uploadSuccess, wpFileError, wpQueueError, videoPressMediaNew */ |
| 2 |
|
| 3 |
/** |
| 4 |
* Routes video uploads from wp-admin/media-new.php to VideoPress. |
| 5 |
* |
| 6 |
* The classic uploader (plupload-handlers) creates a raw plupload.Uploader |
| 7 |
* rather than wp.Uploader, so the override in videopress-plupload.js never |
| 8 |
* engages there. This shim registers the same videopress_check_uploads file |
| 9 |
* filter and re-targets video uploads to VideoPress, then hands the local |
| 10 |
* attachment ID created during the upload back to the stock uploadSuccess |
| 11 |
* handler so the classic UI renders the media item as usual. |
| 12 |
*/ |
| 13 |
( function ( $ ) { |
| 14 |
if ( typeof plupload === 'undefined' ) { |
| 15 |
return; |
| 16 |
} |
| 17 |
|
| 18 |
var originalOptions = {}; |
| 19 |
|
| 20 |
// Videos accepted for VideoPress upload since the page loaded, used to |
| 21 |
// enforce the free plan's single-video limit within an upload session. |
| 22 |
var acceptedVideoCount = 0; |
| 23 |
|
| 24 |
// Video files in the selection currently being added to the uploader, |
| 25 |
// recorded before the file filters run so the free-plan gate can block a |
| 26 |
// multi-video selection before any upload starts. |
| 27 |
var pendingBatchVideoCount = 0; |
| 28 |
|
| 29 |
/** |
| 30 |
* Counts the video files in an addFile() argument. |
| 31 |
* |
| 32 |
* @param {Array|FileList|object} file What was passed to addFile: a single |
| 33 |
* file or an array-like of files. |
| 34 |
* @return {number} How many of them are videos. |
| 35 |
*/ |
| 36 |
function countVideoFiles( file ) { |
| 37 |
var files = file && typeof file.length === 'number' ? file : [ file ]; |
| 38 |
var count = 0; |
| 39 |
|
| 40 |
for ( var i = 0; i < files.length; i++ ) { |
| 41 |
var type = files[ i ] && files[ i ].type; |
| 42 |
|
| 43 |
if ( type && type.split( '/' )[ 0 ] === 'video' ) { |
| 44 |
count++; |
| 45 |
} |
| 46 |
} |
| 47 |
|
| 48 |
return count; |
| 49 |
} |
| 50 |
|
| 51 |
/** |
| 52 |
* Returns the upload limit data localized by the module. |
| 53 |
* |
| 54 |
* @return {object} The limit data. |
| 55 |
*/ |
| 56 |
function getUploadLimits() { |
| 57 |
return typeof videoPressMediaNew !== 'undefined' ? videoPressMediaNew : {}; |
| 58 |
} |
| 59 |
|
| 60 |
// Class of the container the upgrade notice renders in. |
| 61 |
var NOTICE_CLASS = 'videopress-media-new-notice'; |
| 62 |
|
| 63 |
/** |
| 64 |
* Removes any previously rendered upgrade notice. |
| 65 |
*/ |
| 66 |
function removeUpgradeNotice() { |
| 67 |
var notices = document.querySelectorAll( '.' + NOTICE_CLASS ); |
| 68 |
|
| 69 |
for ( var i = 0; i < notices.length; i++ ) { |
| 70 |
notices[ i ].parentNode.removeChild( notices[ i ] ); |
| 71 |
} |
| 72 |
} |
| 73 |
|
| 74 |
/** |
| 75 |
* Shows the free-plan upgrade notice above the upload queue. |
| 76 |
* |
| 77 |
* The message is pre-escaped HTML built server-side, containing the link |
| 78 |
* to the upgrade path. It renders in a container of its own next to the |
| 79 |
* stock #media-upload-error area rather than inside it: the stock |
| 80 |
* FilesAdded handler empties #media-upload-error as soon as any file of |
| 81 |
* the selection is accepted, which would wipe a notice rendered there |
| 82 |
* before the user could read it. |
| 83 |
* |
| 84 |
* @param {string} message The notice HTML. |
| 85 |
*/ |
| 86 |
function showUpgradeNotice( message ) { |
| 87 |
if ( ! message ) { |
| 88 |
return; |
| 89 |
} |
| 90 |
|
| 91 |
var anchor = document.getElementById( 'media-upload-error' ); |
| 92 |
|
| 93 |
if ( ! anchor || ! anchor.parentNode ) { |
| 94 |
// The stock error area is better than no notice at all. |
| 95 |
if ( typeof wpQueueError === 'function' ) { |
| 96 |
wpQueueError( message ); |
| 97 |
} |
| 98 |
return; |
| 99 |
} |
| 100 |
|
| 101 |
removeUpgradeNotice(); |
| 102 |
|
| 103 |
var notice = document.createElement( 'div' ); |
| 104 |
notice.className = 'notice notice-warning ' + NOTICE_CLASS; |
| 105 |
|
| 106 |
var paragraph = document.createElement( 'p' ); |
| 107 |
// The message is localized server-side and passed through wp_kses; |
| 108 |
// it only contains the upgrade link markup. |
| 109 |
paragraph.innerHTML = message; |
| 110 |
|
| 111 |
notice.appendChild( paragraph ); |
| 112 |
anchor.parentNode.insertBefore( notice, anchor.nextSibling ); |
| 113 |
} |
| 114 |
|
| 115 |
/** |
| 116 |
* Free-plan gate for a video file: the free plan includes a single video |
| 117 |
* upload, so reject every video once the site already has a VideoPress |
| 118 |
* video, and allow only one video per upload session otherwise. |
| 119 |
* |
| 120 |
* @param {plupload.File} file The video file being queued. |
| 121 |
* @param {Function} cb The file filter continuation callback. |
| 122 |
* @return {boolean} Whether the file was rejected. |
| 123 |
*/ |
| 124 |
function rejectedByFreePlanLimits( file, cb ) { |
| 125 |
var limits = getUploadLimits(); |
| 126 |
var strings = limits.strings || {}; |
| 127 |
|
| 128 |
if ( limits.hasVideoPressPurchase ) { |
| 129 |
return false; |
| 130 |
} |
| 131 |
|
| 132 |
if ( limits.hasUsedVideo ) { |
| 133 |
showUpgradeNotice( strings.usedVideoUpload ); |
| 134 |
cb( false ); |
| 135 |
return true; |
| 136 |
} |
| 137 |
|
| 138 |
// A selection with more than one video is blocked entirely — including |
| 139 |
// its first video — so no upload starts until the user upgrades. |
| 140 |
if ( pendingBatchVideoCount > 1 ) { |
| 141 |
showUpgradeNotice( strings.multipleVideosSelected ); |
| 142 |
cb( false ); |
| 143 |
return true; |
| 144 |
} |
| 145 |
|
| 146 |
if ( acceptedVideoCount >= 1 ) { |
| 147 |
showUpgradeNotice( strings.multipleVideos ); |
| 148 |
cb( false ); |
| 149 |
return true; |
| 150 |
} |
| 151 |
|
| 152 |
return false; |
| 153 |
} |
| 154 |
|
| 155 |
/** |
| 156 |
* Restores the stock upload settings saved before a VideoPress upload. |
| 157 |
* |
| 158 |
* @param {plupload.Uploader} up Uploader instance. |
| 159 |
*/ |
| 160 |
function restoreOriginalOptions( up ) { |
| 161 |
if ( typeof originalOptions.url === 'undefined' ) { |
| 162 |
return; |
| 163 |
} |
| 164 |
|
| 165 |
up.setOption( 'url', originalOptions.url ); |
| 166 |
up.setOption( 'file_data_name', originalOptions.file_data_name ); |
| 167 |
up.setOption( 'headers', originalOptions.headers ); |
| 168 |
|
| 169 |
originalOptions = {}; |
| 170 |
} |
| 171 |
|
| 172 |
/** |
| 173 |
* For video files, fetch a VideoPress upload token and attach it to the |
| 174 |
* file before it is queued. Mirrors the videopress_check_uploads filter in |
| 175 |
* videopress-uploader.js without depending on wp.media, which is not |
| 176 |
* loaded on media-new.php. Non-video files get the stock max_file_size |
| 177 |
* behavior this filter replaces. |
| 178 |
*/ |
| 179 |
plupload.addFileFilter( 'videopress_check_uploads', function ( maxSize, file, cb ) { |
| 180 |
var self = this; |
| 181 |
|
| 182 |
/** |
| 183 |
* Rejects the file with an uploader error. |
| 184 |
* |
| 185 |
* @param {string} [message] Error message to display. |
| 186 |
*/ |
| 187 |
function fail( message ) { |
| 188 |
self.trigger( 'Error', { |
| 189 |
code: plupload.HTTP_ERROR, |
| 190 |
message: |
| 191 |
message || |
| 192 |
plupload.translate( 'Could not get the VideoPress token needed for uploading' ), |
| 193 |
file: file, |
| 194 |
} ); |
| 195 |
cb( false ); |
| 196 |
} |
| 197 |
|
| 198 |
if ( file.type && file.type.split( '/' )[ 0 ] === 'video' ) { |
| 199 |
if ( rejectedByFreePlanLimits( file, cb ) ) { |
| 200 |
return; |
| 201 |
} |
| 202 |
|
| 203 |
$.post( ajaxurl, { action: 'videopress-get-upload-token', filename: file.name } ) |
| 204 |
.done( function ( response ) { |
| 205 |
if ( |
| 206 |
! response || |
| 207 |
! response.success || |
| 208 |
! response.data || |
| 209 |
! response.data.upload_action_url |
| 210 |
) { |
| 211 |
fail( response && response.data && response.data.message ); |
| 212 |
return; |
| 213 |
} |
| 214 |
|
| 215 |
file.videopress = response.data; |
| 216 |
acceptedVideoCount++; |
| 217 |
cb( true ); |
| 218 |
} ) |
| 219 |
.fail( function () { |
| 220 |
fail(); |
| 221 |
} ); |
| 222 |
|
| 223 |
return; |
| 224 |
} |
| 225 |
|
| 226 |
// Handles the stock max_file_size functionality for non-video files. |
| 227 |
if ( |
| 228 |
typeof file.size !== 'undefined' && |
| 229 |
maxSize && |
| 230 |
file.size > plupload.parseSize( maxSize ) |
| 231 |
) { |
| 232 |
this.trigger( 'Error', { |
| 233 |
code: plupload.FILE_SIZE_ERROR, |
| 234 |
message: pluploadL10n.file_exceeds_size_limit.replace( '%s', file.name ), |
| 235 |
file: file, |
| 236 |
} ); |
| 237 |
cb( false ); |
| 238 |
return; |
| 239 |
} |
| 240 |
|
| 241 |
cb( true ); |
| 242 |
} ); |
| 243 |
|
| 244 |
$( function () { |
| 245 |
// plupload-handlers is a dependency of this script, so its ready |
| 246 |
// callback ran first and the global `uploader` already exists. |
| 247 |
if ( |
| 248 |
typeof wpUploaderInit === 'undefined' || |
| 249 |
typeof window.uploader === 'undefined' || |
| 250 |
! window.uploader |
| 251 |
) { |
| 252 |
return; |
| 253 |
} |
| 254 |
|
| 255 |
var uploader = window.uploader; |
| 256 |
|
| 257 |
/** |
| 258 |
* Record how many videos each selection contains before plupload runs |
| 259 |
* the file filters, so the free-plan gate can reject every video of a |
| 260 |
* multi-video selection. Both the file selector and drag-and-drop |
| 261 |
* funnel their whole selection through a single addFile() call. |
| 262 |
*/ |
| 263 |
var stockAddFile = uploader.addFile; |
| 264 |
|
| 265 |
uploader.addFile = function ( file, fileName ) { |
| 266 |
// Each selection starts clean: a notice about a previous |
| 267 |
// selection no longer applies to this one. |
| 268 |
removeUpgradeNotice(); |
| 269 |
pendingBatchVideoCount = countVideoFiles( file ); |
| 270 |
return stockAddFile.call( this, file, fileName ); |
| 271 |
}; |
| 272 |
|
| 273 |
/** |
| 274 |
* Re-target VideoPress files right before each upload starts and |
| 275 |
* restore the stock settings for everything else, so mixed batches |
| 276 |
* of videos and other media both land in the right place. |
| 277 |
*/ |
| 278 |
uploader.bind( 'BeforeUpload', function ( up, file ) { |
| 279 |
if ( typeof file.videopress === 'undefined' ) { |
| 280 |
restoreOriginalOptions( up ); |
| 281 |
return; |
| 282 |
} |
| 283 |
|
| 284 |
if ( typeof originalOptions.url === 'undefined' ) { |
| 285 |
originalOptions.url = up.getOption( 'url' ); |
| 286 |
originalOptions.file_data_name = up.getOption( 'file_data_name' ); |
| 287 |
originalOptions.headers = up.getOption( 'headers' ); |
| 288 |
} |
| 289 |
|
| 290 |
up.setOption( 'url', file.videopress.upload_action_url ); |
| 291 |
up.setOption( 'file_data_name', 'media[]' ); |
| 292 |
up.setOption( 'headers', { |
| 293 |
Authorization: |
| 294 |
'X_UPLOAD_TOKEN token="' + |
| 295 |
file.videopress.upload_token + |
| 296 |
'" blog_id="' + |
| 297 |
file.videopress.upload_blog_id + |
| 298 |
'"', |
| 299 |
} ); |
| 300 |
} ); |
| 301 |
|
| 302 |
uploader.bind( 'Error', function ( up ) { |
| 303 |
restoreOriginalOptions( up ); |
| 304 |
} ); |
| 305 |
|
| 306 |
uploader.bind( 'UploadComplete', function ( up ) { |
| 307 |
restoreOriginalOptions( up ); |
| 308 |
} ); |
| 309 |
|
| 310 |
/** |
| 311 |
* The stock FileUploaded handler calls the global uploadSuccess with |
| 312 |
* the raw server response, which for VideoPress uploads is the REST |
| 313 |
* API media response instead of the attachment ID async-upload.php |
| 314 |
* returns. Wrap uploadSuccess to translate the former into the |
| 315 |
* latter; the stock handler then loads the media item row from |
| 316 |
* async-upload.php as it would for any other upload. |
| 317 |
*/ |
| 318 |
var stockUploadSuccess = uploadSuccess; |
| 319 |
|
| 320 |
window.uploadSuccess = function ( fileObj, serverData ) { |
| 321 |
if ( typeof fileObj.videopress === 'undefined' ) { |
| 322 |
return stockUploadSuccess( fileObj, serverData ); |
| 323 |
} |
| 324 |
|
| 325 |
var response = null; |
| 326 |
|
| 327 |
try { |
| 328 |
response = JSON.parse( serverData ); |
| 329 |
} catch { |
| 330 |
// Handled below. |
| 331 |
} |
| 332 |
|
| 333 |
var media = response && response.media && response.media[ 0 ]; |
| 334 |
|
| 335 |
if ( ! media || ! media.ID ) { |
| 336 |
var message = response && ( response.message || response.error ); |
| 337 |
wpFileError( fileObj, message || pluploadL10n.default_error ); |
| 338 |
return; |
| 339 |
} |
| 340 |
|
| 341 |
return stockUploadSuccess( fileObj, String( media.ID ) ); |
| 342 |
}; |
| 343 |
} ); |
| 344 |
} )( jQuery ); |
| 345 |
|