<?php
/**
 * Unique functions for Standard EWWW IO plugins.
 *
 * This file contains functions that are unique to the regular EWWW IO plugin.
 *
 * @link https://ewww.io
 * @package EWWW_Image_Optimizer
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// Installation routine for PNGOUT.
add_action( 'admin_action_ewww_image_optimizer_install_pngout', 'ewww_image_optimizer_install_pngout_wrapper' );
// Installation routine for SVGCLEANER.
add_action( 'admin_action_ewww_image_optimizer_install_svgcleaner', 'ewww_image_optimizer_install_svgcleaner_wrapper' );
// Removes the binaries when the plugin is deactivated.
register_deactivation_hook( EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE, 'ewww_image_optimizer_remove_binaries' );

/**
 * Resizes an image with gifsicle to preserve animations.
 *
 * @param string $file The file to resize.
 * @param int    $dst_x X-coordinate of destination image (usually 0).
 * @param int    $dst_y Y-coordinate of destination image (usually 0).
 * @param int    $src_x X-coordinate of source image (usually 0 unless cropping).
 * @param int    $src_y Y-coordinate of source image (usually 0 unless cropping).
 * @param int    $dst_w Desired image width.
 * @param int    $dst_h Desired image height.
 * @param int    $src_w Source width.
 * @param int    $src_h Source height.
 * @return string|WP_Error The image contents or the error message.
 */
function ewww_image_optimizer_gifsicle_resize( $file, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) {
	ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
	$tools = array(
		'gifsicle' => ewwwio()->local->get_path( 'gifsicle' ),
	);
	if ( empty( $tools['gifsicle'] ) ) {
		ewwwio_debug_message( 'no gifsicle found for resizing' );
		return new WP_Error(
			'image_resize_error',
			/* translators: %s: name of a tool like jpegtran */
			sprintf( __( '%s is missing', 'ewww-image-optimizer' ), '<em>gifsicle</em>' )
		);
	}
	ewwwio_debug_message( "file: $file " );
	ewwwio_debug_message( "width: $dst_w" );
	ewwwio_debug_message( "height: $dst_h" );

	list( $orig_w, $orig_h ) = ewwwio()->getimagesize( $file );

	$outfile = "$file.tmp";
	// Run gifsicle.
	if ( (int) $orig_w !== (int) $src_w || (int) $orig_h !== (int) $src_h ) {
		$dim_string  = $dst_w . 'x' . $dst_h;
		$crop_string = $src_x . ',' . $src_y . '+' . $src_w . 'x' . $src_h;
		ewwwio_debug_message( "resize to $dim_string" );
		ewwwio_debug_message( "crop to $crop_string" );
		$cmd = "{$tools['gifsicle']} --crop $crop_string  -o " . ewww_image_optimizer_escapeshellarg( $outfile ) . ' ' . ewww_image_optimizer_escapeshellarg( $file );
		ewwwio_debug_message( "running: $cmd" );
		exec( $cmd, $output, $exit );
		$cmd = "{$tools['gifsicle']} --resize-fit $dim_string  -b " . ewww_image_optimizer_escapeshellarg( $outfile );
		ewwwio_debug_message( "running: $cmd" );
		exec( $cmd, $output, $exit );
	} else {
		$dim_string = $dst_w . 'x' . $dst_h;
		$cmd        = "{$tools['gifsicle']} --resize-fit $dim_string  -o " . ewww_image_optimizer_escapeshellarg( $outfile ) . ' ' . ewww_image_optimizer_escapeshellarg( $file );
		ewwwio_debug_message( "running: $cmd" );
		exec( $cmd, $output, $exit );
	}
	ewwwio_debug_message( "$file resized to $outfile" );

	if ( ewwwio_is_file( $outfile ) ) {
		$new_type = ewww_image_optimizer_mimetype( $outfile, 'i' );
		// Check the filesize of the new JPG.
		$new_size = ewww_image_optimizer_filesize( $outfile );
		ewwwio_debug_message( "$outfile exists, testing type and size" );
	} else {
		return new WP_Error( 'image_resize_error', 'file does not exist' );
	}

	if ( ! $new_size || 'image/gif' !== $new_type ) {
		unlink( $outfile );
		return new WP_Error( 'image_resize_error', 'wrong type or zero bytes' );
	}
	ewwwio_debug_message( 'resize success' );
	$image = file_get_contents( $outfile );
	unlink( $outfile );
	return $image;
}

/**
 * Quantize and reduce the bit-depth of a PNG image using pngquant.
 *
 * @param string $file Name of the PNG image file.
 * @param int    $colors Maximum number of colors allowed.
 * @return bool True if the reduction was successful.
 */
function ewww_image_optimizer_pngquant_reduce_png( $file, $colors ) {
	ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
	$tools['pngquant'] = ewwwio()->local->get_path( 'pngquant', true );
	if ( empty( $tools['pngquant'] ) ) {
		return false;
	}
	$colors = (int) min( $colors, 256 );
	$cmd    = $tools['pngquant'] . ' ' . $colors . ' --nofs --skip-if-larger ' . ewww_image_optimizer_escapeshellarg( $file );
	ewwwio_debug_message( "running: $cmd" );
	exec( $cmd, $output, $exit );
	$quantfile = preg_replace( '/\.\w+$/', '-or8.png', $file );
	if ( ewwwio_is_file( $quantfile ) && filesize( $file ) > filesize( $quantfile ) ) {
		ewwwio_debug_message( 'PNG reduction is better: original - ' . filesize( $file ) . ' vs. lossy - ' . filesize( $quantfile ) );
		rename( $quantfile, $file );
		return true;
	} elseif ( ewwwio_is_file( $quantfile ) ) {
		ewwwio_debug_message( 'PNG reduction is worse: original - ' . filesize( $file ) . ' vs. lossy - ' . filesize( $quantfile ) );
		ewwwio_delete_file( $quantfile );
	} else {
		ewwwio_debug_message( 'pngquant did not produce any output' );
	}
	return false;
}

/**
 * Automatically corrects JPG rotation using local jpegtran tool.
 *
 * @param string $file Name of the file to fix.
 * @param string $type File type of the file.
 * @param int    $orientation The EXIF orientation value.
 * @return bool True if the rotation was successful.
 */
function ewww_image_optimizer_jpegtran_autorotate( $file, $type, $orientation ) {
	ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
	if ( 'image/jpeg' !== $type ) {
		ewwwio_debug_message( 'not a JPG, go away!' );
		return false;
	}
	if ( ! $orientation || 1 === (int) $orientation ) {
		return false;
	}
	$nice = '';
	if ( PHP_OS !== 'WINNT' && ! ewwwio()->cloud_mode && ewwwio()->local->exec_check() ) {
		// Check to see if 'nice' exists.
		$nice = ewwwio()->local->find_nix_binary( 'nice' );
	}
	$tools = array(
		'jpegtran' => ewwwio()->local->get_path( 'jpegtran' ),
	);
	if ( empty( $tools['jpegtran'] ) ) {
		return false;
	}
	switch ( $orientation ) {
		case 1:
			return false;
		case 2:
			$transform = '-flip horizontal';
			break;
		case 3:
			$transform = '-rotate 180';
			break;
		case 4:
			$transform = '-flip vertical';
			break;
		case 5:
			$transform = '-transpose';
			break;
		case 6:
			$transform = '-rotate 90';
			break;
		case 7:
			$transform = '-transverse';
			break;
		case 8:
			$transform = '-rotate 270';
			break;
		default:
			return false;
	}
	$outfile = "$file.rotate";
	// Run jpegtran.
	$cmd = "$nice {$tools['jpegtran']} -trim -copy all $transform -outfile " . ewww_image_optimizer_escapeshellarg( $outfile ) . ' ' . ewww_image_optimizer_escapeshellarg( $file );
	ewwwio_debug_message( "running: $cmd" );
	exec( $cmd, $output, $exit );
	ewwwio_debug_message( "$file rotated to $outfile" );

	if ( ewwwio_is_file( $outfile ) ) {
		$new_type = ewww_image_optimizer_mimetype( $outfile, 'i' );
		// Check the filesize of the new JPG.
		$new_size = filesize( $outfile );
		ewwwio_debug_message( "$outfile exists, testing type and size" );
	} else {
		return false;
	}

	if ( ! $new_size || 'image/jpeg' !== $new_type ) {
		unlink( $outfile );
		return false;
	}
	ewwwio_debug_message( 'rotation success' );
	rename( $outfile, $file );
	return true;
}

/**
 * Process an image.
 *
 * @param string $file Full absolute path to the image file.
 * @param int    $gallery_type 1=WordPress, 2=nextgen, 3=flagallery, 4=aux_images, 5=image editor,
 *                             6=imagestore.
 * @param bool   $converted True if this is a resize and the full image was converted to a
 *                          new format. Deprecated, always false now.
 * @param bool   $new_image True if this is a new image, so it should attempt conversion regardless of
 *                    previous results.
 * @param bool   $fullsize True if this is a full size (original) image.
 * @return array {
 *     Status of the optimization attempt.
 *
 *     @type string $file The filename or false on error.
 *     @type string $results The results of the optimization.
 *     @type bool $converted True if an image changes formats.
 *     @type string The original filename if converted.
 * }
 */
