PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 1.9.8
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v1.9.8
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / assets / js / options.js

options.js in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 1.9.8, at assets/js/options.js

1,184 lines 33.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 window.imagify = window.imagify || {};
2
3 (function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
4 /*
5 * Process an API key check validity.
6 */
7 var busy = false,
8 xhr = false;
9
10 $( '#imagify-settings #api_key' ).on( 'blur.imagify', function() {
11 var obj = $( this ),
12 value = obj.val();
13
14 if ( $.trim( value ) === '' ) {
15 return false;
16 }
17
18 if ( $( '#check_api_key' ).val() === value ) {
19 $( '#imagify-check-api-container' ).html( '<span class="dashicons dashicons-yes"></span> ' + imagifyOptions.labels.ValidApiKeyText );
20 return false;
21 }
22
23 if ( true === busy ) {
24 xhr.abort();
25 } else {
26 $( '#imagify-check-api-container' ).remove();
27 obj.after( '<span id="imagify-check-api-container"><span class="imagify-spinner"></span>' + imagifyOptions.labels.waitApiKeyCheckText + '</span>' );
28 }
29
30 busy = true;
31
32 xhr = $.get( ajaxurl + w.imagify.concat + 'action=imagify_check_api_key_validity&api_key=' + obj.val() + '&imagifycheckapikeynonce=' + $( '#imagifycheckapikeynonce' ).val() )
33 .done( function( response ) {
34 if ( ! response.success ) {
35 $( '#imagify-check-api-container' ).html( '<span class="dashicons dashicons-no"></span> ' + response.data );
36 } else {
37 // Success, the API key is valid.
38 $( '#imagify-check-api-container' ).remove();
39 swal( {
40 title: imagifyOptions.labels.ApiKeyCheckSuccessTitle,
41 html: imagifyOptions.labels.ApiKeyCheckSuccessText,
42 type: 'success',
43 padding: 0,
44 customClass: 'imagify-sweet-alert'
45 } ).then( function() {
46 location.reload();
47 } );
48 }
49
50 busy = false;
51 } );
52 } );
53
54 /**
55 * Check the boxes by clicking "labels" (aria-describedby items).
56 */
57 $( '.imagify-options-line' ).css( 'cursor', 'pointer' ).on( 'click.imagify', function( e ) {
58 if ( 'INPUT' === e.target.nodeName ) {
59 return;
60 }
61 $( 'input[aria-describedby="' + $( this ).attr( 'id' ) + '"]' ).trigger( 'click.imagify' );
62 } );
63
64 $( '.imagify-settings th span' ).on( 'click.imagify', function() {
65 var $input = $( this ).parent().next( 'td' ).find( ':checkbox' );
66
67 if ( 1 === $input.length ) {
68 $input.trigger( 'click.imagify' );
69 }
70 } );
71
72 /**
73 * Auto check on options-line input value change.
74 */
75 $( '.imagify-options-line' ).find( 'input' ).on( 'change.imagify focus.imagify', function() {
76 var $checkbox;
77
78 if ( 'checkbox' === this.type && ! this.checked ) {
79 return;
80 }
81
82 $checkbox = $( this ).closest( '.imagify-options-line' ).prev( 'label' ).prev( ':checkbox' );
83
84 if ( $checkbox.length && ! $checkbox[0].checked ) {
85 $checkbox.prop( 'checked', true );
86 }
87 } );
88
89 /**
90 * Imagify Backup alert.
91 */
92 $( '[name="imagify_settings[backup]"]' ).on( 'change.imagify', function() {
93 var $_this = $( this ),
94 $backupMessage = $_this.siblings( '#backup-dir-is-writable' ),
95 params = {
96 'action': 'imagify_check_backup_dir_is_writable',
97 '_wpnonce': $backupMessage.data( 'nonce' )
98 };
99
100 if ( $_this.is( ':checked' ) ) {
101 $.getJSON( ajaxurl, params )
102 .done( function( r ) {
103 if ( $.isPlainObject( r ) && r.success ) {
104 if ( r.data.is_writable ) {
105 // Hide the error message.
106 $backupMessage.addClass( 'hidden' );
107 } else {
108 // Show the error message.
109 $backupMessage.removeClass( 'hidden' );
110 }
111 }
112 } );
113 return;
114 }
115
116 // Are you sure? No backup?
117 swal( {
118 title: imagifyOptions.labels.noBackupTitle,
119 html: imagifyOptions.labels.noBackupText,
120 type: 'warning',
121 customClass: 'imagify-sweet-alert',
122 padding: 0,
123 showCancelButton: true,
124 cancelButtonText: imagifySwal.labels.cancelButtonText,
125 reverseButtons: true
126 } ).then(
127 function() {
128 // Leave it unchecked, hide the error message.
129 $backupMessage.addClass( 'hidden' );
130 },
131 function() {
132 // Re-check.
133 $_this.prop( 'checked', true );
134 }
135 );
136 } );
137
138 /**
139 * Fade CDN URL field.
140 */
141 $( '[name="imagify_settings[display_webp_method]"]' ).on( 'change.imagify init.imagify', function( e ) {
142 if ( 'picture' === e.target.value ) {
143 $( e.target ).closest( '.imagify-radio-group' ).next( '.imagify-options-line' ).removeClass( 'imagify-faded' );
144 } else {
145 $( e.target ).closest( '.imagify-radio-group' ).next( '.imagify-options-line' ).addClass( 'imagify-faded' );
146 }
147 } ).filter( ':checked' ).trigger( 'init.imagify' );
148
149 } )(jQuery, document, window);
150
151
152 // Display Imagify User data =======================================================================
153 (function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
154
155 if ( ! w.imagifyUser ) {
156 return;
157 }
158
159 $.getJSON( ajaxurl, w.imagifyUser )
160 .done( function( r ) {
161 if ( $.isPlainObject( r ) && r.success ) {
162 r.data.id = null;
163 r.data.plan_id = null;
164 r.data.is = [];
165
166 $.each( r.data, function( k, v ) {
167 var htmlClass = '.imagify-user-' + k.replace( /_/g, '-' );
168
169 if ( k.indexOf( 'is_' ) === 0 ) {
170 if ( v ) {
171 r.data.is.push( htmlClass );
172 }
173 } else if ( 'is' !== k ) {
174 $( htmlClass ).text( v );
175 }
176 } );
177
178 r.data.is.push( 'best-plan' );
179
180 $( r.data.is.join( ',' ) ).removeClass( 'hidden' );
181 }
182 } );
183
184 } )(window, document, jQuery);
185
186
187 // Files tree for "custom folders" =================================================================
188 (function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
189
190 if ( ! imagifyOptions.getFilesTree ) {
191 return;
192 }
193
194 function imagifyInsertFolderRow( value ) {
195 var added = false,
196 prevPath = null,
197 valueTest, template, $wrap, $rows, $field;
198
199 if ( ! value ) {
200 return;
201 }
202
203 $wrap = $( '#imagify-custom-folders-selected' );
204 $rows = $wrap.find( '.imagify-custom-folder-line' );
205 $field = $rows.find( '[value="' + value + '"]' );
206
207 if ( $field.length ) {
208 // Shouldn't happen.
209 return;
210 }
211
212 // Path #///# Label.
213 value = value.split( '#///#' );
214 valueTest = value[1].replace( /\/+$/,'' ).toLowerCase();
215 template = w.imagify.template( 'imagify-custom-folder' );
216
217 $rows.each( function() {
218 var $this = $( this ),
219 thisValueTest = $this.data( 'path' ).replace( /\/+$/,'' ).toLowerCase();
220
221 if ( '' !== thisValueTest && valueTest.indexOf( thisValueTest ) === 0 ) {
222 // We try to add a sub-folder of an already selected folder. It shouldn't happen though, since it can't be selected.
223 added = true;
224 return false;
225 } else if ( valueTest < thisValueTest ) {
226 $this.before( template( {
227 value: value[0],
228 label: value[1]
229 } ) );
230 $rows = $wrap.find( '.imagify-custom-folder-line' );
231 added = true;
232 return false;
233 }
234 } );
235
236 if ( ! added ) {
237 $wrap.append( template( {
238 value: value[0],
239 label: value[1]
240 } ) );
241 $rows = $wrap.find( '.imagify-custom-folder-line' );
242 }
243
244 // Remove sub-paths: if 'a/b/' and 'a/b/c/' are in the array, we keep only the "parent" 'a/b/'.
245 if ( '' !== valueTest ) {
246 $rows.each( function() {
247 var $this = $( this ),
248 thisPath = $this.data( 'path' ).toLowerCase();
249
250 if ( null !== prevPath && thisPath.indexOf( prevPath ) === 0 ) {
251 $this.find( '.imagify-custom-folders-remove' ).trigger( 'click.imagify' );
252 } else {
253 prevPath = thisPath;
254 }
255 } );
256 }
257
258 // Display a message.
259 $wrap.next( '.hidden' ).removeClass( 'hidden' );
260 }
261
262 // Clicking the main button: fetch site's root folders and files, then display them in a modal.
263 $( '#imagify-add-custom-folder' ).on( 'click.imagify', function() {
264 var $button = $( this ),
265 selected = [],
266 $folders;
267
268 if ( $button.attr( 'disabled' ) ) {
269 return;
270 }
271
272 $button.attr( 'disabled', 'disabled' ).next( 'img' ).attr( 'aria-hidden', 'false' );
273
274 $folders = $( '#imagify-custom-folders-selected' );
275
276 $folders.find( 'input' ).each( function() {
277 selected.push( this.value );
278 } );
279
280 $.post( imagifyOptions.getFilesTree, {
281 folder: '/',
282 selected: selected
283 }, null, 'json' )
284 .done( function( response ) {
285 if ( ! response.success ) {
286 swal( {
287 title: imagifyOptions.labels.error,
288 html: response.data || '',
289 type: 'error',
290 padding: 0,
291 customClass: 'imagify-sweet-alert'
292 } );
293 return;
294 }
295
296 swal( {
297 title: imagifyOptions.labels.filesTreeTitle,
298 html: '<div class="imagify-swal-subtitle">' + imagifyOptions.labels.filesTreeSubTitle + '</div><div class="imagify-swal-content"><p class="imagify-folders-information"><i class="dashicons dashicons-info" aria-hidden="true"></i>' + imagifyOptions.labels.cleaningInfo + '</p><ul id="imagify-folders-tree" class="imagify-folders-tree">' + response.data + '</ul></div>',
299 type: '',
300 customClass: 'imagify-sweet-alert imagify-swal-has-subtitle imagify-folders-selection',
301 showCancelButton: true,
302 padding: 0,
303 confirmButtonText: imagifyOptions.labels.confirmFilesTreeBtn,
304 cancelButtonText: imagifySwal.labels.cancelButtonText,
305 reverseButtons: true
306 } ).then( function() {
307 var values = $( '#imagify-folders-tree input' ).serializeArray(); // Don't do `$( '#imagify-folders-tree' ).find( 'input' )`, it won't work.
308
309 if ( ! values.length ) {
310 return;
311 }
312
313 $.each( values, function( i, v ) {
314 imagifyInsertFolderRow( v.value );
315 } );
316 } ).catch( swal.noop );
317 } )
318 .fail( function() {
319 swal( {
320 title: imagifyOptions.labels.error,
321 type: 'error',
322 customClass: 'imagify-sweet-alert',
323 padding: 0
324 } );
325 } )
326 .always( function(){
327 $button.removeAttr( 'disabled' ).next( 'img' ).attr( 'aria-hidden', 'true' );
328 } );
329 } );
330
331 // Clicking a folder icon in the modal: fetch the folder's sub-folders and files, then display them.
332 $( d ).on( 'click.imagify', '#imagify-folders-tree [data-folder]', function() {
333 var $button = $( this ),
334 $tree = $button.nextAll( '.imagify-folders-sub-tree' ),
335 selected = [];
336
337 if ( $button.attr( 'disabled' ) || $button.siblings( ':checkbox' ).is( ':checked' ) ) {
338 return;
339 }
340
341 $button.attr( 'disabled', 'disabled' ).addClass( 'imagify-loading' );
342
343 if ( $tree.length ) {
344 if ( $button.hasClass( 'imagify-is-open' ) ) {
345 $tree.addClass( 'hidden' );
346 $button.removeClass(' imagify-is-open' );
347 } else {
348 $tree.removeClass( 'hidden' );
349 $button.addClass( 'imagify-is-open' );
350 }
351 $button.removeAttr( 'disabled' ).removeClass( 'imagify-loading' );
352 return;
353 }
354
355 $( '#imagify-custom-folders-selected' ).find( 'input' ).each( function() {
356 selected.push( this.value );
357 } );
358
359 $.post( imagifyOptions.getFilesTree, {
360 folder: $button.data( 'folder' ),
361 selected: selected
362 }, null, 'json' )
363 .done( function( response ) {
364 if ( ! response.success ) {
365 swal( {
366 title: imagifyOptions.labels.error,
367 html: response.data || '',
368 type: 'error',
369 padding: 0,
370 customClass: 'imagify-sweet-alert'
371 } );
372 return;
373 }
374
375 $button.addClass( 'imagify-is-open' ).parent().append( '<ul class="imagify-folders-sub-tree">' + response.data + '</ul>' );
376 } )
377 .fail( function(){
378 swal( {
379 title: imagifyOptions.labels.error,
380 type: 'error',
381 padding: 0,
382 customClass: 'imagify-sweet-alert'
383 } );
384 } )
385 .always( function(){
386 $button.removeAttr( 'disabled' ).removeClass( 'imagify-loading' );
387 } );
388 } );
389
390 // Clicking a Remove folder button make it disappear.
391 $( '#imagify-custom-folders' ).on( 'click.imagify', '.imagify-custom-folders-remove', function() {
392 var $row = $( this ).closest( '.imagify-custom-folder-line' ).addClass( 'imagify-will-remove' );
393
394 w.setTimeout( function() {
395 $row.remove();
396 // Display a message.
397 $( '#imagify-custom-folders-selected' ).siblings( '.imagify-success.hidden' ).removeClass( 'hidden' );
398 }, 750 );
399 } );
400
401 // Clicking the "add themes to folders" button.
402 $( '#imagify-add-themes-to-custom-folder' ).on( 'click.imagify', function() {
403 var $this = $( this );
404
405 imagifyInsertFolderRow( $this.data( 'theme' ) );
406 imagifyInsertFolderRow( $this.data( 'theme-parent' ) );
407
408 // Remove clicked button.
409 $this.replaceWith( '<p>' + imagifyOptions.labels.themesAdded + '</p>' );
410 } );
411
412 } )(window, document, jQuery);
413
414
415 // Generate missing webp versions ==================================================================
416 /* eslint-disable no-underscore-dangle, consistent-this */
417 (function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
418
419 if ( ! imagifyOptions.bulk ) {
420 return;
421 }
422
423 w.imagify.optionsBulk = {
424 // Properties ==============================================================================
425 /**
426 * When media IDs have been fetched for a context (or tried, with error), the context is removed from this list.
427 *
428 * @var {array} fetchQueue An array of contexts.
429 */
430 fetchQueue: [],
431 /**
432 * Contexts in queue.
433 *
434 * @var {array} queue An array of objects: {
435 * @type {string} context The context, like 'wp'.
436 * @type {string} optimizeURL The URL to ping to optimize a file.
437 * @type {array} mediaIDs A list of media IDs.
438 * }
439 */
440 queue: [],
441 /**
442 * List of medias being processed.
443 *
444 * @var {array} processingQueue An array of objects: {
445 * @type {string} context The context, like 'wp'.
446 * @type {int} mediaID The media ID.
447 * }
448 */
449 processingQueue: [],
450 /**
451 * Stores the first error ID or message that is occurring when fetching media IDs.
452 *
453 * @var {string|bool} error False if no error.
454 */
455 fetchError: false,
456 /**
457 * The current error ID or message.
458 *
459 * @var {string|bool} error False if no error.
460 */
461 error: false,
462 /**
463 * Set to true at the beginning of the process.
464 *
465 * @var {bool} working
466 */
467 working: false,
468 /**
469 * Set to true to stop the whole thing.
470 *
471 * @var {bool} processIsStopped
472 */
473 processIsStopped: true,
474 /**
475 * Total number of processed media.
476 *
477 * @var {int}
478 */
479 processedMedia: 0,
480 /**
481 * Total number of media to process.
482 *
483 * @var {int}
484 */
485 totalMedia: 0,
486 /**
487 * The button.
488 *
489 * @var {jQuery}
490 */
491 $button: null,
492 /**
493 * The progress bar wrapper.
494 *
495 * @var {jQuery}
496 */
497 $progressWrap: null,
498 /**
499 * The progress bar.
500 *
501 * @var {jQuery}
502 */
503 $progressBar: null,
504 /**
505 * The progress bar text (the %).
506 *
507 * @var {jQuery}
508 */
509 $progressText: null,
510
511 // Methods =================================================================================
512
513 /*
514 * Init.
515 */
516 init: function () {
517 this.$button = $( '#imagify-generate-webp-versions' );
518 this.$progressWrap = this.$button.siblings( '.imagify-progress' );
519 this.$progressBar = this.$progressWrap.find( '.bar' );
520 this.$progressText = this.$progressBar.find( '.percent' );
521
522 // Enable/Disable the button when the "Convert to webp" checkbox is checked/unchecked.
523 $( '#imagify_convert_to_webp' )
524 .on( 'change.imagify init.imagify', { imagifyOptionsBulk: this }, this.toggleButton )
525 .trigger( 'init.imagify' );
526
527 // Launch optimization.
528 this.$button.on( 'click.imagify', { imagifyOptionsBulk: this }, this.maybeLaunchAllProcesses );
529
530 // Imagifybeat for optimization queue.
531 $( d )
532 .on( 'imagifybeat-send', { imagifyOptionsBulk: this }, this.addQueueImagifybeat )
533 .on( 'imagifybeat-tick', { imagifyOptionsBulk: this }, this.processQueueImagifybeat )
534 // Imagifybeat for requirements.
535 .on( 'imagifybeat-send', this.addRequirementsImagifybeat )
536 .on( 'imagifybeat-tick', { imagifyOptionsBulk: this }, this.processRequirementsImagifybeat );
537 },
538
539 // Event callbacks =========================================================================
540
541 /**
542 * Enable/Disable the button when the "Convert to webp" checkbox is checked/unchecked.
543 *
544 * @param {object} e Event object.
545 */
546 toggleButton: function ( e ) {
547 if ( ! this.checked ) {
548 e.data.imagifyOptionsBulk.$button.attr( 'disabled', 'disabled' );
549 } else {
550 e.data.imagifyOptionsBulk.$button.removeAttr( 'disabled' );
551 }
552 },
553
554 /*
555 * Build the queue and launch all processes.
556 *
557 * @param {object} e Event object.
558 */
559 maybeLaunchAllProcesses: function ( e ) {
560 if ( ! e.data.imagifyOptionsBulk || e.data.imagifyOptionsBulk.working ) {
561 return;
562 }
563
564 if ( e.data.imagifyOptionsBulk.hasBlockingError( true ) ) {
565 return;
566 }
567
568 // Reset properties.
569 e.data.imagifyOptionsBulk.fetchQueue = imagifyOptions.bulk.contexts.slice();
570 e.data.imagifyOptionsBulk.queue = [];
571 e.data.imagifyOptionsBulk.processingQueue = [];
572 e.data.imagifyOptionsBulk.fetchError = false;
573 e.data.imagifyOptionsBulk.error = false;
574 e.data.imagifyOptionsBulk.working = true;
575 e.data.imagifyOptionsBulk.processIsStopped = false;
576 e.data.imagifyOptionsBulk.processedMedia = 0;
577 e.data.imagifyOptionsBulk.totalMedia = 0;
578
579 // Disable the button.
580 e.data.imagifyOptionsBulk.$button.attr( 'disabled', 'disabled' ).find( '.dashicons' ).addClass( 'rotate' );
581
582 // Add a message to be displayed when the user wants to quit the page.
583 $( w ).on( 'beforeunload.imagify', e.data.imagifyOptionsBulk.getConfirmMessage );
584
585 // Fasten Imagifybeat: 1 tick every 15 seconds, and disable suspend.
586 w.imagify.beat.interval( 15 );
587 w.imagify.beat.disableSuspend();
588
589 // Fetch IDs of media to optimize.
590 e.data.imagifyOptionsBulk.fetchIDs();
591 },
592
593 /*
594 * Get the message displayed to the user when (s)he leaves the page.
595 *
596 * @return {string}
597 */
598 getConfirmMessage: function () {
599 return imagifyOptions.bulk.labels.processing;
600 },
601
602 // Imagifybeat =============================================================================
603
604 /**
605 * Add a Imagifybeat ID on "imagifybeat-send" event to sync the optimization queue.
606 *
607 * @param {object} e Event object.
608 * @param {object} data Object containing all Imagifybeat IDs.
609 */
610 addQueueImagifybeat: function ( e, data ) {
611 if ( e.data.imagifyOptionsBulk && e.data.imagifyOptionsBulk.processingQueue.length ) {
612 data[ imagifyOptions.bulk.imagifybeatIDs.queue ] = e.data.imagifyOptionsBulk.processingQueue;
613 }
614 },
615
616 /**
617 * Listen for the custom event "imagifybeat-tick" on $(document).
618 * It allows to update various data periodically.
619 *
620 * @param {object} e Event object.
621 * @param {object} data Object containing all Imagifybeat IDs.
622 */
623 processQueueImagifybeat: function ( e, data ) {
624 if ( e.data.imagifyOptionsBulk && typeof data[ imagifyOptions.bulk.imagifybeatIDs.queue ] !== 'undefined' ) {
625 $.each( data[ imagifyOptions.bulk.imagifybeatIDs.queue ], function ( i, mediaData ) {
626 e.data.imagifyOptionsBulk.mediaProcessed( mediaData );
627 } );
628 }
629 },
630
631 /**
632 * Add a Imagifybeat ID for requirements on "imagifybeat-send" event.
633 *
634 * @param {object} e Event object.
635 * @param {object} data Object containing all Imagifybeat IDs.
636 */
637 addRequirementsImagifybeat: function ( e, data ) {
638 data[ imagifyOptions.bulk.imagifybeatIDs.requirements ] = 1;
639 },
640
641 /**
642 * Listen for the custom event "imagifybeat-tick" on $(document).
643 * It allows to update requirements status periodically.
644 *
645 * @param {object} e Event object.
646 * @param {object} data Object containing all Imagifybeat IDs.
647 */
648 processRequirementsImagifybeat: function ( e, data ) {
649 if ( e.data.imagifyOptionsBulk && typeof data[ imagifyOptions.bulk.imagifybeatIDs.requirements ] === 'undefined' ) {
650 return;
651 }
652
653 data = data[ imagifyOptions.bulk.imagifybeatIDs.requirements ];
654
655 imagifyOptions.bulk.curlMissing = data.curl_missing;
656 imagifyOptions.bulk.editorMissing = data.editor_missing;
657 imagifyOptions.bulk.extHttpBlocked = data.external_http_blocked;
658 imagifyOptions.bulk.apiDown = data.api_down;
659 imagifyOptions.bulk.keyIsValid = data.key_is_valid;
660 imagifyOptions.bulk.isOverQuota = data.is_over_quota;
661 },
662
663 // Optimization ============================================================================
664
665 /*
666 * Fetch IDs of media to optimize.
667 */
668 fetchIDs: function () {
669 var _this, context;
670
671 if ( this.processIsStopped ) {
672 return;
673 }
674
675 if ( ! this.fetchQueue.length ) {
676 // No more IDs to fetch.
677 if ( this.queue.length ) {
678 // We have files to process.
679 // Reset and display the progress bar.
680 this.$progressBar.removeAttr( 'style' );
681 this.$progressText.text( '0' + ( this.totalMedia ? '/' + this.totalMedia : '' ) );
682 this.$progressWrap.slideDown().attr( 'aria-hidden', 'false' );
683
684 this.processQueue();
685 return;
686 }
687
688 if ( ! this.fetchError ) {
689 // No files to process.
690 this.fetchError = 'no-images';
691 }
692
693 // Error, or no files to process.
694 this.stopProcess( this.fetchError );
695 this.fetchError = false;
696 return;
697 }
698
699 // Fetch IDs for the next context.
700 _this = this;
701 context = this.fetchQueue.shift();
702
703 $.get( this.getAjaxUrl( 'getMediaIds', context ) )
704 .done( function( response ) {
705 var errorMessage;
706
707 if ( _this.processIsStopped ) {
708 return;
709 }
710
711 if ( response.data && response.data.message ) {
712 errorMessage = response.data.message;
713 } else {
714 errorMessage = imagifyOptions.bulk.ajaxErrorText;
715 }
716
717 if ( ! response.success ) {
718 // Error.
719 if ( ! _this.fetchError ) {
720 _this.fetchError = errorMessage;
721 }
722 return;
723 }
724
725 if ( ! $.isArray( response.data ) ) {
726 // Error: should be an array.
727 if ( ! _this.fetchError ) {
728 _this.fetchError = errorMessage;
729 }
730 return;
731 }
732
733 if ( ! response.data.length ) {
734 // No media to process.
735 return;
736 }
737
738 // Success.
739 _this.totalMedia += response.data.length;
740 _this.queue.push( {
741 context: context,
742 optimizeURL: _this.getAjaxUrl( 'bulkProcess', context ),
743 mediaIDs: response.data
744 } );
745 } )
746 .fail( function() {
747 // Error.
748 if ( ! _this.fetchError ) {
749 _this.fetchError = 'get-unoptimized-images';
750 }
751 } )
752 .always( function() {
753 // Fetch IDs for the next context.
754 _this.fetchIDs();
755 } );
756 },
757
758 /*
759 * Fill the processing queue until the buffer size is reached.
760 */
761 processQueue: function () {
762 var _this = this;
763
764 if ( this.processIsStopped ) {
765 return;
766 }
767
768 if ( ! this.queue.length && ! this.processingQueue.length ) {
769 return;
770 }
771
772 // Optimize the files.
773 $.each( this.queue, function ( i, item ) {
774 if ( _this.processingQueue.length >= imagifyOptions.bulk.bufferSize ) {
775 return false;
776 }
777
778 $.each( item.mediaIDs, function () {
779 _this.processMedia( {
780 context: item.context,
781 mediaID: item.mediaIDs.shift(),
782 optimizeURL: item.optimizeURL
783 } );
784
785 if ( ! item.mediaIDs.length ) {
786 _this.queue.shift();
787 }
788
789 if ( _this.processingQueue.length >= imagifyOptions.bulk.bufferSize ) {
790 return false;
791 }
792 } );
793 } );
794 },
795
796 /*
797 * Process the next media.
798 *
799 * @param {object} item {
800 * @type {string} context The context, like 'wp'.
801 * @type {int} mediaID The media ID.
802 * @type {string} optimizeURL The URL to ping to optimize the media.
803 * }
804 */
805 processMedia: function ( item ) {
806 var _this = this,
807 defaultResponse = {
808 context: item.context,
809 mediaID: item.mediaID
810 };
811
812 this.processingQueue.push( {
813 context: item.context,
814 mediaID: item.mediaID
815 } );
816
817 $.post( {
818 url: item.optimizeURL,
819 data: {
820 media_id: item.mediaID,
821 context: item.context
822 },
823 dataType: 'json'
824 } )
825 .done( function( response ) {
826 if ( response.success ) {
827 // Processing.
828 return;
829 }
830
831 // Error.
832 _this.mediaProcessed( defaultResponse );
833 } )
834 .fail( function() {
835 // Error.
836 _this.mediaProcessed( defaultResponse );
837 } );
838 },
839
840 /**
841 * After a media has been processed.
842 *
843 * @param {object} response {
844 * The response:
845 *
846 * @type {int} mediaID The media ID.
847 * @type {string} context The context.
848 * }
849 */
850 mediaProcessed: function( response ) {
851 var _this = this;
852
853 if ( this.processIsStopped ) {
854 return;
855 }
856
857 // Remove this media from the "being processed" list.
858 $.each( this.processingQueue, function( i, mediaData ) {
859 if ( response.context === mediaData.context && response.mediaID === mediaData.mediaID ) {
860 _this.processingQueue.splice( i, 1 );
861 return false;
862 }
863 } );
864
865 ++this.processedMedia;
866
867 // Update the progress bar.
868 response.progress = Math.floor( this.processedMedia / this.totalMedia * 100 );
869 this.$progressBar.css( 'width', response.progress + '%' );
870 this.$progressText.text( this.processedMedia + '/' + this.totalMedia );
871
872 if ( this.queue.length || this.processingQueue.length ) {
873 this.processQueue();
874 } else if ( this.totalMedia === this.processedMedia ) {
875 this.queueEmpty();
876 }
877 },
878
879 /*
880 * End.
881 */
882 queueEmpty: function () {
883 var errorArgs = {};
884
885 // Maybe display an error.
886 if ( false !== this.error ) {
887 if ( 'invalid-api-key' === this.error ) {
888 errorArgs = {
889 title: imagifyOptions.bulk.labels.invalidAPIKeyTitle,
890 type: 'info'
891 };
892 }
893 else if ( 'over-quota' === this.error ) {
894 errorArgs = {
895 title: imagifyOptions.bulk.labels.overQuotaTitle,
896 html: $( '#tmpl-imagify-overquota-alert' ).html(),
897 type: 'info',
898 customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
899 showConfirmButton: false
900 };
901 }
902 else if ( 'get-unoptimized-images' === this.error || 'consumed-all-data' === this.error ) {
903 errorArgs = {
904 title: imagifyOptions.bulk.labels.getUnoptimizedImagesErrorTitle,
905 html: imagifyOptions.bulk.labels.getUnoptimizedImagesErrorText,
906 type: 'info'
907 };
908 }
909 else if ( 'no-images' === this.error ) {
910 errorArgs = {
911 title: imagifyOptions.bulk.labels.nothingToDoTitle,
912 html: imagifyOptions.bulk.labels.nothingToDoText,
913 type: 'info'
914 };
915 }
916 else if ( 'no-backup' === this.error ) {
917 errorArgs = {
918 title: imagifyOptions.bulk.labels.nothingToDoTitle,
919 html: imagifyOptions.bulk.labels.nothingToDoNoBackupText,
920 type: 'info'
921 };
922 } else {
923 errorArgs = {
924 title: imagifyOptions.bulk.labels.error,
925 html: this.error,
926 type: 'info'
927 };
928 }
929
930 this.displayError( errorArgs );
931
932 // Reset the error.
933 this.error = false;
934 }
935
936 // Reset.
937 this.fetchQueue = [];
938 this.queue = [];
939 this.processingQueue = [];
940 this.fetchError = false;
941 this.working = false;
942 this.processIsStopped = false;
943 this.processedMedia = 0;
944 this.totalMedia = 0;
945
946 // Reset Imagifybeat interval and enable suspend.
947 w.imagify.beat.resetInterval();
948 w.imagify.beat.enableSuspend();
949
950 // Unlink the message displayed when the user wants to quit the page.
951 $( w ).off( 'beforeunload.imagify', this.getConfirmMessage );
952
953 // Reset the progress bar.
954 this.$progressWrap.slideUp().attr( 'aria-hidden', 'true' );
955 this.$progressBar.removeAttr( 'style' );
956 this.$progressText.text( '0' );
957
958 // Enable the button.
959 this.$button.removeAttr( 'disabled' ).find( '.dashicons' ).removeClass( 'rotate' );
960 },
961
962 // Tools ===================================================================================
963
964 /*
965 * Tell if we have a blocking error. Can also display an error message in a swal.
966 *
967 * @param {bool} displayErrorMessage False to not display any error message.
968 * @return {bool}
969 */
970 hasBlockingError: function ( displayErrorMessage ) {
971 displayErrorMessage = undefined !== displayErrorMessage && displayErrorMessage;
972
973 if ( imagifyOptions.bulk.curlMissing ) {
974 if ( displayErrorMessage ) {
975 this.displayError( {
976 html: imagifyOptions.bulk.labels.curlMissing
977 } );
978 }
979 return true;
980 }
981
982 if ( imagifyOptions.bulk.editorMissing ) {
983 if ( displayErrorMessage ) {
984 this.displayError( {
985 html: imagifyOptions.bulk.labels.editorMissing
986 } );
987 }
988 return true;
989 }
990
991 if ( imagifyOptions.bulk.extHttpBlocked ) {
992 if ( displayErrorMessage ) {
993 this.displayError( {
994 html: imagifyOptions.bulk.labels.extHttpBlocked
995 } );
996 }
997 return true;
998 }
999
1000 if ( imagifyOptions.bulk.apiDown ) {
1001 if ( displayErrorMessage ) {
1002 this.displayError( {
1003 html: imagifyOptions.bulk.labels.apiDown
1004 } );
1005 }
1006 return true;
1007 }
1008
1009 if ( ! imagifyOptions.bulk.keyIsValid ) {
1010 if ( displayErrorMessage ) {
1011 this.displayError( {
1012 title: imagifyOptions.bulk.labels.invalidAPIKeyTitle,
1013 type: 'info'
1014 } );
1015 }
1016 return true;
1017 }
1018
1019 if ( imagifyOptions.bulk.isOverQuota ) {
1020 if ( displayErrorMessage ) {
1021 this.displayError( {
1022 title: imagifyOptions.bulk.labels.overQuotaTitle,
1023 html: $( '#tmpl-imagify-overquota-alert' ).html(),
1024 type: 'info',
1025 customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
1026 showConfirmButton: false
1027 } );
1028 }
1029 return true;
1030 }
1031
1032 return false;
1033 },
1034
1035 /*
1036 * Display an error message in a modal.
1037 *
1038 * @param {string} title The modal title.
1039 * @param {string} text The modal text.
1040 * @param {object} args Other less common args.
1041 */
1042 displayError: function ( title, text, args ) {
1043 var def = {
1044 title: '',
1045 html: '',
1046 type: 'error',
1047 customClass: '',
1048 width: 620,
1049 padding: 0,
1050 showCloseButton: true,
1051 showConfirmButton: true
1052 };
1053
1054 if ( $.isPlainObject( title ) ) {
1055 args = $.extend( {}, def, title );
1056 } else {
1057 args = args || {};
1058 args = $.extend( {}, def, {
1059 title: title || '',
1060 html: text || ''
1061 }, args );
1062 }
1063
1064 args.title = args.title || imagifyOptions.bulk.labels.error;
1065 args.customClass += ' imagify-sweet-alert';
1066
1067 swal( args ).catch( swal.noop );
1068 },
1069
1070 /*
1071 * Get the URL used for ajax requests.
1072 *
1073 * @param {string} action An ajax action, or part of it.
1074 * @param {string} context The context.
1075 * @return {string}
1076 */
1077 getAjaxUrl: function ( action, context ) {
1078 var url;
1079
1080 url = ajaxurl + w.imagify.concat + '_wpnonce=' + imagifyOptions.bulk.ajaxNonce;
1081 url += '&action=' + imagifyOptions.bulk.ajaxActions[ action ];
1082 url += '&context=' + context;
1083 url += '&imagify_action=generate_webp';
1084
1085 return url;
1086 },
1087
1088 /*
1089 * Stop everything and set an error.
1090 *
1091 * @param {string} errorId An error ID.
1092 */
1093 stopProcess: function ( errorId ) {
1094 this.processIsStopped = true;
1095
1096 this.error = errorId;
1097
1098 this.queueEmpty();
1099 }
1100 };
1101
1102 w.imagify.optionsBulk.init();
1103
1104 } )(window, document, jQuery);
1105 /* eslint-enable no-underscore-dangle, consistent-this */
1106
1107
1108 // "Select all" checkboxes =========================================================================
1109 (function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
1110
1111 var jqPropHookChecked = $.propHooks.checked;
1112
1113 // Force `.prop()` to trigger a `change` event.
1114 $.propHooks.checked = {
1115 set: function( elem, value, name ) {
1116 var ret;
1117
1118 if ( undefined === jqPropHookChecked ) {
1119 ret = ( elem[ name ] = value );
1120 } else {
1121 ret = jqPropHookChecked( elem, value, name );
1122 }
1123
1124 $( elem ).trigger( 'change.imagify' );
1125
1126 return ret;
1127 }
1128 };
1129
1130 // Check all checkboxes.
1131 $( '.imagify-select-all' ).on( 'click.imagify', function() {
1132 var $_this = $(this),
1133 action = $_this.data( 'action' ),
1134 $btns = $_this.closest( '.imagify-select-all-buttons' ),
1135 $group = $btns.prev( '.imagify-check-group' ),
1136 inactive = 'imagify-is-inactive';
1137
1138 if ( $_this.hasClass( inactive ) ) {
1139 return false;
1140 }
1141
1142 $btns.find( '.imagify-select-all' ).removeClass( inactive ).attr( 'aria-disabled', 'false' );
1143 $_this.addClass( inactive ).attr( 'aria-disabled', 'true' );
1144
1145 $group.find( '.imagify-row-check' )
1146 .prop( 'checked', function() {
1147 var $this = $( this );
1148
1149 if ( $this.is( ':hidden,:disabled' ) ) {
1150 return false;
1151 }
1152
1153 if ( action === 'select' ) {
1154 return true;
1155 }
1156
1157 return false;
1158 } );
1159
1160 } );
1161
1162 // Change buttons status on checkboxes interation.
1163 $( '.imagify-check-group .imagify-row-check' ).on( 'change.imagify', function() {
1164 var $group = $( this ).closest( '.imagify-check-group' ),
1165 $checks = $group.find( '.imagify-row-check' ),
1166 could_be = $checks.filter( ':visible:enabled' ).length,
1167 are_checked = $checks.filter( ':visible:enabled:checked' ).length,
1168 $btns = $group.next( '.imagify-select-all-buttons' ),
1169 inactive = 'imagify-is-inactive';
1170
1171 // Toggle status of "check all" buttons.
1172 if ( are_checked === 0 ) {
1173 $btns.find( '[data-action="unselect"]' ).addClass( inactive ).attr( 'aria-disabled', 'true' );
1174 }
1175 if ( are_checked === could_be ) {
1176 $btns.find( '[data-action="select"]' ).addClass( inactive ).attr( 'aria-disabled', 'true' );
1177 }
1178 if ( are_checked !== could_be && are_checked > 0 ) {
1179 $btns.find( '.imagify-select-all' ).removeClass( inactive ).attr( 'aria-disabled', 'false' );
1180 }
1181 } );
1182
1183 } )(window, document, jQuery);
1184