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
← All changes | assets/js/options.js +1057 -60 1.4 → trunk View file →
@@ -1,21 +1,23 @@
1 -jQuery(function($){
1 +window.imagify = window.imagify || {};
2 +
3 +(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
2 4 /*
3 5 * Process an API key check validity.
4 6 */
5 7 var busy = false,
6 - xhr = false;
8 + xhr = false;
7 9
8 - $('#imagify-settings #api_key').blur(function(){
9 - var obj = $(this),
10 + $( '#imagify-settings #api_key' ).on( 'blur.imagify', function() {
11 + var obj = $( this ),
10 12 value = obj.val();
11 13
12 - if( $.trim(value) === '' ) {
14 + if ( String( value ).trim() === '' ) {
13 15 return false;
14 16 }
15 17
16 - if( $('#check_api_key').val() === value ) {
17 - $('#imagify-check-api-container').html('<span class="dashicons dashicons-yes"></span> ' + imagify.ValidApiKeyText);
18 + if ( $( '#check_api_key' ).val() === value ) {
19 + $( '#imagify-check-api-container' ).html( '<span class="dashicons dashicons-yes"></span> ' + imagifyOptions.labels.ValidApiKeyText );
18 20 return false;
19 21 }
20 22
21 23 if ( true === busy ) {
@@ -20,79 +22,1074 @@
20 22
21 23 if ( true === busy ) {
22 24 xhr.abort();
23 25 } else {
24 - $('#imagify-check-api-container').remove();
25 - obj.after( '<span id="imagify-check-api-container"><span class="imagify-spinner"></span>' + imagify.waitApiKeyCheckText + "</span>" );
26 + $( '#imagify-check-api-container' ).remove();
27 + obj.after( '<span id="imagify-check-api-container"><span class="imagify-spinner"></span>' + imagifyOptions.labels.waitApiKeyCheckText + '</span>' );
26 28 }
27 29
28 30 busy = true;
29 31
30 - xhr = $.get(ajaxurl+"?action=imagify_check_api_key_validity&api_key="+obj.val()+"&imagifycheckapikeynonce="+$('#imagifycheckapikeynonce').val())
31 - .done(function(response){
32 - if( !response.success ) {
33 - $('#imagify-check-api-container').html( '<span class="dashicons dashicons-no"></span> ' + response.data);
34 - } else {
35 - $('#imagify-check-api-container').remove();
36 - swal({
37 - title: imagify.ApiKeyCheckSuccessTitle,
38 - text: imagify.ApiKeyCheckSuccessText,
39 - type: "success",
40 - customClass: "imagify-sweet-alert"
41 - },
42 - function(){
43 - location.reload();
44 - });
45 - }
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 + } );
46 56
47 - busy = false;
48 - });
49 - });
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 + } );
50 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 +
51 75 /**
52 - * Check the boxes by clicking "labels" (aria-describedby items)
76 + * Auto check on options-line input value change.
53 77 */
54 - $('.imagify-options-line').css('cursor', 'pointer').on('click', function(e){
55 - if ( e.target.nodeName === 'INPUT' ) {
78 + $( '.imagify-options-line' ).find( 'input' ).on( 'change.imagify focus.imagify', function() {
79 + var $checkbox;
80 +
81 + if ( 'checkbox' === this.type && ! this.checked ) {
56 82 return;
57 83 }
58 - $('input[aria-describedby="' + $(this).attr('id') + '"]').trigger('click');
59 - return false;
60 - });
61 84
62 - $('.imagify-settings th span').on('click', function(e){
63 - if ( $(this).parent().next('td').find('input:checkbox').length === 1 ) {
64 - $(this).parent().next('td').find('input:checkbox').trigger('click');
85 + $checkbox = $( this ).closest( '.imagify-options-line' ).prev( 'label' ).prev( ':checkbox' );
86 +
87 + if ( $checkbox.length && ! $checkbox[0].checked ) {
88 + $checkbox.prop( 'checked', true );
65 89 }
66 - })
90 + } );
67 91
68 92 /**
69 - * Auto check on options-line input value change
93 + * Imagify Backup alert.
70 94 */
71 - $('.imagify-options-line').find('input').on('change focus', function(){
72 - var $checkbox = $(this).closest('.imagify-options-line').prev('label').prev('input');
73 - if ( ! $checkbox[0].checked ) {
74 - $checkbox.prop('checked', true);
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;
75 117 }
76 - });
77 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 +
78 141 /**
79 - * Imagify Backup alert
142 + * Fade CDN URL field.
80 143 */
81 - $('.imagify-settings-section').find('#backup').on('change', function(){
82 - if ( ! $(this).is(':checked') ) {
83 - var $_this = $(this);
84 - swal({
85 - title: imagifyOptions.noBackupTitle,
86 - text: imagifyOptions.noBackupText,
87 - type: "info",
88 - customClass: "imagify-sweet-alert",
89 - showCancelButton: true
90 - }, function(isConfirm){
91 - if ( ! isConfirm ) {
92 - $_this.prop('checked', true);
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;
93 302 }
94 - });
303 + } );
95 304 }
96 - });
97 305
98 -});
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);