function ewww_image_optimizer( $file, $gallery_type = 4, $converted = false, $new_image = false, $fullsize = false ) {
	ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
	session_write_close();
	if ( function_exists( 'wp_raise_memory_limit' ) ) {
		wp_raise_memory_limit( 'image' );
	}
	if ( apply_filters( 'ewww_image_optimizer_bypass', false, $file ) ) {
		ewwwio_debug_message( "optimization bypassed: $file" );
		// Tell the user optimization was skipped.
		return array( false, __( 'Optimization skipped', 'ewww-image-optimizer' ), $converted, $file );
	}
	global $ewww_image;
	global $ewww_convert;
	// Initialize the original filename.
	$original = $file;
	$result   = '';
	if ( false !== strpos( $file, '../' ) || false !== strpos( $file, '..\\' ) ) {
		$msg = __( 'Path traversal in filename not allowed.', 'ewww-image-optimizer' );
		ewwwio_debug_message( "file is using ../ potential path traversal blocked: $file" );
		return array( false, $msg, $converted, $original );
	}
	if ( ! ewwwio_is_file( $file ) ) {
		/* translators: %s: Image filename */
		$msg = sprintf( __( 'Could not find %s', 'ewww-image-optimizer' ), $file );
		ewwwio_debug_message( "file doesn't appear to exist: $file" );
		return array( false, $msg, $converted, $original );
	}
	if ( ! is_writable( $file ) ) {
		/* translators: %s: Image filename */
		$msg = sprintf( __( '%s is not writable', 'ewww-image-optimizer' ), $file );
		ewwwio_debug_message( "couldn't write to the file $file" );
		return array( false, $msg, $converted, $original );
	}
	// Get the original image size.
	$orig_size = ewww_image_optimizer_filesize( $file );
	$type      = ewww_image_optimizer_mimetype( $file, 'i' );
	if ( ! $type ) {
		ewwwio_debug_message( 'could not find mimetype' );
		// Store a 'no savings' record to prevent this image from being processed without the 'force' flag set.
		ewww_image_optimizer_update_table( $file, $orig_size, $orig_size );
		// Otherwise we store an error message since we couldn't get the mime-type.
		return array( false, __( 'Unknown file type', 'ewww-image-optimizer' ), $converted, $original );
	}
	// Not an image or pdf.
	if ( strpos( $type, 'image' ) === false && strpos( $type, 'pdf' ) === false ) {
		ewwwio_debug_message( "unsupported mimetype: $type" );
		// Store a 'no savings' record to prevent this image from being processed without the 'force' flag set.
		ewww_image_optimizer_update_table( $file, $orig_size, $orig_size );
		return array( false, __( 'Unsupported file type', 'ewww-image-optimizer' ) . ": $type", $converted, $original );
	}
	if ( ! is_object( $ewww_image ) || ! $ewww_image instanceof EWWW_Image || $ewww_image->file !== $file ) {
		$ewww_image = new EWWW_Image( 0, '', $file );
	}
	$nice = '';
	if ( PHP_OS !== 'WINNT' && ! ewwwio()->cloud_mode && ewwwio()->local->exec_check() ) {
		// Check to see if 'nice' exists.
		$nice = ewwwio()->local->find_nix_binary( 'nice' );
	}
	$tools = array();
	if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_skip_full' ) && $fullsize ) {
		$keep_metadata = true;
	} else {
		$keep_metadata = false;
	}
	if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_lossy_skip_full' ) && $fullsize ) {
		$skip_lossy = true;
	} else {
		$skip_lossy = false;
	}
	if ( ini_get( 'max_execution_time' ) && ini_get( 'max_execution_time' ) < 90 && ewww_image_optimizer_stl_check() ) {
		set_time_limit( 0 );
	}
	// Get the original image size.
	$orig_size = ewww_image_optimizer_filesize( $file );
	ewwwio_debug_message( "original filesize: $orig_size" );
	if ( $orig_size < ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_size' ) ) {
		ewwwio_debug_message( "optimization bypassed due to filesize: $file" );
		// Tell the user optimization was skipped.
		return array( false, __( 'Optimization skipped', 'ewww-image-optimizer' ), $converted, $file );
	}
	if ( 'image/png' === $type && ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_png_size' ) && $orig_size > ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_png_size' ) ) {
		ewwwio_debug_message( "optimization bypassed due to filesize: $file" );
		// Tell the user optimization was skipped.
		return array( false, __( 'Optimization skipped', 'ewww-image-optimizer' ), $converted, $file );
	}
	if ( 'image/bmp' === $type && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_bmp_convert' ) && empty( $ewww_convert ) ) {
		ewwwio_debug_message( "BMP skipped, no conversion enabled: $file" );
		return array( false, __( 'Optimization skipped', 'ewww-image-optimizer' ), $converted, $file );
	}
	$backup_hash = '';
	$new_size    = 0;
	// Set the optimization process to OFF.
	$optimize = false;
	// Toggle the convert process to ON.
	$convert = true;
	// Allow other plugins to mangle the image however they like prior to optimization.
	do_action( 'ewww_image_optimizer_pre_optimization', $file, $type, $fullsize );
	// Run the appropriate optimization/conversion for the mime-type.
	switch ( $type ) {
		case 'image/bmp':
			if (
				1 === (int) $gallery_type &&
				$fullsize &&
				( ewww_image_optimizer_get_option( 'ewww_image_optimizer_bmp_convert' ) || ! empty( $ewww_convert ) ) &&
				empty( ewwwio()->webp_only )
			) {
				$jpgfile = ewww_image_optimizer_unique_filename( $file, '.jpg' );
			} else {
				$convert = false;
			}
			if ( $convert ) {
				// We leave newfile (param #4) empty, to let the convert() method find the best filetype & corresponding extension.
				// NOTE: at this point, conversion to PNG is disabled, but we'll keep it as is, just in case.
				$new_file = $ewww_image->convert( $file, true, true );
				$new_size = ewww_image_optimizer_filesize( $new_file );
				if ( $new_file && $new_size && $new_size < $orig_size ) {
					$file        = $new_file;
					$converted   = true;
					$results_msg = ewww_image_optimizer_update_table( $file, $new_size, $orig_size, $original );
					// Update some of the EWWW_Image properties to prevent re-conversion.
					$ewww_image->converted = $original;
					$ewww_image->opt_size  = $new_size;
					// Then, make sure the optimization will not abort due to the record we just inserted.
					$original_force = ewwwio()->force;
					ewwwio()->force = true;
					ewww_image_optimizer( $file, $gallery_type, false, $new_image, true );
					ewwwio()->force = $original_force;
					$new_size       = ewww_image_optimizer_filesize( $file );
				}
			}
			break;
		case 'image/jpeg':
			$png_size = 0;
			// If jpg2png conversion is enabled, and this image is in the WordPress media library.
			if (
				1 === (int) $gallery_type &&
				$fullsize &&
				empty( $ewww_image->converted ) &&
				( ewww_image_optimizer_get_option( 'ewww_image_optimizer_jpg_to_png' ) || ! empty( $ewww_convert ) ) &&
				empty( ewwwio()->webp_only )
			) {
				// Generate the filename for a PNG:
				// If this is a resize version.
				if ( $converted ) {
					// just change the file extension.
					$pngfile = preg_replace( '/\.\w+$/', '.png', $file );
				} else {
					// If this is a full size image.
					// Get a unique filename for the png image.
					$pngfile = ewww_image_optimizer_unique_filename( $file, '.png' );
				}
			} else {
				// Otherwise, turn conversion OFF.
				$convert = false;
				$pngfile = '';
			}
			$compression_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_jpg_level' );
			// Check for previous optimization, so long as the force flag is not on and this isn't a new image that needs converting.
			if ( empty( ewwwio()->force ) && ! ( $new_image && $convert ) && empty( ewwwio()->webp_only ) ) {
				$results_msg = ewww_image_optimizer_check_table( $file, $orig_size );
				$smart_reopt = ! empty( ewwwio()->force_smart ) && ewww_image_optimizer_level_mismatch( $ewww_image->level, $compression_level ) ? true : false;
				if ( $smart_reopt ) {
					ewwwio_debug_message( "smart re-opt found level mismatch for $file, db says " . $ewww_image->level . " vs. current $compression_level" );
					// If the current compression level is less than what was previously used, and the previous level was premium (or premium plus).
					if ( $compression_level && $compression_level < $ewww_image->level && $ewww_image->level > 20 ) {
						ewwwio_debug_message( "smart re-opt triggering restoration for $file" );
						ewww_image_optimizer_cloud_restore_single_image( $ewww_image->record );
					}
				} elseif ( $results_msg ) {
					return array( $file, $results_msg, $converted, $original );
				}
			}
			$ewww_image->level = $compression_level;
			if ( $compression_level > 10 && empty( ewwwio()->webp_only ) ) {
				list( $file, $converted, $result, $new_size, $backup_hash ) = ewww_image_optimizer_cloud_optimizer( $file, $type, $convert, $pngfile, 'image/png', $skip_lossy );
				if ( $converted ) {
					// Check to see if the user wants the originals deleted.
					if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_delete_originals' ) ) {
						// Delete the original JPG.
						ewwwio_delete_file( $original );
					}
					$converted   = true;
					$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, 'image/png', null, $orig_size !== $new_size );
				} else {
					$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, $type, null, $orig_size !== $new_size );
				}
				break;
			}
			$tools['jpegtran'] = ewwwio()->local->get_path( 'jpegtran' );
			$tools['cwebp']    = ewwwio()->local->get_path( 'cwebp' );
			if ( $convert ) {
				$tools['optipng']  = ewwwio()->local->get_path( 'optipng' );
				$tools['pngout']   = ewwwio()->local->get_path( 'pngout' );
				$tools['pngquant'] = ewwwio()->local->get_path( 'pngquant' );
			}
			// For exec-deprived servers, or those where jpegtran doesn't want to work.
			if ( 10 === (int) $compression_level && empty( $tools['jpegtran'] ) ) {
				if ( empty( ewwwio()->webp_only ) ) {
					list( $file, $converted, $result, $new_size, $backup_hash ) = ewww_image_optimizer_cloud_optimizer( $file, $type );
				}
				$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, $type, $tools['cwebp'], $orig_size !== $new_size );
				break;
			}
			// Get the (possibly new) original image size.
			$orig_size = ewww_image_optimizer_filesize( $file );
			if ( ! empty( ewwwio()->webp_only ) ) {
				ewwwio_debug_message( 'creating webp only, skipping convert and optimize' );
				$webp_result = ewww_image_optimizer_webp_create( $file, $orig_size, $type, $tools['cwebp'] );
				break;
			} elseif ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_jpg_level' ) ) {
				// Store an appropriate message in $result.
				$result = __( 'JPG optimization is disabled', 'ewww-image-optimizer' );
				// Otherwise, if jpegtran doesn't exist.
			} elseif ( empty( $tools['jpegtran'] ) ) {
				/* translators: %s: name of a tool like jpegtran */
				$result = sprintf( __( '%s is missing', 'ewww-image-optimizer' ), '<em>jpegtran</em>' );
				// Otherwise, things should be good, so...
			} else {
				// Set the optimization process to ON.
				$optimize = true;
			}
			// If we get this far, we are using local (jpegtran) optimization, so do an autorotate on the image.
			ewww_image_optimizer_autorotate( $file );
			// If local optimization is turned ON.
			if ( $optimize ) {
				ewwwio_debug_message( 'attempting to optimize JPG...' );
				// Generate temporary file-name.
				$progfile = $file . '.prog';
				// Check to see if we are supposed to strip metadata.
				if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_remove' ) && ! $keep_metadata ) {
					// Don't copy metadata.
					$copy_opt = 'none';
				} else {
					// Copy all the metadata.
					$copy_opt = 'all';
				}
				if ( $orig_size > 10240 ) {
					$progressive = '-progressive';
				} else {
					$progressive = '';
				}
				// Run jpegtran.
				$cmd = "$nice " . $tools['jpegtran'] . " -copy $copy_opt -optimize $progressive -outfile " . ewww_image_optimizer_escapeshellarg( $progfile ) . ' ' . ewww_image_optimizer_escapeshellarg( $file );
				ewwwio_debug_message( "running: $cmd" );
				exec( $cmd, $output, $exit );
				// Check the filesize of the new JPG.
				$new_size = ewww_image_optimizer_filesize( $progfile );
				ewwwio_debug_message( "optimized JPG size: $new_size" );
				// If the best-optimized is smaller than the original JPG, and we didn't create an empty JPG.
				if ( $new_size && $orig_size > $new_size && ewww_image_optimizer_mimetype( $progfile, 'i' ) === $type ) {
					// Replace the original with the optimized file.
					rename( $progfile, $file );
					// Store the results of the optimization.
					$result = "$orig_size vs. $new_size";
					// If the optimization didn't produce a smaller JPG.
				} else {
					if ( ewwwio_is_file( $progfile ) ) {
						// Delete the optimized file.
						ewwwio_delete_file( $progfile );
					}
					// Store the results.
					$result   = 'unchanged';
					$new_size = $orig_size;
				}
			} // End if().
			// If the conversion process is turned ON, or if this is a resize and the full-size was converted.
			if ( $convert ) {
				ewwwio_debug_message( "attempting to convert JPG to PNG: $pngfile" );
				if ( empty( $new_size ) ) {
					$new_size = $orig_size;
				}
				// Convert the JPG to PNG.
				if ( ewwwio()->gmagick_support() ) {
					try {
						$gmagick = new Gmagick( $file );
						$gmagick->stripimage();
						$gmagick->setimageformat( 'PNG' );
						$gmagick->writeimage( $pngfile );
					} catch ( Exception $gmagick_error ) {
						ewwwio_debug_message( $gmagick_error->getMessage() );
					}
					$png_size = ewww_image_optimizer_filesize( $pngfile );
				}
				if ( ! $png_size && ewwwio()->imagick_support() ) {
					try {
						$imagick = new Imagick( $file );
						$imagick->stripImage();
						$imagick->setImageFormat( 'PNG' );
						$imagick->writeImage( $pngfile );
					} catch ( Exception $imagick_error ) {
						ewwwio_debug_message( $imagick_error->getMessage() );
					}
					$png_size = ewww_image_optimizer_filesize( $pngfile );
				}
				if ( ! $png_size && ewwwio()->gd_support() ) {
					ewwwio_debug_message( 'converting with GD' );
					imagepng( imagecreatefromjpeg( $file ), $pngfile );
					$png_size = ewww_image_optimizer_filesize( $pngfile );
				}
				// If lossy optimization is ON and full-size exclusion is not active.
				if ( $png_size && 40 === (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_png_level' ) && $tools['pngquant'] && ! $skip_lossy ) {
					ewwwio_debug_message( 'attempting lossy reduction' );
					$cmd = "$nice " . $tools['pngquant'] . ' ' . ewww_image_optimizer_escapeshellarg( $pngfile );
					ewwwio_debug_message( "running: $cmd" );
					exec( $cmd, $output, $exit );
					$quantfile = preg_replace( '/\.\w+$/', '-fs8.png', $pngfile );
					if ( ewwwio_is_file( $quantfile ) && filesize( $pngfile ) > filesize( $quantfile ) ) {
						ewwwio_debug_message( 'lossy reduction is better: original - ' . filesize( $pngfile ) . ' vs. lossy - ' . filesize( $quantfile ) );
						rename( $quantfile, $pngfile );
					} elseif ( ewwwio_is_file( $quantfile ) ) {
						ewwwio_debug_message( 'lossy reduction is worse: original - ' . filesize( $pngfile ) . ' vs. lossy - ' . filesize( $quantfile ) );
						ewwwio_delete_file( $quantfile );
					} else {
						ewwwio_debug_message( 'pngquant did not produce any output' );
					}
				}
				// If optipng isn't disabled.
				if ( $png_size && $tools['optipng'] ) {
					// Retrieve the optipng optimization level.
					$optipng_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_optipng_level' );
					if (
						ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_remove' ) &&
						preg_match( '/0.7/', ewwwio()->local->test_binary( $tools['optipng'], 'optipng' ) ) &&
						! $keep_metadata
					) {
						$strip = '-strip all ';
					} else {
						$strip = '';
					}
					// If the PNG file was created.
					if ( ewwwio_is_file( $pngfile ) ) {
						ewwwio_debug_message( 'optimizing converted PNG with optipng' );
						// Run optipng on the new PNG.
						$cmd = "$nice " . $tools['optipng'] . " -o$optipng_level -quiet $strip " . ewww_image_optimizer_escapeshellarg( $pngfile );
						ewwwio_debug_message( "running: $cmd" );
						exec( $cmd, $output, $exit );
					}
				}
				// If pngout isn't disabled.
				if ( $png_size && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_disable_pngout' ) ) {
					// Retrieve the pngout optimization level.
					$pngout_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_pngout_level' );
					// If the PNG file was created.
					if ( ewwwio_is_file( $pngfile ) ) {
						ewwwio_debug_message( 'optimizing converted PNG with pngout' );
						// Run pngout on the new PNG.
						$cmd = "$nice " . $tools['pngout'] . " -s$pngout_level -q " . ewww_image_optimizer_escapeshellarg( $pngfile );
						ewwwio_debug_message( "running: $cmd" );
						exec( $cmd, $output, $exit );
					}
				}
				$png_size = ewww_image_optimizer_filesize( $pngfile );
				ewwwio_debug_message( "converted PNG size: $png_size" );
				// If the PNG is smaller than the original JPG, and we didn't end up with an empty file.
				if ( $png_size && $new_size > $png_size && ewww_image_optimizer_mimetype( $pngfile, 'i' ) === 'image/png' ) {
					ewwwio_debug_message( "converted PNG is better: $png_size vs. $new_size" );
					// Store the size of the converted PNG.
					$new_size = $png_size;
					// Check to see if the user wants the originals deleted.
					if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_delete_originals' ) ) {
						// Delete the original JPG.
						ewwwio_delete_file( $file );
					}
					// Store the location of the PNG file.
					$file = $pngfile;
					// Let webp know what we're dealing with now.
					$type = 'image/png';
					// Successful conversion and we store the increment.
					$converted = true;
				} else {
					ewwwio_debug_message( 'converted PNG is no good' );
					// Otherwise delete the PNG.
					$converted = false;
					if ( ewwwio_is_file( $pngfile ) ) {
						ewwwio_delete_file( $pngfile );
					}
				}
			} // End if().
			$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, $type, $tools['cwebp'], $orig_size !== $new_size );
			break;
		case 'image/png':
			$jpg_size = 0;
			// Png2jpg conversion is turned on, and the image is in the WordPress media library.
			// We check for transparency later, after optimization, because optipng might fix an empty alpha channel.
			$apng = ewww_image_optimizer_is_animated_png( $file );
			if ( $apng ) {
				$keep_metadata = true;
				$skip_lossy    = true;
			}
			if (
				1 === (int) $gallery_type &&
				$fullsize &&
				empty( $ewww_image->converted ) &&
				( ewww_image_optimizer_get_option( 'ewww_image_optimizer_png_to_jpg' ) || ! empty( $ewww_convert ) ) &&
				! $skip_lossy &&
				empty( ewwwio()->webp_only )
			) {
				ewwwio_debug_message( 'PNG to JPG conversion turned on' );
				$cloud_background = '';
				$r                = '';
				$g                = '';
				$b                = '';
				// If the user set a fill background for transparency.
				$background = ewww_image_optimizer_jpg_background();
				if ( $background ) {
					$cloud_background = "#$background";
					// Set background color for GD.
					$r = hexdec( '0x' . strtoupper( substr( $background, 0, 2 ) ) );
					$g = hexdec( '0x' . strtoupper( substr( $background, 2, 2 ) ) );
					$b = hexdec( '0x' . strtoupper( substr( $background, 4, 2 ) ) );
					// Set the background flag for 'convert'.
					$background = '-background ' . '"' . "#$background" . '"';
				}
				$gquality = ewww_image_optimizer_jpg_quality();
				$gquality = $gquality ? $gquality : '82';
				// If this is a resize version.
				if ( $converted ) {
					// Just replace the file extension with a .jpg.
					$jpgfile = preg_replace( '/\.\w+$/', '.jpg', $file );
					// If this is a full version.
				} else {
					// Construct the filename for the new JPG.
					$jpgfile = ewww_image_optimizer_unique_filename( $file, '.jpg' );
				}
			} else {
				ewwwio_debug_message( 'PNG to JPG conversion turned off' );
				// Turn the conversion process OFF.
				$convert          = false;
				$jpgfile          = '';
				$r                = null;
				$g                = null;
				$b                = null;
				$cloud_background = '';
				$gquality         = null;
			} // End if().
			$compression_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_png_level' );
			// Check for previous optimization, so long as the force flag is not on and this isn't a new image that needs converting.
			if ( empty( ewwwio()->force ) && ! ( $new_image && $convert ) && empty( ewwwio()->webp_only ) ) {
				$results_msg = ewww_image_optimizer_check_table( $file, $orig_size );
				$smart_reopt = ! empty( ewwwio()->force_smart ) && ewww_image_optimizer_level_mismatch( $ewww_image->level, $compression_level ) ? true : false;
				if ( $smart_reopt ) {
					ewwwio_debug_message( "smart re-opt found level mismatch for $file, db says " . $ewww_image->level . " vs. current $compression_level" );
					// If the current compression level is less than what was previously used, and the previous level was premium (or premium plus).
					if ( $compression_level && $compression_level < $ewww_image->level && $ewww_image->level > 20 ) {
						ewwwio_debug_message( "smart re-opt triggering restoration for $file" );
						ewww_image_optimizer_cloud_restore_single_image( $ewww_image->record );
					}
				} elseif ( $results_msg ) {
					return array( $file, $results_msg, $converted, $original );
				}
			}
			$ewww_image->level = $compression_level;
			if (
				$compression_level >= 20 &&
				ewww_image_optimizer_get_option( 'ewww_image_optimizer_cloud_key' ) &&
				empty( ewwwio()->webp_only )
			) {
				list( $file, $converted, $result, $new_size, $backup_hash ) = ewww_image_optimizer_cloud_optimizer(
					$file,
					$type,
					$convert,
					$jpgfile,
					'image/jpeg',
					$skip_lossy,
					$cloud_background,
					$gquality
				);
				if ( $converted ) {
					// Check to see if the user wants the originals deleted.
					if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_delete_originals' ) ) {
						// Delete the original JPG.
						ewwwio_delete_file( $original );
					}
					$converted   = true;
					$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, 'image/jpeg', null, $orig_size !== $new_size );
				} else {
					$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, $type, null, $orig_size !== $new_size );
				}
				break;
			}
			$tools['optipng']  = ewwwio()->local->get_path( 'optipng' );
			$tools['pngout']   = ewwwio()->local->get_path( 'pngout' );
			$tools['pngquant'] = ewwwio()->local->get_path( 'pngquant' );
			$tools['cwebp']    = ewwwio()->local->get_path( 'cwebp' );
			if ( $convert ) {
				$tools['jpegtran'] = ewwwio()->local->get_path( 'jpegtran' );
			}
			// Check if we can (and should) do local PNG optimization.
			if ( ! empty( ewwwio()->webp_only ) ) {
				ewwwio_debug_message( 'creating webp only, skipping convert and optimize' );
				$webp_result = ewww_image_optimizer_webp_create( $file, $orig_size, $type, $tools['cwebp'] );
				break;
			} elseif ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_png_level' ) ) {
				// Tell the user all PNG tools are disabled.
				$result = __( 'PNG optimization is disabled', 'ewww-image-optimizer' );
				// If the utility checking is on, optipng is enabled, but optipng cannot be found.
			} elseif ( empty( $tools['optipng'] ) ) {
				/* translators: %s: name of a tool like jpegtran */
				$result = sprintf( __( '%s is missing', 'ewww-image-optimizer' ), '<em>optipng</em>' );
				// If the utility checking is on, pngout is enabled, but pngout cannot be found.
			} else {
				// Turn optimization on if we made it through all the checks.
				$optimize = true;
			}
			// If optimization is turned on.
			if ( $optimize ) {
				// If lossy optimization is ON and full-size exclusion is not active.
				if ( 40 === (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_png_level' ) && $tools['pngquant'] && ! $skip_lossy ) {
					ewwwio_debug_message( 'attempting lossy reduction' );
					$cmd = "$nice " . $tools['pngquant'] . ' ' . ewww_image_optimizer_escapeshellarg( $file );
					ewwwio_debug_message( "running: $cmd" );
					exec( $cmd, $output, $exit );
					$quantfile = preg_replace( '/\.\w+$/', '-fs8.png', $file );
					if ( ewwwio_is_file( $quantfile ) && filesize( $file ) > filesize( $quantfile ) && ewww_image_optimizer_mimetype( $quantfile, 'i' ) === $type ) {
						ewwwio_debug_message( 'lossy reduction is better: original - ' . filesize( $file ) . ' vs. lossy - ' . filesize( $quantfile ) );
						rename( $quantfile, $file );
					} elseif ( ewwwio_is_file( $quantfile ) ) {
						ewwwio_debug_message( 'lossy reduction is worse: original - ' . filesize( $file ) . ' vs. lossy - ' . filesize( $quantfile ) );
						ewwwio_delete_file( $quantfile );
					} else {
						ewwwio_debug_message( 'pngquant did not produce any output' );
					}
				}
				$tempfile = $file . '.tmp.png';
				copy( $file, $tempfile );
				// If optipng is enabled.
				if ( $tools['optipng'] ) {
					// Retrieve the optimization level for optipng.
					$optipng_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_optipng_level' );
					$strip         = '';
					if (
						ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_remove' ) &&
						preg_match( '/0.7/', ewwwio()->local->test_binary( $tools['optipng'], 'optipng' ) ) &&
						! $keep_metadata
					) {
						$strip = '-strip all ';
					}
					// Run optipng on the PNG file.
					$cmd = "$nice " . $tools['optipng'] . " -o$optipng_level -quiet $strip " . ewww_image_optimizer_escapeshellarg( $tempfile );
					ewwwio_debug_message( "running: $cmd" );
					exec( $cmd, $output, $exit );
				}
				// If pngout is enabled.
				if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_disable_pngout' ) ) {
					// Retrieve the optimization level for pngout.
					$pngout_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_pngout_level' );
					$strip        = '';
					if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_remove' ) || $keep_metadata ) {
						$strip = '-k1';
					}
					// Run pngout on the PNG file.
					$cmd = "$nice " . $tools['pngout'] . " -s$pngout_level -k1 -q " . ewww_image_optimizer_escapeshellarg( $tempfile );
					ewwwio_debug_message( "running: $cmd" );
					exec( $cmd, $output, $exit );
				}
				// Retrieve the filesize of the temporary PNG.
				$new_size = ewww_image_optimizer_filesize( $tempfile );
				// If the new PNG is smaller.
				if ( $new_size && $orig_size > $new_size && ewww_image_optimizer_mimetype( $tempfile, 'i' ) === $type ) {
					// Replace the original with the optimized file.
					rename( $tempfile, $file );
					// Store the results of the optimization.
					$result = "$orig_size vs. $new_size";
					// If the optimization didn't produce a smaller PNG.
				} else {
					if ( ewwwio_is_file( $tempfile ) ) {
						// Delete the optimized file.
						ewwwio_delete_file( $tempfile );
					}
					// Store the results.
					$result   = 'unchanged';
					$new_size = $orig_size;
				}
			} // End if().
			// Retrieve the new filesize of the PNG.
			$new_size = ewww_image_optimizer_filesize( $file );
			// Double check for png2jpg conversion to see if we have an alpha image.
			if ( $convert && ewww_image_optimizer_png_alpha( $file ) && ! ewww_image_optimizer_jpg_background() ) {
				ewwwio_debug_message( 'PNG to JPG conversion turned off due to alpha' );
				$convert = false;
			}
			// If conversion is on and the PNG doesn't have transparency or the user set a background color to replace transparency.
			if ( $convert ) {
				ewwwio_debug_message( "attempting to convert PNG to JPG: $jpgfile" );
				if ( empty( $new_size ) ) {
					$new_size = $orig_size;
				}
				$magick_background = ewww_image_optimizer_jpg_background();
				if ( empty( $magick_background ) ) {
					$magick_background = '000000';
				}
				// Convert the PNG to a JPG with all the proper options.
				if ( ewwwio()->gmagick_support() ) {
					try {
						if ( ewww_image_optimizer_png_alpha( $file ) ) {
							$gmagick_overlay = new Gmagick( $file );
							$gmagick         = new Gmagick();
							$gmagick->newimage( $gmagick_overlay->getimagewidth(), $gmagick_overlay->getimageheight(), '#' . $magick_background );
							$gmagick->compositeimage( $gmagick_overlay, 1, 0, 0 );
						} else {
							$gmagick = new Gmagick( $file );
						}
						$gmagick->setimageformat( 'JPG' );
						$gmagick->setcompressionquality( $gquality );
						$gmagick->writeimage( $jpgfile );
					} catch ( Exception $gmagick_error ) {
						ewwwio_debug_message( $gmagick_error->getMessage() );
					}
					$jpg_size = ewww_image_optimizer_filesize( $jpgfile );
				}
				if ( ! $jpg_size && ewwwio()->imagick_support() ) {
					try {
						$imagick = new Imagick( $file );
						if ( ewww_image_optimizer_png_alpha( $file ) ) {
							$imagick->setImageBackgroundColor( new ImagickPixel( '#' . $magick_background ) );
							$imagick->setImageAlphaChannel( imagick::ALPHACHANNEL_REMOVE );
						}
						$imagick->setImageFormat( 'JPG' );
						$imagick->setImageCompressionQuality( $gquality );
						$imagick->writeImage( $jpgfile );
					} catch ( Exception $imagick_error ) {
						ewwwio_debug_message( $imagick_error->getMessage() );
					}
					$jpg_size = ewww_image_optimizer_filesize( $jpgfile );
				}
				if ( ! $jpg_size && ewwwio()->gd_support() ) {
					ewwwio_debug_message( 'converting with GD' );
					// Retrieve the data from the PNG.
					$input = imagecreatefrompng( $file );
					// Retrieve the dimensions of the PNG.
					list($width, $height) = ewwwio()->getimagesize( $file );
					// Create a new image with those dimensions.
					$output = imagecreatetruecolor( $width, $height );
					if ( '' === $r ) {
						$r = 255;
						$g = 255;
						$b = 255;
					}
					// Allocate the background color.
					$rgb = imagecolorallocate( $output, $r, $g, $b );
					// Fill the new image with the background color.
					imagefilledrectangle( $output, 0, 0, $width, $height, $rgb );
					// Copy the original image to the new image.
					imagecopy( $output, $input, 0, 0, 0, 0, $width, $height );
					// Output the JPG with the quality setting.
					imagejpeg( $output, $jpgfile, $gquality );
				}
				$jpg_size = ewww_image_optimizer_filesize( $jpgfile );
				if ( $jpg_size ) {
					ewwwio_debug_message( "converted JPG filesize: $jpg_size" );
				} else {
					ewwwio_debug_message( 'unable to convert to JPG' );
				}
				// Next we need to optimize that JPG if jpegtran is enabled.
				if ( 10 === (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_jpg_level' ) && ewwwio_is_file( $jpgfile ) && ! empty( $tools['jpegtran'] ) ) {
					// Generate temporary file-name.
					$progfile = $jpgfile . '.prog';
					// Check to see if we are supposed to strip metadata.
					if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_remove' ) && ! $keep_metadata ) {
						// Don't copy metadata.
						$copy_opt = 'none';
					} else {
						// Copy all the metadata.
						$copy_opt = 'all';
					}
					if ( $jpg_size > 10240 ) {
						$progressive = '-progressive';
					} else {
						$progressive = '';
					}
					// Run jpegtran.
					$cmd = "$nice " . $tools['jpegtran'] . " -copy $copy_opt -optimize $progressive -outfile " . ewww_image_optimizer_escapeshellarg( $progfile ) . ' ' . ewww_image_optimizer_escapeshellarg( $jpgfile );
					ewwwio_debug_message( "running: $cmd" );
					exec( $cmd, $output, $exit );
					// Check the filesize of the new JPG.
					$opt_jpg_size = ewww_image_optimizer_filesize( $progfile );
					// If the best-optimized is smaller than the original JPG, and we didn't create an empty JPG.
					if ( $opt_jpg_size && $jpg_size > $opt_jpg_size ) {
						// Replace the original with the optimized file.
						rename( $progfile, $jpgfile );
						// Store the size of the optimized JPG.
						$jpg_size = $opt_jpg_size;
						ewwwio_debug_message( 'optimized JPG was smaller than un-optimized version' );
						// If the optimization didn't produce a smaller JPG.
					} elseif ( ewwwio_is_file( $progfile ) ) {
						ewwwio_delete_file( $progfile );
					}
				}
				ewwwio_debug_message( "converted JPG size: $jpg_size" );
				// If the new JPG is smaller than the original PNG.
				if ( $jpg_size && $new_size > $jpg_size && ewww_image_optimizer_mimetype( $jpgfile, 'i' ) === 'image/jpeg' ) {
					// Store the size of the JPG as the new filesize.
					$new_size = $jpg_size;
					// If the user wants originals delted after a conversion.
					if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_delete_originals' ) ) {
						// Delete the original PNG.
						ewwwio_delete_file( $file );
					}
					// Update the $file location to the new JPG.
					$file = $jpgfile;
					// Let webp know what we're dealing with now.
					$type = 'image/jpeg';
					// Successful conversion, so we store the increment.
					$converted = true;
				} else {
					$converted = false;
					if ( ewwwio_is_file( $jpgfile ) ) {
						// Otherwise delete the new JPG.
						ewwwio_delete_file( $jpgfile );
					}
				}
			} // End if().
			$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, $type, $tools['cwebp'], $orig_size !== $new_size );
			break;
		case 'image/gif':
			// If gif2png is turned on, and the image is in the WordPress media library.
			if (
				empty( ewwwio()->webp_only ) &&
				1 === (int) $gallery_type &&
				$fullsize &&
				empty( $ewww_image->converted ) &&
				( ewww_image_optimizer_get_option( 'ewww_image_optimizer_gif_to_png' ) || ! empty( $ewww_convert ) ) &&
				! ewww_image_optimizer_is_animated( $file )
			) {
				// Generate the filename for a PNG:
				// if this is a resize version...
				if ( $converted ) {
					// just change the file extension.
					$pngfile = preg_replace( '/\.\w+$/', '.png', $file );
				} else {
					// If this is the full version...
					// construct the filename for the new PNG.
					$pngfile = ewww_image_optimizer_unique_filename( $file, '.png' );
				}
			} else {
				// Turn conversion OFF.
				$convert = false;
				$pngfile = '';
			}
			$compression_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_gif_level' );
			// Check for previous optimization, so long as the force flag is on and this isn't a new image that needs converting.
			if ( empty( ewwwio()->force ) && ! ( $new_image && $convert ) && empty( ewwwio()->webp_only ) ) {
				$results_msg = ewww_image_optimizer_check_table( $file, $orig_size );
				$smart_reopt = ! empty( ewwwio()->force_smart ) && ewww_image_optimizer_level_mismatch( $ewww_image->level, $compression_level ) ? true : false;
				if ( $smart_reopt ) {
					ewwwio_debug_message( "smart re-opt found level mismatch for $file, db says " . $ewww_image->level . " vs. current $compression_level" );
					// If the current compression level is less than what was previously used, and the previous level was premium (or premium plus).
					if ( $compression_level && $compression_level < $ewww_image->level && $ewww_image->level > 20 ) {
						ewwwio_debug_message( "smart re-opt triggering restoration for $file" );
						ewww_image_optimizer_cloud_restore_single_image( $ewww_image->record );
					}
				} elseif ( $results_msg ) {
					return array( $file, $results_msg, $converted, $original );
				}
			}
			$ewww_image->level = $compression_level;
			if ( empty( ewwwio()->webp_only ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_cloud_key' ) && 10 === $compression_level ) {
				list( $file, $converted, $result, $new_size, $backup_hash ) = ewww_image_optimizer_cloud_optimizer( $file, $type, $convert, $pngfile, 'image/png', $skip_lossy );
				if ( $converted ) {
					// Check to see if the user wants the originals deleted.
					if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_delete_originals' ) ) {
						// Delete the original GIF.
						ewwwio_delete_file( $original );
					}
					$converted   = true;
					$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, 'image/png', null, $orig_size !== $new_size );
				} else {
					$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, $type, null, $orig_size !== $new_size );
				}
				break;
			}
			$tools['gifsicle'] = ewwwio()->local->get_path( 'gifsicle' );
			if ( $convert ) {
				// NOTE: we can only do local WebP if a GIF is converted to PNG.
				$tools['cwebp']    = ewwwio()->local->get_path( 'cwebp' );
				$tools['optipng']  = ewwwio()->local->get_path( 'optipng' );
				$tools['pngout']   = ewwwio()->local->get_path( 'pngout' );
				$tools['pngquant'] = ewwwio()->local->get_path( 'pngquant' );
			}
			// Check if we can (and should) do local GIF optimization.
			if ( ! empty( ewwwio()->webp_only ) ) {
				// This is for WebP-only mode, no conversion/optimization, and it'll be done via API.
				$webp_result = ewww_image_optimizer_webp_create( $file, $orig_size, $type, null );
				break;
			} elseif ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_gif_level' ) ) {
				$result = __( 'GIF optimization is disabled', 'ewww-image-optimizer' );
				// If utility checking is on, and gifsicle is not installed.
			} elseif ( empty( $tools['gifsicle'] ) ) {
				/* translators: %s: name of a tool like jpegtran */
				$result = sprintf( __( '%s is missing', 'ewww-image-optimizer' ), '<em>gifsicle</em>' );
			} else {
				// Otherwise, turn optimization ON.
				$optimize = true;
			}
			// If local optimization is turned ON.
			if ( $optimize ) {
				$tempfile = $file . '.tmp'; // temporary GIF output.
				// Run gifsicle on the GIF.
				$cmd = "$nice " . $tools['gifsicle'] . ' -O3 --careful -o ' . ewww_image_optimizer_escapeshellarg( $tempfile ) . ' ' . ewww_image_optimizer_escapeshellarg( $file );
				ewwwio_debug_message( "running: $cmd" );
				exec( $cmd, $output, $exit );
				// Retrieve the filesize of the temporary GIF.
				$new_size = ewww_image_optimizer_filesize( $tempfile );
				// If the new GIF is smaller.
				if ( $new_size && $orig_size > $new_size && ewww_image_optimizer_mimetype( $tempfile, 'i' ) === $type ) {
					// Replace the original with the optimized file.
					rename( $tempfile, $file );
					// Store the results of the optimization.
					$result = "$orig_size vs. $new_size";
					// If the optimization didn't produce a smaller GIF.
				} else {
					if ( ewwwio_is_file( $tempfile ) ) {
						// Delete the optimized file.
						ewwwio_delete_file( $tempfile );
					}
					// Store the results.
					$result   = 'unchanged';
					$new_size = $orig_size;
				}
			}
			// Get the new filesize for the GIF.
			$new_size = ewww_image_optimizer_filesize( $file );
			// If conversion is ON and the GIF isn't animated.
			if ( $convert && ! ewww_image_optimizer_is_animated( $file ) ) {
				if ( empty( $new_size ) ) {
					$new_size = $orig_size;
				}
				// If optipng is enabled.
				if ( $tools['optipng'] ) {
					// Retrieve the optipng optimization level.
					$optipng_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_optipng_level' );
					if (
						ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_remove' ) &&
						preg_match( '/0.7/', ewwwio()->local->test_binary( $tools['optipng'], 'optipng' ) ) &&
						! $keep_metadata
					) {
						$strip = '-strip all ';
					} else {
						$strip = '';
					}
					// Run optipng on the GIF file.
					$cmd = "$nice " . $tools['optipng'] . ' -out ' . ewww_image_optimizer_escapeshellarg( $pngfile ) . " -o$optipng_level -quiet $strip " . ewww_image_optimizer_escapeshellarg( $file );
					ewwwio_debug_message( "running: $cmd" );
					exec( $cmd, $output, $exit );
				}
				// If pngout is enabled.
				if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_disable_pngout' ) && $tools['pngout'] ) {
					// Retrieve the pngout optimization level.
					$pngout_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_pngout_level' );
					// Run pngout on the GIF file directly (if optipng didn't work or isn't available).
					$cmd = "$nice " . $tools['pngout'] . " -s$pngout_level -q " . ewww_image_optimizer_escapeshellarg( $file ) . ' ' . ewww_image_optimizer_escapeshellarg( $pngfile );
					// BUT, if $pngfile exists, which means optipng was successful at converting the GIF.
					if ( ewwwio_is_file( $pngfile ) ) {
						// Run pngout on the PNG file.
						$cmd = "$nice " . $tools['pngout'] . " -s$pngout_level -q " . ewww_image_optimizer_escapeshellarg( $pngfile );
					}
					ewwwio_debug_message( "running: $cmd" );
					exec( $cmd, $output, $exit );
				}
				// Retrieve the filesize of the PNG.
				$png_size = ewww_image_optimizer_filesize( $pngfile );
				// If the new PNG is smaller than the original GIF.
				if ( $png_size && $new_size > $png_size && ewww_image_optimizer_mimetype( $pngfile, 'i' ) === 'image/png' ) {
					// Store the PNG size as the new filesize.
					$new_size = $png_size;
					// If the user wants original GIFs deleted after successful conversion.
					if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_delete_originals' ) ) {
						// Delete the original GIF.
						ewwwio_delete_file( $file );
					}
					// Update the $file location with the new PNG.
					$file = $pngfile;
					// Let webp know what we're dealing with now.
					$type = 'image/png';
					// Normally this would be at the end of the section, but we only want to do webp if the image was successfully converted to a png.
					$webp_result = ewww_image_optimizer_webp_create( $file, $new_size, $type, $tools['cwebp'], $orig_size !== $new_size );
					// Successful conversion, so we store the increment.
					$converted = true;
				} else {
					$converted = false;
					if ( ewwwio_is_file( $pngfile ) ) {
						ewwwio_delete_file( $pngfile );
					}
				}
			} // End if().
			break;
		case 'application/pdf':
			if ( ! empty( ewwwio()->webp_only ) ) {
				break;
			}
			$compression_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_pdf_level' );
			if ( empty( ewwwio()->force ) ) {
				$results_msg = ewww_image_optimizer_check_table( $file, $orig_size );
				$smart_reopt = ! empty( ewwwio()->force_smart ) && ewww_image_optimizer_level_mismatch( $ewww_image->level, $compression_level ) ? true : false;
				if ( $smart_reopt ) {
					ewwwio_debug_message( "smart re-opt found level mismatch for $file, db says " . $ewww_image->level . " vs. current $compression_level" );
					// If the current compression level is less than what was previously used, and the previous level was premium (or premium plus).
					if ( $compression_level && $compression_level < $ewww_image->level && $ewww_image->level > 20 ) {
						ewwwio_debug_message( "smart re-opt triggering restoration for $file" );
						ewww_image_optimizer_cloud_restore_single_image( $ewww_image->record );
					}
				} elseif ( $results_msg ) {
					return array( $file, $results_msg, $converted, $original );
				}
			}
			$ewww_image->level = $compression_level;
			if ( $compression_level > 0 ) {
				list( $file, $converted, $result, $new_size, $backup_hash ) = ewww_image_optimizer_cloud_optimizer( $file, $type );
			}
			break;
		case 'image/svg+xml':
			if ( ! empty( ewwwio()->webp_only ) ) {
				break;
			}
			$compression_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_svg_level' );
			// Check for previous optimization, so long as the force flag is not on and this isn't a new image that needs converting.
			if ( empty( ewwwio()->force ) ) {
				$results_msg = ewww_image_optimizer_check_table( $file, $orig_size );
				$smart_reopt = ! empty( ewwwio()->force_smart ) && ewww_image_optimizer_level_mismatch( $ewww_image->level, $compression_level ) ? true : false;
				if ( $smart_reopt ) {
					ewwwio_debug_message( "smart re-opt found level mismatch for $file, db says " . $ewww_image->level . " vs. current $compression_level" );
					// If the current compression level is less than what was previously used, and the previous level was premium (or premium plus).
					if ( $compression_level && $compression_level < $ewww_image->level && $ewww_image->level > 0 ) {
						ewwwio_debug_message( "smart re-opt triggering restoration for $file" );
						ewww_image_optimizer_cloud_restore_single_image( $ewww_image->record );
					}
				} elseif ( $results_msg ) {
					return array( $file, $results_msg, $converted, $original );
				}
			}
			$ewww_image->level = $compression_level;
			if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_cloud_key' ) && $compression_level > 0 ) {
				list( $file, $converted, $result, $new_size, $backup_hash ) = ewww_image_optimizer_cloud_optimizer( $file, $type );
				break;
			}
			$tools['svgcleaner'] = ewwwio()->local->get_path( 'svgcleaner' );
			// If svgcleaner is disabled.
			if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_svg_level' ) ) {
				$result = __( 'SVG optimization is disabled', 'ewww-image-optimizer' );
			} elseif ( empty( $tools['svgcleaner'] ) ) {
				/* translators: %s: name of a tool like jpegtran */
				$result = sprintf( __( '%s is missing', 'ewww-image-optimizer' ), '<em>svgcleaner</em>' );
			} else {
				// Otherwise, turn optimization ON.
				$optimize = true;
			}
			// If local optimization is turned ON.
			if ( $optimize ) {
				$tempfile = $file . '.tmp.svg'; // temporary SVG output (must end with .svg)
				// Run svgcleaner on the SVG.
				$svgcleaner_options = array(
					'--allow-bigger-file',
					'--quiet',
				);
				if ( 1 === $compression_level ) {
					array_push(
						$svgcleaner_options,
						'--paths-to-relative=no',
						'--remove-unused-segments=no',
						'--convert-segments=no',
						'--merge-gradients=no',
						'--trim-ids=no',
						'--trim-colors=no',
						'--simplify-transforms=no',
						'--resolve-use=no'
					);
				}
				$cmd = "$nice " . $tools['svgcleaner'] . ' ' . implode( ' ', $svgcleaner_options ) . ' ' . ewww_image_optimizer_escapeshellarg( $file ) . ' ' . ewww_image_optimizer_escapeshellarg( $tempfile );
				ewwwio_debug_message( "running: $cmd" );
				exec( $cmd, $output, $exit );
				// Retrieve the filesize of the temporary SVG.
				$new_size = ewww_image_optimizer_filesize( $tempfile );
				// If the new SVG is smaller.
				if ( $new_size && $orig_size > $new_size && ewww_image_optimizer_mimetype( $tempfile, 'i' ) === $type ) {
					// Replace the original with the optimized file.
					rename( $tempfile, $file );
					// Store the results of the optimization.
					$result = "$orig_size vs. $new_size";
					// If the optimization didn't produce a smaller SVG.
				} else {
					if ( ewwwio_is_file( $tempfile ) ) {
						// Delete the optimized file.
						ewwwio_delete_file( $tempfile );
					}
					// Store the results.
					$result   = 'unchanged';
					$new_size = $orig_size;
				}
			}
			break;
		case 'image/webp':
			if ( ! empty( ewwwio()->webp_only ) ) {
				break;
			}
			$compression_level = (int) ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_level' );
			if ( empty( ewwwio()->force ) ) {
				$results_msg = ewww_image_optimizer_check_table( $file, $orig_size );
				if ( $results_msg ) {
					return array( $file, $results_msg, $converted, $original );
				}
			}
			$ewww_image->level = $compression_level;
			if ( $compression_level > 0 ) {
				list( $file, $converted, $result, $new_size, $backup_hash ) = ewww_image_optimizer_cloud_optimizer( $file, $type );
			}
			break;
		default:
			// If not a JPG, PNG, GIF, PDF or SVG tell the user we don't work with strangers.
			return array( false, __( 'Unsupported file type', 'ewww-image-optimizer' ) . ": $type", $converted, $original );
	} // End switch().
	// Allow other plugins to run operations on the images after optimization.
	// NOTE: it is recommended to do any image modifications prior to optimization, otherwise you risk un-optimizing your images here.
	do_action( 'ewww_image_optimizer_post_optimization', $file, $type, $fullsize );
	// If their cloud api license limit has been exceeded.
	if ( 'exceeded' === $result ) {
		return array( false, __( 'License exceeded', 'ewww-image-optimizer' ), $converted, $original );
	} elseif ( 'exceeded subkey' === $result ) {
		return array( false, __( 'Out of credits', 'ewww-image-optimizer' ), $converted, $original );
	} elseif ( 'exceeded quota' === $result ) {
		return array( false, __( 'Soft Quota Reached', 'ewww-image-optimizer' ), $converted, $original );
	}
	if ( ! empty( $new_size ) ) {
		// Set correct file permissions.
		$stat = stat( dirname( $file ) );
		ewwwio_debug_message( 'folder mode: ' . $stat['mode'] );
		$perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits.
		ewwwio_debug_message( "attempting chmod with $perms" );
		ewwwio_chmod( $file, $perms );

		$results_msg = ewww_image_optimizer_update_table( $file, $new_size, $orig_size, $original, $backup_hash );
		if ( ! empty( $webp_result ) ) {
			$results_msg .= '<br>' . $webp_result;
		}
		ewwwio_memory( __FUNCTION__ );
		return array( $file, $results_msg, $converted, $original );
	}
	ewwwio_memory( __FUNCTION__ );
	// If this is WebP-only mode, and we got a WebP file.
	if ( ! empty( $webp_result ) && ! empty( ewwwio()->webp_only ) ) {
		$result = $webp_result;
		return array( true, $result, $converted, $original );
	}
	// Otherwise, send back the filename, the results (some sort of error message), the $converted flag, and the name of the original image.
	return array( false, $result, $converted, $original );
}

