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

1,096 lines 32.2 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 return w.imagify.getProgress( total, remaining );
645 },
646
647 /**
648 * Listen for the custom event "imagifybeat-tick" on $(document).
649 * It allows to update various data periodically.
650 *
651 * @param {object} e Event object.
652 * @param {object} data Object containing all Imagifybeat IDs.
653 */
654 processQueueImagifybeat: function ( e, data ) {
655 var images_status, progress;
656
657 if ( e.data.imagifyOptionsBulk && typeof data[ imagifyOptions.bulk.imagifybeatIDs.progress ] === 'undefined' ) {
658 return;
659 }
660
661 if ( e.data.imagifyOptionsBulk.processIsStopped ) {
662 e.data.imagifyOptionsBulk.processFinished();
663 return;
664 }
665
666 images_status = data[ imagifyOptions.bulk.imagifybeatIDs.progress ];
667
668 if ( images_status.remaining === 0 ) {
669 e.data.imagifyOptionsBulk.processFinished();
670 return;
671 }
672
673 progress = e.data.imagifyOptionsBulk.getProgress( images_status.total, images_status.remaining );
674 e.data.imagifyOptionsBulk.$progressBar.css( 'width', progress.percent + '%' );
675 e.data.imagifyOptionsBulk.$progressText.text( progress.processed + '/' + progress.total );
676 },
677
678 /**
679 * Add a Imagifybeat ID for requirements on "imagifybeat-send" event.
680 *
681 * @param {object} e Event object.
682 * @param {object} data Object containing all Imagifybeat IDs.
683 */
684 addRequirementsImagifybeat: function ( e, data ) {
685 data[ imagifyOptions.bulk.imagifybeatIDs.requirements ] = 1;
686 },
687
688 /**
689 * Listen for the custom event "imagifybeat-tick" on $(document).
690 * It allows to update requirements status periodically.
691 *
692 * @param {object} e Event object.
693 * @param {object} data Object containing all Imagifybeat IDs.
694 */
695 processRequirementsImagifybeat: function ( e, data ) {
696 if ( e.data.imagifyOptionsBulk && typeof data[ imagifyOptions.bulk.imagifybeatIDs.requirements ] === 'undefined' ) {
697 return;
698 }
699
700 data = data[ imagifyOptions.bulk.imagifybeatIDs.requirements ];
701
702 imagifyOptions.bulk.curlMissing = data.curl_missing;
703 imagifyOptions.bulk.editorMissing = data.editor_missing;
704 imagifyOptions.bulk.extHttpBlocked = data.external_http_blocked;
705 imagifyOptions.bulk.apiDown = data.api_down;
706 imagifyOptions.bulk.keyIsValid = data.key_is_valid;
707 imagifyOptions.bulk.isOverQuota = data.is_over_quota;
708 },
709
710 // Optimization ============================================================================
711
712 /*
713 * launch the missing WebP generation process.
714 */
715 launchProcess: function () {
716 var _this;
717
718 if ( this.processIsStopped ) {
719 return;
720 }
721
722 _this = this;
723
724 $.get( this.getAjaxUrl( 'MissingNextGen', imagifyOptions.bulk.contexts ) )
725 .done( function( response ) {
726 var errorMessage;
727
728 if ( _this.processIsStopped ) {
729 return;
730 }
731
732 if ( response.data && response.data.message ) {
733 errorMessage = response.data.message;
734 } else {
735 errorMessage = imagifyOptions.bulk.ajaxErrorText;
736 }
737
738 if ( ! response.success ) {
739 // Error.
740 if ( ! _this.error ) {
741 _this.stopProcess( errorMessage );
742 }
743 return;
744 }
745
746 if ( 0 === response.data.total ) {
747 // No media to process.
748 _this.stopProcess( 'no-images' );
749
750 return;
751 }
752
753 _this.$missingWebpMessage.hide().attr('aria-hidden', 'true');
754
755 // Reset and display the progress bar.
756 _this.$progressText.text( '0' + ( response.data.total ? '/' + response.data.total : '' ) );
757 _this.$progressWrap.slideDown().attr( 'aria-hidden', 'false' ).removeClass( 'hidden' );
758 } )
759 .fail( function() {
760 // Error.
761 if ( ! _this.error ) {
762 _this.stopProcess( 'get-unoptimized-images' );
763 }
764 } );
765 },
766
767 /*
768 * End.
769 */
770 processFinished: function () {
771 var errorArgs = {};
772
773 // Maybe display an error.
774 if ( false !== this.error ) {
775 if ( 'invalid-api-key' === this.error ) {
776 errorArgs = {
777 title: imagifyOptions.bulk.labels.invalidAPIKeyTitle,
778 type: 'info'
779 };
780 }
781 else if ( 'over-quota' === this.error ) {
782 errorArgs = {
783 title: imagifyOptions.bulk.labels.overQuotaTitle,
784 html: $( '#tmpl-imagify-overquota-alert' ).html(),
785 type: 'info',
786 customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
787 showConfirmButton: false
788 };
789 }
790 else if ( 'get-unoptimized-images' === this.error ) {
791 errorArgs = {
792 title: imagifyOptions.bulk.labels.getUnoptimizedImagesErrorTitle,
793 html: imagifyOptions.bulk.labels.getUnoptimizedImagesErrorText,
794 type: 'info'
795 };
796 }
797 else if ( 'no-images' === this.error ) {
798 errorArgs = {
799 title: imagifyOptions.bulk.labels.nothingToDoTitle,
800 html: imagifyOptions.bulk.labels.nothingToDoText,
801 type: 'info'
802 };
803 }
804 else if ( 'no-backup' === this.error ) {
805 errorArgs = {
806 title: imagifyOptions.bulk.labels.nothingToDoTitle,
807 html: imagifyOptions.bulk.labels.nothingToDoNoBackupText,
808 type: 'info'
809 };
810 } else {
811 errorArgs = {
812 title: imagifyOptions.bulk.labels.error,
813 html: this.error,
814 type: 'info'
815 };
816 }
817
818 this.displayError( errorArgs );
819
820 // Reset the error.
821 this.error = false;
822 }
823
824 // Reset.
825 this.working = false;
826 this.processIsStopped = false;
827
828 // Reset Imagifybeat interval and enable suspend.
829 w.imagify.beat.resetInterval();
830 w.imagify.beat.enableSuspend();
831
832 // Reset the progress bar.
833 this.$progressWrap.slideUp().attr( 'aria-hidden', 'true' ).addClass( 'hidden' );
834 this.$progressText.text( '0' );
835 this.$missingWebpElement.hide().attr('aria-hidden', 'true');
836 this.$button.find( '.dashicons' ).removeClass( 'rotate' );
837 },
838
839 // Tools ===================================================================================
840
841 /*
842 * Tell if we have a blocking error. Can also display an error message in a swal.
843 *
844 * @param {bool} displayErrorMessage False to not display any error message.
845 * @return {bool}
846 */
847 hasBlockingError: function ( displayErrorMessage ) {
848 displayErrorMessage = undefined !== displayErrorMessage && displayErrorMessage;
849
850 if ( imagifyOptions.bulk.curlMissing ) {
851 if ( displayErrorMessage ) {
852 this.displayError( {
853 html: imagifyOptions.bulk.labels.curlMissing
854 } );
855 }
856 return true;
857 }
858
859 if ( imagifyOptions.bulk.editorMissing ) {
860 if ( displayErrorMessage ) {
861 this.displayError( {
862 html: imagifyOptions.bulk.labels.editorMissing
863 } );
864 }
865 return true;
866 }
867
868 if ( imagifyOptions.bulk.extHttpBlocked ) {
869 if ( displayErrorMessage ) {
870 this.displayError( {
871 html: imagifyOptions.bulk.labels.extHttpBlocked
872 } );
873 }
874 return true;
875 }
876
877 if ( imagifyOptions.bulk.apiDown ) {
878 if ( displayErrorMessage ) {
879 this.displayError( {
880 html: imagifyOptions.bulk.labels.apiDown
881 } );
882 }
883 return true;
884 }
885
886 if ( ! imagifyOptions.bulk.keyIsValid ) {
887 if ( displayErrorMessage ) {
888 this.displayError( {
889 title: imagifyOptions.bulk.labels.invalidAPIKeyTitle,
890 type: 'info'
891 } );
892 }
893 return true;
894 }
895
896 if ( imagifyOptions.bulk.isOverQuota ) {
897 if ( displayErrorMessage ) {
898 this.displayError( {
899 title: imagifyOptions.bulk.labels.overQuotaTitle,
900 html: $( '#tmpl-imagify-overquota-alert' ).html(),
901 type: 'info',
902 customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
903 showConfirmButton: false
904 } );
905 }
906 return true;
907 }
908
909 return false;
910 },
911
912 /*
913 * Display an error message in a modal.
914 *
915 * @param {string} title The modal title.
916 * @param {string} text The modal text.
917 * @param {object} args Other less common args.
918 */
919 displayError: function ( title, text, args ) {
920 var def = {
921 title: '',
922 html: '',
923 type: 'error',
924 customClass: '',
925 width: 620,
926 padding: 0,
927 showCloseButton: true,
928 showConfirmButton: true
929 };
930
931 if ( $.isPlainObject( title ) ) {
932 args = $.extend( {}, def, title );
933 } else {
934 args = args || {};
935 args = $.extend( {}, def, {
936 title: title || '',
937 html: text || ''
938 }, args );
939 }
940
941 args.title = args.title || imagifyOptions.bulk.labels.error;
942 args.customClass += ' imagify-sweet-alert';
943
944 swal( args ).catch( swal.noop );
945 },
946
947 /*
948 * Get the URL used for ajax requests.
949 *
950 * @param {string} action An ajax action, or part of it.
951 * @param {array} context The contexts.
952 * @return {string}
953 */
954 getAjaxUrl: function ( action, contexts ) {
955 var url;
956
957 url = ajaxurl + w.imagify.concat + '_wpnonce=' + imagifyOptions.bulk.ajaxNonce;
958 url += '&action=' + imagifyOptions.bulk.ajaxActions[ action ];
959 url += '&context=' + contexts.join('_');
960
961 return url;
962 },
963
964 /*
965 * Stop everything and set an error.
966 *
967 * @param {string} errorId An error ID.
968 */
969 stopProcess: function ( errorId ) {
970 this.processIsStopped = true;
971
972 this.error = errorId;
973
974 this.processFinished();
975 }
976 };
977
978 w.imagify.optionsBulk.init();
979
980 } )(window, document, jQuery);
981 /* eslint-enable no-underscore-dangle, consistent-this */
982
983
984 // "Select all" checkboxes =========================================================================
985 (function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
986
987 var jqPropHookChecked = $.propHooks.checked;
988
989 // Force `.prop()` to trigger a `change` event.
990 $.propHooks.checked = {
991 set: function( elem, value, name ) {
992 var ret;
993
994 if ( undefined === jqPropHookChecked ) {
995 ret = ( elem[ name ] = value );
996 } else {
997 ret = jqPropHookChecked( elem, value, name );
998 }
999
1000 $( elem ).trigger( 'change.imagify' );
1001
1002 return ret;
1003 }
1004 };
1005
1006 // Check all checkboxes.
1007 $( '.imagify-select-all' ).on( 'click.imagify', function() {
1008 var $_this = $(this),
1009 action = $_this.data( 'action' ),
1010 $btns = $_this.closest( '.imagify-select-all-buttons' ),
1011 $group = $btns.prev( '.imagify-check-group' ),
1012 inactive = 'imagify-is-inactive';
1013
1014 if ( $_this.hasClass( inactive ) ) {
1015 return false;
1016 }
1017
1018 $btns.find( '.imagify-select-all' ).removeClass( inactive ).attr( 'aria-disabled', 'false' );
1019 $_this.addClass( inactive ).attr( 'aria-disabled', 'true' );
1020
1021 $group.find( '.imagify-row-check' )
1022 .prop( 'checked', function() {
1023 var $this = $( this );
1024
1025 if ( $this.is( ':hidden,:disabled' ) ) {
1026 return false;
1027 }
1028
1029 if ( action === 'select' ) {
1030 return true;
1031 }
1032
1033 return false;
1034 } );
1035
1036 } );
1037
1038 // Change buttons status on checkboxes interation.
1039 $( '.imagify-check-group .imagify-row-check' ).on( 'change.imagify', function() {
1040 var $group = $( this ).closest( '.imagify-check-group' ),
1041 $checks = $group.find( '.imagify-row-check' ),
1042 could_be = $checks.filter( ':visible:enabled' ).length,
1043 are_checked = $checks.filter( ':visible:enabled:checked' ).length,
1044 $btns = $group.next( '.imagify-select-all-buttons' ),
1045 inactive = 'imagify-is-inactive';
1046
1047 // Toggle status of "check all" buttons.
1048 if ( are_checked === 0 ) {
1049 $btns.find( '[data-action="unselect"]' ).addClass( inactive ).attr( 'aria-disabled', 'true' );
1050 }
1051 if ( are_checked === could_be ) {
1052 $btns.find( '[data-action="select"]' ).addClass( inactive ).attr( 'aria-disabled', 'true' );
1053 }
1054 if ( are_checked !== could_be && are_checked > 0 ) {
1055 $btns.find( '.imagify-select-all' ).removeClass( inactive ).attr( 'aria-disabled', 'false' );
1056 }
1057 } );
1058
1059 } )(window, document, jQuery);
1060
1061 // Imagify Analytics opt-in toggle ================================================================
1062 (function($) {
1063
1064 var $checkbox = $( '#imagify-analytics-enabled' );
1065
1066 if ( ! $checkbox.length ) {
1067 return;
1068 }
1069
1070 $checkbox.on( 'change.imagify-analytics', function() {
1071 var nonce = $( this ).data( 'nonce' );
1072
1073 $.post( ajaxurl, {
1074 action: 'imagify_toggle_tracking_optin',
1075 value: $( this ).prop( 'checked' ) ? 1 : 0,
1076 nonce: nonce
1077 } );
1078 } );
1079
1080 $( document ).on( 'click.imagify-analytics', '#imagify-analytics-enable-from-modal', function() {
1081 $( '.imagify-modal.modal-is-open .close-btn' ).trigger( 'click.imagify' );
1082 $checkbox.prop( 'checked', true ).trigger( 'change.imagify-analytics' );
1083 } );
1084
1085 })(jQuery);
1086
1087 // Imagify Analytics opt-in notice: toggle the data preview =======================================
1088 (function($) {
1089
1090 $( document ).on( 'click.imagify-analytics', '.imagify-analytics-preview-toggle', function( e ) {
1091 e.preventDefault();
1092 $( this ).closest( 'p' ).next( '.imagify-analytics-data-container' ).slideToggle();
1093 } );
1094
1095 })(jQuery);
1096