/* global WP_Smush */ /* global ajaxurl */ /* global wp_smushit_data */ import {GlobalStats} from "../common/globalStats"; import SmushProgress from "../common/progressbar"; let perf = 0; import MixPanel from "../mixpanel"; /** * Smush class. * * @since 2.9.0 Moved from admin.js into a dedicated ES6 class. */ class Smush { /** * Class constructor. * * @param {Object} button Button object that made the call. * @param {boolean} bulk Bulk smush or not. * @param {string} type Accepts: 'nextgen', 'media'. */ constructor( button, bulk, type = 'media' ) { this.onStart(); //If smush attribute is not defined, Need not skip re-Smush IDs. this.skip_resmush = ! ( 'undefined' === typeof button.data( 'smush' ) || ! button.data( 'smush' ) ); this.button = jQuery( button[ 0 ] ); this.is_bulk = typeof bulk ? bulk : false; this.url = ajaxurl; this.log = jQuery( '.smush-final-log' ); this.setIds(); this.mixPanel = MixPanel.getInstance(); this.is_bulk_resmush = 0 < wp_smushit_data.resmush.length && ! this.skip_resmush; this.status = this.button.parent().prev( '.smush-status' ); // Added for NextGen support. this.smush_type = type; this.single_ajax_suffix = 'nextgen' === this.smush_type ? 'smush_manual_nextgen' : 'wp_smushit_manual'; this.bulk_ajax_suffix = 'nextgen' === this.smush_type ? 'wp_smushit_nextgen_bulk' : 'wp_smushit_bulk'; this.get_stats_action = 'nextgen' === this.smush_type ? 'nextgen_get_stats' : 'get_stats'; this.url = this.is_bulk ? Smush.smushAddParams( this.url, { action: this.bulk_ajax_suffix, } ) : Smush.smushAddParams( this.url, { action: this.single_ajax_suffix, } ); this.start(); ( ! this.is_bulk ) && this.run(); this.bindDeferredEvents(); // Handle cancel ajax. // this.cancelAjax(); return this; } /** * Add params to the URL. * * @param {string} url URL to add the params to. * @param {Object} data Object with params. * @return {string} URL with params. */ static smushAddParams( url, data ) { if ( ! jQuery.isEmptyObject( data ) ) { url += ( url.indexOf( '?' ) >= 0 ? '&' : '?' ) + jQuery.param( data ); } return url; } /** * Check membership validity. * * @param {Object} data * @param {number} data.show_warning */ static membershipValidity( data ) { const memberValidityNotice = jQuery( '#wp-smush-invalid-member' ); // Check for membership warning. if ( 'undefined' !== typeof data && 'undefined' !== typeof data.show_warning && memberValidityNotice.length > 0 ) { if ( data.show_warning ) { memberValidityNotice.show(); } else { memberValidityNotice.hide(); } } } /** * Send Ajax request for compressing the image. * * @param {boolean} isBulkResmush * @param {number} id * @param {string} sendUrl * @param {string} nonce * @param {boolean} newBulkSmushStarted * @return {*|jQuery.promise|void} Compression results. */ static ajax( isBulkResmush, id, sendUrl, nonce, newBulkSmushStarted= false ) { const param = jQuery.param( { is_bulk_resmush: isBulkResmush, attachment_id: id, _nonce: nonce, new_bulk_smush_started: newBulkSmushStarted } ); return jQuery.ajax( { type: 'GET', data: param, url: sendUrl, /** @param {Array} wp_smushit_data */ timeout: wp_smushit_data.timeout, dataType: 'json', } ); } /** * Sets this.ids. */ setIds() { let ids = []; if ( 0 < wp_smushit_data.resmush.length && ! this.skip_resmush ) { if ( 0 < wp_smushit_data.unsmushed.length ) { ids = wp_smushit_data.resmush.concat( wp_smushit_data.unsmushed ); } else { ids = wp_smushit_data.resmush; } } else { ids = wp_smushit_data.unsmushed; } if ( 'object' === typeof ids ) { // If button has re-Smush class, and we do have ids that needs to re-Smushed, put them in the list. this.ids = ids.filter( function( itm, i, a ) { return i === a.indexOf( itm ); } ); } else { this.ids = ids; } } /** * Show loader in button for single and bulk Smush. */ start() { this.button.prop( 'disabled', true ); this.button.addClass( 'wp-smush-started' ); this.bulkStart(); this.singleStart(); } /** * Start bulk Smush. */ bulkStart() { if ( ! this.is_bulk ) { return; } // Hide the bulk div. jQuery( '.wp-smush-bulk-wrapper' ).addClass( 'sui-hidden' ); // Hide the bulk limit message. jQuery( '.wp-smush-bulk-progress-bar-wrapper .sui-notice-warning:first-of-type' ).hide(); // Hide parent wrapper, if there are no other messages. if ( 0 >= jQuery( 'div.smush-final-log .smush-bulk-error-row' ).length ) { jQuery( 'div.smush-final-log' ).addClass('sui-hidden'); } // Show the progress bar. jQuery( '.bulk-smush-wrapper .wp-smush-bulk-progress-bar-wrapper, #wp-smush-running-notice' ).removeClass( 'sui-hidden' ); } /** * Start single image Smush. */ singleStart() { if ( this.is_bulk ) { return; } this.button.html( '' + window.wp_smush_msgs.smushing + '' ); this.status.removeClass( 'error' ); } /** * Enable button. */ enableButton() { this.button.prop( 'disabled', false ); jQuery('.wp-smush-all').prop('disabled', false); jQuery('.wp-smush-restore').prop('disabled', false); // For bulk process, enable other buttons. jQuery( 'a.wp-smush-lossy-enable, button.wp-smush-resize-enable, button#save-settings-button' ).prop('disabled', false); } /** * Finish single image Smush. */ singleDone() { if ( this.is_bulk ) { return; } const self = this; this.request .done( function( response ) { if ( 'undefined' !== typeof response.data ) { // Check if stats div exists. const parent = self.status.parent(); // Check whether to show membership validity notice or not. Smush.membershipValidity( response.data ); if ( ! response.success ) { if ( response.data.html_stats ) { parent.html( response.data.html_stats ); } else { self.status.addClass( 'smush-warning' ); /** @param {string} response.data.error_msg */ self.status.html( response.data.error_msg ); self.button.html( window.smush_vars.strings.stats_label ); } } else { // If we've updated status, replace the content. parent.html( response.data ); self.button.html( window.wp_smush_msgs.all_done ); } /** * Update image size in attachment info panel. * * @param {string|number} response.data.new_size */ Smush.updateImageStats( response.data.new_size ); } } ) .fail( function( response ) { self.status.html( response.data ); self.status.addClass( 'smush-warning' ); self.enableButton(); } ); } /** * Get total images left to optimize. * * @see get_total_images_to_smush() in Abstract_Summary_Page class. * * @since 3.10.0 */ static getTotalImagesToSmush() { const imagesToResmush = wp_smushit_data.resmush.length; const unsmushedCount = wp_smushit_data.count_total - wp_smushit_data.count_smushed; if ( unsmushedCount > 0 ) { return imagesToResmush + unsmushedCount; } return imagesToResmush; } /** * Update the "optimized images" score on the summary meta box. * * @see get_grade_data() in Abstract_Summary_Page class. * * @since 3.10.0 */ static updateScoreProgress() { let grade = 'sui-grade-dismissed'; let percentOptimized = 0; let percentMetric = 0; const totalImagesToSmush = Smush.getTotalImagesToSmush(); const totalImages = parseInt( wp_smushit_data.count_total ); if ( totalImages === totalImagesToSmush ) { if ( totalImages > 0 ) { grade = 'sui-grade-f'; } percentMetric = 100; } else if ( 0 < totalImages ) { percentOptimized = Math.floor( ( totalImages - totalImagesToSmush ) * 100 / totalImages ); percentMetric = percentOptimized; grade = 'sui-grade-f'; if ( percentOptimized >= 60 && percentOptimized < 90 ) { grade = 'sui-grade-c'; } else if ( percentOptimized >= 90 ) { grade = 'sui-grade-a'; } } const imageScore = jQuery( '#smush-image-score' ); imageScore .removeClass( function( index, className ) { const matchedClasses = className.match( /(^|\s)sui-grade-\S+/g ); return ( matchedClasses || [] ).join( ' ' ); } ) .addClass( grade ) .attr( 'data-score', percentOptimized ) .find( '.sui-circle-score-label' ).html( percentOptimized ); imageScore .find( 'circle:last-child' ) .attr( 'style', '--metric-array:' + ( 2.63893782902 * percentMetric ) + ' ' + ( 263.893782902 - percentMetric ) ); } /** * Update all stats sections based on the response. * * @param {string} scanType Current scan type. */ static updateStats( scanType ) { const isNextgen = 'undefined' !== typeof scanType && 'nextgen' === scanType; // Calculate updated savings in bytes. wp_smushit_data.savings_bytes = parseInt( wp_smushit_data.size_before ) - parseInt( wp_smushit_data.size_after ); const formattedSize = WP_Smush.helpers.formatBytes( wp_smushit_data.savings_bytes, 0 ); const statsHuman = jQuery( '.wp-smush-savings .wp-smush-stats-human' ); if ( isNextgen ) { statsHuman.html( formattedSize ); } else { statsHuman.html( WP_Smush.helpers.getFormatFromString( formattedSize ) ); jQuery( '.sui-summary-large.wp-smush-stats-human' ) .html( WP_Smush.helpers.getSizeFromString( formattedSize ) ); } // Update the savings percent. wp_smushit_data.savings_percent = WP_Smush.helpers.precise_round( ( parseInt( wp_smushit_data.savings_bytes ) / parseInt( wp_smushit_data.size_before ) ) * 100, 1 ); if ( ! isNaN( wp_smushit_data.savings_percent ) ) { jQuery( '.wp-smush-savings .wp-smush-stats-percent' ) .html( wp_smushit_data.savings_percent ); } // Update image count. if ( isNextgen ) { jQuery( '.sui-summary-details span.wp-smush-total-optimised' ) .html( wp_smushit_data.count_images ); } else { jQuery( 'span.smushed-items-count span.wp-smush-count-total span.wp-smush-total-optimised' ) .html( wp_smushit_data.count_images ); } // Update resize image count. if ( wp_smushit_data.count_resize > 0 ) { jQuery( 'span.smushed-items-count span.wp-smush-count-resize-total' ).removeClass( 'sui-hidden' ); jQuery( 'span.smushed-items-count span.wp-smush-count-resize-total span.wp-smush-total-optimised' ) .html( wp_smushit_data.count_resize ); } // Update super-Smushed image count. const smushedCountDiv = jQuery( 'li.super-smush-attachments .smushed-count' ); if ( smushedCountDiv.length && 'undefined' !== typeof wp_smushit_data.count_supersmushed ) { smushedCountDiv.html( wp_smushit_data.count_supersmushed ); } // Update conversion savings. const smushConversionSavings = jQuery( '.smush-conversion-savings' ); if ( smushConversionSavings.length > 0 && 'undefined' !== typeof wp_smushit_data.savings_conversion && wp_smushit_data.savings_conversion !== '' ) { const conversionSavings = smushConversionSavings.find( '.wp-smush-stats' ); if ( conversionSavings.length > 0 ) { conversionSavings.html( WP_Smush.helpers.formatBytes( wp_smushit_data.savings_conversion, 1 ) ); } } } /** * Update image size in attachment info panel. * * @since 2.8 * * @param {number} newSize */ static updateImageStats( newSize ) { if ( 0 === newSize ) { return; } const attachmentSize = jQuery( '.attachment-info .file-size' ); const currentSize = attachmentSize .contents() .filter( function() { return this.nodeType === 3; } ) .text(); // There is a space before the size. if ( currentSize !== ' ' + newSize ) { const sizeStrongEl = attachmentSize .contents() .filter( function() { return this.nodeType === 1; } ) .text(); attachmentSize.html( '' + sizeStrongEl + ' ' + newSize ); } } /** * Sync stats. */ syncStats() { const messageHolder = jQuery( 'div.wp-smush-bulk-progress-bar-wrapper div.wp-smush-count.tc' ); // Store the existing content in a variable. const progressMessage = messageHolder.html(); /** @param {string} wp_smush_msgs.sync_stats */ messageHolder.html( window.wp_smush_msgs.sync_stats ); const self = this; // Send ajax. return jQuery .ajax( { type: 'GET', url: ajaxurl, data: { action: this.get_stats_action, _ajax_nonce: window.wp_smush_msgs.nonce, }, success( response ) { if ( ! response?.success ) { WP_Smush.helpers.showNotice( response, { showdismiss: true, autoclose: false, } ); return; } GlobalStats.updateGlobalStatsFromSmushScriptData( response.data ); GlobalStats.renderStats(); SmushProgress.update( 0, response.data.remaining_count ); jQuery('.wp-smush-scan').prop('disabled', false); self.hideBulkFreeLimitReachedNotice(); }, } ) .always( () => messageHolder.html( progressMessage ) ); } /** * After the bulk optimization has been finished. */ bulkDone() { if ( ! this.is_bulk ) { return; } // Enable the button. this.enableButton(); // Show notice. if ( 0 === this.ids.length ) { jQuery('.bulk-smush-wrapper .wp-smush-all-done').removeClass( 'sui-hidden' ); jQuery( '.wp-smush-bulk-wrapper' ).addClass( 'sui-hidden' ); // Hide the progress bar if scan is finished. jQuery( '.wp-smush-bulk-progress-bar-wrapper' ).addClass( 'sui-hidden' ); // Reset the progress when we finish so the next smushing starts from zero. this._updateProgress(0, 0); } else { // TODO: REMOVE "re-smush-notice" since no longer used. And maybe for "wp-smush-remaining" too. const notice = jQuery( '.bulk-smush-wrapper .wp-smush-resmush-notice' ); if ( notice.length > 0 ) { notice.show(); } else { jQuery( '.bulk-smush-wrapper .wp-smush-remaining' ).removeClass( 'sui-hidden' ); } } // Enable re-Smush and scan button. jQuery( '.wp-resmush.wp-smush-action' ).removeProp( 'disabled' ); } showAnimatedUpsellNotice() { if ( ! this.errors.length ) { return; } // Only show animated upsell if exists an animated error in first 5 errors. // Note, this.errors will be reset each we resume so let detect animated error from elements. const bulkErrors = document.querySelector('.smush-bulk-errors'); if ( ! bulkErrors ) { return; } const firstAnimatedError = bulkErrors.querySelector( '[data-error-code="animated"]' ); if ( ! firstAnimatedError ) { return; } const first5Errors = Array.prototype.slice.call(bulkErrors.childNodes, 0, 5 ); return first5Errors.includes( firstAnimatedError ); } maybeShowCDNActivationNotice() { // Only for pro users. if ( ! wp_smush_msgs.smush_cdn_activation_notice || ! this.showAnimatedUpsellNotice() ) { return; } WP_Smush.helpers.renderActivationCDNNotice( wp_smush_msgs.smush_cdn_activation_notice ); } maybeShowUnlimitedUpsellNotice() { const unlimitedUpsellNotice = document.querySelector('.wp-smush-global-upsell'); if ( ! unlimitedUpsellNotice ) { return; } unlimitedUpsellNotice.classList.remove( 'sui-hidden' ); } maybeShowBulkErrorActions() { if ( ! this.errors.length ) { return; } const bulkErrorActionsElement = document.querySelector('.smush-bulk-errors-actions'); if ( ! bulkErrorActionsElement ) { return; } bulkErrorActionsElement.classList.remove('sui-hidden'); } /** * Free Smush limit exceeded. */ freeExceeded() { const progress = jQuery( '.wp-smush-bulk-progress-bar-wrapper' ); progress.addClass( 'wp-smush-exceed-limit' ).removeClass('sui-hidden'); progress .find( '.sui-progress-block .wp-smush-cancel-bulk' ) .removeClass( 'sui-hidden' ); progress .find( '.sui-progress-block .wp-smush-all' ) .addClass( 'sui-hidden' ); progress .find( 'i.sui-icon-loader' ) .addClass( 'sui-icon-info' ) .removeClass( 'sui-icon-loader' ) .removeClass( 'sui-loading' ); document .getElementById( 'bulk-smush-resume-button' ) .classList.remove( 'sui-hidden' ); this.showBulkFreeLimitReachedNotice(); } showBulkFreeLimitReachedNotice() { const bulkFreeLimitReachedNotice = document.getElementById( 'smush-limit-reached-notice' ); if ( bulkFreeLimitReachedNotice ) { bulkFreeLimitReachedNotice.classList.remove( 'sui-hidden' ); } } hideBulkFreeLimitReachedNotice() { const bulkFreeLimitReachedNotice = document.getElementById( 'smush-limit-reached-notice' ); if ( bulkFreeLimitReachedNotice ) { bulkFreeLimitReachedNotice.classList.add( 'sui-hidden' ); } } /** * Adds the stats for the current image to existing stats. * * @param {Array} imageStats * @param {string} imageStats.count * @param {boolean} imageStats.is_lossy * @param {Array} imageStats.savings_resize * @param {Array} imageStats.savings_conversion * @param {string} imageStats.size_before * @param {string} imageStats.size_after * @param {string} type */ static updateLocalizedStats( imageStats, type ) { // Increase the Smush count. if ( 'undefined' === typeof window.wp_smushit_data ) { return; } // No need to increase attachment count, resize, conversion savings for directory Smush. if ( 'media' === type ) { wp_smushit_data.count_smushed = parseInt( wp_smushit_data.count_smushed ) + 1; // Increase Smushed image count. wp_smushit_data.count_images = parseInt( wp_smushit_data.count_images ) + parseInt( imageStats.count ); // Increase super Smush count, if applicable. if ( imageStats.is_lossy ) { wp_smushit_data.count_supersmushed = parseInt( wp_smushit_data.count_supersmushed ) + 1; } // Add to resize savings. wp_smushit_data.savings_resize = 'undefined' !== typeof imageStats.savings_resize.bytes ? parseInt( wp_smushit_data.savings_resize ) + parseInt( imageStats.savings_resize.bytes ) : parseInt( wp_smushit_data.savings_resize ); // Update resize count. wp_smushit_data.count_resize = 'undefined' !== typeof imageStats.savings_resize.bytes ? parseInt( wp_smushit_data.count_resize ) + 1 : wp_smushit_data.count_resize; // Add to conversion savings. wp_smushit_data.savings_conversion = 'undefined' !== typeof imageStats.savings_conversion && 'undefined' !== typeof imageStats.savings_conversion.bytes ? parseInt( wp_smushit_data.savings_conversion ) + parseInt( imageStats.savings_conversion.bytes ) : parseInt( wp_smushit_data.savings_conversion ); } else if ( 'directory_smush' === type ) { //Increase smushed image count wp_smushit_data.count_images = parseInt( wp_smushit_data.count_images ) + 1; } else if ( 'nextgen' === type ) { wp_smushit_data.count_smushed = parseInt( wp_smushit_data.count_smushed ) + 1; wp_smushit_data.count_supersmushed = parseInt( wp_smushit_data.count_supersmushed ) + 1; // Increase Smushed image count. wp_smushit_data.count_images = parseInt( wp_smushit_data.count_images ) + parseInt( imageStats.count ); } // If we have savings. Update savings. if ( imageStats.size_before > imageStats.size_after ) { wp_smushit_data.size_before = 'undefined' !== typeof imageStats.size_before ? parseInt( wp_smushit_data.size_before ) + parseInt( imageStats.size_before ) : parseInt( wp_smushit_data.size_before ); wp_smushit_data.size_after = 'undefined' !== typeof imageStats.size_after ? parseInt( wp_smushit_data.size_after ) + parseInt( imageStats.size_after ) : parseInt( wp_smushit_data.size_after ); } // Add stats for resizing. Update savings. if ( 'undefined' !== typeof imageStats.savings_resize ) { wp_smushit_data.size_before = 'undefined' !== typeof imageStats.savings_resize.size_before ? parseInt( wp_smushit_data.size_before ) + parseInt( imageStats.savings_resize.size_before ) : parseInt( wp_smushit_data.size_before ); wp_smushit_data.size_after = 'undefined' !== typeof imageStats.savings_resize.size_after ? parseInt( wp_smushit_data.size_after ) + parseInt( imageStats.savings_resize.size_after ) : parseInt( wp_smushit_data.size_after ); } // Add stats for conversion. Update savings. if ( 'undefined' !== typeof imageStats.savings_conversion ) { wp_smushit_data.size_before = 'undefined' !== typeof imageStats.savings_conversion.size_before ? parseInt( wp_smushit_data.size_before ) + parseInt( imageStats.savings_conversion.size_before ) : parseInt( wp_smushit_data.size_before ); wp_smushit_data.size_after = 'undefined' !== typeof imageStats.savings_conversion.size_after ? parseInt( wp_smushit_data.size_after ) + parseInt( imageStats.savings_conversion.size_after ) : parseInt( wp_smushit_data.size_after ); } } /** * Update progress. * * @param {Object} _res */ updateProgress( _res ) { if ( ! this.is_bulk_resmush && ! this.is_bulk ) { return; } let progress = 0; // Update localized stats. if ( _res && 'undefined' !== typeof _res.data && 'undefined' !== typeof _res.data.stats ) { Smush.updateLocalizedStats( _res.data.stats, this.smush_type ); } if ( ! this.is_bulk_resmush ) { // Handle progress for normal bulk smush. progress = ( ( this.smushed + this.errors.length ) / this.total ) * 100; } else { // If the request was successful, update the progress bar. if ( _res.success ) { // Handle progress for super Smush progress bar. if ( wp_smushit_data.resmush.length > 0 ) { // Update the count. jQuery( '.wp-smush-images-remaining' ).html( wp_smushit_data.resmush.length ); } else if ( 0 === wp_smushit_data.resmush.length && 0 === this.ids.length ) { // If all images are re-Smushed, show the All Smushed message. jQuery('.bulk-resmush-wrapper .wp-smush-all-done').removeClass( 'sui-hidden' ); // Hide everything else. jQuery( '.wp-smush-resmush-wrap, .wp-smush-bulk-progress-bar-wrapper' ).addClass( 'sui-hidden' ); } } // Handle progress for normal bulk Smush. Set progress bar width. if ( 'undefined' !== typeof this.ids && 'undefined' !== typeof this.total && this.total > 0 ) { progress = ( ( this.smushed + this.errors.length ) / this.total ) * 100; } } // Reset the lossless images count in case of pending images for resmush ( Nextgen only ). if ( 'nextgen' === this.smush_type && wp_smushit_data.resmush.length > 0 && (this.smushed + this.errors.length <= 1) ) { wp_smushit_data.count_images -= (wp_smushit_data.resmush.length + 1); } // No more images left. Show bulk wrapper and Smush notice. if ( 0 === this.ids.length ) { // TODO: Check it with BO and maybe move these into progress bar module for reusing. jQuery('.bulk-smush-wrapper .wp-smush-all-done').removeClass( 'sui-hidden' ); jQuery( '.wp-smush-bulk-wrapper' ).addClass( 'sui-hidden' ); } // Increase the progress bar and counter. this._updateProgress( this.smushed + this.errors.length, WP_Smush.helpers.precise_round( progress, 1 ) ); // Avoid updating the stats twice when the bulk smush ends on Smush's page. if (0 !== this.ids.length || 'nextgen' === this.smush_type) { // Update stats and counts. Smush.updateStats(this.smush_type); } } /** * Update progress. * * @param {number} count Number of images optimized. * @param {string} width Percentage complete. * @private */ _updateProgress( count, width ) { if ( ! this.is_bulk && ! this.is_bulk_resmush ) { return; } // Progress bar label. jQuery( 'span.wp-smush-images-percent' ).html( width + '%' ); // Progress bar. jQuery( '.bulk-smush-wrapper .wp-smush-progress-inner' ).css( 'width', width + '%' ); // Progress bar status. jQuery( '.bulk-smush-wrapper .sui-progress-state-text' ) .find( 'span:first-child' ) .html( count ) .find( 'span:last-child' ) .html( this.total ); } /** * Whether to send the ajax requests further or not. * * @return {*|boolean} Should continue or not. */ continue() { return this.continueSmush && this.ids.length > 0 && this.is_bulk; let continueSmush = this.button.attr( 'continue_smush' ); if ( 'undefined' === typeof continueSmush ) { continueSmush = true; } if ( 'false' === continueSmush || ! continueSmush ) { continueSmush = false; } return continueSmush && this.ids.length > 0 && this.is_bulk; } onStart() { this.deferred = jQuery.Deferred(); this.deferred.errors = []; this.continueSmush = true; // TODO: errors will reset after bulk smush limit is reached and user clicks continue. this.errors = []; // Smushed and total we take from the progress bar... I don't like this :-( const progressBar = jQuery( '.bulk-smush-wrapper .sui-progress-state-text' ); this.smushed = parseInt( progressBar.find( 'span:first-child' ).html() ); this.total = parseInt( progressBar.find( 'span:last-child' ).html() ); jQuery('.wp-smush-restore').prop('disabled', true); } /** * Send ajax request for optimizing single and bulk, call update_progress on ajax response. * * @return {*} Ajax call response. */ callAjax(newBulkSmushStarted = false) { /** * This here little piece of code allows to track auto continue clicks and halts bulk Smush until the page * is reloaded. * * @since 3.5.0 * @see https://wordpress.org/plugins/wp-nonstop-smushit/ */ if ( 0 !== perf && 'undefined' !== typeof perf && 10 > performance.now() - perf ) { this.freeExceeded(); return this.deferred; } let nonceValue = window.wp_smush_msgs.nonce; // Remove from array while processing so we can continue where left off. this.current_id = this.is_bulk ? this.ids.shift() : this.button.data( 'id' ); // Remove the ID from respective variable as well. Smush.updateSmushIds( this.current_id ); const nonceField = this.button.parent().find( '#_wp_smush_nonce' ); if ( nonceField.length > 0 ) { nonceValue = nonceField.val(); } const self = this; this.request = Smush.ajax( this.is_bulk_resmush, this.current_id, this.url, nonceValue, newBulkSmushStarted ) .done( function( res ) { // If no response or success is false, do not process further. Increase the error count except if bulk request limit exceeded. if ( 'undefined' === typeof res.success || ( 'undefined' !== typeof res.success && false === res.success && 'undefined' !== typeof res.data && 'limit_exceeded' !== res.data.error ) ) { self.errors.push( self.current_id ); const error = res.data.error; /** @param {string} res.data.file_name */ const errorMsg = WP_Smush.helpers.prepareBulkSmushErrorRow( error.error_message, error.file_name, error.thumbnail, self.current_id, self.smush_type, error.error_code ); self.log.removeClass('sui-hidden'); // Print the error on screen. self.log.find( '.smush-bulk-errors' ).append( errorMsg ); if ( self.errors.length > 4 ) { self.log.find( '.smush-bulk-errors' ).addClass('overflow-box'); jQuery( '.smush-bulk-errors-actions' ).removeClass( 'sui-hidden' ); } } else if ( 'undefined' !== typeof res.success && res.success ) { // Increment the smushed count if image smushed without errors. self.smushed++; } // Check whether to show the warning notice or not. Smush.membershipValidity( res.data ); /** * Bulk Smush limit exceeded: Stop ajax requests, remove progress bar, append the last image ID * back to Smush variable, and reset variables to allow the user to continue bulk Smush. */ if ( 'undefined' !== typeof res.data && 'limit_exceeded' === res.data.error && 'resolved' !== self.deferred.state() ) { // Hide bulk running message. const bulkRunning = document.getElementById( 'wp-smush-running-notice' ); bulkRunning.classList.add( 'sui-hidden' ); // Add a data attribute to the Smush button, to stop sending ajax. // self.button.attr( 'continue_smush', false ); self.continueSmush = false; // Reinsert the current ID. wp_smushit_data.unsmushed.unshift( self.current_id ); self.ids.unshift( self.current_id ); perf = performance.now(); self.freeExceeded(); } else if ( self.is_bulk ) { self.updateProgress( res ); Smush.updateScoreProgress(); } if (0 === self.ids.length && self.is_bulk ) { self.onBulkSmushCompleted(); } self.singleDone(); } ) .always( function() { if ( ! self.continue() || ! self.is_bulk ) { // Calls deferred.done() self.deferred.resolve(); } else { self.callAjax(false); } } ); this.deferred.errors = this.errors; return this.deferred; } maybeShowCDNUpsellForPreSiteOnCompleted() { // Show upsell cdn. const upsellCdn = document.querySelector('.wp-smush-upsell-cdn'); if ( upsellCdn ) { upsellCdn.querySelector('p').innerHTML = wp_smush_msgs.processed_cdn_for_free; upsellCdn.classList.remove('sui-hidden'); } } onBulkSmushCompleted() { // Show upsell unlimited on completed. this.maybeShowUnlimitedUpsellNotice(); // Show CDN activation notice for pro users. this.maybeShowCDNActivationNotice(); // Show CDN upsell for old users. this.maybeShowCDNUpsellForPreSiteOnCompleted(); this.maybeShowBulkErrorActions(); const callback = this.is_bulk ? () => this.trackBulkSmushCompleted() : () => false; this.syncStats().done(callback); } getPercentOptimized(totalImages, totalImagesToSmush) { if (totalImages === totalImagesToSmush || totalImages <= 0) { return 100; } else { return Math.floor((totalImages - totalImagesToSmush) * 100 / totalImages); } } /** * Prepare error row. Will only allow to hide errors for WP media attachments (not nextgen). * * @since 1.9.0 * * @param {string} errorMsg Error message. * @param {string} fileName File name. * @param {string} thumbnail Thumbnail for image (if available). * @param {number} id Image ID. * @param {string} type Smush type: media or netxgen. * * @return {string} Row with error. */ static prepareErrorRow( errorMsg, fileName, thumbnail, id, type ) { const thumbDiv = 'undefined' === typeof thumbnail ? '' : thumbnail; const fileLink = 'undefined' === fileName || 'undefined' === typeof fileName ? 'undefined' : fileName; let tableDiv = '
' + '
' + thumbDiv + '' + fileLink + '' + '' + errorMsg + '' + '
'; if ( 'media' === type ) { tableDiv = tableDiv + '
' + '' + '
'; } tableDiv = tableDiv + '
'; return tableDiv; } trackBulkSmushCompleted() { this.mixPanel.trackBulkSmushCompleted( GlobalStats.getGlobalStats() ); } /** * Send ajax request for single and bulk Smushing. */ run() { // If bulk and we have a definite number of IDs. if ( this.is_bulk && this.ids.length > 0 ) { this.callAjax(true); } if ( ! this.is_bulk ) { this.callAjax(); } } /** * Show bulk Smush errors, and disable bulk Smush button on completion. */ bindDeferredEvents() { const self = this; this.deferred.done( function() { // self.button.removeAttr( 'continue_smush' ); self.continueSmush = true; if ( self.errors.length ) { /** @param {string} wp_smush_msgs.error_in_bulk */ const msg = self.errors.length === self.total ? window.wp_smush_msgs.all_failed : window.wp_smush_msgs.error_in_bulk .replace( '{{errors}}', self.errors.length ) .replace( '{{total}}', self.total ) .replace( '{{smushed}}', self.smushed ); jQuery( '.wp-smush-all-done' ) .addClass( 'sui-notice-warning' ) .removeClass( 'sui-notice-success' ) .find( 'p' ) .html( msg ); } self.bulkDone(); // Re-enable the buttons. jQuery( '.wp-smush-all:not(.wp-smush-finished)' ).prop('disabled', false); } ); } /** * Handles the cancel button click. * Update the UI, and enable the bulk Smush button. */ cancelAjax() { // Add a data attribute to the Smush button, to stop sending ajax. // this.button.attr( 'continue_smush', false ); this.continueSmush = false; // Sync and update stats. this.syncStats(); this.request.abort(); this.enableButton(); this.button.removeClass( 'wp-smush-started' ); wp_smushit_data.unsmushed.unshift( this.current_id ); jQuery( '.wp-smush-bulk-wrapper' ).removeClass( 'sui-hidden' ); // Hide the progress bar. jQuery( '.wp-smush-bulk-progress-bar-wrapper' ).addClass( 'sui-hidden' ); this.mixPanel.trackBulkSmushCancel(); this.hideBulkFreeLimitReachedNotice(); } /** * Remove the current ID from the unSmushed/re-Smush variable. * * @param {number} currentId */ static updateSmushIds( currentId ) { if ( 'undefined' !== typeof wp_smushit_data.unsmushed && wp_smushit_data.unsmushed.length > 0 ) { const uIndex = wp_smushit_data.unsmushed.indexOf( currentId ); if ( uIndex > -1 ) { wp_smushit_data.unsmushed.splice( uIndex, 1 ); } } // Remove from the re-Smush list. if ( 'undefined' !== typeof wp_smushit_data.resmush && wp_smushit_data.resmush.length > 0 ) { const index = wp_smushit_data.resmush.indexOf( currentId ); if ( index > -1 ) { wp_smushit_data.resmush.splice( index, 1 ); } } } } export default Smush;