/**
 * Returns the WebP file path of the given image based on naming mode.
 *
 * @param string $file The full filesystem path to the source image.
 * @return string The full filesystem path to the WebP image.
 */
function ewww_image_optimizer_get_webp_path( $file ) {
	$naming_mode = ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_naming_mode', 'append' );
	$info        = pathinfo( $file );

	if ( 'replace' === $naming_mode ) {
		if ( empty( $info['dirname'] ) || '.' === $info['dirname'] ) {
			$info['dirname'] = '';
		} else {
			$info['dirname'] = trailingslashit( $info['dirname'] );
		}
		$webp_path = $info['dirname'] . $info['filename'] . '.webp';
	} else {
		$webp_path = $file . '.webp';
	}

	return apply_filters( 'ewww_image_optimizer_webp_path', $webp_path, $file, $naming_mode );
}

/**
 * Returns all possible WebP file paths of the given image.
 *
 * @param string $path The full filesystem path to the source image.
 * @return array Returns array of both append and replace WebP naming paths.
 */
function ewww_image_optimizer_get_all_webp_paths( $path ) {
	if ( empty( $path ) ) {
		return array( '', '' );
	}
	$naming_mode = ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_naming_mode', 'append' );
	$append      = $path . '.webp';
	$info        = pathinfo( $path );
	if ( empty( $info['dirname'] ) || '.' === $info['dirname'] ) {
		$info['dirname'] = '';
	} else {
		$info['dirname'] = trailingslashit( $info['dirname'] );
	}
	$replace = $info['dirname'] . $info['filename'] . '.webp';

	if ( 'replace' === $naming_mode ) {
		return array( $replace, $append );
	}
	return array( $append, $replace );
}

