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

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

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