PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.3
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.3
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 2.3.3, at assets/js/options.js

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