/**
 * Removes obsolete WebP files created with previous naming conventions.
 *
 * @param string $path The full filesystem path to the source image.
 */
function ewww_image_optimizer_cleanup_legacy_webp( $path ) {
	$current  = ewww_image_optimizer_get_webp_path( $path );
	$variants = ewww_image_optimizer_get_all_webp_paths( $path );

	foreach ( $variants as $legacy ) {
		if ( $legacy !== $current && ewwwio_is_file( $legacy ) ) {
			ewwwio_debug_message( "removing legacy webp file: $legacy" );
			ewwwio_delete_file( $legacy );
		}
	}
}

/**
 * Returns the WebP URL of the given image.
 *
 * @param string $path The full filesystem path to the source image.
 * @param string $url The full URL to the source image.
 * @return string URL to the existing WebP image.
 */
function ewww_image_optimizer_get_webp_url( $path, $url ) {
	ewwwio_debug_message( "finding .webp path for source path $path and url $url" );
	$webp_urls = ewww_image_optimizer_get_all_webp_paths( $url );
	if ( empty( $path ) ) {
		ewwwio_debug_message( "no path, returning {$webp_urls[0]}" );
		return $webp_urls[0];
	}
	$webp_paths = ewww_image_optimizer_get_all_webp_paths( $path );

	if ( ewwwio_is_file( $webp_paths[0] ) ) {
		ewwwio_debug_message( "{$webp_paths[0]} found, returning {$webp_urls[0]}" );
		return $webp_urls[0];
	} elseif ( ewwwio_is_file( $webp_paths[1] ) ) {
		ewwwio_debug_message( "{$webp_paths[1]} found, returning {$webp_urls[1]}" );
		return $webp_urls[1];
	}
	ewwwio_debug_message( "no local file found, returning {$webp_urls[0]}" );
	return $webp_urls[0];
}

/**
 * Creates WebP images alongside JPG and PNG files.
 *
 * @param string $file The name of the JPG/PNG file.
 * @param int    $orig_size The filesize of the JPG/PNG file.
 * @param string $type The mime-type of the incoming file.
 * @param string $tool The path to the cwebp binary, if installed.
 * @param bool   $recreate True to re-generate the .webp image even if one exists, usually because the source image has been modified.
 * @return string Results of the WebP operation for display.
 */
function ewww_image_optimizer_webp_create( $file, $orig_size, $type, $tool, $recreate = false ) {
	ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
	$orig_size = ewww_image_optimizer_filesize( $file );
	$webpfile  = ewww_image_optimizer_get_webp_path( $file );
	if ( apply_filters( 'ewww_image_optimizer_bypass_webp', false, $file ) ) {
		ewwwio_debug_message( "webp generation bypassed: $file" );
		return '';
	} elseif ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp' ) ) {
		return '';
	} elseif ( ! ewwwio_is_file( $file ) ) {
		ewwwio_debug_message( 'original file not found' );
		return esc_html__( 'Could not find file.', 'ewww-image-optimizer' );
	} elseif ( ! is_writable( $file ) ) {
		ewwwio_debug_message( 'original file not writable' );
		ewww_image_optimizer_update_webp_results( $file, 0, 2 );
		return ewww_image_optimizer_webp_error_message( 2 );
	} elseif ( ewwwio_is_file( $webpfile ) && empty( ewwwio()->force ) && ! $recreate ) {
		ewwwio_debug_message( 'webp file exists, not forcing or recreating' );
		return esc_html__( 'WebP image already exists.', 'ewww-image-optimizer' );
	} elseif ( 'image/png' === $type && ewww_image_optimizer_is_animated_png( $file ) ) {
		ewwwio_debug_message( 'APNG found, WebP not possible' );
		ewww_image_optimizer_update_webp_results( $file, 0, 3 );
		return ewww_image_optimizer_webp_error_message( 3 );
	}
	list( $width, $height ) = ewwwio()->getimagesize( $file );
	if ( $width > 16383 || $height > 16383 ) {
		ewww_image_optimizer_update_webp_results( $file, 0, 4 );
		return ewww_image_optimizer_webp_error_message( 4 );
	}
	if ( empty( $tool ) || 'image/gif' === $type ) {
		$use_cloud_webp = false;
		if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_cloud_key' ) ) {
			$use_cloud_webp = true;
			if (
				'local' === ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_conversion_method' ) &&
				'image/gif' !== $type &&
				ewwwio()->imagick_supports_webp()
			) {
				$use_cloud_webp = false;
			}
		}
		if ( $use_cloud_webp ) {
			ewww_image_optimizer_cloud_optimizer( $file, $type, false, $webpfile, 'image/webp' );
		} elseif ( ewwwio()->imagick_supports_webp() ) {
			ewww_image_optimizer_imagick_create_webp( $file, $type, $webpfile );
		} elseif ( ewwwio()->gd_supports_webp() ) {
			ewww_image_optimizer_gd_create_webp( $file, $type, $webpfile );
		} else {
			ewww_image_optimizer_cloud_optimizer( $file, $type, false, $webpfile, 'image/webp' );
		}
	} elseif ( ewwwio()->imagick_supports_webp() ) {
		// Because we prefer Imagick for sharpening/quality over cwebp.
		ewww_image_optimizer_imagick_create_webp( $file, $type, $webpfile );
	} else {
		$nice = '';
		if ( PHP_OS !== 'WINNT' && ! ewwwio()->cloud_mode && ewwwio()->local->exec_check() ) {
			// Check to see if 'nice' exists.
			$nice = ewwwio()->local->find_nix_binary( 'nice' );
		}
		// Check to see if we are supposed to strip metadata.
		$copy_opt = ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_remove' ) ? 'icc' : 'all';
		$quality  = (int) apply_filters( 'webp_quality', 75, 'image/webp' );
		if ( $quality < 50 || $quality > 100 ) {
			$quality = 75;
		}
		$sharp_yuv = defined( 'EIO_WEBP_SHARP_YUV' ) && EIO_WEBP_SHARP_YUV ? '-sharp_yuv' : '';
		if ( empty( $sharp_yuv ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_sharpen' ) ) {
			$sharp_yuv = '-sharp_yuv';
		}
		$lossless = '-lossless';
		if ( defined( 'EWWW_IMAGE_OPTIMIZER_LOSSY_PNG2WEBP' ) && EWWW_IMAGE_OPTIMIZER_LOSSY_PNG2WEBP ) {
			$lossless = "-q $quality $sharp_yuv";
		}
		switch ( $type ) {
			case 'image/jpeg':
				$resize_string = '';
				$source_image  = $file;
				global $ewww_image;
				if ( ! empty( $ewww_image->attachment_id ) ) {
					$original_image = ewwwio_get_original_image_path_from_thumb( $file, $ewww_image->attachment_id );
					if ( $original_image ) {
						$resize_string = ewww_image_optimizer_get_cwebp_resize_params( $file );
						if ( $resize_string && ewwwio_is_file( $original_image ) ) {
							$source_image = $original_image;
						}
					}
				}
				ewwwio_debug_message( "$nice " . $tool . " -q $quality $sharp_yuv $resize_string -metadata $copy_opt -quiet " . ewww_image_optimizer_escapeshellarg( $source_image ) . ' -o ' . ewww_image_optimizer_escapeshellarg( $webpfile ) . ' 2>&1' );
				exec( "$nice " . $tool . " -q $quality $sharp_yuv $resize_string -metadata $copy_opt -quiet " . ewww_image_optimizer_escapeshellarg( $source_image ) . ' -o ' . ewww_image_optimizer_escapeshellarg( $webpfile ) . ' 2>&1', $cli_output );
				if ( ! ewwwio_is_file( $webpfile ) && ewwwio()->imagick_supports_webp() && ewww_image_optimizer_is_cmyk( $source_image ) ) {
					ewwwio_debug_message( 'cmyk image skipped, trying imagick' );
					ewww_image_optimizer_imagick_create_webp( $file, $type, $webpfile );
				} elseif ( ewwwio_is_file( $webpfile ) && 'image/webp' !== ewww_image_optimizer_mimetype( $webpfile, 'i' ) ) {
					ewwwio_debug_message( 'non-webp file produced' );
				}
				break;
			case 'image/png':
				ewwwio_debug_message( "$nice " . $tool . " $lossless -metadata $copy_opt -quiet " . ewww_image_optimizer_escapeshellarg( $file ) . ' -o ' . ewww_image_optimizer_escapeshellarg( $webpfile ) . ' 2>&1' );
				exec( "$nice " . $tool . " $lossless -metadata $copy_opt -quiet " . ewww_image_optimizer_escapeshellarg( $file ) . ' -o ' . ewww_image_optimizer_escapeshellarg( $webpfile ) . ' 2>&1', $cli_output );
				break;
		}
	}
	$webp_size = ewww_image_optimizer_filesize( $webpfile );
	ewwwio_debug_message( "webp is $webp_size vs. $type is $orig_size" );
	if ( ewwwio_is_file( $webpfile ) && $orig_size < $webp_size && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_force' ) ) {
		ewwwio_debug_message( 'webp file was too big, deleting' );
		ewwwio_delete_file( $webpfile );
		ewww_image_optimizer_update_webp_results( $file, 0, 5 );
		return ewww_image_optimizer_webp_error_message( 5 );
	} elseif ( ewwwio_is_file( $webpfile ) && 'image/webp' === ewww_image_optimizer_mimetype( $webpfile, 'i' ) ) {
		// Set correct file permissions.
		$stat  = stat( dirname( $webpfile ) );
		$perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits.
		ewwwio_chmod( $webpfile, $perms );
		ewww_image_optimizer_update_webp_results( $file, $webp_size );
		if ( $orig_size < $webp_size && ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_force' ) ) {
			return esc_html__( 'WebP image larger than original, saved anyway with Force WebP option.', 'ewww-image-optimizer' );
		}
		return 'WebP: ' . ewww_image_optimizer_image_results( $orig_size, $webp_size );
	} elseif ( ewwwio_is_file( $webpfile ) ) {
		ewwwio_debug_message( 'webp file mimetype did not validate, deleting' );
		ewwwio_delete_file( $webpfile );
		ewww_image_optimizer_update_webp_results( $file, 0, 6 );
		return ewww_image_optimizer_webp_error_message( 6 );
	}
	ewww_image_optimizer_update_webp_results( $file, 0, 1 );
	return ewww_image_optimizer_webp_error_message( 1 );
}

/**
 * Get resize/crop parameters for cwebp when converting a thumbnail image.
 *
 * @param string $file The path of the thumb to be converted.
 * @return string CLI args to use for resizing a thumb from the original, or an empty string.
 */
function ewww_image_optimizer_get_cwebp_resize_params( $file ) {
	ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
	$resize_params = '';
	list( $webp_width, $webp_height, $webp_crop, $fullsize_image ) = ewww_image_optimizer_get_webp_resize_params( $file );
	if ( $webp_width && $webp_height && $fullsize_image ) {
		ewwwio_debug_message( 'building resize params' );
		if ( $webp_crop ) {
			ewwwio_debug_message( 'cropping' );
			list( $full_width, $full_height ) = ewwwio()->getimagesize( $fullsize_image );
			if ( ! empty( $full_width ) && ! empty( $full_height ) ) {
				ewwwio_debug_message( 'found full-size dims' );
				$dims = image_resize_dimensions( $full_width, $full_height, $webp_width, $webp_height, $webp_crop );
				if ( $dims && is_array( $dims ) ) {
					ewwwio_debug_message( 'image_resize_dimensions() returned: ' . implode( ', ', $dims ) );
					list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
					// Build it with both crop and resize args, the crop will chop off the edges for the needed aspect ratio, then resize scales it (if needed).
					$resize_params = "-resize $webp_width $webp_height -crop $src_x $src_y $src_w $src_h";
				}
			}
		} else {
			ewwwio_debug_message( 'scaling' );
			$resize_params = "-resize $webp_width $webp_height";
		}
		ewwwio_debug_message( "final CLI resize args: $resize_params" );
	}
	return $resize_params;
}

/**
 * Get an error message for the given WebP status code.
 *
 * @param int $webp_code The error code for WebP conversion.
 * @return string The error message for the given status code.
 */
function ewww_image_optimizer_webp_error_message( $webp_code = 0 ) {
	switch ( $webp_code ) {
		case 1:
			return __( 'Image could not be converted to WebP.', 'ewww-image-optimizer' );
		case 2:
			return __( 'File is not writable.', 'ewww-image-optimizer' );
		case 3:
			return __( 'APNG cannot be converted to WebP.', 'ewww-image-optimizer' );
		case 4:
			return __( 'Image dimensions too large for WebP conversion.', 'ewww-image-optimizer' );
		case 5:
			return __( 'WebP image was larger than original.', 'ewww-image-optimizer' );
		case 6:
			return __( 'WebP conversion error.', 'ewww-image-optimizer' );
		default:
			return '';
	}
}

/**
 * Redirects back to previous page after PNGOUT installation.
 */
function ewww_image_optimizer_install_pngout_wrapper() {
	ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
	check_admin_referer( 'ewww_image_optimizer_options-options' );
	if ( ! current_user_can( apply_filters( 'ewww_image_optimizer_admin_permissions', '' ) ) ) {
		wp_die( esc_html__( 'You do not have permission to install image optimizer utilities.', 'ewww-image-optimizer' ) );
	}
	$sendback = ewww_image_optimizer_install_pngout();
	wp_safe_redirect( $sendback );
	ewwwio_memory( __FUNCTION__ );
	exit( 0 );
}

/**
 * Installs pngout from the official site.
 *
 * @return string The url from whence we came (settings page), with success or error parameters added.
 */
function ewww_image_optimizer_install_pngout() {
	$pngout_error = '';
	if ( ! extension_loaded( 'zlib' ) || ! class_exists( 'PharData' ) ) {
		$pngout_error = __( 'zlib or phar extension missing from PHP', 'ewww-image-optimizer' );
	}
	$os_string = '';
	if ( PHP_OS === 'Linux' ) {
		$os_string = 'linux';
	}
	if ( PHP_OS === 'FreeBSD' ) {
		$os_string = 'bsd';
	}
	$latest          = '20200115';
	$tool_path       = trailingslashit( EWWW_IMAGE_OPTIMIZER_TOOL_PATH );
	$download_result = false;
	if ( empty( $pngout_error ) ) {
		if ( PHP_OS === 'Linux' || PHP_OS === 'FreeBSD' ) {
			$download_result = download_url( 'http://www.jonof.id.au/files/kenutils/pngout-' . $latest . '-' . $os_string . '-static.tar.gz' );
			if ( is_wp_error( $download_result ) ) {
				$pngout_error = $download_result->get_error_message();
			} else {
				if ( ! ewwwio_check_memory_available( filesize( $download_result ) + 1000 ) ) {
					$pngout_error = __( 'insufficient memory available for installation', 'ewww-image-optimizer' );
				} else {
					$arch_type = 'i686';
					if ( ewww_image_optimizer_function_exists( 'php_uname' ) ) {
						$arch_type = php_uname( 'm' );
						if ( 'x86_64' === $arch_type ) {
							$arch_type = 'amd64';
						}
					}

					$tmpname  = current( explode( '.', $download_result ) );
					$tmpname .= '-' . uniqid() . '.tar.gz';
					rename( $download_result, $tmpname );
					$download_result = $tmpname;

					$pngout_gzipped  = new PharData( $download_result );
					$pngout_tarball  = $pngout_gzipped->decompress();
					$download_result = $pngout_tarball->getPath();
					$pngout_tarball->extractTo(
						EWWW_IMAGE_OPTIMIZER_BINARY_PATH,
						'pngout-' . $latest . '-' . $os_string . '-static/' . $arch_type . '/pngout-static',
						true
					);

					if ( ewwwio_is_file( EWWW_IMAGE_OPTIMIZER_BINARY_PATH . 'pngout-' . $latest . '-' . $os_string . '-static/' . $arch_type . '/pngout-static' ) ) {
						if ( ! rename( EWWW_IMAGE_OPTIMIZER_BINARY_PATH . 'pngout-' . $latest . '-' . $os_string . '-static/' . $arch_type . '/pngout-static', $tool_path . 'pngout-static' ) ) {
							if ( empty( $pngout_error ) ) {
								$pngout_error = __( 'could not move pngout', 'ewww-image-optimizer' );
							}
						}
						if ( ! chmod( $tool_path . 'pngout-static', 0755 ) ) {
							if ( empty( $pngout_error ) ) {
								$pngout_error = __( 'could not set permissions', 'ewww-image-optimizer' );
							}
						}
						$pngout_version = ewwwio()->local->test_binary( ewww_image_optimizer_escapeshellarg( $tool_path ) . 'pngout-static', 'pngout' );
					} else {
						$pngout_error = __( 'extraction of files failed', 'ewww-image-optimizer' );
					}
				}
			}
		} elseif ( PHP_OS === 'Darwin' ) {
			$latest          = '20200115';
			$os_ext          = 'tar.gz';
			$os_ext          = 'zip';
			$download_result = download_url( 'http://www.jonof.id.au/files/kenutils/pngout-' . $latest . '-macos.' . $os_ext );
			if ( is_wp_error( $download_result ) ) {
				$pngout_error = $download_result->get_error_message();
			} else {
				if ( ! ewwwio_check_memory_available( filesize( $download_result ) + 1000 ) ) {
					$pngout_error = __( 'insufficient memory available for installation', 'ewww-image-optimizer' );
				} else {
					$tmpname  = current( explode( '.', $download_result ) );
					$tmpname .= '-' . uniqid() . '.' . $os_ext;
					rename( $download_result, $tmpname );
					$download_result = $tmpname;

					if ( 'zip' === $os_ext ) {
						WP_Filesystem();
						$unzipped = unzip_file(
							$download_result,
							EWWW_IMAGE_OPTIMIZER_BINARY_PATH
						);
					} else {
						$pngout_gzipped  = new PharData( $download_result );
						$pngout_tarball  = $pngout_gzipped->decompress();
						$download_result = $pngout_tarball->getPath();
						$pngout_tarball->extractTo(
							EWWW_IMAGE_OPTIMIZER_BINARY_PATH,
							'pngout-' . $latest . '-darwin/pngout',
							true
						);
					}
					if ( ewwwio_is_file( EWWW_IMAGE_OPTIMIZER_BINARY_PATH . 'pngout-' . $latest . '-macos/pngout' ) ) {
						if ( ! rename( EWWW_IMAGE_OPTIMIZER_BINARY_PATH . 'pngout-' . $latest . '-macos/pngout', $tool_path . 'pngout-static' ) ) {
							if ( empty( $pngout_error ) ) {
								$pngout_error = __( 'could not move pngout', 'ewww-image-optimizer' );
							}
						}
						if ( ! chmod( $tool_path . 'pngout-static', 0755 ) ) {
							if ( empty( $pngout_error ) ) {
								$pngout_error = __( 'could not set permissions', 'ewww-image-optimizer' );
							}
						}
						$pngout_version = ewwwio()->local->test_binary( ewww_image_optimizer_escapeshellarg( $tool_path ) . 'pngout-static', 'pngout' );
					} elseif ( ! empty( $unzipped ) && is_wp_error( $unzipped ) ) {
						$pngout_error = $unzipped->get_error_message();
					} else {
						$pngout_error = __( 'extraction of files failed', 'ewww-image-optimizer' );
					}
				}
			}
		}
	} // End if().
	if ( PHP_OS === 'WINNT' ) {
		$download_result = download_url( 'http://advsys.net/ken/util/pngout.exe' );
		if ( is_wp_error( $download_result ) ) {
			$pngout_error = $download_result->get_error_message();
		} else {
			if ( ! rename( $download_result, $tool_path . 'pngout.exe' ) ) {
				if ( empty( $pngout_error ) ) {
					$pngout_error = __( 'could not move pngout', 'ewww-image-optimizer' );
				}
			}
			$pngout_version = ewwwio()->local->test_binary( '"' . $tool_path . 'pngout.exe"', 'pngout' );
		}
	}
	if ( is_string( $download_result ) && is_writable( $download_result ) ) {
		unlink( $download_result );
	}
	if ( ! empty( $pngout_version ) ) {
		$sendback = add_query_arg( 'ewww_pngout', 'success', remove_query_arg( array( 'ewww_pngout', 'ewww_error' ), wp_get_referer() ) );
	}
	if ( ! isset( $sendback ) ) {
		$sendback = add_query_arg(
			array(
				'ewww_pngout' => 'failed',
				'ewww_error'  => urlencode( $pngout_error ),
			),
			remove_query_arg( array( 'ewww_pngout', 'ewww_error' ), wp_get_referer() )
		);
	}
	return $sendback;
}

/**
 * Redirects back to previous page after SVGCLEANER installation.
 */
function ewww_image_optimizer_install_svgcleaner_wrapper() {
	ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
	check_admin_referer( 'ewww_image_optimizer_options-options' );
	if ( ! current_user_can( apply_filters( 'ewww_image_optimizer_admin_permissions', '' ) ) ) {
		wp_die( esc_html__( 'You do not have permission to install image optimizer utilities.', 'ewww-image-optimizer' ) );
	}
	$sendback = ewww_image_optimizer_install_svgcleaner();
	wp_safe_redirect( $sendback );
	ewwwio_memory( __FUNCTION__ );
	exit( 0 );
}

/**
 * Installs svgcleaner from the official site.
 *
 * @return string The url from whence we came (settings page), with success or error parameters added.
 */
function ewww_image_optimizer_install_svgcleaner() {
	$download_error = '';
	if ( ! extension_loaded( 'zlib' ) || ! class_exists( 'PharData' ) ) {
		$download_error = __( 'zlib or phar extension missing from PHP', 'ewww-image-optimizer' );
	}
	if ( ! ewwwio()->local->exec_check() ) {
		$download_error = __( 'Your web server does not meet the requirements for free server-based compression with EWWW Image Optimizer.', 'ewww-image-optimizer' );
	}
	$os_chmod  = true;
	$os_binary = 'svgcleaner';
	$os_ext    = 'tar.gz';
	$os_string = '';
	if ( PHP_OS === 'Linux' ) {
		$arch_type = 'x86_64';
		if ( ewww_image_optimizer_function_exists( 'php_uname' ) ) {
			if ( php_uname( 'm' ) !== $arch_type ) {
				$download_error = __( 'svgcleaner on Linux only supports the x86_64 architecture', 'ewww-image-optimizer' );
			}
		}
		$os_string = 'linux_' . $arch_type;
	} elseif ( PHP_OS === 'Darwin' ) {
		$os_string = 'macos';
		$os_ext    = 'zip';
	} elseif ( PHP_OS === 'WINNT' ) {
		$os_chmod  = false;
		$os_string = 'win32';
		$os_binary = 'svgcleaner.exe';
		$os_ext    = 'zip';
	} elseif ( PHP_OS === 'FreeBSD' ) {
		$download_error = __( 'svgcleaner is not available for FreeBSD', 'ewww-image-optimizer' );
	}
	$latest    = '0.9.5';
	$tool_path = trailingslashit( EWWW_IMAGE_OPTIMIZER_TOOL_PATH );
	if ( empty( $download_error ) ) {
		$download_result = download_url( 'https://github.com/RazrFalcon/svgcleaner/releases/download/v' . $latest . '/svgcleaner_' . $os_string . '_' . $latest . '.' . $os_ext );
		if ( is_wp_error( $download_result ) ) {
			$download_error = $download_result->get_error_message();
		} else {
			if ( ! ewwwio_check_memory_available( filesize( $download_result ) + 1000 ) ) {
				$download_error = __( 'insufficient memory available for installation', 'ewww-image-optimizer' );
			} else {
				$tmpname  = current( explode( '.', $download_result ) );
				$tmpname .= '-' . uniqid() . '.' . $os_ext;
				rename( $download_result, $tmpname );
				$download_result = $tmpname;

				if ( 'zip' === $os_ext ) {
					WP_Filesystem();
					$unzipped = unzip_file(
						$download_result,
						EWWW_IMAGE_OPTIMIZER_BINARY_PATH
					);
					if ( is_wp_error( $unzipped ) ) {
						$download_error = $unzipped->get_error_message();
					}
				} else {
					$pkg_gzipped     = new PharData( $download_result );
					$pkg_tarball     = $pkg_gzipped->decompress();
					$download_result = $pkg_tarball->getPath();
					$pkg_tarball->extractTo(
						EWWW_IMAGE_OPTIMIZER_BINARY_PATH,
						'svgcleaner',
						true
					);
				}
				if ( ewwwio_is_file( EWWW_IMAGE_OPTIMIZER_BINARY_PATH . $os_binary ) ) {
					if ( ! rename( EWWW_IMAGE_OPTIMIZER_BINARY_PATH . $os_binary, $tool_path . $os_binary ) ) {
						if ( empty( $download_error ) ) {
							$download_error = __( 'could not move svgcleaner', 'ewww-image-optimizer' );
						}
					}
					if ( $os_chmod && ! chmod( $tool_path . $os_binary, 0755 ) ) {
						if ( empty( $download_error ) ) {
							$download_error = __( 'could not set permissions', 'ewww-image-optimizer' );
						}
					}
					if ( PHP_OS === 'WINNT' ) {
						$pkg_version = ewwwio()->local->test_binary( '"' . $tool_path . $os_binary . '"', 'svgcleaner' );
					} else {
						$pkg_version = ewwwio()->local->test_binary( ewww_image_optimizer_escapeshellarg( $tool_path ) . $os_binary, 'svgcleaner' );
					}
				} else {
					$download_error = __( 'extraction of files failed', 'ewww-image-optimizer' );
				}
			}
		}
	}
	if ( isset( $download_result ) && is_string( $download_result ) && is_writable( $download_result ) ) {
		unlink( $download_result );
	}
	if ( ! empty( $pkg_version ) ) {
		ewww_image_optimizer_set_option( 'ewww_image_optimizer_disable_svgcleaner', false );
		if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_svg_level' ) ) {
			ewww_image_optimizer_set_option( 'ewww_image_optimizer_svg_level', 10 );
		}
		$sendback = add_query_arg( 'ewww_svgcleaner', 'success', remove_query_arg( array( 'ewww_svgcleaner', 'ewww_error' ), wp_get_referer() ) );
	}
	if ( ! isset( $sendback ) ) {
		$sendback = add_query_arg(
			array(
				'ewww_svgcleaner' => 'failed',
				'ewww_error'      => urlencode( $download_error ),
			),
			remove_query_arg( array( 'ewww_svgcleaner', 'ewww_error' ), wp_get_referer() )
		);
	}
	return $sendback;
}

/**
 * Checks availability of svgcleaner installer.
 */
function ewww_image_optimizer_svgcleaner_installer_available() {
	if ( ! ewwwio()->local->exec_check() ) {
		return false;
	}
	if ( PHP_OS === 'Linux' ) {
		if ( ewww_image_optimizer_function_exists( 'php_uname' ) ) {
			if ( php_uname( 'm' ) !== 'x86_64' ) {
				return false;
			}
		}
		return true;
	} elseif ( PHP_OS === 'Darwin' ) {
		return true;
	} elseif ( PHP_OS === 'WINNT' ) {
		return true;
	}
	return false;
}

/**
 * Removes any binaries that have been installed in the wp-content/ewww/ folder.
 */
function ewww_image_optimizer_remove_binaries() {
	if ( ! class_exists( 'RecursiveIteratorIterator' ) ) {
		return;
	}
	if ( ! is_dir( EWWW_IMAGE_OPTIMIZER_TOOL_PATH ) ) {
		return;
	}
	$iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( EWWW_IMAGE_OPTIMIZER_TOOL_PATH ), RecursiveIteratorIterator::CHILD_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD );
	foreach ( $iterator as $file ) {
		if ( $file->isFile() ) {
			$path = $file->getPathname();
			if ( strpos( $path, 'image-backup' ) ) {
				continue;
			}
			if ( is_writable( $path ) ) {
				unlink( $path );
			}
		}
	}
	if ( ! class_exists( 'FilesystemIterator' ) ) {
		return;
	}
	clearstatcache();
	$iterator = new FilesystemIterator( EWWW_IMAGE_OPTIMIZER_TOOL_PATH );
	if ( ! $iterator->valid() ) {
		rmdir( EWWW_IMAGE_OPTIMIZER_TOOL_PATH );
	}
}
