????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
?????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
???????????????????????????????????????
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
PNG
\x49\x44\x41\x54?\x89\x50
\x4E\x47\x0D\x0A\x1A\x0A
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................??????????????????????
??? ???????????????????????????????????????...............................
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
..................................................................................................................................... ????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
?????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
???????????????????????????????????????
PNG
\x49\x44\x41\x54?\x89\x50
\x4E\x47\x0D\x0A\x1A\x0A
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................??????????????????????
??? ???????????????????????????????????????...............................
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................????????????????????????????????
???????????????????????????????
???????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
Warning : Undefined variable $auth in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 695
Warning : Trying to access array offset on value of type null in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 695
Warning : Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 332
Warning : Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 333
Warning : Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 334
Warning : Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 335
Warning : Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 336
Warning : Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 337
PK OD\}~ core/class-settings.phpnu [ true, // works with CDN.
'lossy' => 0, // works with CDN.
'strip_exif' => true, // works with CDN.
'resize' => false,
'detection' => false,
'original' => true,
'backup' => true,
'no_scale' => false,
'png_to_jpg' => false, // works with CDN.
'nextgen' => false,
's3' => false,
'gutenberg' => false,
'js_builder' => false,
'gform' => false,
'cdn' => false,
'auto_resizing' => false,
'cdn_dynamic_sizes' => false,
self::$next_gen_cdn_key => self::$webp_cdn_mode,
'usage' => false,
'accessible_colors' => false,
'keep_data' => true,
'lazy_load' => false,
'background_images' => true,
'rest_api_support' => false, // CDN option.
'webp_mod' => false, // WebP module.
'background_email' => false,
'webp_direct_conversion' => false,
'webp_fallback' => false,
'disable_streams' => false,
'avif_mod' => false,
'avif_fallback' => false,
'image_dimensions' => false,
'preload_images' => false,
);
}
/**
* Available modules.
*
* @since 3.2.2
* @since 3.8.0 Added webp.
* @var array $modules
*/
private function get_modules() {
return array( 'bulk', 'integrations', self::$lazy_preload_module_name, 'cdn', 'next_gen', 'settings' );
}
/**
* List of features/settings that are free.
*
* @var array $basic_features
*/
public static $basic_features = array( 'bulk', 'auto', 'strip_exif', 'resize', 'original', 'directory_smush', 'gutenberg', 'js_builder', 'gform', 'lazy_load', 'lossy' );
/**
* List of fields in bulk smush form.
*
* @used-by save_settings()
*
* @var array
*/
private $bulk_fields = array( 'lossy', 'bulk', 'auto', 'strip_exif', 'resize', 'original', 'backup', 'png_to_jpg', 'no_scale', 'background_email' );
/**
* @since 3.12.6
*
* Upsell fields.
*/
private $upsell_fields = array( 'background_email', 'png_to_jpg' );
/**
* List of fields in integration form.
*
* @used-by save_settings()
*
* @var array
*/
private $integrations_fields = array( 'gutenberg', 'gform', 'js_builder', 's3', 'nextgen' );
/**
* List of fields in CDN form.
*
* @used-by save_settings()
*
* @var array
*/
public function get_cdn_fields() {
return array( 'cdn', 'background_images', 'cdn_dynamic_sizes', self::$next_gen_cdn_key, 'rest_api_support' );
}
/**
* List of fields in CDN form.
*
* @used-by save_settings()
*
* @since 3.8.0
*
* @var array
*/
private $webp_fields = array( 'webp_mod', 'webp_direct_conversion', 'webp_fallback' );
/**
* @var array
*/
private $avif_fields = array( 'avif_mod', 'avif_fallback' );
/**
* List of fields in Settings form.
*
* @used-by save_settings()
*
* @var array
*/
private $settings_fields = array( 'detection', 'accessible_colors', 'usage', 'keep_data', 'api_auth', 'disable_streams' );
/**
* List of fields in lazy loading form.
*
* @used-by save_settings()
*
* @var array
*/
private $lazy_load_fields = array( 'lazy_load', 'auto_resizing', 'image_dimensions' );
/**
* @var array
*/
private $preload_fields = array( 'preload_images' );
/**
* @var array
*/
private $activated_subsite_modules;
/**
* @var bool
*/
private $is_switching_subsite = false;
/**
* Return the plugin instance.
*
* @since 3.0
*
* @return Settings
*/
public static function get_instance() {
if ( empty( self::$instance ) ) {
$pro_file = __DIR__ . '/class-settings-pro.php';
if ( ! class_exists( '\\Smush\\Core\\Settings_Pro' ) && file_exists( $pro_file ) ) {
require_once $pro_file;
}
if ( class_exists( '\\Smush\\Core\\Settings_Pro' ) ) {
self::$instance = new Settings_Pro();
} else {
self::$instance = new self();
}
}
return self::$instance;
}
public function __call( $method_name, $arguments ) {
_deprecated_function( esc_html( $method_name ), '3.24.0' );
}
/**
* WP_Smush_Settings constructor.
*
* WARNING: Any new class added to this constructor must be loaded before use.
* This constructor is called when the plugin is activated.
*/
protected function __construct() {
// Handle settings cache and subsite switching when switching between sites in a multisite network.
add_action( 'switch_blog', array( $this, 'maybe_reset_cache_site_settings' ), 10, 2 );
add_action( 'switch_blog', array( $this, 'toggle_switching_subsite' ) );
// Do not initialize if not in admin area
// wp_head runs specifically in the frontend, good check to make sure we're accidentally not loading settings on required pages.
if ( ! is_admin() && ! wp_doing_ajax() && did_action( 'wp_head' ) ) {
return;
}
// Save Settings.
add_action( 'wp_ajax_smush_save_settings', array( $this, 'save_settings' ) );
// Reset Settings.
add_action( 'wp_ajax_reset_settings', array( $this, 'reset' ) );
add_filter( 'wp_smush_settings', array( $this, 'remove_unavailable' ) );
$this->init();
}
public function toggle_switching_subsite() {
$this->is_switching_subsite = ! $this->is_switching_subsite;
}
/**
* Remove settings that are not available on a specific version of WordPress.
*
* @since 3.9.1
*
* @param array $settings Current settings.
*
* @return array
*/
public function remove_unavailable( $settings ) {
global $wp_version;
if ( version_compare( $wp_version, '5.3', '<' ) ) {
if ( isset( $this->bulk_fields['no_scale'] ) ) {
unset( $this->bulk_fields['no_scale'] );
}
if ( isset( $settings['no_scale'] ) ) {
unset( $settings['no_scale'] );
}
}
return $settings;
}
/**
* Get descriptions for all settings.
*
* @since 3.8.6 Moved from Core
*
* @param string $id Setting ID to get data for.
* @param string $type What value to get. Accepts: label, short_label or desc.
*
* @return string
*/
public static function get_setting_data( $id, $type = '' ) {
$s3_plugin_url = esc_url( 'https://wordpress.org/plugins/amazon-s3-and-cloudfront/' );
$mail_recipient = get_option( 'admin_email' );
$bg_email_desc = sprintf(
/* translators: %s Email address */
esc_html__( "Be notified via email about the bulk smush status when the process has completed. You'll receive an email at %s.", 'wp-smushit' ),
'' . $mail_recipient . ' '
);
$settings = array(
'background_email' => array(
'label' => esc_html__( 'Enable email notification', 'wp-smushit' ),
'short_label' => esc_html__( 'Email Notification', 'wp-smushit' ),
'desc' => $bg_email_desc,
),
'bulk' => array(
'short_label' => esc_html__( 'Image Sizes', 'wp-smushit' ),
'desc' => esc_html__( 'WordPress creates multiple thumbnails for each uploaded image. Select which sizes to include in bulk smushing.', 'wp-smushit' ),
),
'auto' => array(
'label' => esc_html__( 'Automatically compress my images on upload', 'wp-smushit' ),
'short_label' => esc_html__( 'Automatic compression', 'wp-smushit' ),
'desc' => esc_html__( 'When you upload images to your site, we will automatically optimize and compress them for you.', 'wp-smushit' ),
),
'lossy' => array(
'label' => esc_html__( 'Choose Compression Level', 'wp-smushit' ),
'short_label' => esc_html__( 'Smush Mode', 'wp-smushit' ),
'desc' => sprintf(
/* translators: 1: Opening 2: Closing */
esc_html__( 'Choose the level of compression that suits your needs. We recommend %1$sUltra%2$s for faster sites and impressive image quality.', 'wp-smushit' ),
'',
' '
),
),
'strip_exif' => array(
'label' => esc_html__( 'Remove image metadata', 'wp-smushit' ),
'short_label' => esc_html__( 'Metadata', 'wp-smushit' ),
'desc' => esc_html__( 'Photos can include camera settings, date or location. Removing this EXIF data reduces the file size.', 'wp-smushit' ),
),
'resize' => array(
'label' => esc_html__( 'Resize large images', 'wp-smushit' ),
'short_label' => esc_html__( 'Large Image Resizing', 'wp-smushit' ),
'desc' => esc_html__( 'WordPress scales large images (over 2560px) and keeps the originals as a backup. You can adjust the size limit or turn scaling off entirely.', 'wp-smushit' ),
),
'no_scale' => array(
'label' => esc_html__( 'Disable scaled images', 'wp-smushit' ),
'short_label' => esc_html__( 'Disable Scaled Images', 'wp-smushit' ),
'desc' => esc_html__( 'When enabled, WordPress won’t create scaled versions of large images; only your original upload is kept.', 'wp-smushit' ),
),
'detection' => array(
'label' => esc_html__( 'Detect and show incorrectly sized images', 'wp-smushit' ),
'short_label' => esc_html__( 'Image Resize Detection', 'wp-smushit' ),
'desc' => esc_html__( 'This will add functionality to your website that highlights images that are either too large or too small for their containers.', 'wp-smushit' ),
),
'original' => array(
'label' => esc_html__( 'Optimize original images', 'wp-smushit' ),
'short_label' => esc_html__( 'Original Images', 'wp-smushit' ),
'desc' => esc_html__( 'Control how Smush processes your original image files when running bulk smush.', 'wp-smushit' ),
),
'backup' => array(
'label' => esc_html__( 'Backup original images', 'wp-smushit' ),
'short_label' => esc_html__( 'Backup Original Images', 'wp-smushit' ),
'desc' => esc_html__( 'Keep a backup of your original images so you can restore them anytime. Be aware this may increase the size of your uploads folder.', 'wp-smushit' ),
),
'png_to_jpg' => array(
'label' => esc_html__( 'Auto-convert PNGs to JPEGs (lossy)', 'wp-smushit' ),
'short_label' => esc_html__( 'PNG to JPEG Conversion', 'wp-smushit' ),
'desc' => esc_html__( 'When you compress a PNG, Smush will check if converting it to JPEG could further reduce its size.', 'wp-smushit' ),
),
'accessible_colors' => array(
'label' => esc_html__( 'Enable high contrast mode', 'wp-smushit' ),
'short_label' => esc_html__( 'Color Accessibility', 'wp-smushit' ),
'desc' => esc_html__( 'Increase the visibility and accessibility of elements and components to meet WCAG AAA requirements.', 'wp-smushit' ),
),
'usage' => array(
'label' => esc_html__( 'Allow usage tracking', 'wp-smushit' ),
'short_label' => esc_html__( 'Usage Tracking', 'wp-smushit' ),
'desc' => esc_html__( 'Help make Smush better by letting our designers learn how you’re using the plugin.', 'wp-smushit' ),
),
'image_dimensions' => array(
'label' => esc_html__( 'Automatically add missing image dimensions', 'wp-smushit' ),
'short_label' => esc_html__( 'Add Missing Image Dimensions', 'wp-smushit' ),
'desc' => esc_html__( 'Automatically add width and height attributes to images missing dimensions for better layout stability and performance.', 'wp-smushit' ),
),
'nextgen' => array(
'label' => esc_html__( 'Enable NextGen Gallery integration', 'wp-smushit' ),
'short_label' => esc_html__( 'NextGen Gallery', 'wp-smushit' ),
'desc' => esc_html__( 'Allow smushing images directly through NextGen Gallery settings.', 'wp-smushit' ),
),
's3' => array(
'label' => __( 'Enable Amazon S3 support', 'wp-smushit' ),
'short_label' => __( 'Amazon S3', 'wp-smushit' ),
'desc' => sprintf( /* translators: %1$s - , %2$s - */
esc_html__(
"Storing your image on S3 buckets using %1\$sWP Offload Media%2\$s? Smush can detect and smush those assets for you, including when you're removing files from your host server.",
'wp-smushit'
),
"",
' '
),
),
'gform' => array(
'label' => esc_html__( 'Enable Gravity Forms integration', 'wp-smushit' ),
'short_label' => esc_html__( 'Gravity Forms', 'wp-smushit' ),
'desc' => esc_html__( 'Allow compressing images uploaded with Gravity Forms.', 'wp-smushit' ),
),
'js_builder' => array(
'label' => esc_html__( 'Enable WPBakery Page Builder integration', 'wp-smushit' ),
'short_label' => esc_html__( 'WPBakery Page Builder', 'wp-smushit' ),
'desc' => esc_html__( 'Allow smushing images resized in WPBakery Page Builder editor.', 'wp-smushit' ),
),
'gutenberg' => array(
'label' => esc_html__( 'Show Smush stats in Gutenberg blocks', 'wp-smushit' ),
'short_label' => esc_html__( 'Gutenberg Support', 'wp-smushit' ),
'desc' => esc_html__(
'Add statistics and the manual smush button to Gutenberg blocks that display images.',
'wp-smushit'
),
),
);
$settings = apply_filters( 'wp_smush_settings', $settings );
if ( ! isset( $settings[ $id ] ) ) {
return '';
}
if ( 'short-label' === $type ) {
return ! empty( $settings[ $id ]['short_label'] ) ? $settings[ $id ]['short_label'] : $settings[ $id ]['label'];
}
if ( 'label' === $type ) {
return ! empty( $settings[ $id ]['label'] ) ? $settings[ $id ]['label'] : $settings[ $id ]['short_label'];
}
if ( 'desc' === $type ) {
return $settings[ $id ]['desc'];
}
return $settings[ $id ];
}
/**
* Getter method for bulk settings fields.
*
* @since 3.2.2
* @return array
*/
public function get_bulk_fields() {
if ( $this->is_directory_smush_active() ) {
$this->bulk_fields[] = 'directory_smush';
}
return $this->bulk_fields;
}
/**
* Getter method for integration fields.
*
* @since 3.2.2
* @return array
*/
public function get_integrations_fields() {
return $this->integrations_fields;
}
public function is_upsell_field( $field ) {
return in_array( $field, $this->upsell_fields, true );
}
public function is_pro_field( $field ) {
return ! in_array( $field, self::$basic_features, true );
}
public function can_access_pro_field( $field ) {
return false;
}
public function should_enforce_bulk_limit() {
return true;
}
public function get_api_key() {
return '';
}
/**
* Getter method for settings fields.
*
* @since 3.2.2
* @return array
*/
public function get_settings_fields() {
return $this->settings_fields;
}
/**
* Getter method for lazy loading fields.
*
* @since 3.3.0
* @return array
*/
public function get_lazy_load_fields() {
return $this->lazy_load_fields;
}
public function get_preload_fields() {
return $this->preload_fields;
}
public function get_webp_fields() {
return $this->webp_fields;
}
public function get_avif_fields() {
return $this->avif_fields;
}
public function get_next_gen_fields() {
return array_merge( $this->get_webp_fields(), $this->get_avif_fields() );
}
/**
* Init settings.
*
* If there are no settings in the database, populate it with the defaults, if settings are present
*/
public function init() {
}
/**
* Checks whether the settings are applicable for the whole network/site or sitewise (multisite).
*/
public function is_network_enabled() {
return $this->is_network_setting( self::$settings_option_id );
}
public function is_network_setting( $option_id ) {
if ( ! is_multisite() ) {
return false;
}
$global_setting_keys = array(
'wp_smush_api_auth',
self::$subsite_controls_option_id,
);
if ( in_array( $option_id, $global_setting_keys, true ) ) {
return true;
}
$subsite_modules = $this->get_activated_subsite_modules();
if ( empty( $subsite_modules ) ) {
return true;
}
$module_option_keys = array(
'wp-smush-image_sizes' => 'bulk',
'wp-smush-resize_sizes' => 'bulk',
'wp-smush-lazy_load' => self::$lazy_preload_module_name,
'wp-smush-preload' => self::$lazy_preload_module_name,
'wp-smush-cdn_status' => 'cdn',
);
if ( ! isset( $module_option_keys[ $option_id ] ) ) {
if ( $this->is_switching_subsite ) {
return false;
}
return self::is_ajax_network_admin() || is_network_admin();
}
$module = $module_option_keys[ $option_id ];
return ! in_array( $module, $subsite_modules, true );
}
/**
* Check if user is able to access the page.
*
* @since 3.2.2
*
* @param string|bool $module Check if a specific module is allowed.
* @param bool $top_menu Is this a top level menu point? Defaults to a Smush sub page.
*
* @return bool|array Can access page or not. If custom access rules defined - return custom rules array.
*/
public static function can_access( $module = false, $top_menu = false ) {
// Allow all access on single site installs.
if ( ! is_multisite() ) {
return true;
}
$access = get_site_option( self::$subsite_controls_option_id );
// Check to if the settings update is network-wide or not ( only if in network admin ).
$action = filter_input( INPUT_POST, 'action', FILTER_SANITIZE_SPECIAL_CHARS );
$is_network_admin = is_network_admin() || 'save_settings' === $action;
if ( self::is_ajax_network_admin() ) {
$is_network_admin = true;
}
if ( $is_network_admin && ! $access && $top_menu ) {
return true;
}
if ( current_user_can( 'manage_options' ) && ( '1' === $access || 'custom' === $access && $top_menu ) ) {
return true;
}
if ( is_array( $access ) && current_user_can( 'manage_options' ) ) {
if ( ! $module ) {
return $access;
}
if ( $is_network_admin && ! in_array( $module, $access, true ) ) {
return true;
} elseif ( ! $is_network_admin && in_array( $module, $access, true ) ) {
return true;
}
return false;
}
return false;
}
public function maybe_reset_cache_site_settings( $new_blog_id, $prev_blog_id ) {
$this->reset_cache_site_settings();
}
public function reset_cache_site_settings() {
$this->settings = array();// Reset settings, leave force update the settings for get_site_settings.
}
private function update_site_settings( $new_settings ) {
$new_settings = (array) $new_settings;
$site_settings = $this->get_site_settings();
foreach ( $new_settings as $setting => $value ) {
if ( isset( $site_settings[ $setting ], $value ) ) {
$site_settings[ $setting ] = $value;
}
}
$this->update_site_option( self::$settings_option_id, $site_settings );
$this->reset_cache_site_settings();
}
public function get_site_settings() {
if ( empty( $this->settings ) ) {
$this->settings = $this->prepare_site_settings();
}
return $this->settings;
}
private function prepare_site_settings() {
$is_multisite = is_multisite();
if ( ! $is_multisite ) {
// Make sure the new default settings are included into the old configs.
$site_settings = get_option( self::$settings_option_id, array() );
return wp_parse_args( $this->ensure_array( $site_settings ), $this->get_defaults() );
}
$network_settings = get_site_option( self::$settings_option_id, array() );
$network_settings = $this->ensure_array( $network_settings );
$network_settings = wp_parse_args( $network_settings, $this->get_defaults() );
if ( $this->is_network_enabled() ) {
return $network_settings;
}
$subsite_modules = $this->get_activated_subsite_modules();
$network_modules = array_diff( $this->get_modules(), $subsite_modules );
if ( in_array( self::$lazy_preload_module_name, $network_modules, true ) ) {
// Lazy & preload modules include 2 modules: lazy_load and preload.
$network_modules[] = 'preload';
}
$subsite_settings = get_option( self::$settings_option_id, array() );
$subsite_settings = $this->ensure_array( $subsite_settings );
foreach ( $network_modules as $key ) {
// Remove values that are network wide from subsite settings.
$get_module_fields = "get_{$key}_fields";
if ( method_exists( $this, $get_module_fields ) ) {
$subsite_settings = array_diff_key( $subsite_settings, array_flip( $this->$get_module_fields() ) );
}
}
// And append subsite settings to the site settings.
$network_settings = array_merge( $network_settings, $subsite_settings );
return $network_settings;
}
/**
* Ensure the input is an array.
*
* @param mixed $array_value Array value.
* @return array
*/
private function ensure_array( $array_value ) {
return empty( $array_value ) || ! is_array( $array_value )
? array()
: $array_value;
}
/**
* Getter method for $settings.
*
* @since 3.0
*
* @param string $setting Setting to get. Default: get all settings.
*
* @return array|bool Return either a setting value or array of settings.
*/
public function get( $setting = '' ) {
$settings = $this->get_site_settings();
if ( ! empty( $setting ) ) {
return isset( $settings[ $setting ] ) ? $settings[ $setting ] : false;
}
return $settings;
}
/**
* Setter method for $settings.
*
* @since 3.0
*
* @param string $setting Setting to update.
* @param bool $value Value to set. Default: false.
*/
public function set( $setting = '', $value = false ) {
if ( empty( $setting ) ) {
return;
}
$this->update_site_settings( array( $setting => $value ) );
}
public function delete( $setting ) {
if ( empty( $setting ) ) {
return;
}
$settings = $this->get_site_settings();
if ( isset( $settings[ $setting ] ) ) {
unset( $settings[ $setting ] );
$this->update_site_settings( $settings );
}
}
/**
* Get all Smush settings, based on if network settings are enabled or not.
*
* @param string $name Setting to fetch.
* @param mixed $default Default value.
*
* @return bool|mixed
*/
public function get_setting( $name = '', $default = false ) {
if ( empty( $name ) ) {
return false;
}
if ( ! is_multisite() ) {
return get_option( $name, $default );
}
$global = $this->is_network_setting( $name );
$global_settings = get_site_option( $name, $default );
if ( $global ) {
return $global_settings;
}
$subsite_settings = get_option( $name, $default );
$subsite_settings = false !== $subsite_settings ? $subsite_settings : $global_settings;
return $subsite_settings;
}
/**
* Update value for given setting key
*
* @param string $name Key.
* @param mixed $value Value.
*
* @return bool If the setting was updated or not
*/
public function set_setting( $name = '', $value = '' ) {
if ( empty( $name ) ) {
return false;
}
if ( self::$settings_option_id === $name ) {
return $this->update_site_settings( $value );
}
return $this->update_site_option( $name, $value );
}
private function update_site_option( $name, $value ) {
$global = $this->is_network_setting( $name );
return $global ? update_site_option( $name, $value ) : update_option( $name, $value );
}
/**
* Delete the given key name.
*
* @param string $name Key.
*
* @return bool If the setting was updated or not
*/
public function delete_setting( $name = '' ) {
if ( empty( $name ) ) {
return false;
}
$global = $this->is_network_setting( $name );
return $global ? delete_site_option( $name ) : delete_option( $name );
}
/**
* Reset settings to defaults.
*
* @since 3.2.0
*/
public function reset() {
check_ajax_referer( 'wp_smush_reset' );
// Check capability.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
}
delete_site_option( self::$subsite_controls_option_id );
delete_site_option( 'wp-smush-webp_hide_wizard' );
delete_site_option( 'wp-smush-preset_configs' );
$this->delete_setting( 'wp-smush-image_sizes' );
$this->delete_setting( 'wp-smush-resize_sizes' );
$this->delete_setting( 'wp-smush-cdn_status' );
$this->delete_setting( 'wp-smush-lazy_load' );
$this->delete_setting( 'wp-smush-cdn-advanced-settings' );
$this->delete_setting( 'wp-smush-hide-tutorials' );
delete_option( 'wp-smush-png2jpg-rewrite-rules-flushed' );
delete_option( 'wp_smush_scan_slice_size' );
LCP_Helper::delete_all_lcp_data();
// We used update_option for skip-smush-setup,
// so let's reset it with delete_option instead of delete_site_option for MU site.
delete_option( 'skip-smush-setup' );
// Reset site settings.
$this->reset_site_settings();
// Reset sub-sites.
$this->reset_sub_sites();
wp_send_json_success();
}
private function reset_site_settings() {
$this->delete_setting( self::$settings_option_id );
$this->reset_cache_site_settings();
// The action wp_smush_settings_updated only triggers after option is updated, does not trigger on add_(site_)option.
// So to support this, we need to add the default option first.
$this->add_default_site_settings();
}
private function add_default_site_settings() {
$this->update_site_settings( $this->get_defaults() );
}
public function initial_default_site_settings() {
if ( false === $this->get_setting( self::$settings_option_id, false ) ) {
$this->add_default_site_settings();
}
}
private function reset_sub_sites() {
if ( ! is_multisite() ) {
return;
}
$site_args = array(
'fields' => 'ids',
'public' => 1,
'number' => 250, // Limit to 250 sites to avoid performance issues.
);
$site_ids = get_sites( $site_args );
if ( empty( $site_ids ) ) {
return;
}
foreach ( $site_ids as $site_id ) {
switch_to_blog( $site_id );
$this->reset_sub_site_settings();
restore_current_blog();
}
}
private function reset_sub_site_settings() {
delete_option( self::$settings_option_id );
delete_option( 'wp-smush-image_sizes' );
delete_option( 'wp-smush-resize_sizes' );
delete_option( 'wp-smush-cdn_status' );
delete_option( 'wp-smush-lazy_load' );
delete_option( 'wp-smush-cdn-advanced-settings' );
delete_option( 'wp-smush-hide-tutorials' );
delete_option( 'skip-smush-setup' );
delete_option( 'wp_smush_scan_slice_size' );
LCP_Helper::delete_all_lcp_data();
}
/**
* Save settings.
*
* @since 3.8.6
*/
public function save_settings() {
check_ajax_referer( 'wp-smush-ajax' );
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_send_json_error(
array(
'message' => esc_html__( "You don't have permission to do this.", 'wp-smushit' ),
)
);
}
// Delete S3 alert flag, if S3 option is disabled again.
if ( ! isset( $_POST['wp-smush-s3'] ) && isset( $settings['integration']['s3'] ) && $settings['integration']['s3'] ) {
delete_site_option( 'wp-smush-hide_s3support_alert' );
}
$page = filter_input( INPUT_POST, 'page', FILTER_SANITIZE_SPECIAL_CHARS );
if ( ! isset( $page ) ) {
wp_send_json_error(
array( 'message' => __( 'The page these settings belong to is missing.', 'wp-smushit' ) )
);
}
$new_settings = array();
$status = array(
'is_outdated_stats' => false,
'page' => $page,
);
if ( 'bulk' === $page ) {
foreach ( $this->get_bulk_fields() as $field ) {
if ( ! isset( $this->get_defaults()[ $field ] ) ) {
continue;
}
if ( 'lossy' == $field ) {
$new_settings['lossy'] = filter_input( INPUT_POST, $field, FILTER_SANITIZE_NUMBER_INT );
continue;
}
$new_settings[ $field ] = (bool) filter_input( INPUT_POST, $field, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
}
$this->parse_bulk_settings();
}
if ( 'lazy-load' === $page ) {
$this->parse_lazy_load_settings();
$new_settings['auto_resizing'] = (bool) filter_input( INPUT_POST, 'auto_resizing', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
$new_settings['image_dimensions'] = (bool) filter_input( INPUT_POST, 'image_dimensions', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
} elseif ( 'preload' === $page ) {
$preload_images = filter_input( INPUT_POST, 'preload_images', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
$new_settings['preload_images'] = (bool) $preload_images;
$this->parse_preload_settings();
}
if ( 'cdn' === $page ) {
foreach ( $this->get_cdn_fields() as $field ) {
// Skip the module enable/disable option.
if ( 'cdn' === $field ) {
continue;
}
if ( self::$next_gen_cdn_key === $field ) {
$new_settings[ self::$next_gen_cdn_key ] = $this->parse_next_gen_cdn_from_input();
continue;
}
$new_settings[ $field ] = (bool) filter_input( INPUT_POST, $field, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
}
$this->parse_cdn_settings();
}
if ( 'next-gen' === $page ) {
$this->parse_next_gen_settings();
// Check whether Next-Gen Formats have changed (WebP <-> AVIF).
$status['next_gen_format_changed'] = did_action( 'wp_smush_next_gen_after_format_switch' );
// Check whether WebP method is changed (Direct Conversion <-> Server Configuration).
$status['webp_method_changed'] = did_action( 'wp_smush_webp_method_changed' );
}
if ( 'integrations' === $page ) {
foreach ( $this->get_integrations_fields() as $field ) {
$new_settings[ $field ] = (bool) filter_input( INPUT_POST, $field, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
}
}
if ( 'settings' === $page ) {
$tab = filter_input( INPUT_POST, 'tab', FILTER_SANITIZE_SPECIAL_CHARS );
if ( ! isset( $tab ) ) {
wp_send_json_error(
array( 'message' => __( 'The tab these settings belong to is missing.', 'wp-smushit' ) )
);
}
if ( 'general' === $tab ) {
$new_settings['usage'] = (bool) filter_input( INPUT_POST, 'usage', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
$new_settings['detection'] = (bool) filter_input( INPUT_POST, 'detection', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
$new_settings['image_dimensions'] = (bool) filter_input( INPUT_POST, 'image_dimensions', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
}
if ( 'permissions' === $tab ) {
$new_settings['networkwide'] = $this->parse_access_settings();
}
if ( 'data' === $tab ) {
$new_settings['keep_data'] = (bool) filter_input( INPUT_POST, 'keep_data', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
}
if ( 'accessibility' === $tab ) {
$new_settings['accessible_colors'] = (bool) filter_input( INPUT_POST, 'accessible_colors', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
}
}
$this->update_site_settings( $new_settings );
$status['is_outdated_stats'] = Global_Stats::get()->is_outdated();
wp_send_json_success( $status );
}
private function parse_next_gen_cdn_from_input() {
$cdn_next_gen_mode = filter_input( INPUT_POST, 'next-gen-cdn', FILTER_VALIDATE_INT );
return $this->sanitize_cdn_next_gen_conversion_mode( $cdn_next_gen_mode );
}
/**
* Parse bulk Smush specific settings.
*
* Nonce processed in parent method.
*
* @since 3.2.0 Moved from save method.
*/
private function parse_bulk_settings() {
// Save the selected image sizes.
if ( isset( $_POST['wp-smush-auto-image-sizes'] ) && 'all' === $_POST['wp-smush-auto-image-sizes'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$this->delete_setting( 'wp-smush-image_sizes' );
} else {
if ( ! isset( $_POST['wp-smush-image_sizes'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$image_sizes = array();
} else {
$image_sizes = array_filter( array_map( 'sanitize_text_field', wp_unslash( $_POST['wp-smush-image_sizes'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
}
$this->set_setting( 'wp-smush-image_sizes', $image_sizes );
}
// Update Resize width and height settings if set.
$resize_sizes['width'] = isset( $_POST['wp-smush-resize_width'] ) ? (int) $_POST['wp-smush-resize_width'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$resize_sizes['height'] = isset( $_POST['wp-smush-resize_height'] ) ? (int) $_POST['wp-smush-resize_height'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$this->set_setting( 'wp-smush-resize_sizes', $resize_sizes );
}
/**
* Parse CDN specific settings.
*
* @since 3.2.0 Moved from save method.
*/
private function parse_cdn_settings() {
// $status = connect to CDN.
if ( ! CDN_Helper::get_instance()->is_cdn_active() ) {
$response = WP_Smush::get_instance()->api()->enable();
// Probably an exponential back-off.
if ( is_wp_error( $response ) ) {
sleep( 1 ); // This is needed so we don't trigger the 597 API response.
$response = WP_Smush::get_instance()->api()->enable( true );
}
// Logged error inside API.
if ( ! is_wp_error( $response ) ) {
$response = json_decode( $response['body'] );
$this->set_setting( 'wp-smush-cdn_status', $response->data );
}
}
$cdn_advanced_settings = $this->get_setting( 'wp-smush-cdn-advanced-settings', array() );
if ( isset( $_POST['excluded-keywords'] ) ) {
$exclusion_keywords = filter_input(
INPUT_POST,
'excluded-keywords',
FILTER_CALLBACK,
array(
'options' => 'sanitize_text_field',
)
);
$exclusion_keywords = preg_split( '/[\r\n\t ]+/', trim( $exclusion_keywords ) );
$cdn_advanced_settings['excluded-keywords'] = $exclusion_keywords;
$this->set_setting( 'wp-smush-cdn-advanced-settings', $cdn_advanced_settings );
}
}
/**
* Parse lazy loading specific settings.
*
* @since 3.2.0
*/
private function parse_lazy_load_settings() {
$previous_settings = $this->get_setting( 'wp-smush-lazy_load' );
$args = array(
'format' => array(
'filter' => FILTER_VALIDATE_BOOLEAN,
'flags' => FILTER_REQUIRE_ARRAY,
),
'output' => array(
'filter' => FILTER_VALIDATE_BOOLEAN,
'flags' => FILTER_REQUIRE_ARRAY,
),
'include' => array(
'filter' => FILTER_VALIDATE_BOOLEAN,
'flags' => FILTER_REQUIRE_ARRAY,
),
'exclude-pages' => array(
'filter' => FILTER_CALLBACK,
'options' => 'sanitize_text_field',
),
'exclude-classes' => array(
'filter' => FILTER_CALLBACK,
'options' => 'sanitize_text_field',
),
'footer' => FILTER_VALIDATE_BOOLEAN,
'native' => FILTER_VALIDATE_BOOLEAN,
'noscript_fallback' => FILTER_VALIDATE_BOOLEAN,
);
$settings = filter_input_array( INPUT_POST, $args );
// Verify lazyload.
if ( ! empty( $_POST['animation'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$settings['animation'] = map_deep( wp_unslash( $_POST['animation'] ), 'sanitize_text_field' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
}
// Fade-in settings.
$settings['animation']['fadein']['duration'] = 0;
if ( isset( $settings['animation']['duration'] ) ) {
$settings['animation']['fadein']['duration'] = absint( $settings['animation']['duration'] );
unset( $settings['animation']['duration'] );
}
$settings['animation']['fadein']['delay'] = 0;
if ( isset( $settings['animation']['delay'] ) ) {
$settings['animation']['fadein']['delay'] = absint( $settings['animation']['delay'] );
unset( $settings['animation']['delay'] );
}
/**
* Spinner and placeholder settings.
*/
$items = array( 'spinner', 'placeholder' );
foreach ( $items as $item ) {
$settings['animation'][ $item ]['selected'] = isset( $settings['animation']["$item-icon"] ) ? $settings['animation']["$item-icon"] : 1;
unset( $settings['animation']["$item-icon"] );
// Custom spinners.
if ( ! isset( $previous_settings['animation'][ $item ]['custom'] ) || ! is_array( $previous_settings['animation'][ $item ]['custom'] ) ) {
$settings['animation'][ $item ]['custom'] = array();
} else {
// Remove empty values.
$settings['animation'][ $item ]['custom'] = array_filter( $previous_settings['animation'][ $item ]['custom'] );
}
// Add uploaded custom spinner.
if ( isset( $settings['animation']["custom-$item"] ) ) {
if ( ! empty( $settings['animation']["custom-$item"] ) && ! in_array( $settings['animation']["custom-$item"], $settings['animation'][ $item ]['custom'], true ) ) {
$settings['animation'][ $item ]['custom'][] = $settings['animation']["custom-$item"];
$settings['animation'][ $item ]['selected'] = $settings['animation']["custom-$item"];
}
unset( $settings['animation']["custom-$item"] );
}
}
// Custom color for placeholder.
if ( ! isset( $settings['animation']['color'] ) ) {
$settings['animation']['placeholder']['color'] = $previous_settings['animation']['placeholder']['color'];
} else {
$settings['animation']['placeholder']['color'] = $settings['animation']['color'];
unset( $settings['animation']['color'] );
}
/**
* Exclusion rules.
*/
// Convert to array.
if ( ! empty( $settings['exclude-pages'] ) ) {
$settings['exclude-pages'] = preg_split( '/[\r\n\t ]+/', $settings['exclude-pages'] );
} else {
$settings['exclude-pages'] = array();
}
if ( ! empty( $settings['exclude-classes'] ) ) {
$settings['exclude-classes'] = preg_split( '/[\r\n\t ]+/', $settings['exclude-classes'] );
} else {
$settings['exclude-classes'] = array();
}
$this->set_setting( 'wp-smush-lazy_load', $settings );
}
/**
* Parse preload specific settings.
*
* @since 3.20.0
*/
private function parse_preload_settings() {
$args = array(
'exclude-pages' => array(
'filter' => FILTER_CALLBACK,
'options' => 'sanitize_text_field',
),
'lcp_fetchpriority' => FILTER_VALIDATE_BOOLEAN,
);
$settings = filter_input_array( INPUT_POST, $args );
/**
* Exclusion rules.
*/
// Convert to array.
if ( ! empty( $settings['exclude-pages'] ) ) {
$settings['exclude-pages'] = array_filter( preg_split( '/[\r\n\t ]+/', $settings['exclude-pages'] ) );
} else {
$settings['exclude-pages'] = array();
}
$this->set_setting( 'wp-smush-preload', $settings );
}
private function parse_next_gen_settings() {
$next_gen_manager = Next_Gen_Manager::get_instance();
$next_gen_format = filter_input( INPUT_POST, 'next-gen-format', FILTER_SANITIZE_SPECIAL_CHARS );
$next_gen_method = filter_input( INPUT_POST, 'next-gen-method', FILTER_SANITIZE_SPECIAL_CHARS );
$next_gen_manager->activate_format( $next_gen_format );
$next_gen_configuration = $next_gen_manager->get_active_format_configuration();
// Update Next-Gen method.
$next_gen_configuration->set_next_gen_method( $next_gen_method );
// Update Next-Gen fallback.
if ( $next_gen_configuration->direct_conversion_enabled() ) {
$next_gen_fallback_active = filter_input( INPUT_POST, 'next-gen-fallback', FILTER_VALIDATE_BOOLEAN );
$next_gen_configuration->set_next_gen_fallback( (bool) $next_gen_fallback_active );
}
}
/**
* Parse access control settings on multisite.
*
* @since 3.2.2
*
* @return mixed
*/
private function parse_access_settings() {
$current_value = get_site_option( self::$subsite_controls_option_id );
$new_value = filter_input( INPUT_POST, 'wp-smush-subsite-access', FILTER_SANITIZE_SPECIAL_CHARS );
$access = filter_input( INPUT_POST, 'wp-smush-access', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY );
if ( 'custom' === $new_value ) {
$new_value = $access;
}
if ( $current_value !== $new_value ) {
update_site_option( self::$subsite_controls_option_id, $new_value );
}
return $new_value;
}
/**
* Apply a default configuration to lazy loading on first activation.
*
* @since 3.2.0
*/
public function init_lazy_load_defaults() {
$defaults = array(
'format' => array(
'jpeg' => true,
'png' => true,
'webp' => true,
'gif' => true,
'svg' => true,
'iframe' => true,
'embed_video' => false,
),
'output' => array(
'content' => true,
'widgets' => true,
'thumbnails' => true,
'gravatars' => true,
),
'animation' => array(
'selected' => 'fadein', // Accepts: fadein, spinner, placeholder, false.
'fadein' => array(
'duration' => 400,
'delay' => 0,
),
'spinner' => array(
'selected' => 1,
'custom' => array(),
),
'placeholder' => array(
'selected' => 1,
'custom' => array(),
'color' => '#F3F3F3',
),
),
'include' => array(
'frontpage' => true,
'home' => true,
'page' => true,
'single' => true,
'archive' => true,
'category' => true,
'tag' => true,
),
'exclude-pages' => array(),
'exclude-classes' => array(),
'footer' => true,
'native' => false,
'noscript_fallback' => false,
);
$this->set_setting( 'wp-smush-lazy_load', $defaults );
}
/**
* Check if in network admin.
*
* The is_network_admin() check does not work in ajax calls.
*
* @since 3.10.3
*
* @return bool
*/
public static function is_ajax_network_admin() {
return defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_SERVER['HTTP_REFERER'] ) && preg_match( '#^' . network_admin_url() . '#i', wp_unslash( $_SERVER['HTTP_REFERER'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
}
public function is_optimize_original_images_active() {
return ! empty( self::get_instance()->get( 'original' ) );
}
public function is_png2jpg_module_active() {
return $this->is_module_active( 'png_to_jpg' );
}
public function is_webp_module_active() {
return $this->is_module_active( 'webp_mod' );
}
public function is_avif_module_active() {
return $this->is_module_active( 'avif_mod' );
}
public function is_avif_fallback_active() {
return $this->is_avif_module_active()
&& ! empty( self::get_instance()->get( 'avif_fallback' ) );
}
public function is_resize_module_active() {
return $this->is_module_active( 'resize' );
}
public function is_backup_active() {
return $this->is_module_active( 'backup' );
}
public function is_s3_active() {
return $this->is_module_active( 's3' );
}
public function is_cdn_webp_conversion_active() {
return $this->is_cdn_active()
&& self::$webp_cdn_mode === $this->get_cdn_next_gen_conversion_mode();
}
public function is_cdn_avif_conversion_active() {
return $this->is_cdn_active()
&& self::$avif_cdn_mode === $this->get_cdn_next_gen_conversion_mode();
}
public function is_cdn_next_gen_conversion_active() {
return $this->is_cdn_active()
&& ! empty( $this->get_cdn_next_gen_conversion_mode() );
}
public function get_cdn_next_gen_conversion_mode() {
$cdn_next_gen_mode = (int) self::get_instance()->get( self::$next_gen_cdn_key );
return $this->sanitize_cdn_next_gen_conversion_mode( $cdn_next_gen_mode );
}
public function get_cdn_next_gen_conversion_label( $cdn_next_gen_mode ) {
$cdn_next_gen_mode = $this->sanitize_cdn_next_gen_conversion_mode( $cdn_next_gen_mode );
$cdn_next_gen_modes = $this->get_cdn_next_gen_modes();
return $cdn_next_gen_modes[ $cdn_next_gen_mode ];
}
public function sanitize_cdn_next_gen_conversion_mode( $cdn_next_gen_mode ) {
$cdn_next_gen_mode = (int) $cdn_next_gen_mode;
$cdn_next_gen_modes = $this->get_cdn_next_gen_modes();
if ( ! isset( $cdn_next_gen_modes[ $cdn_next_gen_mode ] ) ) {
$cdn_next_gen_mode = self::$none_cdn_mode;
}
return $cdn_next_gen_mode;
}
private function get_cdn_next_gen_modes() {
return array(
self::$none_cdn_mode => __( 'None', 'wp-smushit' ),
self::$webp_cdn_mode => __( 'WebP', 'wp-smushit' ),
self::$avif_cdn_mode => __( 'AVIF', 'wp-smushit' ),
);
}
public function is_webp_direct_conversion_active() {
return $this->is_webp_module_active()
&& ! empty( self::get_instance()->get( 'webp_direct_conversion' ) );
}
public function is_automatic_compression_active() {
return self::get_instance()->get( 'auto' );
}
public function is_cdn_active() {
return $this->is_module_active( 'cdn' );
}
public function is_webp_fallback_active() {
return $this->is_webp_module_active()
&& ! empty( self::get_instance()->get( 'webp_fallback' ) );
}
public function is_lazyload_active() {
return self::get_instance()->get( 'lazy_load' );
}
public function is_auto_resizing_active() {
return $this->is_module_active( 'auto_resizing' );
}
public function should_add_missing_dimensions() {
return self::get_instance()->get( 'image_dimensions' );
}
protected function get_placeholder_modules() {
return array(
'cdn',
'png_to_jpg',
'webp_mod',
'avif_mod',
's3',
'nextgen',
'ultra',
'preload_images',
'auto_resizing',
'image_dimensions',
);
}
public function is_module_active( $module ) {
$advanced_modules = $this->get_placeholder_modules();
if ( in_array( $module, $advanced_modules, true ) ) {
return false;
}
return self::get_instance()->get( $module );
}
public function get_lossy_level_setting() {
$current_level = self::get_instance()->get( 'lossy' );
return $this->sanitize_lossy_level( $current_level );
}
public function sanitize_lossy_level( $lossy_level ) {
$highest_level = $this->get_highest_lossy_level();
if ( $lossy_level > $highest_level ) {
return $highest_level;
}
if ( $lossy_level > self::$level_lossless ) {
return (int) $lossy_level;
}
return self::$level_lossless;
}
public function get_highest_lossy_level() {
return self::$level_super_lossy;
}
public function get_current_lossy_level_label() {
$current_level = $this->get_lossy_level_setting();
return $this->get_lossy_level_label( $current_level );
}
public function get_lossy_level_label( $lossy_level ) {
$smush_modes = array(
self::$level_lossless => __( 'Basic', 'wp-smushit' ),
self::$level_super_lossy => __( 'Super', 'wp-smushit' ),
self::$level_ultra_lossy => __( 'Ultra', 'wp-smushit' ),
);
if ( ! isset( $smush_modes[ $lossy_level ] ) ) {
$lossy_level = self::$level_lossless;
}
return $smush_modes[ $lossy_level ];
}
public function get_large_file_cutoff() {
return apply_filters( 'wp_smush_large_file_cut_off', 32 * 1024 * 1024 );
}
public function has_bulk_smush_page() {
return $this->is_page_active( 'bulk' );
}
public function has_cdn_page() {
return $this->is_page_active( 'cdn' );
}
public function has_webp_page() {
_deprecated_function( __METHOD__, '3.8.0', 'Settings::has_next_gen_page()' );
return $this->has_next_gen_page();
}
public function has_next_gen_page() {
return $this->is_page_active( 'next-gen' );
}
public function has_lazy_preload_page() {
return $this->is_page_active( self::$lazy_preload_module_name );
}
public function streaming_enabled() {
if ( defined( 'WP_SMUSH_USE_STREAMS' ) ) {
return (bool) WP_SMUSH_USE_STREAMS;
}
return self::get_instance()->get( 'disable_streams' ) != WP_SMUSH_VERSION;
}
public function is_lcp_preload_enabled() {
return $this->is_module_active( 'preload_images' );
}
private function is_page_active( $page_slug ) {
if ( ! is_multisite() ) {
return true;
}
$module = $this->slug_to_module( $page_slug );
$is_page_active_on_subsite = in_array( $module, $this->get_activated_subsite_modules(), true );
if ( is_network_admin() ) {
return ! $is_page_active_on_subsite;
}
return $is_page_active_on_subsite;
}
private function slug_to_module( $page_slug ) {
return str_replace( '-', '_', $page_slug );
}
/**
* Check if the directory smush module is active.
*
* @return bool
*/
public function is_directory_smush_active() {
if ( ! is_multisite() || is_super_admin() ) {
return true;
}
$activated_subsite_modules = $this->get_activated_subsite_modules();
return in_array( 'directory_smush', $activated_subsite_modules, true ) && in_array( 'bulk', $activated_subsite_modules, true );
}
/**
* @return array
*/
private function get_activated_subsite_modules() {
if ( ! is_array( $this->activated_subsite_modules ) ) {
$this->activated_subsite_modules = $this->get_activated_subsite_modules_list();
}
return $this->activated_subsite_modules;
}
/**
* @return array
*/
public function get_activated_subsite_modules_list() {
$subsite_controls = get_site_option( self::$subsite_controls_option_id );
// None:false|All:1|Custom:array list page modules.
if ( empty( $subsite_controls ) ) {
return array();
}
$subsite_modules = $this->get_subsite_modules();
if ( is_array( $subsite_controls ) ) {
$subsite_modules = $subsite_controls;
}
return $subsite_modules;
}
private function get_subsite_modules() {
return array(
'bulk',
'directory_smush',
'integrations',
self::$lazy_preload_module_name,
'cdn',
);
}
/**
* Get the maximum content width for images.
*
* @return int
*/
public function max_content_width() {
// Get global content width (if content width is empty, set 2560).
$content_width = isset( $GLOBALS['content_width'] ) ? (int) $GLOBALS['content_width'] : $this->get_default_size_threshold();
// Avoid situations, when themes misuse the global.
if ( 0 === $content_width ) {
$content_width = $this->get_default_size_threshold();
}
$resize_module_active = $this->is_resize_module_active();
if ( ! $resize_module_active ) {
return $content_width;
}
// Check to see if we are resizing the images (can not go over that value).
$resize_sizes = $this->get_setting( 'wp-smush-resize_sizes' );
if ( isset( $resize_sizes['width'] ) && $resize_sizes['width'] < $content_width ) {
return $resize_sizes['width'];
}
return $content_width;
}
/**
* Get the default size threshold for images.
*
* WordPress sets the default threshold value to 2560 pixels.
*
* @return int
*/
public function get_default_size_threshold() {
return apply_filters( 'wp_smush_default_size_threshold', 2560 );
}
/**
* Get avif_cdn_mode.
*
* @return int
*/
public static function get_avif_cdn_mode() {
return self::$avif_cdn_mode;
}
/**
* Get lazy_preload_module_name.
*
* @return string
*/
public static function get_lazy_preload_module_name() {
return self::$lazy_preload_module_name;
}
/**
* Get level_lossless.
*
* @return int
*/
public static function get_level_lossless() {
return self::$level_lossless;
}
/**
* Get level_super_lossy.
*
* @return int
*/
public static function get_level_super_lossy() {
return self::$level_super_lossy;
}
/**
* Get level_ultra_lossy.
*
* @return int
*/
public static function get_level_ultra_lossy() {
return self::$level_ultra_lossy;
}
/**
* Get next_gen_cdn_key.
*
* @return string
*/
public static function get_next_gen_cdn_key() {
return self::$next_gen_cdn_key;
}
/**
* Get none_cdn_mode.
*
* @return int
*/
public static function get_none_cdn_mode() {
return self::$none_cdn_mode;
}
/**
* Get settings_key.
*
* @return string
*/
public static function get_settings_option_id() {
return self::$settings_option_id;
}
/**
* Get subsite_controls_option_key.
*
* @return string
*/
public static function get_subsite_controls_option_id() {
return self::$subsite_controls_option_id;
}
/**
* Get webp_cdn_mode.
*
* @return int
*/
public static function get_webp_cdn_mode() {
return self::$webp_cdn_mode;
}
}
PK TD\
) core/class-animated-status-controller.phpnu [ media_item_cache = Media_Item_Cache::get_instance();
$this->global_stats = Global_Stats::get();
$this->logger = Helper::logger();
$this->register_filter( 'wp_smush_scan_library_slice_handle_attachment', array(
$this,
'maybe_update_animated_status_during_scan',
), 10, 2 );
$this->register_action( 'wp_smush_after_attachment_upload', array(
$this,
'maybe_update_animated_status_on_upload',
) );
$this->register_action( 'wp_smush_before_smush_attempt', array(
$this,
'maybe_update_animated_status_before_optimization',
) );
}
public function maybe_update_animated_status_during_scan( $slice_data, $attachment_id ) {
$this->maybe_update_animated_status( $attachment_id );
return $slice_data;
}
public function maybe_update_animated_status_on_upload( $attachment_id ) {
$this->maybe_update_animated_status( $attachment_id );
}
/**
* TODO: add test
*
* @param $attachment_id
*
* @return void
*/
public function maybe_update_animated_status_before_optimization( $attachment_id ) {
$this->maybe_update_animated_status( $attachment_id );
}
private function maybe_update_animated_status( $attachment_id ) {
$media_item = $this->media_item_cache->get( $attachment_id );
if ( ! $media_item->is_valid() ) {
$this->logger->error( 'Tried to check animated value but encountered an problem with the media item' );
return;
}
if ( apply_filters( 'wp_smush_skip_image_animation_check', false, $attachment_id ) ) {
// The image is explicitly excluded from the animation check
return;
}
if ( $media_item->animated_meta_exists() ) {
// We already marked this item, no need to check again.
return;
}
if ( ! $media_item->has_animated_mime_type() ) {
// The media item is not even a GIF so no need to check.
return;
}
$file_path = $media_item->get_full_or_scaled_size()->get_file_path();
$is_animated = Helper::check_animated_file_contents( $file_path );
$this->logger->log( 'Setting animated meta value' );
$set_animated = $media_item->set_animated( $is_animated );
if ( $set_animated ) {
$media_item->save();
}
if ( $is_animated ) {
do_action( 'wp_smush_attachment_animated_status_changed', $attachment_id );
}
}
}
PK YD\2+ + . core/lazy-load/class-lazy-load-video-embed.phpnu [ embed_url = $embed_url;
$this->iframe_element = $iframe_element;
$this->helper = Video_Embed_Helper::get_instance();
$this->url_utils = new Url_Utils();
}
private function get_embed_provider() {
if ( ! $this->embed_provider ) {
$this->embed_provider = $this->prepare_embed_provider();
}
return $this->embed_provider;
}
private function prepare_embed_provider() {
return $this->helper->create_embed_object( $this->embed_url );
}
public function can_lazy_load() {
$embed_provider = $this->get_embed_provider();
$can_lazy_load = ! empty( $embed_provider );
return apply_filters( 'wp_smush_should_lazy_load_video', $can_lazy_load, $embed_provider, $this->iframe_element );
}
private function is_auto_play_enabled() {
$query_vars = $this->url_utils->get_query_vars( $this->embed_url );
return ! empty( $query_vars['autoplay'] );
}
public function transform() {
if ( ! $this->get_embed_provider() ) {
return;
}
$wrapper_markup_parts = $this->generate_video_wrapper_parts();
if ( empty( $wrapper_markup_parts ) ) {
return;
}
list( $wrapper_before, $wrapper_after ) = $wrapper_markup_parts;
$this->iframe_element->set_wrapper_markup( $wrapper_before, $wrapper_after );
$this->convert_src_to_data_src();
$this->iframe_element->add_attribute( new Element_Attribute( 'src', Lazy_Load_Transform::get_temp_src() ) );
}
private function generate_video_wrapper_parts() {
// Try the cover attribute.
$wrapper_markup = $this->generate_video_wrapper_parts_from_cover_attribute();
if ( ! empty( $wrapper_markup ) ) {
return $wrapper_markup;
}
// Use cached thumbnail data.
$wrapper_markup = $this->generate_video_wrapper_parts_from_cached_video_thumbnail();
if ( ! empty( $wrapper_markup ) ) {
return $wrapper_markup;
}
// Generate a custom redirect URL.
$wrapper_markup = $this->generate_video_wrapper_parts_with_custom_url();
if ( ! empty( $wrapper_markup ) ) {
return $wrapper_markup;
}
return null;
}
private function convert_src_to_data_src() {
$src_attribute = $this->iframe_element->get_attribute( 'src' );
if ( $src_attribute ) {
$original_value = $src_attribute->get_value();
$data_attribute = new Element_Attribute( 'data-src', $original_value );
$this->iframe_element->replace_attribute( 'src', $data_attribute );
}
}
private function generate_video_wrapper_parts_with_custom_url() {
list( $video_width, $video_height ) = $this->get_video_dimensions( $this->iframe_element );
if ( ! $video_width && ! $video_height ) {
return null;
}
$video_thumbnail_url = $this->helper->make_video_thumbnail_url( $this->embed_url, $video_width, $video_height );
$aspect_ratio = $this->get_aspect_ratio( $video_width, $video_height );
return $this->generate_video_wrapper_markup_parts( $aspect_ratio, $video_thumbnail_url );
}
private function generate_video_wrapper_parts_from_cached_video_thumbnail() {
list( $video_width, $video_height ) = $this->get_video_dimensions( $this->iframe_element );
if ( ! $video_width && ! $video_height ) {
return null;
}
$video_embed = $this->get_embed_provider();
$cached_video_thumbnail = $video_embed->get_cached_video_thumbnail( $video_width, $video_height );
if ( ! $cached_video_thumbnail ) {
return null;
}
$video_thumbnail_url = $cached_video_thumbnail->get_url();
$aspect_ratio = $this->get_aspect_ratio( $video_width, $video_height );
$fallback_background_image_attribute = $this->get_next_gen_fallback_background_image_attribute( $cached_video_thumbnail );
return $this->generate_video_wrapper_markup_parts( $aspect_ratio, $video_thumbnail_url, $fallback_background_image_attribute );
}
private function generate_video_wrapper_parts_from_cover_attribute() {
$embed_provider = $this->get_embed_provider();
list( $video_width, $video_height ) = $this->get_video_dimensions( $this->iframe_element );
$video_thumbnail = $this->get_video_thumbnail_from_attribute( $this->iframe_element, $video_width, $video_height );
if ( empty( $video_thumbnail ) || empty( $embed_provider ) ) {
return null;
}
// Generate markup components.
$aspect_ratio = $this->get_aspect_ratio( $video_width, $video_height, $video_thumbnail );
$fallback_background_image_attribute = $this->get_next_gen_fallback_background_image_attribute( $video_thumbnail );
return $this->generate_video_wrapper_markup_parts(
$aspect_ratio,
$video_thumbnail->get_url(),
$fallback_background_image_attribute
);
}
private function generate_video_wrapper_markup_parts( $aspect_ratio, $video_thumbnail_url, $fallback_background_image_attribute = '' ) {
$embed_provider = $this->get_embed_provider();
$wrapper_classes = $this->generate_wrapper_classes();
$background_image_attribute = $this->get_background_image_attribute( $video_thumbnail_url );
// Build wrapper markup.
$wrapper_markup_before = sprintf(
'
',
esc_attr( implode( ' ', $wrapper_classes ) ),
esc_attr( $aspect_ratio ),
$background_image_attribute,
$fallback_background_image_attribute
);
$wrapper_markup_before = apply_filters(
'wp_smush_lazy_load_video_wrapper_markup_before',
$wrapper_markup_before,
$embed_provider,
$this->iframe_element
);
$wrapper_markup_after = $this->get_play_button( $this->iframe_element );
$wrapper_markup_after .= '
';
$wrapper_markup_after = apply_filters(
'wp_smush_lazy_load_video_wrapper_markup_after',
$wrapper_markup_after,
$embed_provider,
$this->iframe_element
);
if ( empty( $wrapper_markup_before ) || empty( $wrapper_markup_after ) ) {
return null;
}
return array( $wrapper_markup_before, $wrapper_markup_after );
}
private function get_aspect_ratio( $video_width, $video_height, $video_thumbnail = null ) {
$aspect_ratio = '16/9';
if ( $video_width && $video_height ) {
$aspect_ratio = "{$video_width}/{$video_height}";
} else if ( $video_thumbnail ) {
$aspect_ratio = $video_thumbnail->get_aspect_ratio();
}
return apply_filters(
'wp_smush_lazy_load_video_aspect_ratio',
$aspect_ratio,
$this->get_embed_provider(),
$this->iframe_element
);
}
private function generate_wrapper_classes() {
$embed_provider = $this->get_embed_provider();
$classes = array(
Lazy_Load_Transform::get_lazyload_class(),
self::$class_smush_video,
'smush-lazyload-' . $embed_provider->get_name(),
);
if ( $this->is_auto_play_enabled() ) {
$classes[] = 'smush-lazyload-autoplay';
}
return $classes;
}
private function get_background_image_attribute( $video_thumbnail_url ) {
$thumb_url = apply_filters(
'wp_smush_lazy_load_video_thumbnail_url',
$video_thumbnail_url,
$this->get_embed_provider(),
$this->iframe_element
);
return sprintf( 'data-bg-image="url(%s)"', esc_url( $thumb_url ) );
}
/**
* @param Video_Thumbnail $video_thumbnail
*
* @return string
*/
private function get_next_gen_fallback_background_image_attribute( $video_thumbnail ) {
$fallback_thumb_url = $video_thumbnail->get_fallback_url();
$has_next_gen_url = $video_thumbnail->has_next_gen_url();
// Return early if fallback URL or next-gen URL is not available.
if ( empty( $fallback_thumb_url ) || ! $has_next_gen_url ) {
return '';
}
$next_gen_manager = Next_Gen_Manager::get_instance();
$is_next_gen_fallback_active = $next_gen_manager->is_active() && $next_gen_manager->is_fallback_activated();
if ( ! $is_next_gen_fallback_active ) {
return '';
}
$format_key = Next_Gen_Manager::get_instance()->get_active_format_key();
$fallback_data = wp_json_encode(
array(
'data-bg-image' => sprintf( 'url(%s)', esc_url( $fallback_thumb_url ) ),
)
);
return sprintf( 'data-smush-%s-fallback=\'%s\'', esc_attr( $format_key ), esc_attr( $fallback_data ) );
}
private function get_video_thumbnail_from_attribute( $iframe_element, $video_width = false, $video_height = false ) {
$poster = $iframe_element->get_attribute_value( 'data-poster' );
if ( ! $poster ) {
return null;
}
if ( ! empty( $video_width ) && ! empty( $video_height ) ) {
$width = $video_width;
$height = $video_height;
} else {
list( $width, $height ) = $this->url_utils->get_image_dimensions( $poster );
}
if ( ! $width || ! $height ) {
return null;
}
$extension = $this->url_utils->get_extension( $poster );
$is_next_gen_format = 'avif' === $extension || 'webp' === $extension;
$next_gen_url = $is_next_gen_format ? $poster : null;
$fallback_url = $is_next_gen_format ? null : $poster;
$video_thumbnail = new Video_Thumbnail();
$video_thumbnail->from_array(
array(
'width' => $width,
'height' => $height,
'next_gen_url' => $next_gen_url,
'fallback_url' => $fallback_url,
)
);
return $video_thumbnail;
}
private function get_play_button( $iframe_element ) {
$play_label = $iframe_element->get_attribute_value( 'data-play-label' );
if ( ! $play_label ) {
$play_label = esc_html__( 'Play', 'wp-smushit' );
}
$player_button = sprintf(
'
%2$s
',
__( 'Play video', 'wp-smushit' ),
$play_label,
);
return apply_filters( 'wp_smush_lazy_load_video_player_button', $player_button, $iframe_element );
}
private function get_video_dimensions( $iframe_element ) {
$width = $iframe_element->get_attribute_value( 'width' );
$height = $iframe_element->get_attribute_value( 'height' );
$width = strpos( $width, '%' ) ? 0 : (int) $width;
$height = strpos( $height, '%' ) ? 0 : (int) $height;
if ( empty( $width ) && empty( $height ) ) {
$width = $this->get_video_max_width();
}
$video_dimensions = array(
$width,
$height,
);
return (array) apply_filters( 'wp_smush_lazy_load_video_dimensions', $video_dimensions, $iframe_element );
}
private function get_video_max_width() {
if ( defined( 'WP_SMUSH_LAZYLOAD_MAX_VIDEO_WIDTH' ) && WP_SMUSH_LAZYLOAD_MAX_VIDEO_WIDTH > 0 ) {
return WP_SMUSH_LAZYLOAD_MAX_VIDEO_WIDTH;
}
return Settings::get_instance()->max_content_width();
}
}
PK [D\2?d d ) core/lazy-load/class-lazy-load-helper.phpnu [ settings = Settings::get_instance();
$this->array_utils = new Array_Utils();
$this->server_utils = new Server_Utils();
}
public function should_skip_lazyload() {
return is_admin() ||
is_feed() ||
is_preview() ||
is_embed() ||
! $this->settings->is_module_active( 'lazy_load' ) ||
$this->skip_lazy_load() ||
$this->is_excluded_uri() ||
$this->is_excluded_wp_location();
}
/**
* @return mixed|null
*/
private function skip_lazy_load() {
/**
* Internal filter to disable page parsing.
*
* Because the page parser module is universal, we need to make sure that all modules have the ability to skip
* parsing of certain pages. For example, lazy loading should skip if_preview() pages. In order to achieve this
* functionality, I've introduced this filter. Filter priority can be used to overwrite the $skip param.
*
* @param bool $skip Skip status.
*
* @since 3.2.2
*
* Note: This is named weirdly, but we are keeping it like it is for backward compatibility.
*/
$skip_lazyload = apply_filters_deprecated( 'wp_smush_should_skip_parse', array( false ), '3.16.1', 'wp_smush_should_skip_lazy_load' );
return apply_filters( 'wp_smush_should_skip_lazy_load', $skip_lazyload );
}
private function get_lazy_load_options() {
if ( ! $this->lazy_load_options ) {
$setting = $this->settings->get_setting( 'wp-smush-lazy_load' );
$this->lazy_load_options = $this->array_utils->ensure_array( $setting );
}
return $this->lazy_load_options;
}
public function set_lazy_load_options( $options ) {
$this->lazy_load_options = $options;
}
public function is_native_lazy_loading_enabled() {
$options = $this->get_lazy_load_options();
return ! empty( $options['native'] );
}
public function get_excluded_classes() {
$exclude_classes = $this->array_utils->get_array_value( $this->get_lazy_load_options(), 'exclude-classes' );
return $this->array_utils->ensure_array( $exclude_classes );
}
public function is_noscript_fallback_enabled() {
$noscript_fallback = $this->array_utils->get_array_value( $this->get_lazy_load_options(), 'noscript_fallback' );
return ! empty( $noscript_fallback );
}
private function get_excluded_pages() {
$exclude_pages = $this->array_utils->get_array_value( $this->get_lazy_load_options(), 'exclude-pages' );
return array_filter( $this->array_utils->ensure_array( $exclude_pages ) );
}
public function is_excluded_uri() {
$excluded_page_urls = $this->get_excluded_pages();
if ( empty( $excluded_page_urls ) ) {
return false;
}
$request_uri = $this->server_utils->get_request_uri();
$uri_pattern = implode( '|', $excluded_page_urls );
return ! ! preg_match( "#{$uri_pattern}#i", $request_uri );
}
private function get_wp_location() {
$blog_is_frontpage = ( 'posts' === get_option( 'show_on_front' ) && ! is_multisite() ) ? true : false;
if ( is_front_page() ) {
return 'frontpage';
} elseif ( is_home() && ! $blog_is_frontpage ) {
return 'home';
} elseif ( is_page() ) {
return 'page';
} elseif ( is_single() ) {
return 'single';
} elseif ( is_category() ) {
return 'category';
} elseif ( is_tag() ) {
return 'tag';
} elseif ( is_archive() ) {
return 'archive';
} else {
return get_post_type();
}
}
private function get_included_locations() {
$include = $this->array_utils->get_array_value( $this->get_lazy_load_options(), 'include' );
return $this->array_utils->ensure_array( $include );
}
public function is_excluded_wp_location() {
$included_locations = $this->get_included_locations();
if ( empty( $included_locations ) ) {
// If not settings are set, probably, all are disabled.
return true;
}
// Check if location is disabled.
$wp_location = $this->get_wp_location();
return isset( $included_locations[ $wp_location ] ) && empty( $included_locations[ $wp_location ] );
}
public function is_image_extension_supported( $ext, $src ) {
if ( empty( $ext ) ) {
return $this->is_image_without_extension_supported( $src );
}
$ext = strtolower( $ext );
if ( ! in_array( $ext, array( 'jpg', 'jpeg', 'gif', 'png', 'svg', 'webp' ), true ) ) {
return false;
}
return ! $this->is_format_excluded( $ext );
}
private function is_image_without_extension_supported( $src ) {
$pattern = '#(gravatar.com|googleusercontent.com)#is';
$pattern = apply_filters( 'wp_smush_without_extension_supported_regex', $pattern );
return preg_match( $pattern, $src );
}
public function is_format_excluded( $needle ) {
$supported_formats = $this->array_utils->get_array_value( $this->get_lazy_load_options(), 'format' );
$supported_formats = $this->array_utils->ensure_array( $supported_formats );
// Ensure 'jpeg' and 'jpg' are treated as the same format.
if ( isset( $supported_formats['jpeg'] ) ) {
$supported_formats['jpg'] = $supported_formats['jpeg'];
}
return in_array( false, $supported_formats, true ) && isset( $supported_formats[ $needle ] ) && ! $supported_formats[ $needle ];
}
public function should_lazy_load_embed_video() {
if ( $this->is_format_excluded( 'iframe' ) ) {
return false;
}
$supported_formats = $this->array_utils->get_array_value( $this->get_lazy_load_options(), 'format' );
return ! empty( $supported_formats['embed_video'] );
}
}
PK cD\j
4 core/lazy-load/video-embed/class-video-thumbnail.phpnu [ width;
}
private function set_width( $width ) {
$this->width = (int) $width;
}
public function get_height() {
return $this->height;
}
private function set_height( $height ) {
$this->height = (int) $height;
}
public function get_next_gen_url() {
return $this->next_gen_url;
}
private function set_next_gen_url( $next_gen_url ) {
$this->next_gen_url = $next_gen_url;
}
private function get_cdn_url() {
if ( is_null( $this->cdn_url ) ) {
$this->cdn_url = $this->prepare_cdn_url();
}
return $this->cdn_url;
}
private function prepare_cdn_url() {
$cdn_helper = CDN_Helper::get_instance();
$thumbnail_url = $this->get_fallback_url();
if (
! $cdn_helper->is_cdn_active()
|| ! $cdn_helper->is_supported_url( $thumbnail_url )
|| $cdn_helper->skip_image_url( $thumbnail_url )
) {
return false;
}
return $cdn_helper->generate_cdn_url( $thumbnail_url );
}
private function set_cdn_url( $cdn_url ) {
$this->cdn_url = $cdn_url;
}
public function get_fallback_url() {
return $this->fallback_url;
}
private function set_fallback_url( $fallback_url ) {
$this->fallback_url = $fallback_url;
}
public function get_url() {
if ( $this->has_next_gen_url() ) {
return $this->get_next_gen_url();
}
$cdn_url = $this->get_cdn_url();
if ( $cdn_url ) {
return $cdn_url;
}
return $this->get_fallback_url();
}
public function has_next_gen_url() {
if ( ! $this->should_use_next_gen_format() ) {
return false;
}
return ! empty( $this->next_gen_url );
}
private function should_use_next_gen_format() {
return Next_Gen_Manager::get_instance()->is_active()
|| Settings::get_instance()->is_cdn_next_gen_conversion_active();
}
public function get_aspect_ratio() {
if ( ! $this->aspect_ratio ) {
$this->aspect_ratio = $this->width && $this->height ? "{$this->width}/{$this->height}" : 'auto';
}
return $this->aspect_ratio;
}
public function to_array() {
return array(
'width' => $this->get_width(),
'height' => $this->get_height(),
'next_gen_url' => $this->get_next_gen_url(),
'cdn_url' => $this->get_cdn_url(),
'fallback_url' => $this->get_fallback_url(),
);
}
public function from_array( $array_values ) {
$this->set_width( $this->get_array_value( $array_values, 'width' ) );
$this->set_height( $this->get_array_value( $array_values, 'height' ) );
$this->set_next_gen_url( $this->get_array_value( $array_values, 'next_gen_url' ) );
$this->set_cdn_url( $this->get_array_value( $array_values, 'cdn_url' ) );
$this->set_fallback_url( $this->get_array_value( $array_values, 'fallback_url' ) );
}
private function get_array_value( $array_values, $key ) {
return isset( $array_values[ $key ] ) ? $array_values[ $key ] : null;
}
}
PK eD\kq ? core/lazy-load/video-embed/class-video-thumbnail-controller.phpnu [ video_helper = Video_Embed_Helper::get_instance();
$this->lazy_helper = Lazy_Load_Helper::get_instance();
$this->settings = Settings::get_instance();
$this->server_utils = new Server_Utils();
$this->url_utils = new Url_Utils();
$this->register_action( 'wp_ajax_smush_video_thumbnail', array( $this, 'redirect_to_original_video_thumbnail' ) );
$this->register_action( 'wp_ajax_nopriv_smush_video_thumbnail', array( $this, 'redirect_to_original_video_thumbnail' ) );
}
public function should_run() {
return parent::should_run()
&& $this->settings->is_lazyload_active()
&& $this->lazy_helper->should_lazy_load_embed_video();
}
public function redirect_to_original_video_thumbnail() {
$thumbnail_url = $this->get_video_thumbnail_url_from_request( $_GET );
if ( is_wp_error( $thumbnail_url ) ) {
status_header( 404 );
exit;
}
$expires = 31536000;
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expires ) . ' GMT' );
header( "Cache-Control: public, max-age={$expires}, immutable" );
header( "Location: {$thumbnail_url}", true, 301 );
exit();
}
public function get_video_thumbnail_url_from_request( $request ) {
$embed_url = empty( $request['url'] ) ? '' : html_entity_decode( rawurldecode( $request['url'] ) );
$width = empty( $request['video_width'] ) ? '' : (int) $request['video_width'];
$height = empty( $request['video_height'] ) ? '' : (int) $request['video_height'];
if ( ! filter_var( $embed_url, FILTER_VALIDATE_URL ) || ( ! $width && ! $height ) ) {
return new WP_Error( 'invalid_params', __( 'Invalid video URL or dimensions.', 'wp-smushit' ) );
}
$embed = $this->video_helper->create_embed_object( $embed_url );
$video_thumbnail = $embed->fetch_video_thumbnail( $width, $height );
if ( ! $video_thumbnail ) {
return new WP_Error( 'not_found', __( 'Video thumbnail not found.', 'wp-smushit' ) );
}
$thumbnail_url = '';
$nextgen_url = $video_thumbnail->get_next_gen_url();
if ( $nextgen_url ) {
$nextgen_extension = $this->url_utils->get_extension( $nextgen_url );
$nextgen_supported = $this->server_utils->browser_supports_nextgen_format( $nextgen_extension );
if ( $nextgen_supported ) {
$thumbnail_url = $nextgen_url;
}
}
if ( empty( $thumbnail_url ) ) {
$thumbnail_url = $video_thumbnail->get_fallback_url();
}
return $thumbnail_url ? $thumbnail_url : new WP_Error( 'not_found', __( 'Thumbnail URL not found.', 'wp-smushit' ) );
}
}
PK hD\ڔ 7 core/lazy-load/video-embed/class-video-embed-helper.phpnu [ can_lazy_load() ) {
return $provider_instance;
}
}
return null;
}
private function get_embed_provider_classes() {
$embed_provider_classes = array(
Youtube_Embed::class,
Vimeo_Embed::class,
);
return apply_filters( 'wp_smush_lazy_load_embed_provider_classes', $embed_provider_classes );
}
}
PK jD\3 0 core/lazy-load/video-embed/class-vimeo-embed.phpnu [ [\d]+)#i';
private static $vimeo_oembed_endpoint = 'https://vimeo.com/api/oembed.json';
/**
*
* @var string
*/
private $embed_url;
/**
* Video id.
*
* @var string
*/
private $video_id;
/**
* Thumbnail sizes.
*
* @var array
*/
private $thumb_sizes;
private Video_Thumbnail_Cache $video_thumbnail_cache;
public function __construct( $embed_url ) {
$this->embed_url = $embed_url;
$this->video_thumbnail_cache = Video_Thumbnail_Cache::get_instance();
}
public function get_name() {
return self::$name;
}
public function get_embed_url() {
return $this->embed_url;
}
public function can_lazy_load() {
return $this->is_valid_embed_url();
}
private function is_valid_embed_url() {
return ! empty( $this->get_video_id() );
}
public function get_video_id() {
if ( ! $this->video_id ) {
$this->video_id = $this->prepare_video_id();
}
return $this->video_id;
}
private function prepare_video_id() {
$video_id_regex = apply_filters( 'wp_smush_lazy_load_vimeo_id_regex', self::$video_id_regex );
if ( preg_match( $video_id_regex, $this->embed_url, $matches ) ) {
return $matches['video_id'] ?? '';
}
return '';
}
public function fetch_video_thumbnail( $video_width, $video_height ) {
$video_id = $this->get_video_id();
if ( ! $video_id ) {
return null;
}
list( $requested_thumb_width, $requested_thumb_height ) = $this->determine_best_thumbnail_size( $video_width, $video_height );
$cached = $this->video_thumbnail_cache->get( $video_id, self::$name, $requested_thumb_width, $requested_thumb_height );
if ( $cached ) {
return $cached;
}
$thumbnail_info = $this->fetch_thumbnail_info( $requested_thumb_width, $requested_thumb_height );
if ( empty( $thumbnail_info ) ) {
return null;
}
list(
$thumb_url,
$actual_thumb_width,
$actual_thumb_height ) = $thumbnail_info;
$video_thumbnail = new Video_Thumbnail();
$video_thumbnail->from_array(
array(
'fallback_url' => $thumb_url,
'width' => $actual_thumb_width,
'height' => $actual_thumb_height,
)
);
$this->video_thumbnail_cache->add( $video_id, self::$name, $requested_thumb_width, $requested_thumb_height, $video_thumbnail );
return $video_thumbnail;
}
/**
* @param $video_width
* @param $video_height
*
* @return array|int[]|mixed
*/
private function determine_best_non_retina_thumbnail_size( $video_width, $video_height ) {
if ( $video_width > 0 && $video_height > 0 ) {
return array( $video_width, $video_height );
}
$thumb_sizes = $this->get_thumbnail_sizes();
$thumb_size = array();
foreach ( $thumb_sizes as $size ) {
list( $thumb_width, $thumb_height ) = $size;
if ( $this->is_smaller_than_video( $thumb_width, $thumb_height, $video_width, $video_height ) ) {
continue;
}
$thumb_size = $size;
break;
}
if ( empty( $thumb_size ) ) {
$thumb_size = array( 640, 360 );// Standard Definition.
}
return $thumb_size;
}
private function determine_best_thumbnail_size( $video_width, $video_height ) {
list( $non_retina_width, $non_retina_height ) = $this->determine_best_non_retina_thumbnail_size( $video_width, $video_height );
$retina_ratio = $this->get_retina_ratio( $non_retina_width, $non_retina_height );
$retina_width = $non_retina_width * $retina_ratio;
$retina_height = $non_retina_height * $retina_ratio;
return array( (int) ceil( $retina_width ), (int) ceil( $retina_height ) );
}
private function is_smaller_than_video( $thumb_width, $thumb_height, $video_width, $video_height ) {
return $thumb_width < $video_width || ( empty( $video_width ) && $thumb_height < $video_height );
}
private function get_thumbnail_sizes() {
if ( ! $this->thumb_sizes ) {
$this->thumb_sizes = $this->prepare_thumbnail_sizes();
}
return $this->thumb_sizes;
}
private function prepare_thumbnail_sizes() {
$thumb_sizes = array(
array( 295, 166 ), // Small 16:9.
array( 640, 360 ), // Standard Definition SD) 16:9.
array( 640, 480 ), // Standard Definition SD) 4:3.
array( 1280, 720 ), // High Definition HD) 16:9.
array( 1280, 960 ), // High Definition HD) 4:3.
array( 1920, 1080 ), // Full High Definition Full HD.
);
$thumb_sizes = apply_filters( 'wp_smush_lazy_load_vimeo_thumbnail_sizes', $thumb_sizes, $this->get_video_id(), $this->embed_url );
// Sort by width.
usort(
$thumb_sizes,
function ( $a, $b ) {
if ( isset( $a[1], $b[1] ) ) {
return $a[1] - $b[1];
} else {
return 0;
}
}
);
return $thumb_sizes;
}
private function fetch_thumbnail_info( $width, $height ) {
$embed_api_url = $this->get_oembed_api_url( $width, $height );
$timeout = apply_filters( 'wp_smush_lazy_load_oembed_api_timeout', 15, $this->get_video_id(), $this->embed_url );
$response = wp_remote_get(
$embed_api_url,
array(
'timeout' => $timeout,
)
);
$video_info = wp_remote_retrieve_body( $response );
$thumb_info = array();
if ( ! empty( $video_info ) ) {
$video_info = json_decode( $video_info, true );
if (
! empty( $video_info['thumbnail_url'] ) &&
! empty( $video_info['thumbnail_width'] ) &&
! empty( $video_info['thumbnail_height'] )
) {
$thumb_info = array(
$video_info['thumbnail_url'],
$video_info['thumbnail_width'],
$video_info['thumbnail_height'],
);
}
} elseif ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
$error_code = $response->get_error_code();
$error_code = $error_code ? $error_code : 'unknown_error';
$this->logger()->error(
sprintf(
'Vimeo: Error fetching thumbnail info: %s (%s) | oEmbed API URL: %s',
$error_message,
$error_code,
$embed_api_url
)
);
}
return $thumb_info;
}
private function get_retina_ratio( $width, $height ) {
$retina_ratio = (int) apply_filters( 'wp_smush_lazy_load_vimeo_retina_ratio', null, $width, $height );
if ( $retina_ratio > 0 ) {
return $retina_ratio;
}
if ( $this->thumbnail_size_exists( $width, $height ) ) {
return 1;
}
// Increase the video dimensions because video thumbnails are often smaller than the original video dimensions,
// typically by a factor of 1.25 to 1.69.
$retina_ratio = 1.5;
return $retina_ratio;
}
private function thumbnail_size_exists( $width, $height ) {
$common_thumbnail_sizes = $this->get_thumbnail_sizes();
foreach ( $common_thumbnail_sizes as $size ) {
if ( isset( $size[0], $size[1] ) && $size[0] === $width && $size[1] === $height ) {
return true;
}
}
return false;
}
private function get_oembed_api_url( $width, $height ) {
$oembed_api_url = add_query_arg(
array(
'width' => $width,
'height' => $height,
'url' => urlencode( $this->embed_url ),
),
self::$vimeo_oembed_endpoint
);
return apply_filters( 'wp_smush_lazyload_vimeo_oembed_api_url', $oembed_api_url, $this->get_video_id(), $this->embed_url );
}
private function logger() {
// Logger is a dynamic object, we will switch to another log file when point to another module,
// so keep it as a function instead of a fixed variable to log into correct log file.
return Helper::logger()->lazy();
}
public function get_cached_video_thumbnail( $video_width, $video_height ) {
list( $thumb_width, $thumb_height ) = $this->determine_best_thumbnail_size( $video_width, $video_height );
return $this->video_thumbnail_cache->get( $this->get_video_id(), self::$name, $thumb_width, $thumb_height );
}
}
PK mD\ 0 core/lazy-load/video-embed/class-video-embed.phpnu [ [a-zA-Z0-9_-]{11})#i';
private static $thumbnail_url_format = 'https://i.ytimg.com/%1$s/%2$s/%3$s.%4$s';
private static $thumbnail_default = 'default';
private static $thumbnail_medium = 'mqdefault';
private static $thumbnail_high = 'hqdefault';
private static $thumbnail_sd = 'sddefault';
private static $thumbnail_max = 'maxresdefault';
/**
* @var Settings
*/
private $settings;
/**
*
* @var string
*/
private $embed_url;
/**
* Video id.
*
* @var string
*/
private $video_id;
/**
* @var Video_Embed_Helper
*/
protected $video_helper;
/**
* Thumbnail sizes.
*
* @var array
*/
private $thumb_sizes;
private Video_Thumbnail_Cache $video_thumbnail_cache;
private Url_Utils $url_utils;
public function __construct( $embed_url ) {
$this->embed_url = $embed_url;
$this->settings = Settings::get_instance();
$this->video_helper = Video_Embed_Helper::get_instance();
$this->video_thumbnail_cache = Video_Thumbnail_Cache::get_instance();
$this->url_utils = new Url_Utils();
}
public function get_name() {
return self::$name;
}
public function get_embed_url() {
return $this->embed_url;
}
public function can_lazy_load() {
return $this->is_valid_embed_url();
}
private function is_valid_embed_url() {
$video_id = $this->get_video_id();
return ! empty( $video_id );
}
public function get_video_id() {
if ( ! $this->video_id ) {
$this->video_id = $this->prepare_video_id();
}
return $this->video_id;
}
private function prepare_video_id() {
$video_id_regex = apply_filters( 'wp_smush_lazy_load_youtube_id_regex', self::$video_id_regex );
if ( ! preg_match( $video_id_regex, $this->embed_url, $matches ) ) {
return null;
}
$video_id = $matches['video_id'] ?? '';
// TODO: check if we need to support data attribute for youtube playlist.
$video_id = apply_filters( 'wp_smush_lazy_load_youtube_video_id', $video_id, $this->embed_url );
$is_playlist = 'videoseries' === $video_id;
return $is_playlist ? null : $video_id;
}
public function fetch_video_thumbnail( $video_width, $video_height ) {
$video_id = $this->get_video_id();
if ( ! $video_id ) {
return null;
}
list( $thumb_size_name, $thumb_width, $thumb_height ) = $this->determine_best_thumbnail_size( $video_width, $video_height );
$cached = $this->video_thumbnail_cache->get( $video_id, self::$name, $thumb_width, $thumb_height );
if ( $cached ) {
return $cached;
}
$next_gen_thumbnail_url = $this->get_thumbnail_url( $thumb_size_name, true );
$fallback_thumbnail_url = $this->get_thumbnail_url( $thumb_size_name, false );
$video_ratio = $video_width > 0 && $video_height > 0 ? "{$video_width}/{$video_height}" : "{$thumb_width}/{$thumb_height}";
$video_thumbnail = new Video_Thumbnail();
$video_thumbnail->from_array(
array(
'width' => $thumb_width,
'height' => $thumb_height,
'next_gen_url' => $next_gen_thumbnail_url,
'fallback_url' => $fallback_thumbnail_url,
'aspect_ratio' => $video_ratio,
)
);
$this->video_thumbnail_cache->add( $video_id, self::$name, $thumb_width, $thumb_height, $video_thumbnail );
return $video_thumbnail;
}
private function determine_best_thumbnail_size( $video_width, $video_height ) {
$thumb_sizes = $this->get_thumbnail_sizes();
$thumb_size = array();
$larger_size = null;
foreach ( $thumb_sizes as $size ) {
list( , $thumb_width, $thumb_height ) = $size;
if ( $this->is_smaller_than_video( $thumb_width, $thumb_height, $video_width, $video_height ) ) {
continue;
}
if ( empty( $video_width ) || empty( $video_height ) ) {
return $size;
}
if ( $this->is_aspect_ratio_match( $video_width, $video_height, $thumb_width, $thumb_height ) ) {
return $size;
}
if ( empty( $larger_size ) ) {
$larger_size = $size;
}
}
if ( empty( $thumb_size ) ) {
$thumb_size = $larger_size ?? end( $thumb_sizes );
}
if (
empty( $thumb_size ) ||
! isset( $thumb_size[0] ) ||
! $this->is_thumbnail_available( $thumb_size[0] )
) {
// Try standard size which should exist for all video
// due to Youtube will automatically generate three thumbnail sizes (Default, Medium, High-Resolution).
$thumb_size = array( self::$thumbnail_high, 480, 360 );
}
return $thumb_size;
}
private function is_smaller_than_video( $thumb_width, $thumb_height, $video_width, $video_height ) {
return $thumb_width < $video_width || ( empty( $video_width ) && $thumb_height < $video_height );
}
private function is_aspect_ratio_match( $video_width, $video_height, $thumb_width, $thumb_height ) {
return wp_fuzzy_number_match( $video_width / $thumb_width, $video_height / $thumb_height, 0.1 );
}
private function is_thumbnail_available( $thumb_size_name ) {
$standard_sizes = array(
self::$thumbnail_default,
self::$thumbnail_medium,
self::$thumbnail_high,
);
if ( in_array( $thumb_size_name, $standard_sizes, true ) ) {
return true;
}
$thumbnail_url = $this->get_thumbnail_url( $thumb_size_name );
return $this->url_utils->url_has_200_response( $thumbnail_url );
}
private function get_thumbnail_sizes() {
if ( ! $this->thumb_sizes ) {
$this->thumb_sizes = $this->prepare_thumbnail_sizes();
}
return $this->thumb_sizes;
}
private function prepare_thumbnail_sizes() {
// @see https://gist.github.com/a1ip/be4514c1fd392a8c13b05e082c4da363.
$thumb_sizes = array(
array( self::$thumbnail_default, 120, 90 ), // - Default 4:3.
array( self::$thumbnail_medium, 320, 180 ), // - Medium Quality 16:9.
array( self::$thumbnail_high, 480, 360 ), // - High Quality 4:3.
array( self::$thumbnail_sd, 640, 480 ), // - Standard Definition 4:3.
array( self::$thumbnail_max, 1280, 720 ), // - Maximum Resolution 16:9.
);
$thumb_sizes = apply_filters( 'wp_smush_lazy_load_youtube_thumbnail_sizes', $thumb_sizes, $this->get_video_id(), $this->embed_url );
// Sort by width.
usort(
$thumb_sizes,
function ( $a, $b ) {
if ( isset( $a[1], $b[1] ) ) {
return $a[1] - $b[1];
} else {
return 0;
}
}
);
return $thumb_sizes;
}
private function get_thumbnail_url( $thumb_size_name, $use_next_gen_format = false ) {
if ( $use_next_gen_format ) {
$extension = 'webp';
$extension_uri = 'vi_webp';
} else {
$extension = 'jpg';
$extension_uri = 'vi';
}
return sprintf( self::$thumbnail_url_format, $extension_uri, $this->get_video_id(), $thumb_size_name, $extension );
}
public function get_cached_video_thumbnail( $video_width, $video_height ) {
list( , $thumb_width, $thumb_height ) = $this->determine_best_thumbnail_size( $video_width, $video_height );
return $this->video_thumbnail_cache->get( $this->get_video_id(), $this->get_name(), $thumb_width, $thumb_height );
}
/**
* Get thumbnail_url_format.
*
* @return string
*/
public static function get_thumbnail_url_format() {
return self::$thumbnail_url_format;
}
}
PK rD\݄ : core/lazy-load/video-embed/class-video-thumbnail-cache.phpnu [ get_transient_key( $video_id, $provider, $thumb_width, $thumb_height );
set_transient( $transient_key, $video_thumbnail->to_array() );
}
public function get( $video_id, $provider, $thumb_width, $thumb_height ) {
$transient_key = $this->get_transient_key( $video_id, $provider, $thumb_width, $thumb_height );
$video_thumbnail_data = get_transient( $transient_key );
if ( ! $video_thumbnail_data ) {
return null;
}
$video_thumbnail = new Video_Thumbnail();
$video_thumbnail->from_array( $video_thumbnail_data );
return $video_thumbnail;
}
private function get_transient_key( $video_id, $provider, $thumb_width, $thumb_height ) {
return sprintf( 'wp-smush-video-thumbnail-%s-%s-%d-%d', $provider, $video_id, (int) $thumb_width, (int) $thumb_height );
}
}
PK tD\lJ J - core/lazy-load/class-lazy-load-controller.phpnu [ settings = Settings::get_instance();
$this->helper = Lazy_Load_Helper::get_instance();
$this->register_action( 'wp_smush_content_transforms', array(
$this,
'register_lazy_load_transform',
), self::$lazy_load_transform_priority );
// Only run on front end and if lazy loading is enabled.
if ( is_admin() || ! $this->settings->is_module_active( 'lazy_load' ) ) {
return;
}
$this->options = $this->settings->get_setting( 'wp-smush-lazy_load' );
// Enabled without settings? Don't think so... Exit.
if ( ! $this->options ) {
return;
}
// Disable WordPress native lazy load.
$this->register_filter( 'wp_lazy_loading_enabled', array( $this, 'should_enable_wordpress_native_lazyload' ) );
$this->register_filter( 'wp_smush_transformed_page_markup', array( $this, 'add_has_smush_lazyload_video_class' ) );
// Load js file that is required in public facing pages.
$this->register_action( 'wp_head', array( $this, 'add_inline_styles' ) );
$this->register_action( 'wp_head', array( $this, 'add_early_inline_styles' ), 5 );
$this->register_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ), 99 );
if ( defined( 'WP_SMUSH_ASYNC_LAZY' ) && WP_SMUSH_ASYNC_LAZY ) {
$this->register_filter( 'script_loader_tag', array( $this, 'async_load' ), 10, 2 );
}
// Allow lazy load attributes in img tag.
$this->register_filter( 'wp_kses_allowed_html', array( $this, 'add_lazy_load_attributes' ) );
// Filter images.
if ( ! isset( $this->options['output']['content'] ) || ! $this->options['output']['content'] ) {
$this->register_filter( 'the_content', array( $this, 'exclude_from_lazy_loading' ), 100 );
}
if ( ! isset( $this->options['output']['thumbnails'] ) || ! $this->options['output']['thumbnails'] ) {
$this->register_filter( 'post_thumbnail_html', array( $this, 'exclude_from_lazy_loading' ), 100 );
}
if ( ! isset( $this->options['output']['gravatars'] ) || ! $this->options['output']['gravatars'] ) {
$this->register_filter( 'get_avatar', array( $this, 'exclude_from_lazy_loading' ), 100 );
}
if ( ! isset( $this->options['output']['widgets'] ) || ! $this->options['output']['widgets'] ) {
$this->register_action( 'dynamic_sidebar_before', array( $this, 'filter_sidebar_content_start' ), 0 );
$this->register_action( 'dynamic_sidebar_after', array( $this, 'filter_sidebar_content_end' ), 1000 );
}
}
public function add_early_inline_styles() {
if ( $this->helper->should_skip_lazyload() ) {
return;
}
?>
helper->should_skip_lazyload() ) {
return;
}
// Lazy load embed video styles.
$this->add_inline_embed_video_css();
// Fix for poorly coded themes that do not remove the no-js in the HTML class.
?>
options['animation']['selected'] ) || 'none' === $this->options['animation']['selected'] ) {
return;
}
// Spinner.
if ( 'spinner' === $this->options['animation']['selected'] ) {
$loader = WP_SMUSH_URL . 'app/assets/images/smush-lazyloader-' . $this->options['animation']['spinner']['selected'] . '.gif';
if ( isset( $this->options['animation']['spinner']['selected'] ) && 5 < (int) $this->options['animation']['spinner']['selected'] ) {
$loader = wp_get_attachment_image_src( $this->options['animation']['spinner']['selected'], 'full' );
$loader = $loader[0];
}
$background = 'rgba(255, 255, 255, 0)';
} else {
// Placeholder.
$loader = WP_SMUSH_URL . 'app/assets/images/smush-placeholder.png';
$background = '#FAFAFA';
if ( isset( $this->options['animation']['placeholder']['selected'] ) && 2 === (int) $this->options['animation']['placeholder']['selected'] ) {
$background = '#333333';
}
if ( isset( $this->options['animation']['placeholder']['selected'] ) && 2 < (int) $this->options['animation']['placeholder']['selected'] ) {
$loader = wp_get_attachment_image_src( (int) $this->options['animation']['placeholder']['selected'], 'full' );
// Can't find a loader on multisite? Try main site.
if ( ! $loader && is_multisite() ) {
switch_to_blog( 1 );
$loader = wp_get_attachment_image_src( (int) $this->options['animation']['placeholder']['selected'], 'full' );
restore_current_blog();
}
$loader = $loader[0];
}
if ( isset( $this->options['animation']['placeholder']['color'] ) ) {
$background = $this->options['animation']['placeholder']['color'];
}
}
// Fade in.
$fadein = isset( $this->options['animation']['fadein']['duration'] ) ? $this->options['animation']['fadein']['duration'] : 0;
$delay = isset( $this->options['animation']['fadein']['delay'] ) ? $this->options['animation']['fadein']['delay'] : 0;
?>
helper->should_lazy_load_embed_video() ) {
return;
}
?>
helper->should_skip_lazyload()
|| ( $this->helper->is_native_lazy_loading_enabled() && ! $this->helper->should_lazy_load_embed_video() )
) {
return;
}
$script = WP_SMUSH_URL . 'app/assets/js/smush-lazy-load.min.js';
$in_footer = isset( $this->options['footer'] ) ? $this->options['footer'] : true;
wp_enqueue_script(
'smush-lazy-load',
$script,
array(),
WP_SMUSH_VERSION,
$in_footer
);
$lazy_load_script_options = apply_filters(
'smush_lazy_load_script_options',
array(
'autoResizingEnabled' => $this->settings->is_auto_resizing_active(),
'autoResizeOptions' => array(
'precision' => 5, //5px.
'skipAutoWidth' => true, // Whether to skip the image has 'auto' width.
),
)
);
wp_add_inline_script(
'smush-lazy-load',
'var smushLazyLoadOptions = ' . wp_json_encode( $lazy_load_script_options ) . ';',
'before'
);
$this->add_masonry_support();
if ( defined( 'WP_SMUSH_LAZY_LOAD_AVADA' ) && WP_SMUSH_LAZY_LOAD_AVADA ) {
$this->add_avada_support();
}
$this->add_divi_support();
$this->add_soliloquy_support();
}
/**
* Async load the lazy load scripts.
*
* @param string $tag The
scanner->init_scan();
do_action( 'wp_smush_directory_smush_start' );
wp_send_json_success();
}
/**
* Directory Smush: Smush step.
*
* @since 2.8.1
*/
public function directory_smush_check_step() {
check_ajax_referer( 'wp-smush-ajax' );
// Check for permission.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
}
$urls = $this->get_scanned_images();
$current_step = isset( $_POST['step'] ) ? absint( $_POST['step'] ) : 0;
$this->scanner->update_current_step( $current_step );
if ( isset( $urls[ $current_step ] ) ) {
$this->optimise_image( (int) $urls[ $current_step ]['id'] );
}
wp_send_json_success();
}
/**
* Directory Smush: Finish smush.
*
* @since 2.8.1
*/
public function directory_smush_finish() {
check_ajax_referer( 'wp-smush-ajax' );
// Check for permission.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
}
$items = isset( $_POST['items'] ) ? absint( $_POST['items'] ) : 0;
$failed = isset( $_POST['failed'] ) ? absint( $_POST['failed'] ) : 0;
$skipped = isset( $_POST['skipped'] ) ? absint( $_POST['skipped'] ) : 0;
// If any images failed to smush, store count.
if ( $failed > 0 ) {
set_transient( 'wp-smush-dir-scan-failed-items', $failed, 60 * 5 ); // 5 minutes max.
}
if ( $skipped > 0 ) {
set_transient( 'wp-smush-dir-scan-skipped-items', $skipped, 60 * 5 ); // 5 minutes max.
}
// Store optimized items count.
set_transient( 'wp-smush-show-dir-scan-notice', $items, 60 * 5 ); // 5 minutes max.
$this->scanner->reset_scan();
wp_send_json_success();
}
/**
* Directory Smush: Cancel smush.
*
* @since 2.8.1
*/
public function directory_smush_cancel() {
check_ajax_referer( 'wp-smush-ajax' );
// Check for permission.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
}
$this->scanner->reset_scan();
wp_send_json_success();
}
/**
* Handles the ajax request for image optimisation in a folder
*
* @param int $id Image ID.
*/
private function optimise_image( $id ) {
global $wpdb;
$error_msg = '';
// No image ID.
if ( $id < 1 ) {
$error_msg = esc_html__( 'Incorrect image id', 'wp-smushit' );
wp_send_json_error( $error_msg );
}
// Free version bulk smush, check the transient counter value.
$should_continue = Core::should_continue_smush( false, 'dir_sent_count' );
// Send a error for the limit.
if ( ! $should_continue ) {
wp_send_json_error(
array(
'error' => 'dir_smush_limit_exceeded',
'continue' => false,
)
);
}
$scanned_images = $this->get_unsmushed_images();
$image = $this->get_image( $id, '', $scanned_images );
if ( empty( $image ) ) {
wp_send_json_success( array( 'skipped' => true ) );
}
$path = $image['path'];
if ( false !== stripos( $path, 'phar://' ) ) {
wp_send_json_error(
array(
'error' => esc_html__( 'Potential Phar PHP Object Injection detected', 'wp-smushit' ),
'image' => array(
'id' => $id,
),
)
);
}
// We have the image path, optimise.
$results = WP_Smush::get_instance()->core()->mod->smush->do_smushit( $path );
if ( is_wp_error( $results ) ) {
/**
* Smush results.
*
* @var WP_Error $results
*/
$error_msg = $results->get_error_message();
} elseif ( empty( $results['data'] ) ) {
// If there are no stats.
$error_msg = esc_html__( "Image couldn't be optimized", 'wp-smushit' );
}
if ( ! empty( $error_msg ) ) {
// Store the error in DB. All good, Update the stats.
$wpdb->query(
$wpdb->prepare(
"UPDATE {$wpdb->base_prefix}smush_dir_images SET error=%s WHERE id=%d LIMIT 1",
$error_msg,
$id
)
); // Db call ok; no-cache ok.
wp_send_json_error(
array(
'error' => $error_msg,
'image' => array(
'id' => $id,
),
)
);
}
if ( ! $this->settings ) {
$this->settings = Settings::get_instance();
}
// All good, Update the stats.
$wpdb->query(
$wpdb->prepare(
"UPDATE {$wpdb->base_prefix}smush_dir_images SET error=NULL, image_size=%d, file_time=%d, lossy=%d, meta=%d WHERE id=%d LIMIT 1",
$results['data']->after_size,
filectime( $path ), // Get file time.
$this->settings->get( 'lossy' ),
$this->settings->get( 'strip_exif' ),
$id
)
); // Db call ok; no-cache ok.
// Update bulk limit transient.
Core::update_smush_count( 'dir_sent_count' );
}
/**
* Create the Smush image table to store the paths of scanned images, and stats
*/
public function create_table() {
// If table is already created, returns.
if ( self::table_exist() ) {
return;
}
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
/**
* Table: wp_smush_dir_images
* Columns:
* id -> Auto Increment ID
* path -> Absolute path to the image file
* resize -> Whether the image was resized or not
* lossy -> Whether the image was super-smushed/lossy or not
* image_size -> Current image size post optimisation
* orig_size -> Original image size before optimisation
* file_time -> Unix time for the file creation, to match it against the current creation time,
* in order to confirm if it is optimised or not
* last_scan -> Timestamp, Get images form last scan by latest timestamp
* are from latest scan only and not the whole list from db
* meta -> For any future use
*/
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->base_prefix}smush_dir_images (
id mediumint(9) UNSIGNED NOT NULL AUTO_INCREMENT,
path text NOT NULL,
path_hash CHAR(32),
resize varchar(55),
lossy varchar(55),
error varchar(55) DEFAULT NULL,
image_size int(10) unsigned,
orig_size int(10) unsigned,
file_time int(10) unsigned,
last_scan timestamp DEFAULT '0000-00-00 00:00:00',
meta text,
PRIMARY KEY (id),
UNIQUE KEY path_hash (path_hash),
KEY image_size (image_size)
) $charset_collate;";
// Include the upgrade library to initialize a table.
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
/**
* PHP 8.1 trigger an error when calling query "DESCRIBE {$table};" if the table doesn't exists.
* Since we only create table when it doesn't exists, so don't need to use dbDelta for this case.
*/
// Hide errors.
$wpdb->hide_errors();
// Create table.
$wpdb->query( $sql );
// Set flag.
self::$table_exist = self::table_exist( true );
}
/**
* Get the image ids and path for last scanned images
*
* @return array Array of last scanned images containing image id and path
*/
public function get_scanned_images() {
global $wpdb;
$results = $wpdb->get_results( "SELECT id, path, orig_size FROM {$wpdb->base_prefix}smush_dir_images WHERE last_scan = (SELECT MAX(last_scan) FROM {$wpdb->base_prefix}smush_dir_images ) GROUP BY id ORDER BY id", ARRAY_A ); // Db call ok; no-cache ok.
// Return image ids.
if ( is_wp_error( $results ) ) {
Helper::logger()->dir()->error( sprintf( 'Query error: %s', $results->get_error_message() ) );
$results = array();
}
return $results;
}
/**
* Get only images that need compressing.
*
* @since 3.6.1
*
* @return array Array of images that require compression.
*/
public function get_unsmushed_images() {
global $wpdb;
$condition = 'image_size IS NULL';
$lossy_level = $this->settings->get_lossy_level_setting();
if ( $lossy_level > 0 ) {
$condition .= ' OR lossy IS NULL OR lossy < ' . intval( $lossy_level );
}
if ( $this->settings->get( 'strip_exif' ) ) {
$condition .= ' OR meta <> 1';
}
$results = $wpdb->get_results( "SELECT id, path, orig_size FROM {$wpdb->base_prefix}smush_dir_images WHERE {$condition} && last_scan = (SELECT MAX(last_scan) FROM {$wpdb->base_prefix}smush_dir_images ) GROUP BY id ORDER BY id", ARRAY_A ); // Db call ok; no-cache ok.
// Return image ids.
if ( is_wp_error( $results ) ) {
Helper::logger()->dir()->error( sprintf( 'Query error: %s', $results->get_error_message() ) );
$results = array();
}
return $results;
}
/**
* Get the paths and errors from last scan.
*
* @since 3.0
*
* @param int $limit Limit the number of results.
*
* @return array Array of last scanned images
*/
public function get_image_errors( $limit = 50 ) {
global $wpdb;
return $wpdb->get_results(
"SELECT id, path, error
FROM {$wpdb->base_prefix}smush_dir_images
WHERE error IS NOT NULL
AND last_scan = ( SELECT MAX(last_scan) FROM {$wpdb->base_prefix}smush_dir_images )
LIMIT $limit",
ARRAY_A
); // Db call ok; no-cache ok.
}
/**
* Return the number of errors.
*
* @since 3.0
*
* @return int
*/
public function get_image_errors_count() {
global $wpdb;
return (int) $wpdb->get_var(
"SELECT COUNT(id)
FROM {$wpdb->base_prefix}smush_dir_images
WHERE error IS NOT NULL AND last_scan = ( SELECT MAX(last_scan) FROM {$wpdb->base_prefix}smush_dir_images )"
); // Db call ok.
}
/**
* Check if the image file is media library file
*
* @param string $file_path File path.
*
* @return bool
*/
private function is_media_library_file( $file_path ) {
$upload_dir = wp_upload_dir();
$upload_path = $upload_dir['path'];
// Get the base path of file.
$base_dir = dirname( $file_path );
if ( $base_dir === $upload_path ) {
return true;
}
return false;
}
/**
* Return a directory/File list
*
* PHP Connector
*/
public function directory_list() {
// Check For permission.
if ( ! Helper::is_user_allowed( 'manage_options' ) || ! is_user_logged_in() ) {
Helper::logger()->dir()->error( 'Unauthorized - Permission access.' );
wp_send_json_error( __( 'Unauthorized', 'wp-smushit' ) );
}
// Verify nonce.
check_ajax_referer( 'smush_get_dir_list', 'list_nonce' );
$dir = filter_input( INPUT_GET, 'dir', FILTER_SANITIZE_SPECIAL_CHARS );
$tree = $this->get_directory_tree( $dir );
if ( ! is_array( $tree ) ) {
Helper::logger()->dir()->error( 'Unauthorized - Directory empty.' );
wp_send_json_error( __( 'Unauthorized', 'wp-smushit' ) );
}
wp_send_json( $tree );
}
/**
* Gets the directory tree data for the given path.
*
* @since 3.8.3
*
* @param string $dir Directory path.
* @return array|bool False on failure. Array with the data on success.
*/
private function get_directory_tree( $dir = null ) {
// Get the root path for a main site or subsite.
$root = realpath( $this->get_root_path() );
// PHP 8.1 strlen doesn't accept null.
if ( ! is_null( $dir ) && strlen( $dir ) >= 1 ) {
$post_dir = realpath( path_join( $root, $dir ) );
} else {
$post_dir = $root;
}
// If the final path doesn't contain the root path, bail out.
if ( ! $root || false === $post_dir || 0 !== strpos( $post_dir, $root ) ) {
return false;
}
$supported_image = array(
'gif',
'jpg',
'jpeg',
'png',
);
if ( file_exists( $post_dir ) && is_dir( $post_dir ) ) {
$files = scandir( $post_dir );
// Exclude hidden files.
if ( ! empty( $files ) ) {
$files = preg_grep( '/^([^.])/', $files );
}
$return_dir = substr( $post_dir, strlen( $root ) );
natcasesort( $files );
if ( count( $files ) !== 0 && ! $this->skip_dir( $post_dir ) ) {
$tree = array();
foreach ( $files as $file ) {
$html_rel = htmlentities( ltrim( path_join( $return_dir, $file ), '/' ) );
$html_name = htmlentities( $file );
$ext = preg_replace( '/^.*\./', '', $file );
$file_path = path_join( $post_dir, $file );
if ( ! file_exists( $file_path ) || '.' === $file || '..' === $file ) {
continue;
}
// Skip unsupported files and files that are already in the media library.
if ( ! is_dir( $file_path ) && ( ! in_array( $ext, $supported_image, true ) || $this->is_media_library_file( $file_path ) ) ) {
continue;
}
$skip_path = $this->skip_dir( $file_path );
$tree[] = array(
'title' => $html_name,
'key' => $html_rel,
'folder' => is_dir( $file_path ),
'lazy' => ! $skip_path,
'checkbox' => true,
'unselectable' => $skip_path, // Skip Uploads folder - Media Files.
);
}
return $tree;
}
}
return array();
}
/**
* Get root path of the installation.
*
* @return string Root path.
*/
public function get_root_path() {
// If main site.
if ( is_super_admin() ) {
/**
* Sometimes content directories may reside outside
* the installation sub-directory. We need to make sure
* we are selecting the root directory, not installation
* directory.
*
* @see https://xnau.com/finding-the-wordpress-root-path-for-an-alternate-directory-structure/
* @see https://app.asana.com/0/14491813218786/487682361460247/f
*/
$content_path = explode( '/', wp_normalize_path( WP_CONTENT_DIR ) );
// Get root path and explode.
$root_path = explode( '/', get_home_path() );
// Find the length of the shortest one.
$end = min( count( $content_path ), count( $root_path ) );
$i = 0;
$common_path = array();
// Add the component if they are the same in both paths.
while ( $content_path[ $i ] === $root_path[ $i ] && $i < $end ) {
$common_path[] = $content_path[ $i ];
$i++;
}
$is_valid_path = count( $common_path ) > 1 || ! empty( $common_path[0] );
if ( $is_valid_path ) {
return implode( '/', $common_path );
}
// If couldn't detect the root folder, it seems there is custom directory structure, e.g Flywheel.
// Let's try to use parent folder of WP_CONTENT_DIR.
return dirname( wp_normalize_path( WP_CONTENT_DIR ) );
}
$up = wp_upload_dir();
return $up['basedir'];
}
/**
* Checks whether the user-submitted paths are among our allowed ones.
*
* @since 3.8.3
*
* @param string $path_to_check User-submitted path.
* @return bool
*/
private function validate_path( $path_to_check ) {
$is_valid = true;
// Verify that every directory in the path is allowed.
while ( $is_valid && dirname( $path_to_check ) !== $path_to_check ) {
$path_contents = $this->get_directory_tree( $path_to_check );
if ( empty( $path_contents ) ) {
return false;
}
$is_valid = false;
foreach ( $path_contents as $tree_data ) {
if ( false !== strpos( $tree_data['key'], $path_to_check ) && ! $tree_data['unselectable'] ) {
$is_valid = true;
break;
}
}
if ( ! $is_valid ) {
$path_to_check = dirname( $path_to_check );
} else {
// Valid path, break out of the loop.
break;
}
}
return $is_valid;
}
/**
* Get the image list in a specified directory path.
*
* @since 2.8.1 Added support for selecting files.
*
* @param string|array $paths Path where to look for images, or selected images.
*
* @throws Exception Never, actually. Supposedly, when an invalid directory was selected.
* @return array
*/
private function get_image_list( $paths = '' ) {
// Error with directory tree.
if ( ! is_array( $paths ) ) {
$this->send_error( __( 'There was a problem getting the selected directories', 'wp-smushit' ) );
}
$count = 0;
$images = array();
$values = array();
$timestamp = gmdate( 'Y-m-d H:i:s' );
// Temporary increase the limit.
wp_raise_memory_limit( 'image' );
// Avoid checking already validated paths.
$validated_dirs = array();
$root_path = $this->get_root_path();
// Iterate over all the selected items (can be either an image or directory).
foreach ( $paths as $relative_path ) {
if ( ! is_string( $relative_path ) ) {
continue;
}
// Make the path absolute.
$path = trim( $root_path . '/' . $relative_path );
// Prevent phar deserialization vulnerability.
if ( stripos( $path, 'phar://' ) !== false ) {
continue;
}
/**
* Path is an image.
*
* TODO: The is_dir() check fails directories with spaces.
*/
if ( ! is_dir( $path ) && ! $this->is_media_library_file( $path ) && ! strpos( $path, '.bak' ) ) {
if ( ! $this->is_image( $path ) ) {
continue;
}
// Image already added. Skip.
if ( in_array( $path, $images, true ) ) {
continue;
}
// Skip if the parent directory isn't allowed.
if ( ! in_array( dirname( $relative_path ), $validated_dirs, true ) ) {
if ( ! $this->validate_path( dirname( $relative_path ) ) ) {
continue;
}
$validated_dirs[] = dirname( $relative_path );
}
$images[] = $path;
$images[] = md5( $path );
$images[] = filesize( $path ); // Get the file size.
$images[] = filectime( $path ); // Get the file modification time.
$images[] = $timestamp;
$values[] = '(%s, %s, %d, %d, %s)';
$count++;
// Store the images in db at an interval of 5k.
if ( $count >= 5000 ) {
$count = 0;
$this->store_images( $values, $images );
$images = $values = array();
}
continue;
}
/**
* Path is a directory.
*/
$base_dir = realpath( rawurldecode( $path ) );
if ( ! $base_dir ) {
$this->send_error( __( 'Unauthorized', 'wp-smushit' ) );
}
// Skip if this directory isn't allowed.
if ( ! in_array( $relative_path, $validated_dirs, true ) ) {
if ( ! $this->validate_path( $relative_path ) ) {
continue;
}
$validated_dirs[] = $relative_path;
}
// Directory Iterator, Exclude . and ..
$filtered_dir = new Helpers\Iterator( new RecursiveDirectoryIterator( $base_dir ) );
// File Iterator.
$iterator = new RecursiveIteratorIterator( $filtered_dir, RecursiveIteratorIterator::CHILD_FIRST );
foreach ( $iterator as $file ) {
// Used in place of Skip Dots, For php 5.2 compatibility.
if ( basename( $file ) === '..' || basename( $file ) === '.' ) {
continue;
}
// Not a file. Skip.
if ( ! $file->isFile() ) {
continue;
}
$file_path = $file->getPathname();
if ( $this->is_image( $file_path ) && ! $this->is_media_library_file( $file_path ) && strpos( $file, '.bak' ) === false ) {
/** To be stored in DB, Part of code inspired from Ewwww Optimiser */
$images[] = $file_path;
$images[] = md5( $file_path );
$images[] = $file->getSize();
$images[] = filectime( $file_path ); // Get the file modification time.
$images[] = $timestamp;
$values[] = '(%s, %s, %d, %d, %s)';
$count++;
}
// Store the images in db at an interval of 5k.
if ( $count >= 5000 ) {
$count = 0;
$this->store_images( $values, $images );
$images = array();
$values = array();
}
}
}
if ( empty( $images ) || 0 === $count ) {
return array();
}
// Update rest of the images.
$this->store_images( $values, $images );
// Get the image ids.
return $this->get_scanned_images();
}
/**
* Write to the database.
*
* @since 2.8.1
*
* @param array $values Values for query build.
* @param array $images Array of images.
*/
private function store_images( $values, $images ) {
global $wpdb;
$query = $this->build_query( $values, $images );
$wpdb->query( $query ); // Db call ok; no-cache ok.
}
/**
* Build and prepare query from the given values and image array.
*
* @param array $values Values.
* @param array $images Images.
*
* @return bool|string
*/
private function build_query( $values, $images ) {
if ( empty( $images ) || empty( $values ) ) {
return false;
}
global $wpdb;
$values = implode( ',', $values );
// Replace with image path and respective parameters.
$query = "INSERT INTO {$wpdb->base_prefix}smush_dir_images (path, path_hash, orig_size, file_time, last_scan) VALUES $values ON DUPLICATE KEY UPDATE image_size = IF( file_time < VALUES(file_time), NULL, image_size ), file_time = IF( file_time < VALUES(file_time), VALUES(file_time), file_time ), last_scan = VALUES( last_scan )";
return $wpdb->prepare( $query, $images ); // Db call ok; no-cache ok.
}
/**
* Sends a Ajax response with a message to be shown in a floating warning notice.
*
* @param string $message The message for the notice.
*/
private function send_error( $message ) {
wp_send_json_error(
array(
'message' => sprintf( '%s
', esc_html( $message ) ),
)
);
}
/**
* Handles Ajax request to obtain the Image list within a selected directory path
*/
public function image_list() {
// Check For permission.
if ( ! current_user_can( 'manage_options' ) ) {
$this->send_error( __( 'Unauthorized', 'wp-smushit' ) );
}
// Verify nonce.
check_ajax_referer( 'smush_get_image_list', 'image_list_nonce' );
// Check if directory path is set or not.
if ( empty( $_POST['smush_path'] ) ) { // Input var ok.
$this->send_error( __( 'Empty Directory Path', 'wp-smushit' ) );
}
// FILTER_SANITIZE_URL is trimming the space if a folder contains space.
$smush_path = filter_input( INPUT_POST, 'smush_path', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY );
try {
// This will add the images to the database and get the file list.
$files = $this->get_image_list( $smush_path );
} catch ( Exception $e ) {
$this->send_error( $e->getMessage() );
}
// If files array is empty, send a message.
if ( empty( $files ) ) {
$this->send_error( __( 'We could not find any images in the selected directory.', 'wp-smushit' ) );
}
// Clear cache.
wp_cache_delete( 'wp-smush-dir_total_stats', 'wp-smush' );
// Send response.
wp_send_json_success( count( $files ) );
}
/**
* Check whether the given path is a image or not.
*
* Do not include backup files.
*
* @param string $path Image path.
*
* @return bool
*/
private function is_image( $path ) {
// Check if the path is valid.
if ( ! file_exists( $path ) || ! $this->is_image_from_extension( $path ) ) {
return false;
}
if ( false !== stripos( $path, 'phar://' ) ) {
return false;
}
$a = wp_getimagesize( $path );
// If $a is not set.
if ( empty( $a ) ) {
return false;
}
$image_type = $a[2];
if ( in_array( $image_type, array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG ) ) ) {
return true;
}
return false;
}
/**
* Obtain the path to the admin directory.
*
* @return string
*
* Thanks @andrezrv (Github)
* TODO: this does not properly get the admin path in Bedrock
*/
private function get_admin_path() {
// Replace the site base URL with the absolute path to its installation directory.
$admin_path = rtrim( str_replace( get_bloginfo( 'url' ) . '/', ABSPATH, get_admin_url() ), '/' );
// Make it filterable, so other plugins can hook into it.
return apply_filters( 'wp_smush_get_admin_path', $admin_path );
}
/**
* Check if the given file path is a supported image format
*
* @param string $path File path.
*
* @return bool Whether an image or not
*/
private function is_image_from_extension( $path ) {
$supported_image = array( 'gif', 'jpg', 'jpeg', 'png' );
$ext = strtolower( pathinfo( $path, PATHINFO_EXTENSION ) ); // Using strtolower to overcome case sensitive.
if ( in_array( $ext, $supported_image, true ) ) {
return true;
}
return false;
}
/**
* Excludes the Media Upload Directory ( Checks for Year and Month ).
*
* Borrowed from Shortpixel - (y)*
* TODO: Add a option to filter images if User have turned off the Year and Month Organize option
*
* @param string $path Path.
*
* @return bool
*/
public function skip_dir( $path ) {
// Admin directory path.
$admin_dir = $this->get_admin_path();
// Includes directory path.
$includes_dir = ABSPATH . WPINC;
// Upload directory.
$upload_dir = wp_upload_dir();
$base_dir = $upload_dir['basedir'];
$skip = false;
// Don't skip the whole sites folder but only skip media upload year folder for multi-sites.
if ( false !== strpos( $path, $base_dir . '/sites' ) ) {
// If matches the current upload path contains one of the year sub folders of the media library.
$path_arr = explode( '/', str_replace( $base_dir . '/sites/', '', $path ) );
if ( is_array( $path_arr ) && count( $path_arr ) > 1
&& is_numeric( $path_arr[1] ) && $path_arr[1] > 1900 && $path_arr[1] < 2100 // Contains the year sub folder.
) {
$skip = true;
}
} elseif ( false !== strpos( $path, $base_dir ) ) {
// If matches the current upload path contains one of the year sub folders of the media library.
$path_arr = explode( '/', str_replace( $base_dir . '/', '', $path ) );
if ( count( $path_arr ) >= 1
&& is_numeric( $path_arr[0] ) && $path_arr[0] > 1900 && $path_arr[0] < 2100 // Contains the year sub folder.
&& ( 1 === count( $path_arr ) // If there is another sub folder then it's the month sub folder.
|| ( is_numeric( $path_arr[1] ) && $path_arr[1] > 0 && $path_arr[1] < 13 ) )
) {
$skip = true;
}
} elseif ( ( false !== strpos( $path, $admin_dir ) ) || false !== strpos( $path, $includes_dir ) ) {
$skip = true;
}
// Can be used to skip/include folders matching a specific directory path.
return apply_filters( 'wp_smush_skip_folder', $skip, $path );
}
/**
* Search for image from given image id or path.
*
* @param string $id Image id to search for.
* @param string $path Image path to search for.
* @param array $images Image array to search within.
*
* @return array Image array or empty array.
*/
private function get_image( $id, $path, $images ) {
foreach ( $images as $key => $val ) {
if ( ! empty( $id ) && (int) $val['id'] === $id ) {
return $images[ $key ];
} elseif ( ! empty( $path ) && $val['path'] === $path ) {
return $images[ $key ];
}
}
return array();
}
/**
* Fetch all the optimised image, calculate stats.
*
* @param bool $force_update Should force update or not.
*
* @return array Total stats.
*/
public function total_stats( $force_update = false ) {
// If not forced to update.
if ( ! $force_update ) {
// Get stats from cache.
$total_stats = wp_cache_get( 'wp-smush-dir_total_stats', 'wp-smush' );
// If we have already calculated the stats and found in cache, return it.
if ( false !== $total_stats ) {
return $total_stats;
}
}
global $wpdb;
$offset = 0;
$optimised = 0;
$limit = 1000;
$images = array();
$continue = true;
while ( $continue ) {
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT path, image_size, orig_size FROM {$wpdb->base_prefix}smush_dir_images WHERE image_size IS NOT NULL ORDER BY `id` LIMIT %d, %d",
$offset,
$limit
),
ARRAY_A
); // Db call ok; no-cache ok.
if ( ! $results ) {
break;
}
$images = array_merge( $images, $results );
$offset += $limit;
}
// Iterate over stats, return count and savings.
if ( ! empty( $images ) ) {
// Init the stats array.
$this->stats = array(
'path' => '',
'image_size' => 0,
'orig_size' => 0,
);
foreach ( $images as $im ) {
foreach ( $im as $key => $val ) {
if ( 'path' === $key ) {
$this->optimised_images[ $val ] = $im;
continue;
}
$this->stats[ $key ] += (int) $val;
}
$optimised++;
}
}
// Get the savings in bytes and percent.
if ( ! empty( $this->stats ) && ! empty( $this->stats['orig_size'] ) ) {
$this->stats['bytes'] = ( $this->stats['orig_size'] > $this->stats['image_size'] ) ? $this->stats['orig_size'] - $this->stats['image_size'] : 0;
$this->stats['percent'] = number_format_i18n( ( ( $this->stats['bytes'] / $this->stats['orig_size'] ) * 100 ), 1 );
// Convert to human-readable form.
$this->stats['human'] = size_format( $this->stats['bytes'], 1 );
}
$this->stats['total'] = count( $images );
$this->stats['optimised'] = $optimised;
// Set stats in cache.
wp_cache_set( 'wp-smush-dir_total_stats', $this->stats, 'wp-smush' );
return $this->stats;
}
/**
* Returns the number of images scanned and optimised
*
* @return array
*/
private function last_scan_stats() {
global $wpdb;
$results = $wpdb->get_results( "SELECT id, image_size, orig_size FROM {$wpdb->base_prefix}smush_dir_images WHERE last_scan = (SELECT MAX(last_scan) FROM {$wpdb->base_prefix}smush_dir_images ) GROUP BY id", ARRAY_A ); // Db call ok; no-cache ok.
$total = count( $results );
$smushed = 0;
$stats = array(
'image_size' => 0,
'orig_size' => 0,
);
// Get the Smushed count, and stats sum.
foreach ( $results as $image ) {
if ( ! is_null( $image['image_size'] ) ) {
$smushed++;
}
// Summation of stats.
foreach ( $image as $k => $v ) {
if ( 'id' === $k ) {
continue;
}
$stats[ $k ] += $v;
}
}
// Stats.
$stats['total'] = $total;
$stats['smushed'] = $smushed;
return $stats;
}
/**
* Combine the stats from Directory Smush and Media Library Smush.
*
* @param array $stats Directory Smush stats.
*
* @return array Combined array of stats.
*/
public function combine_stats( $stats ) {
if ( empty( $stats ) || empty( $stats['percent'] ) || empty( $stats['bytes'] ) ) {
return array();
}
$dasharray = 125.663706144;
$core = WP_Smush::get_instance()->core();
// Initialize global stats.
$core->setup_global_stats();
// Get the total/Smushed attachment count.
$total_attachments = $core->total_count + $stats['total'];
$total_images = $core->stats['total_images'] + $stats['total'];
$smushed = $core->smushed_count + $stats['optimised'];
$savings = ! empty( $core->stats ) ? $core->stats['bytes'] + $stats['bytes'] : $stats['bytes'];
$size_before = ! empty( $core->stats ) ? $core->stats['size_before'] + $stats['orig_size'] : $stats['orig_size'];
$percent = $size_before > 0 ? ( $savings / $size_before ) * 100 : 0;
// Store the stats in array.
return array(
'total_count' => $total_attachments,
'smushed_count' => $smushed,
'savings' => size_format( $savings ),
'percent' => round( $percent, 1 ),
'image_count' => $total_images,
'dash_offset' => $total_attachments > 0 ? $dasharray - ( $dasharray * ( $smushed / $total_attachments ) ) : $dasharray,
/* translators: %s: total number of images */
'tooltip_text' => ! empty( $total_images ) ? sprintf( __( "You've smushed %d images in total.", 'wp-smushit' ), $total_images ) : '',
);
}
/**
* Check and create dir smush table if required.
*
* @since 2.9.0
*/
public function check_table() {
// Get current screen.
$current_screen = get_current_screen();
// Only run on required pages.
if ( ! empty( $current_screen ) && false === strpos( $current_screen->id, 'page_smush' ) ) {
return;
}
// Create custom table for directory smush.
if ( ! self::table_exist() ) {
Installer::directory_smush_table();
}
}
/**
* Check if required directory smush table exist.
*
* @param bool $force Should force check?.
*
* @since 2.9.0
*
* @return bool
*/
public static function table_exist( $force = false ) {
global $wpdb;
// If not forced, try to get from cache.
if ( ! $force && isset( self::$table_exist ) ) {
return self::$table_exist;
}
// If not already checked, check.
$table_exist = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $wpdb->base_prefix . 'smush_dir_images' ) ) ); // Db call ok; no-cache ok.
self::$table_exist = (bool) $table_exist;
return self::$table_exist;
}
/**
* Remove directory smush from tabs.
*
* If not in main site, do not show directory smush.
*
* @param array $tabs Tabs.
*
* @return array
*/
public function remove_directory_tab( $tabs ) {
if ( isset( $tabs['directory'] ) ) {
unset( $tabs['directory'] );
}
return $tabs;
}
/**
* Display a admin notice on smush screen if the custom table wasn't created
*/
public function show_table_error() {
if ( ! self::table_exist() ) { // Display a notice.
?>
settings = Settings::get_instance();
$this->scan_background_process = Background_Media_Library_Scanner::get_instance()->get_background_process();
$this->media_library_last_process = Media_Library_Last_Process::get_instance();
$this->product_analytics = Product_Analytics::get_instance();
$this->array_utils = new Array_Utils();
$this->hook_actions();
}
public static function get_instance() {
return new self();
}
public function __call( $method_name, $arguments ) {
_deprecated_function( esc_html( $method_name ), '3.24.0' );
}
private function hook_actions() {
// Setting events.
add_action( 'wp_smush_settings_updated', array( $this, 'track_opt_toggle' ), 10, 2 );
add_action( 'wp_smush_settings_updated', array( $this, 'intercept_settings_update' ), 10, 2 );
add_action( 'wp_smush_settings_deleted', array( $this, 'intercept_reset' ) );
add_action( 'wp_smush_settings_updated', array( $this, 'track_integrations_saved' ), 10, 2 );
add_action( 'wp_smush_settings_updated', array( $this, 'track_resizing_setting_update' ), 10, 2 );
add_action( 'wp_ajax_smush_track_deactivate', array( $this, 'ajax_track_deactivation_survey' ) );
add_action( 'wp_ajax_smush_analytics_track_event', array( $this, 'ajax_handle_track_request' ) );
if ( ! $this->is_usage_tracking_enabled() ) {
return;
}
// Other events.
add_action( 'wp_smush_directory_smush_start', array( $this, 'track_directory_smush' ) );
add_action( 'wp_smush_bulk_smush_start', array( $this, 'track_bulk_smush_start' ), 20 );
add_action( 'wp_smush_config_applied', array( $this, 'track_config_applied' ) );
$identifier = $this->scan_background_process->get_identifier();
$scan_started_action = "{$identifier}_started";
$scan_dead_action = "{$identifier}_dead";
add_action( "{$identifier}_before_start", array( $this, 'record_scan_death' ), 10, 2 );
add_action( $scan_started_action, array( $this, 'track_background_scan_start' ), 10, 2 );
add_action( "{$identifier}_completed", array( $this, 'track_background_scan_end' ), 10, 2 );
add_action( $scan_dead_action, array( $this, 'track_background_scan_process_death' ) );
add_action( 'wp_smush_plugin_activated', array( $this, 'track_plugin_activation' ) );
if ( defined( 'WP_SMUSH_BASENAME' ) ) {
$plugin_basename = WP_SMUSH_BASENAME;
add_action( "deactivate_$plugin_basename", array( $this, 'track_plugin_deactivation' ) );
}
add_action( 'wp_smush_bulk_smush_stuck', array( $this, 'track_bulk_smush_progress_stuck' ) );
add_action( 'wp_smush_lazy_load_updated', array( $this, 'track_lazy_load_settings_updated' ), 10, 2 );
add_action( 'wp_smush_bulk_restore_completed', array( $this, 'track_bulk_restore_completed' ) );
}
protected function is_usage_tracking_enabled() {
return $this->settings->get( 'usage' );
}
protected function track( $event, $properties = array() ) {
$this->product_analytics->track( $event, $properties );
}
public function intercept_settings_update( $old_settings, $settings ) {
if ( empty( $settings['usage'] ) ) {
// Use the most up-to-data value of 'usage'
return;
}
$settings = $this->remove_unchanged_settings( $old_settings, $settings );
$handled = $this->maybe_track_feature_toggle( $settings );
if ( ! $handled ) {
$this->maybe_track_cdn_update( $settings );
}
}
private function maybe_track_feature_toggle( $settings ) {
$has_tracked = false;
foreach ( $settings as $setting_key => $setting_value ) {
$handler = "track_{$setting_key}_feature_toggle";
if ( method_exists( $this, $handler ) ) {
call_user_func( array( $this, $handler ), $setting_value );
$has_tracked = true;
}
}
return $has_tracked;
}
protected function remove_unchanged_settings( $old_settings, $settings ) {
$default_settings = $this->settings->get_defaults();
$not_null_callback = function ( $value ) {
return ! is_null( $value );
};
$old_settings = array_filter( $old_settings, $not_null_callback );
$old_settings = array_merge( $default_settings, $old_settings );
$settings = array_filter( $settings, $not_null_callback );
$settings = array_merge( $default_settings, $settings );
$changed = array();
foreach ( $settings as $setting_key => $setting_value ) {
$old_setting_value = isset( $old_settings[ $setting_key ] ) ? $old_settings[ $setting_key ] : '';
if ( $old_setting_value !== $setting_value ) {
$changed[ $setting_key ] = $setting_value;
}
}
return $changed;
}
public function get_bulk_properties() {
$bulk_property_labels = array(
'auto' => 'Automatic Compression',
'strip_exif' => 'Metadata',
'resize' => 'Resize Original Images',
'original' => 'Compress original images',
'backup' => 'Backup original images',
'png_to_jpg' => 'Auto-convert PNGs to JPEGs (lossy)',
'no_scale' => 'Disable scaled images',
'background_email' => 'Email notification',
);
$image_sizes = Settings::get_instance()->get_setting( 'wp-smush-image_sizes' );
$bulk_properties = array(
'Image Sizes' => empty( $image_sizes ) ? 'All' : 'Custom',
'Mode' => $this->get_current_lossy_level_label(),
'Parallel Processing' => $this->get_parallel_processing_status(),
'Smush Type' => $this->get_smush_type(),
);
foreach ( $bulk_property_labels as $bulk_setting => $bulk_property_label ) {
$property_value = Settings::get_instance()->get( $bulk_setting )
? 'Enabled'
: 'Disabled';
$bulk_properties[ $bulk_property_label ] = $property_value;
}
return $bulk_properties;
}
private function get_parallel_processing_status() {
return defined( 'WP_SMUSH_PARALLEL' ) && WP_SMUSH_PARALLEL ? 'Enabled' : 'Disabled';
}
protected function get_smush_type() {
if ( $this->settings->is_webp_module_active() ) {
return 'WebP';
}
if ( $this->settings->is_avif_module_active() ) {
return 'AVIF';
}
return 'Classic';
}
protected function get_current_lossy_level_label() {
$lossy_level = $this->settings->get_lossy_level_setting();
$smush_modes = array(
Settings::get_level_lossless() => 'Basic',
Settings::get_level_super_lossy() => 'Super',
Settings::get_level_ultra_lossy() => 'Ultra',
);
if ( ! isset( $smush_modes[ $lossy_level ] ) ) {
$lossy_level = Settings::get_level_lossless();
}
return $smush_modes[ $lossy_level ];
}
private function track_detection_feature_toggle( $setting_value ) {
return $this->track_feature_toggle( $setting_value, 'Image Resize Detection' );
}
private function track_lazy_load_feature_toggle( $setting_value ) {
$this->track_lazy_load_feature_updated_on_toggle( $setting_value );
return $this->track_feature_toggle( $setting_value, 'Lazy Load' );
}
private function track_lazy_load_feature_updated_on_toggle( $activate ) {
$this->track_lazy_load_updated(
array(
'update_type' => $activate ? 'activate' : 'deactivate',
'modified_settings' => 'na',
),
$this->settings->get_setting( 'wp-smush-lazy_load', array() )
);
}
protected function track_feature_toggle( $active, $feature ) {
$event = $active
? 'Feature Activated'
: 'Feature Deactivated';
$this->track(
$event,
array(
'Feature' => $feature,
'Triggered From' => $this->identify_referrer(),
)
);
return true;
}
protected function identify_referrer() {
$wizard_setup_actions = array( 'smush_setup', 'smush_free_setup' );
$onboarding_request = ! empty( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], $wizard_setup_actions, true );
if ( $onboarding_request ) {
return 'Wizard';
}
$page = $this->get_referer_page();
$triggered_from = array(
'smush' => 'Dashboard',
'smush-bulk' => 'Bulk Smush',
'smush-lazy-preload' => 'Lazy Load',
'smush-cdn' => 'CDN',
'smush-next-gen' => 'Next-Gen Formats',
'smush-integrations' => 'Integrations',
'smush-settings' => 'Settings',
);
return empty( $triggered_from[ $page ] )
? ''
: $triggered_from[ $page ];
}
protected function maybe_track_cdn_update( $settings ) {
return false;
}
public function track_directory_smush() {
$this->track( 'Directory Smushed' );
}
public function track_bulk_smush_start() {
$properties = $this->get_bulk_properties();
$properties = array_merge(
$properties,
array(
'process_id' => $this->get_process_id(),
'Background Optimization' => $this->get_background_optimization_status(),
'Cron' => $this->get_cron_healthy_status(),
)
);
$this->track( 'Bulk Smush Started', $properties );
}
protected function get_process_id() {
return md5( $this->media_library_last_process->get_process_start_time() );
}
/**
* Add extra properties to the bulk smush completed event for Bulk Smush include ajax method.
*
* @param array $properties Bulk Smush completed properties.
*/
protected function filter_bulk_smush_completed_properties( $properties ) {
return array_merge(
$properties,
array(
'process_id' => $this->get_process_id(),
'Background Optimization' => $this->get_background_optimization_status(),
'Cron' => $this->get_cron_healthy_status(),
'Time Elapsed' => $this->media_library_last_process->get_process_elapsed_time(),
'Smush Type' => $this->get_smush_type(),
'Mode' => $this->get_current_lossy_level_label(),
)
);
}
public function track_config_applied( $config_name ) {
$properties = $config_name
? array( 'Config Name' => $config_name )
: array();
$properties['Triggered From'] = $this->identify_referrer();
$this->track( 'Config Applied', $properties );
}
public function track_opt_toggle( $old_settings, $settings ) {
$settings = $this->remove_unchanged_settings( $old_settings, $settings );
if ( isset( $settings['usage'] ) ) {
// Following the new change, the location for Opt In/Out is lowercase and none whitespace.
// @see SMUSH-1538.
$location = str_replace( ' ', '_', $this->identify_referrer() );
$location = strtolower( $location );
$this->track(
$settings['usage'] ? 'Opt In' : 'Opt Out',
array(
'Location' => $location,
'active_plugins' => $this->get_active_plugins(),
)
);
}
}
public function track_integrations_saved( $old_settings, $settings ) {
if ( empty( $settings['usage'] ) ) {
return;
}
$settings = $this->remove_unchanged_settings( $old_settings, $settings );
if ( empty( $settings ) ) {
return;
}
$this->maybe_track_integrations_toggle( $settings );
}
private function maybe_track_integrations_toggle( $settings ) {
$integrations = array(
'gutenberg' => 'Gutenberg',
'gform' => 'Gravity Forms',
'js_builder' => 'WP Bakery',
's3' => 'Amazon S3',
'nextgen' => 'NextGen Gallery',
);
foreach ( $settings as $integration_slug => $is_activated ) {
if ( ! array_key_exists( $integration_slug, $integrations ) ) {
continue;
}
if ( $is_activated ) {
$this->track(
'Integration Activated',
array(
'Integration' => $integrations[ $integration_slug ],
)
);
} else {
$this->track(
'Integration Deactivated',
array(
'Integration' => $integrations[ $integration_slug ],
)
);
}
}
}
public function intercept_reset() {
if ( $this->is_usage_tracking_enabled() ) {
$this->track(
'Opt Out',
array(
'Location' => 'reset',
'active_plugins' => $this->get_active_plugins(),
)
);
}
}
public function record_scan_death() {
$this->scan_background_process_dead = $this->scan_background_process->get_status()->is_dead();
}
public function track_background_scan_start( $identifier, $background_process ) {
$type = $this->scan_background_process_dead
? 'Retry'
: 'New';
$this->_track_background_scan_start( $type, $background_process );
}
private function _track_background_scan_start( $type, $background_process ) {
$properties = array(
'Scan Type' => $type,
);
$this->track(
'Scan Started',
array_merge(
$properties,
$this->get_bulk_properties(),
$this->get_scan_properties()
)
);
}
/**
* @param $identifier
* @param $background_process Background_Process
*
* @return void
*/
public function track_background_scan_end( $identifier, $background_process ) {
$properties = array(
'Retry Attempts' => $background_process->get_revival_count(),
'Time Elapsed' => $this->media_library_last_process->get_process_elapsed_time(),
);
$this->track(
'Scan Ended',
array_merge(
$properties,
$this->get_bulk_properties(),
$this->get_scan_properties()
)
);
}
public function track_background_scan_process_death() {
$this->track(
'Background Process Dead',
array_merge(
array(
'Process Type' => 'Scan',
'Slice Size' => $this->get_scanner_slice_size(),
'Time Elapsed' => $this->media_library_last_process->get_process_elapsed_time(),
'Smush Type' => $this->get_smush_type(),
'Mode' => $this->get_current_lossy_level_label(),
),
$this->get_scan_background_process_properties()
)
);
}
private function get_scan_properties() {
$global_stats = Global_Stats::get();
$global_stats_array = $global_stats->to_array();
$properties = array(
'process_id' => $this->get_process_id(),
'Slice Size' => $this->get_scanner_slice_size(),
);
$labels = array(
'image_attachment_count' => 'Image Attachment Count',
'optimized_images_count' => 'Optimized Images Count',
'optimize_count' => 'Optimize Count',
'reoptimize_count' => 'Reoptimize Count',
'ignore_count' => 'Ignore Count',
'animated_count' => 'Animated Count',
'error_count' => 'Error Count',
'percent_optimized' => 'Percent Optimized',
'size_before' => 'Size Before',
'size_after' => 'Size After',
'savings_percent' => 'Savings Percent',
);
$savings_keys = array(
'size_before',
'size_after',
);
foreach ( $labels as $key => $label ) {
if ( isset( $global_stats_array[ $key ] ) ) {
$properties[ $label ] = $global_stats_array[ $key ];
if ( in_array( $key, $savings_keys, true ) ) {
$properties[ $label ] = $this->convert_to_megabytes( $properties[ $label ] );
}
}
}
return $properties;
}
protected function get_bulk_background_process_properties() {
$process_id = $this->get_process_id();
return array(
'process_id' => $process_id,
);
}
protected function get_scan_background_process_properties() {
$query = new Media_Item_Query();
$total_enqueued_images = $query->get_image_attachment_count();
$total_items = $this->scan_background_process->get_status()->get_total_items();
$processed_items = $this->scan_background_process->get_status()->get_processed_items();
$scanner_slice_size = $this->get_scanner_slice_size();
$total_processed_images = $processed_items * $scanner_slice_size;
$total_processed_images = min( $total_processed_images, $total_enqueued_images );
return array(
'process_id' => $this->get_process_id(),
'Retry Attempts' => $this->scan_background_process->get_revival_count(),
'Total Enqueued Images' => $total_enqueued_images,
'Completion Percentage' => $this->get_background_process_completion_percentage( $total_items, $processed_items ),
'Total Processed Images' => $total_processed_images,
);
}
protected function get_background_process_completion_percentage( $total_items, $processed_items ) {
if ( $total_items < 1 ) {
return 0;
}
return ceil( $processed_items * 100 / $total_items );
}
protected function convert_to_megabytes( $size_in_bytes ) {
if ( empty( $size_in_bytes ) ) {
return 0;
}
$unit_mb = pow( 1024, 2 );
return round( $size_in_bytes / $unit_mb, 2 );
}
protected function get_scanner_slice_size() {
if ( is_null( $this->scanner_slice_size ) ) {
$this->scanner_slice_size = ( new Media_Library_Scanner() )->get_slice_size();
}
return $this->scanner_slice_size;
}
protected function get_referer_page() {
$path = parse_url( wp_get_referer(), PHP_URL_QUERY );
$query_vars = array();
parse_str( $path, $query_vars );
return empty( $query_vars['page'] ) ? '' : $query_vars['page'];
}
public function track_plugin_activation() {
$this->track(
'Opt In',
array(
'Location' => 'reactivate',
'active_plugins' => $this->get_active_plugins(),
)
);
}
public function track_plugin_deactivation() {
$location = $this->get_deactivation_location();
$this->track(
'Opt Out',
array(
'Location' => $location,
'active_plugins' => $this->get_active_plugins(),
)
);
}
private function get_deactivation_location() {
$is_hub_request = ! empty( $_REQUEST['wpmudev-hub'] );
if ( $is_hub_request ) {
return 'deactivate_hub';
}
$is_dashboard_request = wp_doing_ajax() &&
! empty( $_REQUEST['action'] ) &&
'wdp-project-deactivate' === wp_unslash( $_REQUEST['action'] );
if ( $is_dashboard_request ) {
return 'deactivate_dashboard';
}
return 'deactivate_pluginlist';
}
private function get_active_plugins() {
$active_plugins = array();
$active_plugin_files = $this->get_active_and_valid_plugin_files();
foreach ( $active_plugin_files as $plugin_file ) {
$plugin_name = $this->get_plugin_name( $plugin_file );
if ( $plugin_name ) {
$active_plugins[] = $plugin_name;
}
}
return $active_plugins;
}
private function get_active_and_valid_plugin_files() {
$active_plugins = is_multisite() ? wp_get_active_network_plugins() : array();
$active_plugins = array_merge( $active_plugins, wp_get_active_and_valid_plugins() );
return array_unique( $active_plugins );
}
private function get_plugin_name( $plugin_file ) {
$plugin_data = get_plugin_data( $plugin_file );
return ! empty( $plugin_data['Name'] ) ? $plugin_data['Name'] : '';
}
private function get_cron_healthy_status() {
$is_cron_healthy = Background_Pre_Flight_Controller::get_instance()->is_cron_healthy();
return $is_cron_healthy ? 'Enabled' : 'Disabled';
}
protected function get_background_optimization_status() {
return 'Disabled';
}
public function ajax_handle_track_request() {
$event_name = $this->get_event_name();
if ( ! check_ajax_referer( 'wp-smush-ajax' ) || ! Helper::is_user_allowed() || empty( $event_name ) ) {
wp_send_json_error();
}
$properties = $this->get_event_properties( $event_name );
if ( ! $this->allow_to_track( $event_name, $properties ) ) {
wp_send_json_error();
}
$this->track(
$event_name,
$properties
);
wp_send_json_success();
}
private function allow_to_track( $event_name, $properties ) {
$trackable_events = array(
'Setup Wizard' => true,
'Setup Wizard New' => true,
'smush_pro_upsell' => isset( $properties['Location'] ) && 'wizard' === $properties['Location'],
'Disconnect Site' => true,
);
$is_trackable_event = ! empty( $trackable_events[ $event_name ] );
return $is_trackable_event || $this->is_usage_tracking_enabled();
}
private function get_event_name() {
return isset( $_POST['event'] ) ? sanitize_text_field( wp_unslash( $_POST['event'] ) ) : '';
}
private function get_event_properties( $event_name ) {
$properties = isset( $_POST['properties'] ) && is_array( $_POST['properties'] ) ? wp_unslash( $_POST['properties'] ) : array();
$properties = map_deep( $properties, 'sanitize_text_field' );
$filter_callback = $this->get_filter_properties_callback( $event_name );
if ( method_exists( $this, $filter_callback ) ) {
$properties = call_user_func( array( $this, $filter_callback ), $properties );
}
return $properties;
}
private function get_filter_properties_callback( $event_name ) {
$event_name = str_replace( ' ', '_', $event_name );
$event_name = sanitize_key( $event_name );
return "filter_{$event_name}_properties";
}
/**
* Filter properties for Scan Interrupted event.
*
* @param array $properties JS properties.
*/
protected function filter_scan_interrupted_properties( $properties ) {
return array_merge(
$properties,
array(
'Slice Size' => $this->get_scanner_slice_size(),
'Background Optimization' => $this->get_background_optimization_status(),
'Cron' => $this->get_cron_healthy_status(),
'Time Elapsed' => $this->media_library_last_process->get_process_elapsed_time(),
'Smush Type' => $this->get_smush_type(),
'Mode' => $this->get_current_lossy_level_label(),
'WP Loopback Status' => $this->get_wp_loopback_status( $properties ),
),
$this->get_scan_background_process_properties(),
$this->get_last_image_process_properties()
);
}
private function get_last_image_process_properties() {
$last_image_id = $this->media_library_last_process->get_last_process_attachment_id();
if ( ! $last_image_id ) {
return array();
}
$media_item = Media_Item_Cache::get_instance()->get( $last_image_id );
$last_image_time_elapsed = $this->media_library_last_process->get_last_process_attachment_elapsed_time();
$properties = array(
'Last Image Time Elapsed' => $last_image_time_elapsed,
);
if ( ! $media_item->is_valid() ) {
return $properties;
}
$full_size = $media_item->get_full_or_scaled_size();
if ( ! $full_size ) {
return $properties;
}
$file_size = $this->convert_to_megabytes( $full_size->get_filesize() );
$image_width = $full_size->get_width();
$image_height = $full_size->get_height();
$image_type = strtoupper( $full_size->get_extension() );
return array(
'Last Image Time Elapsed' => $last_image_time_elapsed,
'Last Image Size' => $file_size,
'Last Image Width' => $image_width,
'Last Image Height' => $image_height,
'Last Image Type' => $image_type,
);
}
/**
* Filter properties for Bulk Smush interrupted event.
*
* @param array $properties JS properties.
*/
protected function filter_bulk_smush_interrupted_properties( $properties ) {
return array_merge(
$properties,
array(
'Background Optimization' => $this->get_background_optimization_status(),
'Cron' => $this->get_cron_healthy_status(),
'Parallel Processing' => $this->get_parallel_processing_status(),
'Time Elapsed' => $this->media_library_last_process->get_process_elapsed_time(),
'Smush Type' => $this->get_smush_type(),
'Mode' => $this->get_current_lossy_level_label(),
'WP Loopback Status' => $this->get_wp_loopback_status( $properties ),
),
$this->get_bulk_background_process_properties(),
$this->get_last_image_process_properties()
);
}
public function ajax_track_deactivation_survey() {
$event_name = $this->get_event_name();
if ( ! check_ajax_referer( 'wp-smush-ajax' ) || ! Helper::is_user_allowed() || empty( $event_name ) ) {
wp_send_json_error();
}
$properties = $this->get_event_properties( $event_name );
$properties = array_merge(
$properties,
array(
'active_features' => $this->get_active_features(),
'active_plugins' => $this->get_active_plugins(),
'Smush API Connection' => $this->get_api_connection_status(),
)
);
$this->track(
$event_name,
$properties
);
wp_send_json_success();
}
private function get_api_connection_status() {
if ( Hub_Connector::is_logged_in() ) {
return 'connected';
}
if ( Membership::get_instance()->is_api_hub_access_required() ) {
return 'disconnected';
}
return 'na';
}
private function get_active_features() {
$lossy_level = $this->settings->get_lossy_level_setting();
$features = array(
'lazy_load' => $this->settings->is_lazyload_active(),
'smush_basic' => Settings::get_level_lossless() === $lossy_level,
'smush_super' => Settings::get_level_super_lossy() === $lossy_level,
'wp_bakery' => $this->settings->get( 'js_builder' ),
'gravity_forms' => $this->settings->get( 'gform' ),
'gutenberg_blocks' => $this->settings->get( 'gutenberg' ),
);
// Merge in pro features.
$features = array_merge(
$features,
$this->get_active_pro_features()
);
return array_keys( array_filter( $features ) );
}
protected function get_active_pro_features() {
return array();
}
private function get_wp_loopback_status( $properties ) {
$is_loopback_error = ! empty( $properties['Trigger'] ) && 'loopback_error' === $properties['Trigger'];
if ( $is_loopback_error ) {
$loopback_status = Helper::loopback_supported() ? 'Pass' : 'Fail';
} else {
$loopback_status = 'na';
}
return $loopback_status;
}
public function track_bulk_smush_progress_stuck() {
$properties = array(
'Trigger' => 'stuck_notice',
'Modal Action' => 'na',
'Troubleshoot' => 'na',
);
$properties = $this->filter_bulk_smush_interrupted_properties( $properties );
$this->track( 'Bulk Smush Interrupted', $properties );
}
public function track_lazy_load_settings_updated( $old_settings, $settings ) {
$changed_settings = $this->remove_unchanged_settings( (array) $old_settings, (array) $settings );
$modified_settings = 'na';
if ( ! empty( $changed_settings ) ) {
$modified_settings_map = array(
'format' => 'media_type',
'output' => 'output_location',
'animation' => 'display_animation',
'include' => 'include_exclude_posttype',
'exclude-pages' => 'include_exclude_url',
'exclude-classes' => 'include_exclude_keyword',
'footer' => 'script_method',
'native' => 'native_lazyload',
'noscript_fallback' => 'noscript',
);
$modified_settings = array_intersect_key( $modified_settings_map, $changed_settings );
$modified_settings = ! empty( $modified_settings ) ? array_values( $modified_settings ) : 'na';
}
$this->track_lazy_load_updated(
array(
'update_type' => 'modify',
'modified_settings' => $modified_settings,
),
$settings
);
}
public function track_resizing_setting_update( $old_settings, $settings ) {
if ( empty( $settings['usage'] ) ) {
return;
}
$changed_settings = $this->remove_unchanged_settings( $old_settings, $settings );
if ( 'Lazy Load' !== $this->identify_referrer() ) {
return;
}
$modified_settings = 'na';
if ( ! empty( $changed_settings ) ) {
$modified_settings_map = array(
'auto_resizing' => 'auto_resizing',
'image_dimensions' => 'image_dimensions',
);
$modified_settings = array_intersect_key( $modified_settings_map, $changed_settings );
$modified_settings = ! empty( $modified_settings ) ? array_values( $modified_settings ) : 'na';
}
$properties = array(
'update_type' => 'modify',
'modified_settings' => $modified_settings,
'auto_resizing_status' => $settings['auto_resizing'] ? 'Enabled' : 'Disabled',
'image_dimensions_status' => $settings['image_dimensions'] ? 'Enabled' : 'Disabled',
);
$this->track_lazy_load_updated(
$properties,
$this->settings->get_setting( 'wp-smush-lazy_load' )
);
}
private function track_lazy_load_updated( $properties, $settings ) {
$exclusion_enabled = $this->is_lazy_load_exclusion_enabled( $settings );
$native_lazyload_enabled = ! empty( $settings['native'] );
$noscript_fallback_enabled = ! empty( $settings['noscript_fallback'] );
$embed_content = empty( $settings['format']['iframe'] )
? 'Disabled'
: ( empty( $settings['format']['embed_video'] ) ? 'Enabled' : 'Preview Images' );
$properties = array_merge(
array(
'Location' => $this->identify_referrer(),
'embed_content' => $embed_content,
'exclusions' => $exclusion_enabled ? 'Enabled' : 'Disabled',
'native_lazy_status' => $native_lazyload_enabled ? 'Enabled' : 'Disabled',
'noscript_status' => $noscript_fallback_enabled ? 'Enabled' : 'Disabled',
'auto_resizing_status' => $this->settings->get( 'auto_resizing' ) ? 'Enabled' : 'Disabled',
'image_dimensions_status' => $this->settings->get( 'image_dimensions' ) ? 'Enabled' : 'Disabled',
),
$properties
);
$this->track( 'lazy_load_updated', $properties );
}
private function is_lazy_load_exclusion_enabled( $settings ) {
if ( ! empty( $settings['exclude-pages'] ) || ! empty( $settings['exclude-classes'] ) ) {
return true;
}
if ( empty( $settings['include'] ) || ! is_array( $settings['include'] ) ) {
return false;
}
$included_post_types = $settings['include'];
// By default, we activated for all post types, so this option is changed when any post type is unchecked.
return in_array( false, $included_post_types, true );
}
/**
* Track the completion of a bulk restore process.
*
* @param array $args Restore arguments.
*/
public function track_bulk_restore_completed( $args ) {
$this->track(
'Bulk Restore Triggered',
$this->filter_bulk_restore_triggered_properties(
array(
'Type' => 'Bulk',
'Total images restored' => (int) $this->array_utils->get_array_value( $args, 'restored_count', 0 ),
'Total images' => (int) $this->array_utils->get_array_value( $args, 'total_count', 0 ),
'Backup not found' => (int) $this->array_utils->get_array_value( $args, 'missing_backup_count', 0 ),
)
)
);
}
/**
* Filter the properties for the bulk restore triggered event.
*
* @param mixed $properties Properties.
*
* @return array
*/
public function filter_bulk_restore_triggered_properties( $properties ) {
return array_merge(
$properties,
array(
'Backup Status' => $this->settings->is_backup_active() ? 'Enabled' : 'Disabled',
)
);
}
}
PK 9E\%2 ' core/modules/class-resize-detection.phpnu [
*
* @copyright (c) 2018, Incsub (http://incsub.com)
*/
namespace Smush\Core\Modules;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Resize_Detection
*/
class Resize_Detection extends Abstract_Module {
/**
* Is auto detection enabled.
*
* @var bool
*/
private $can_auto_detect = false;
/**
* Resize_Detection constructor.
*/
public function init() {
// Set auto resize flag.
add_action( 'wp', array( $this, 'init_flags' ) );
// Load js file that is required in public facing pages.
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_resize_assets' ) );
// Set a flag to media library images.
add_filter( 'wp_smush_updated_element_markup', array( $this, 'skip_image_resize_detection' ) );
// Generate markup for the template engine.
add_action( 'wp_footer', array( $this, 'generate_markup' ) );
}
/**
* Check if auto resize can be performed.
*
* Allow only if current user is admin and auto resize
* detection is enabled in settings.
*/
public function init_flags() {
// Only required for admin users.
if ( $this->settings->get( 'detection' ) && current_user_can( 'manage_options' ) ) {
$this->can_auto_detect = true;
}
}
/**
* Enqueue JS files required in public pages.
*
* Enqueue resize detection js and css files to public
* facing side of the site. Load only if auto detect
* is enabled.
*
* @return void
*/
public function enqueue_resize_assets() {
// Required only if auto detection is required.
if ( ! $this->can_auto_detect ) {
return;
}
// Required scripts for front end.
wp_enqueue_script(
'smush-resize-detection',
WP_SMUSH_URL . 'app/assets/js/smush-rd.min.js',
array( 'jquery' ),
WP_SMUSH_VERSION,
true
);
// Required styles for front end.
wp_enqueue_style(
'smush-resize-detection',
WP_SMUSH_URL . 'app/assets/css/smush-rd.min.css',
array(),
WP_SMUSH_VERSION
);
// Define ajaxurl var.
wp_localize_script(
'smush-resize-detection',
'wp_smush_resize_vars',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'ajax_nonce' => wp_create_nonce( 'smush_resize_nonce' ),
// translators: %s - width, %s - height.
'large_image' => sprintf( __( 'This image is too large for its container. Adjust the image dimensions to %1$s x %2$spx for optimal results.', 'wp-smushit' ), 'width', 'height' ),
// translators: %s - width, %s - height.
'small_image' => sprintf( __( 'This image is too small for its container. Adjust the image dimensions to %1$s x %2$spx for optimal results.', 'wp-smushit' ), 'width', 'height' ),
)
);
}
/**
* Generate markup for the template engine.
*
* @since 2.9
*/
public function generate_markup() {
// Required only if auto detection is required.
if ( ! $this->can_auto_detect ) {
return;
}
?>
can_auto_detect ) {
return $image;
}
// CDN with auto resize need to be enabled.
if ( ! $this->settings->is_lazyload_active() || ! $this->settings->is_auto_resizing_active() ) {
return $image;
}
Helpers\Parser::add_attribute( $image, 'no-resize-detection' );
return $image;
}
}
PK @E\u)# # + core/modules/async/class-abstract-async.phpnu [ action ) ) {
throw new Exception( 'Action not defined for class ' . __CLASS__ );
}
// Handle the actual action.
add_action( $this->action, array( $this, 'launch' ), $this->priority, $this->argument_count );
if ( $auth_level & self::$logged_in ) {
add_action( "admin_post_wp_async_$this->action", array( $this, 'handle_postback' ) );
}
if ( $auth_level & self::$logged_out ) {
add_action( "admin_post_nopriv_wp_async_$this->action", array( $this, 'handle_postback' ) );
}
}
/**
* Add the shutdown action for launching the real postback if we don't
* get an exception thrown by prepare_data().
*
* @uses func_get_args() To grab any arguments passed by the action
*
* @return mixed|void
*/
public function launch() {
$data = func_get_args();
$result = isset( $data[0] ) ? $data[0] : null;
try {
$data = $this->prepare_data( $data );
if ( ! $this->should_run( $data ) ) {
return $result;
}
} catch ( Exception $e ) {
Helper::logger()->error( sprintf( 'Async Smush: Error in prepare_data: %s', $e->getMessage() ) );
return;
}
$data['action'] = "wp_async_$this->action";
$data['_nonce'] = $this->create_async_nonce();
$this->body_data = $data;
$has_shutdown_action = has_action( 'shutdown', array( $this, 'process_request' ) );
$is_upload_attachment_action = ! empty( $_POST['action'] ) && 'upload-attachment' === $_POST['action'];
$is_post_id_non_empty = ! empty( $_POST ) && isset( $_POST['post_id'] ) && $_POST['post_id'];
$is_async_upload = isset( $_POST['post_id'] ) && empty( $_POST['post_id'] ) && isset( $_FILES['async-upload'] );
$should_hook_to_shutdown = $is_upload_attachment_action || $is_post_id_non_empty || $is_async_upload;
// Do not use this, as in case of importing, only the last image gets processed
// It's very important that all the Media uploads, are handled via shutdown action, else, sometimes the image meta updated
// by smush is earlier, and then original meta update causes discrepancy.
if ( $should_hook_to_shutdown && ! $has_shutdown_action ) {
add_action( 'shutdown', array( $this, 'process_request' ) );
} else {
// Send a ajax request to process image and return image metadata, added for compatibility with plugins like
// WP All Import, and RSS aggregator, which upload multiple images at once.
$this->process_request();
}
// If we have image metadata return it.
return $result;
}
protected function should_run( $data ) {
return true;
}
/**
* Launch the request on the WordPress shutdown hook
*
* On VIP we got into data races due to the postback sometimes completing
* faster than the data could propogate to the database server cluster.
* This made WordPress get empty data sets from the database without
* failing. On their advice, we're moving the actual firing of the async
* postback to the shutdown hook. Supposedly that will ensure that the
* data at least has time to get into the object cache.
*
* @uses $_COOKIE To send a cookie header for async postback
* @uses apply_filters()
* @uses admin_url()
* @uses wp_remote_post()
*/
public function process_request() {
if ( ! empty( $this->body_data ) ) {
$request_args = array(
'timeout' => apply_filters( 'smush_async_time_out', 0 ),
'blocking' => false,
'sslverify' => false,
'body' => $this->body_data,
'cookies' => wp_unslash( $_COOKIE ),
);
$url = admin_url( 'admin-post.php' );
wp_remote_post( $url, $request_args );
}
}
/**
* Verify the postback is valid, then fire any scheduled events.
*
* @uses $_POST['_nonce']
* @uses is_user_logged_in()
* @uses add_filter()
* @uses wp_die()
*/
public function handle_postback() {
if ( isset( $_POST['_nonce'] ) && $this->verify_async_nonce( $_POST['_nonce'] ) ) {
$this->run_action();
}
add_filter( 'wp_die_handler', array( $this, 'handle_die' ) );
wp_die();
}
/**
* Handle Die
*/
public function handle_die() {
die();
}
/**
* Create a random, one time use token.
*
* Based entirely on wp_create_nonce() but does not tie the nonce to the
* current logged-in user.
*
* @uses wp_nonce_tick()
* @uses wp_hash()
*
* @return string The one-time use token
*/
protected function create_async_nonce() {
$action = $this->get_nonce_action();
$i = wp_nonce_tick();
return substr( wp_hash( $i . $action . get_class( $this ), 'nonce' ), - 12, 10 );
}
/**
* Verify that the correct nonce was used within the time limit.
*
* @uses wp_nonce_tick()
* @uses wp_hash()
*
* @param string $nonce Nonce to be verified.
*
* @return bool Whether the nonce check passed or failed
*/
protected function verify_async_nonce( $nonce ) {
$action = $this->get_nonce_action();
$i = wp_nonce_tick();
// Nonce generated 0-12 hours ago.
if ( substr( wp_hash( $i . $action . get_class( $this ), 'nonce' ), - 12, 10 ) === $nonce ) {
return 1;
}
// Nonce generated 12-24 hours ago.
if ( substr( wp_hash( ( $i - 1 ) . $action . get_class( $this ), 'nonce' ), - 12, 10 ) === $nonce ) {
return 2;
}
// Invalid nonce.
return false;
}
/**
* Get a nonce action based on the $action property of the class
*
* @return string The nonce action for the current instance
*/
protected function get_nonce_action() {
$action = $this->action;
if ( substr( $action, 0, 7 ) === 'nopriv_' ) {
$action = substr( $action, 7 );
}
return "wp_async_$action";
}
/**
* Prepare any data to be passed to the asynchronous postback
*
* The array this function receives will be a numerically keyed array from
* func_get_args(). It is expected that you will return an associative array
* so that the $_POST values used in the asynchronous call will make sense.
*
* The array you send back may or may not have anything to do with the data
* passed into this method. It all depends on the implementation details and
* what data is needed in the asynchronous postback.
*
* Do not set values for 'action' or '_nonce', as those will get overwritten
* later in launch().
*
* @throws Exception If the postback should not occur for any reason.
*
* @param array $data The raw data received by the launch method.
*
* @return array The prepared data
*/
abstract protected function prepare_data( $data );
/**
* Run the do_action function for the asynchronous postback.
*
* This method needs to fetch and sanitize any and all data from the $_POST
* superglobal and provide them to the do_action call.
*
* The action should be constructed as "wp_async_task_$this->action"
*/
abstract protected function run_action();
}
PK CE\O # core/modules/async/class-editor.phpnu [
*
* @copyright (c) 2016, Incsub (http://incsub.com)
*/
namespace Smush\Core\Modules\Async;
use Exception;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Editor
*/
class Editor extends Abstract_Async {
/**
* Argument count.
*
* @var int $argument_count
*/
protected $argument_count = 2;
/**
* Priority.
*
* @var int $priority
*/
protected $priority = 12;
/**
* Whenever a attachment metadata is generated
* Had to be hooked on generate and not update, else it goes in infinite loop
*
* @var string
*/
protected $action = 'wp_save_image_editor_file';
/**
* Prepare data for the asynchronous request
*
* @throws Exception If for any reason the request should not happen.
*
* @param array $data An array of data sent to the hook.
*
* @return array
*/
protected function prepare_data( $data ) {
// Store the post data in $data variable.
if ( ! empty( $data ) ) {
$data = array_merge( $data, $_POST );
}
// Store the image path.
$data['filepath'] = ! empty( $data[1] ) ? $data[1] : '';
$data['wp-action'] = ! empty( $data['action'] ) ? $data['action'] : '';
unset( $data['action'], $data[1] );
return $data;
}
/**
* Run the async task action
*
* TODO: Add a check for image
* TODO: See if auto smush is enabled or not
* TODO: Check if async is enabled or not
*/
protected function run_action() {
if ( isset( $_POST['wp-action'], $_POST['do'], $_POST['postid'] )
&& 'image-editor' === $_POST['wp-action']
&& check_ajax_referer( 'image_editor-' . (int) $_POST['postid'] )
&& 'open' !== $_POST['do']
) {
$postid = ! empty( $_POST['postid'] ) ? (int) $_POST['postid'] : '';
// Allow the Asynchronous task to run.
do_action( "wp_async_$this->action", $postid, $_POST );
}
}
}
PK EE\)` " core/modules/async/class-async.phpnu [
*
* @copyright (c) 2016, Incsub (http://incsub.com)
*/
namespace Smush\Core\Modules\Async;
use Exception;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Async
*/
class Async extends Abstract_Async {
/**
* Argument count.
*
* @var int $argument_count
*/
protected $argument_count = 2;
/**
* Priority.
*
* @var int $priority
*/
protected $priority = 12;
/**
* Whenever a attachment metadata is generated
* Had to be hooked on generate and not update, else it goes in infinite loop
*
* @var string
*/
protected $action = 'wp_generate_attachment_metadata';
/**
* Prepare data for the asynchronous request
*
* @throws Exception If for any reason the request should not happen.
*
* @param array $data An array of data sent to the hook.
*
* @return array
*/
protected function prepare_data( $data ) {
// We don't have the data, bail out.
if ( empty( $data ) ) {
return $data;
}
// Return a associative array.
$image_meta = array();
$image_meta['metadata'] = ! empty( $data[0] ) ? $data[0] : '';
$image_meta['id'] = ! empty( $data[1] ) ? $data[1] : '';
/**
* AJAX Thumbnail Rebuild integration.
*
* @see https://app.asana.com/0/14491813218786/730814863045197/f
*/
if ( ! empty( $_POST['action'] ) && 'ajax_thumbnail_rebuild' === $_POST['action'] && ! empty( $_POST['thumbnails'] ) ) { // Input var ok.
$image_meta['regen'] = wp_unslash( $_POST['thumbnails'] ); // Input var ok.
}
return $image_meta;
}
/**
* Run the async task action
*
* TODO: See if auto smush is enabled or not.
* TODO: Check if async is enabled or not.
*/
protected function run_action() {
// Nonce validated in parent method.
$id = ! empty( $_POST['id'] ) ? (int) $_POST['id'] : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
// Get metadata from $_POST.
if ( ! empty( $_POST['metadata'] ) && wp_attachment_is_image( $id ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
// Allow the Asynchronous task to run.
do_action( "wp_async_$this->action", $id );
}
}
protected function should_run( $data ) {
if ( empty( $data['metadata'] ) && empty( $data['id'] ) ) {
return false;
}
$attachment_id = $data['id'];
$smush = WP_Smush::get_instance()->core()->mod->smush;
return $smush->should_auto_smush( $attachment_id );
}
}
PK HE\oK K core/modules/class-lazy.phpnu [ get( $attachment_id );
return $media_item->can_be_restored();
}
/**
* Generate unique .bak file.
*
* @param string $bak_file The .bak file.
* @param int $attachment_id Attachment ID.
* @return string Returns a unique backup file.
*/
private function generate_unique_bak_file( $bak_file, $attachment_id ) {
if ( strpos( $bak_file, '.bak' ) && Helper::file_exists( $bak_file, $attachment_id ) ) {
$count = 1;
$ext = Helper::get_file_ext( $bak_file );
$ext = ".bak.$ext";
$file_without_ext = rtrim( $bak_file, $ext );
$bak_file = $file_without_ext . '-' . $count . $ext;
while ( Helper::file_exists( $bak_file, $attachment_id ) ) {
$count++;
$bak_file = $file_without_ext . '-' . $count . $ext;
}
return $bak_file;
}
return $bak_file;
}
/**
* Creates a backup of file for the given attachment path.
*
* Checks if there is an existing backup, else create one.
*
* @param string $file_path File path.
* @param int $attachment_id Attachment ID.
*
* @return void
*/
public function create_backup( $file_path, $attachment_id ) {
if ( empty( $file_path ) || empty( $attachment_id ) ) {
return;
}
// If backup not enabled, return.
if ( ! $this->is_active() ) {
return;
}
/**
* If [ not compress original ]:
* if [ is-scaled.file ]:
* Backup original file.
* elseif [ no-resize + no-png2jpg ]:
* We don't need to backup, let user try to use regenerate plugin
* to restore the compressed thumbnails size.
* else: continue as compress_original.
* else:
* We don't need to backup if we had a backup file for PNG2JPG,
* or .bak file. But if the .bak file is from third party, we will generate our new backup file.
* end.
*/
// We might not need to backup the file if we're not compressing original.
if ( ! $this->settings->get( 'original' ) ) {
/**
* Add WordPress 5.3 support for -scaled images size, and those can always be used to restore.
* Maybe user doesn't want to auto-scale JPG from WP for some images,
* so we allow user to restore it even we don't Smush this image.
*/
if ( false !== strpos( $file_path, '-scaled.' ) && function_exists( 'wp_get_original_image_path' ) ) {
// Scaled images already have a backup. Use that and don't create a new one.
$file_path = Helper::get_attached_file( $attachment_id, 'backup' );// Supported S3.
if ( file_exists( $file_path ) ) {
/**
* We do not need an additional backup file if we're not compressing originals.
* But we need to save the original file as a backup file in the metadata to allow restoring this image later.
*/
$this->add_to_image_backup_sizes( $attachment_id, $file_path );
return;
}
}
$mod = WP_Smush::get_instance()->core()->mod;
// If there is not *-scaled.jpg file, we don't need to backup the file if we don't work with original file.
if ( ! $mod->resize->is_active() && ! $mod->png2jpg->is_active() ) {
/**
* In this case, we can add the meta to save the original file as a backup file,
* but if there is a lot of images, might take a lot of row for postmeta table,
* so leave it for user to use a "regenerate thumbnail" plugin instead.
*/
Helper::logger()->backup()->info( sprintf( 'Not modify the original file [%s(%d)], skip the backup.', Helper::clean_file_path( $file_path ), $attachment_id ) );
return;
}
$should_backup = false;
// We should backup this image if we can resize it.
if ( $mod->resize->is_active() && $mod->resize->should_resize( $attachment_id ) ) {
$should_backup = true;
}
// We should backup this image if we can convert it from PNG to JPEG.
if (
! $should_backup && $mod->png2jpg->is_active() && Helper::get_file_ext( $file_path, 'png' )
&& $mod->png2jpg->can_be_converted( $attachment_id, 'full', 'image/png', $file_path )
) {
$should_backup = true;
}
// As we don't work with the original file, so we don't back it up.
if ( ! $should_backup ) {
Helper::logger()->backup()->info( sprintf( 'Not modify the original file [%s(%d)], skip the backup.', Helper::clean_file_path( $file_path ), $attachment_id ) );
return;
}
}
/**
* Check if exists backup file from meta,
* Because we will compress the original file,
* so we only keep the backup file if there is PNG2JPG or .bak file.
*/
$backup_path = $this->get_backup_file( $attachment_id, $file_path );
if ( $backup_path ) {
/**
* We will compress the original file so the backup file have to different from current file.
* And the backup file should be the same folder with the main file.
*/
if ( $backup_path !== $file_path && dirname( $file_path ) === dirname( $backup_path ) ) {
// Check if there is a .bak file or PNG2JPG file.
if ( strpos( $backup_path, '.bak' ) || ( Helper::get_file_ext( $backup_path, 'png' ) && Helper::get_file_ext( $file_path, 'jpg' ) ) ) {
Helper::logger()->backup()->info( sprintf( 'Found backed up file [%s(%d)].', Helper::clean_file_path( $backup_path ), $attachment_id ) );
return;
}
}
}
/**
* To avoid the conflict with 3rd party, we will generate a new backup file.
* Because how about if 3rd party delete the backup file before trying to restore it from Smush?
* We only try to use their bak file while restoring the backup file.
*/
$backup_path = $this->generate_unique_bak_file( $this->get_image_backup_path( $file_path ), $attachment_id );
/**
* We need to save the .bak file to the meta. Because if there is a PNG, when we convert PNG2JPG,
* the converted file is .jpg, so the bak file will be .bak.jpg not .bak.png
*/
// Store the backup path in image backup sizes.
if ( copy( $file_path, $backup_path ) ) {
$this->add_to_image_backup_sizes( $attachment_id, $backup_path );
} else {
Helper::logger()->backup()->error( sprintf( 'Cannot backup file [%s(%d)].', Helper::clean_file_path( $file_path ), $attachment_id ) );
}
}
/**
* Store new backup path for the image.
*
* @param int $attachment_id Attachment ID.
* @param string $backup_path Backup path.
* @param string $backup_key Backup key.
*/
public function add_to_image_backup_sizes( $attachment_id, $backup_path, $backup_key = '' ) {
if ( empty( $attachment_id ) || empty( $backup_path ) ) {
return;
}
// Get the Existing backup sizes.
$backup_sizes = $this->get_backup_sizes( $attachment_id );
if ( empty( $backup_sizes ) ) {
$backup_sizes = array();
}
// Prevent phar deserialization vulnerability.
if ( false !== stripos( $backup_path, 'phar://' ) ) {
Helper::logger()->backup()->info( sprintf( 'Prevent phar deserialization vulnerability [%s(%d)].', Helper::clean_file_path( $backup_path ), $attachment_id ) );
return;
}
// Return if backup file doesn't exist.
if ( ! file_exists( $backup_path ) ) {
Helper::logger()->backup()->notice( sprintf( 'Back file [%s(%d)] does not exist.', Helper::clean_file_path( $backup_path ), $attachment_id ) );
return;
}
list( $width, $height ) = getimagesize( $backup_path );
// Store our backup path.
$backup_key = empty( $backup_key ) ? $this->backup_key : $backup_key;
$backup_sizes[ $backup_key ] = array(
'file' => wp_basename( $backup_path ),
'width' => $width,
'height' => $height,
);
wp_cache_delete( 'images_with_backups', 'wp-smush' );
update_post_meta( $attachment_id, '_wp_attachment_backup_sizes', $backup_sizes );
}
/**
* Get backup sizes.
*
* @param int $attachment_id Attachment ID.
* @return mixed False or an array of backup sizes.
*/
public function get_backup_sizes( $attachment_id ) {
return get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
}
/**
* Back up an image if it hasn't backed up yet.
*
* @since 3.9.6
*
* @param int $attachment_id Image id.
* @param string $backup_file File path to back up.
*
* Note, we used it to manage backup PNG2JPG to keep the backup file is the original file to avoid conflicts with a duplicate PNG file.
* If the backup file exists it will rename the original backup file to
* the new backup file.
*
* @return bool True if added this file to the backup sizes, false if the image was backed up before.
*/
public function maybe_backup_image( $attachment_id, $backup_file ) {
if ( ! file_exists( $backup_file ) ) {
return false;
}
// We don't use .bak file from 3rd party while backing up.
$backed_up_file = $this->get_backup_file( $attachment_id, $backup_file );
$was_backed_up = true;
if ( $backed_up_file && $backed_up_file !== $backup_file && dirname( $backed_up_file ) === dirname( $backup_file ) ) {
$was_backed_up = rename( $backed_up_file, $backup_file );
}
// Backup the image.
if ( $was_backed_up ) {
$this->add_to_image_backup_sizes( $attachment_id, $backup_file );
}
return $was_backed_up;
}
/**
* Get the backup file from the meta.
*
* @since 3.9.6
*
* @param int $id Image ID.
* @param string $file_path Current file path.
*
* @return bool|null Backup file or false|null if the image doesn't exist.
*/
public function get_backup_file( $id, $file_path = false ) {
if ( empty( $id ) ) {
return null;
}
if ( empty( $file_path ) ) {
// Get unfiltered path file.
$file_path = Helper::get_attached_file( $id, 'original' );
// If the file path is still empty, nothing to check here.
if ( empty( $file_path ) ) {
return null;
}
}
// Initial result.
$backup_file = false;
// Try to get the backup file from _wp_attachment_backup_sizes.
$backup_sizes = $this->get_backup_sizes( $id );
// Check if we have backup file from the metadata.
if ( $backup_sizes ) {
// Try to get the original file first.
if ( isset( $backup_sizes[ $this->backup_key ]['file'] ) ) {
$original_file = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes[ $this->backup_key ]['file'] ), $file_path );
if ( Helper::file_exists( $original_file, $id ) ) {
$backup_file = $original_file;
}
}
// Try to check it from legacy original file or from the resized PNG file.
if ( ! $backup_file ) {
// If we don't have the original backup path in backup sizes, check for legacy original file path. It's for old version < V.2.7.0.
$original_file = get_post_meta( $id, 'wp-smush-original_file', true );
if ( ! empty( $original_file ) ) {
// For old version < v.2.7.0, we are saving meta['file'] or _wp_attached_file.
$original_file = Helper::original_file( $original_file );
if ( Helper::file_exists( $original_file, $id ) ) {
$backup_file = $original_file;
// As we don't use this meta key so save it as a full backup file and delete the old metadata.
WP_Smush::get_instance()->core()->mod->backup->add_to_image_backup_sizes( $id, $backup_file );
delete_post_meta( $id, 'wp-smush-original_file' );
}
}
// Check the backup file from resized PNG file.
if ( ! $backup_file && isset( $backup_sizes['smush_png_path']['file'] ) ) {
$original_file = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes['smush_png_path']['file'] ), $file_path );
if ( Helper::file_exists( $original_file, $id ) ) {
$backup_file = $original_file;
}
}
}
}
return $backup_file;
}
/**
* Restore the image and its sizes from backup
*
* @param string $attachment_id Attachment ID.
* @param bool $resp Send JSON response or not.
*
* @return bool
*/
public function restore_image( $attachment_id = '', $resp = true ) {
// TODO: (stats refactor) handle properly
// If no attachment id is provided, check $_POST variable for attachment_id.
if ( empty( $attachment_id ) ) {
// Check Empty fields.
if ( empty( $_POST['attachment_id'] ) || empty( $_POST['_nonce'] ) ) {
wp_send_json_error(
array(
'error_msg' => esc_html__( 'Error in processing restore action, fields empty.', 'wp-smushit' ),
)
);
}
$nonce_value = filter_input( INPUT_POST, '_nonce', FILTER_SANITIZE_SPECIAL_CHARS );
$attachment_id = filter_input( INPUT_POST, 'attachment_id', FILTER_SANITIZE_NUMBER_INT );
if ( ! wp_verify_nonce( $nonce_value, "wp-smush-restore-$attachment_id" ) ) {
wp_send_json_error(
array(
'error_msg' => esc_html__( 'Image not restored, nonce verification failed.', 'wp-smushit' ),
)
);
}
// Check capability.
if ( ! Helper::is_user_allowed( 'upload_files' ) ) {
wp_send_json_error(
array(
'error_msg' => esc_html__( "You don't have permission to work with uploaded files.", 'wp-smushit' ),
)
);
}
}
$attachment_id = (int) $attachment_id;
$mod = WP_Smush::get_instance()->core()->mod;
// Set an option to avoid the smush-restore-smush loop.
set_transient( 'wp-smush-restore-' . $attachment_id, 1, HOUR_IN_SECONDS );
/**
* Delete WebP.
*
* Run WebP::delete_images always even when the module is deactivated.
*
* @since 3.8.0
*/
$mod->webp->delete_images( $attachment_id );
// Restore Full size -> get other image sizes -> restore other images.
// Get the Original Path, supported S3.
$file_path = Helper::get_attached_file( $attachment_id, 'original' );
// Store the restore success/failure for full size image.
$restored = false;
// Retrieve backup file.
$backup_full_path = $this->get_backup_file( $attachment_id, $file_path );
// Is restoring the PNG which is converted to JPG or not.
$restore_png = false;
/**
* Fires before restoring a file.
*
* @since 3.9.6
*
* @param string|false $backup_full_path Full backup path.
* @param int $attachment_id Attachment id.
* @param string $file_path Original unfiltered file path.
*
* @hooked Smush\Core\Integrations\s3::maybe_download_file()
*/
do_action( 'wp_smush_before_restore_backup', $backup_full_path, $attachment_id, $file_path );
// Finally, if we have the backup path, perform the restore operation.
if ( ! empty( $backup_full_path ) ) {
// If the backup file is the same as the main file, we only need to re-generate the metadata.
if ( $backup_full_path === $file_path ) {
$restored = true;
} else {
// Is real backup file or .bak file.
$is_real_filename = false === strpos( $backup_full_path, '.bak' );
$restore_png = Helper::get_file_ext( trim( $backup_full_path ), 'png' ) && ! Helper::get_file_ext( $file_path, 'png' );
if ( $restore_png ) {
// Restore PNG full size.
$org_backup_full_path = $backup_full_path;
if ( ! $is_real_filename ) {
// Try to get a unique file name.
$dirname = dirname( $backup_full_path );
$new_file_name = wp_unique_filename( $dirname, wp_basename( str_replace( '.bak', '', $backup_full_path ) ) );
$new_png_file = path_join( $dirname, $new_file_name );
// Restore PNG full size.
$restored = copy( $backup_full_path, $new_png_file );
if ( $restored ) {
// Assign the new PNG file to the backup file.
$backup_full_path = $new_png_file;
}
} else {
$restored = true;
}
// Restore all other image sizes.
if ( $restored ) {
$metadata = $this->restore_png( $attachment_id, $backup_full_path, $file_path );
$restored = ! empty( $metadata );
if ( $restored && ! $is_real_filename ) {
// Reset the backup file to delete it later.
$backup_full_path = $org_backup_full_path;
}
}
} else {
// If file exists, corresponding to our backup path - restore.
if ( ! $is_real_filename ) {
$restored = copy( $backup_full_path, $file_path );
} else {
$restored = true;
}
}
// Remove the backup, if we were able to restore the image.
if ( $restored ) {
// Remove our backup file.
$this->remove_from_backup_sizes( $attachment_id );
/**
* Delete our backup file if it's .bak file, we will try to backup later when running Smush.
*/
if ( ! $is_real_filename ) {
// It will also delete file from the cloud, e.g. S3.
Helper::delete_permanently( array( $this->backup_key => $backup_full_path ), $attachment_id, false );
}
}
}
} else {
Helper::logger()->backup()->warning( sprintf( 'Backup file [%s(%d)] does not exist.', Helper::clean_file_path( $backup_full_path ), $attachment_id ) );
}
/**
* Regenerate thumbnails
*
* All this is handled in self::restore_png().
*/
if ( $restored ) {
if ( ! $restore_png ) {
// Generate all other image size, and update attachment metadata.
$metadata = wp_generate_attachment_metadata( $attachment_id, $file_path );
}
// Update metadata to db if it was successfully generated.
if ( ! empty( $metadata ) && ! is_wp_error( $metadata ) ) {
Helper::wp_update_attachment_metadata( $attachment_id, $metadata );
} else {
Helper::logger()->backup()->warning( sprintf( 'Meta file [%s(%d)] is empty.', Helper::clean_file_path( $file_path ), $attachment_id ) );
}
}
/**
* Fires before restoring a file.
*
* @since 3.9.6
*
* @param bool $restored Restore status.
* @param string|false $backup_full_path Full backup path.
* @param int $attachment_id Attachment id.
* @param string $file_path Original unfiltered file path.
*/
do_action( 'wp_smush_after_restore_backup', $restored, $backup_full_path, $attachment_id, $file_path );
// If any of the image is restored, we count it as success.
if ( $restored ) {
// Remove the Meta, And send json success.
delete_post_meta( $attachment_id, Smush::$smushed_meta_key );
// Remove PNG to JPG conversion savings.
delete_post_meta( $attachment_id, 'wp-smush-pngjpg_savings' );
// Remove Original File.
delete_post_meta( $attachment_id, 'wp-smush-original_file' );
// Delete resize savings.
delete_post_meta( $attachment_id, 'wp-smush-resize_savings' );
// Remove lossy flag.
delete_post_meta( $attachment_id, 'wp-smush-lossy' );
// Clear backups cache.
wp_cache_delete( 'images_with_backups', 'wp-smush' );
Core::remove_from_smushed_list( $attachment_id );
// Get the Button html without wrapper.
$button_html = WP_Smush::get_instance()->library()->generate_markup( $attachment_id );
// Release the attachment after restoring.
delete_transient( 'wp-smush-restore-' . $attachment_id );
if ( ! $resp ) {
return true;
}
$size = file_exists( $file_path ) ? filesize( $file_path ) : 0;
if ( $size > 0 ) {
$update_size = size_format( $size ); // Used in js to update image stat.
}
wp_send_json_success(
array(
'stats' => $button_html,
'new_size' => isset( $update_size ) ? $update_size : 0,
)
);
}
// Release the attachment after restoring.
delete_transient( 'wp-smush-restore-' . $attachment_id );
if ( $resp ) {
wp_send_json_error( array( 'error_msg' => esc_html__( 'Unable to restore image', 'wp-smushit' ) ) );
}
return false;
}
/**
* Restore PNG.
*
* @param int $attachment_id Attachment ID.
* @param string $backup_file_path Full backup file, the result of self::get_backup_file().
* @param string $file_path File path.
*
* @since 3.9.10 Moved wp_update_attachment_metadata into self::restore_image() after deleting the backup file,
* in order to support S3 - @see SMUSH-1141.
*
* @return bool|array
*/
private function restore_png( $attachment_id, $backup_file_path, $file_path ) {
if ( empty( $attachment_id ) || empty( $backup_file_path ) || empty( $file_path ) ) {
return false;
}
$meta = array();
// Else get the Attachment details.
/**
* For Full Size
* 1. Get the original file path
* 2. Update the attachment metadata and all other meta details
* 3. Delete the JPEG
* 4. And we're done
* 5. Add an action after updating the URLs, that'd allow the users to perform an additional search, replace action
*/
if ( file_exists( $backup_file_path ) ) {
$mod = WP_Smush::get_instance()->core()->mod;
// Update the path details in meta and attached file, replace the image.
$meta = $mod->png2jpg->update_image_path( $attachment_id, $file_path, $backup_file_path, $meta, 'full', 'restore' );
$files_to_remove = array();
// Unlink JPG after updating attached file.
if ( ! empty( $meta['file'] ) && wp_basename( $backup_file_path ) === wp_basename( $meta['file'] ) ) {
/**
* Note, we use size key smush-png2jpg-full for PNG2JPG file to support S3 private media,
* to remove converted JPG file after restoring in private folder.
*
* @see Smush\Core\Integrations\S3::get_object_key()
*/
$files_to_remove['smush-png2jpg-full'] = $file_path;
}
$jpg_meta = wp_get_attachment_metadata( $attachment_id );
foreach ( $jpg_meta['sizes'] as $size_key => $size_data ) {
$size_path = str_replace( wp_basename( $backup_file_path ), wp_basename( $size_data['file'] ), $backup_file_path );
// Add to delete the thumbnails jpg.
$files_to_remove[ $size_key ] = $size_path;
}
// Re-generate metadata for PNG file.
$metadata = wp_generate_attachment_metadata( $attachment_id, $backup_file_path );
// Perform an action after the image URL is updated in post content.
do_action( 'wp_smush_image_url_updated', $attachment_id, $file_path, $backup_file_path );
} else {
Helper::logger()->backup()->warning( sprintf( 'Backup file [%s(%d)] does not exist.', Helper::clean_file_path( $backup_file_path ), $attachment_id ) );
}
if ( ! empty( $metadata ) ) {
// Delete jpg files, we also try to delete these files on cloud, e.g S3.
Helper::delete_permanently( $files_to_remove, $attachment_id, false );
return $metadata;
} else {
Helper::logger()->backup()->warning( sprintf( 'Meta file [%s(%d)] is empty.', Helper::clean_file_path( $backup_file_path ), $attachment_id ) );
}
return false;
}
/**
* Remove a specific backup key from the backup size array.
*
* @param int $attachment_id Attachment ID.
*/
private function remove_from_backup_sizes( $attachment_id ) {
// Get backup sizes.
$backup_sizes = $this->get_backup_sizes( $attachment_id );
// If we don't have any backup sizes list or if the particular key is not set, return.
if ( empty( $backup_sizes ) || ! isset( $backup_sizes[ $this->backup_key ] ) ) {
return;
}
unset( $backup_sizes[ $this->backup_key ] );
if ( empty( $backup_sizes ) ) {
delete_post_meta( $attachment_id, '_wp_attachment_backup_sizes' );
} else {
update_post_meta( $attachment_id, '_wp_attachment_backup_sizes', $backup_sizes );
}
}
/**
* Get the attachments that can be restored.
*
* @since 3.6.0 Changed from private to public.
*
* @return array Array of attachments IDs.
*/
public function get_attachments_with_backups() {
global $wpdb;
$images_to_restore = $wpdb->get_col(
"SELECT post_id FROM {$wpdb->postmeta}
WHERE meta_key='_wp_attachment_backup_sizes'
AND (`meta_value` LIKE '%smush-full%'
OR `meta_value` LIKE '%smush_png_path%')"
);
return $images_to_restore;
}
/**
* Get the number of attachments that can be restored.
*
* @since 3.2.2
*/
public function get_image_count() {
check_ajax_referer( 'smush_bulk_restore' );
// Check for permission.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
}
wp_send_json_success(
array(
'items' => $this->get_attachments_with_backups(),
)
);
}
/**
* Bulk restore images from the modal.
*
* @since 3.2.2
*/
public function restore_step() {
check_ajax_referer( 'smush_bulk_restore' );
// Check for permission.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
}
$id = filter_input( INPUT_POST, 'item', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE );
$media_item = Media_Item_Cache::get_instance()->get( $id );
if ( ! $media_item->is_mime_type_supported() ) {
wp_send_json_error(
array(
/* translators: %s: Error message */
'error_msg' => sprintf( esc_html__( 'Image not restored. %s', 'wp-smushit' ), $media_item->get_errors()->get_error_message() ),
)
);
}
$optimizer = new Media_Item_Optimizer( $media_item );
$status = $id && $optimizer->restore();
$file_name = $media_item->get_full_or_scaled_size()->get_file_name();
wp_send_json_success(
array(
'success' => $status,
'src' => ! empty( $file_name ) ? $file_name : __( 'Error getting file name', 'wp-smushit' ),
'thumb' => wp_get_attachment_image( $id ),
'link' => Helper::get_image_media_link( $id, $file_name, true ),
'error_code' => $status ? '' : $optimizer->get_restoration_errors()->get_error_code(),
)
);
}
/**
* Returns the backup path for attachment
*
* @param string $attachment_path Attachment path.
*
* @return string
*/
public function get_image_backup_path( $attachment_path ) {
if ( empty( $attachment_path ) ) {
return '';
}
$path = pathinfo( $attachment_path );
if ( empty( $path['extension'] ) ) {
return '';
}
return trailingslashit( $path['dirname'] ) . $path['filename'] . '.bak.' . $path['extension'];
}
/**
* Clear up all the backup files for the image while deleting the image.
*
* @since 3.9.6
* Note, we only call this method while deleting the image, as it will delete
* .bak file and might be the original file too.
*
* Note, for the old version < 3.9.6 we also save all PNG files (original file and thumbnails)
* when the site doesn't compress original file.
* But it's not safe to remove them if the user add another image with the same PNG file name, and didn't convert it.
* So we still leave them there.
*
* @param int $attachment_id Attachment ID.
*/
public function delete_backup_files( $attachment_id ) {
$smush_meta = get_post_meta( $attachment_id, Smush::$smushed_meta_key, true );
if ( empty( $smush_meta ) ) {
return;
}
// Save list files to remove.
$files_to_remove = array();
$unfiltered = false;
$file_path = get_attached_file( $attachment_id, false );
// We only work with the real file path, not cloud URL like S3.
if ( false === strpos( $file_path, ABSPATH ) ) {
$unfiltered = true;
$file_path = get_attached_file( $attachment_id, true );
}
// Remove from the cache.
wp_cache_delete( 'images_with_backups', 'wp-smush' );
/**
* We only remove the backup file from the metadata,
* keep the backup file from 3rd-party.
*/
$backup_path = null;// Reset backup file.
$backup_sizes = $this->get_backup_sizes( $attachment_id );
if ( isset( $backup_sizes[ $this->backup_key ]['file'] ) ) {
$backup_path = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes[ $this->backup_key ]['file'] ), $file_path );
// Add to remove the backup file.
$files_to_remove[ $this->backup_key ] = $backup_path;
}
// Check the backup file from resized PNG file (< 3.9.6).
if ( isset( $backup_sizes['smush_png_path']['file'] ) ) {
$backup_path = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes['smush_png_path']['file'] ), $file_path );
// Add to remove the backup file.
$files_to_remove['smush_png_path'] = $backup_path;
}
if ( ! $backup_path ) {
// Check for legacy original file path. It's for old version < V.2.7.0.
$original_file = get_post_meta( $attachment_id, 'wp-smush-original_file', true );
if ( ! empty( $original_file ) ) {
// For old version < v.2.7.0, we are saving meta['file'] or _wp_attached_file.
$backup_path = Helper::original_file( $original_file );
// Add to remove the backup file.
$files_to_remove[] = $backup_path;
}
}
// Check meta for rest of the sizes.
$meta = wp_get_attachment_metadata( $attachment_id, $unfiltered );
if ( empty( $meta ) || empty( $meta['sizes'] ) ) {
Helper::logger()->backup()->info( sprintf( 'Empty meta sizes [%s(%d)]', $file_path, $attachment_id ) );
return;
}
foreach ( $meta['sizes'] as $size ) {
if ( empty( $size['file'] ) ) {
continue;
}
// Image path and backup path.
$image_size_path = path_join( dirname( $file_path ), $size['file'] );
$image_backup_path = $this->get_image_backup_path( $image_size_path );
// Add to remove the backup file.
$files_to_remove[] = $image_backup_path;
}
// We also try to delete this file on cloud, e.g. S3.
Helper::delete_permanently( $files_to_remove, $attachment_id, false );
}
}
PK ME\)p3 3 core/modules/class-resize.phpnu [
*
* @copyright (c) 2016, Incsub (http://incsub.com)
*/
namespace Smush\Core\Modules;
use Smush\Core\Core;
use Smush\Core\Helper;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Resize
*/
class Resize extends Abstract_Module {
/**
* Module slug.
*
* @var string
*/
protected $slug = 'resize';
/**
* Specified width for resizing images
*
* @var int
*/
public $max_w = 0;
/**
* Specified Height for resizing images
*
* @var int
*/
public $max_h = 0;
/**
* If resizing is enabled or not
*
* @var bool
*/
public $resize_enabled = false;
/**
* Resize constructor.
*
* Initialize class variables, after all stuff has been loaded.
*/
public function init() {
add_action( 'admin_init', array( $this, 'initialize' ) );
add_action( 'admin_init', array( $this, 'maybe_disable_module' ), 15 );
// Apply filter(s) if activated resizing.
if ( $this->is_active() ) {
// Add a filter to check if the image should resmush.
//add_filter( 'wp_smush_should_resmush', array( $this, 'should_resmush' ), 10, 2 );
}
}
/**
* Get the settings for resizing
*
* @param bool $skip_check Added for Mobile APP uploads.
*/
public function initialize( $skip_check = false ) {
// Do not initialize unless in the WP Backend Or On one of the smush pages.
if ( ! is_user_logged_in() || ( ! is_admin() && ! $skip_check ) ) {
return;
}
// Make sure the screen function exists.
$current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false;
if ( ! empty( $current_screen ) && ! $skip_check ) {
// Do not Proceed if not on one of the required screens.
if ( ! in_array( $current_screen->base, Core::$external_pages, true ) && false === strpos( $current_screen->base, 'page_smush' ) ) {
return;
}
}
// If resizing is enabled.
$this->resize_enabled = $this->is_active();
$resize_sizes = $this->settings->get_setting( 'wp-smush-resize_sizes', array() );
// Resize width and Height.
$this->max_w = ! empty( $resize_sizes['width'] ) ? $resize_sizes['width'] : 0;
$this->max_h = ! empty( $resize_sizes['height'] ) ? $resize_sizes['height'] : 0;
}
/**
* We do not need this module on WordPress 5.3+.
*
* @since 3.3.2
*/
public function maybe_disable_module() {
global $wp_version;
$this->resize_enabled = version_compare( $wp_version, '5.3.0', '<' ) || $this->settings->get( 'no_scale' );
}
/**
* Checks whether the image should be resized.
*
* @uses self::check_should_resize().
*
* @param string $id Attachment ID.
* @param string $meta Attachment Metadata.
*
* @return bool Should resize or not
*/
public function should_resize( $id = '', $meta = '' ) {
/**
* If resizing not enabled, or if both max width and height is set to 0, return.
*
* Do not use $this->resize_enabled here, because the initialize does not always detect the proper screen
* in the media library or via ajax requests.
*/
if ( ! $this->is_active() || ( 0 === $this->max_w && 0 === $this->max_h ) || ! Helper::is_smushable( $id ) ) {
return false;
}
// Check it from the cache.
if ( null !== Helper::cache_get( $id, 'should_resize' ) ) {
return Helper::cache_get( $id, 'should_resize' );
}
/**
* Filter whether the uploaded image should be resized or not
*
* @since 2.3
*
* @param bool $should_resize Whether to resize the image.
* @param array $id Attachment ID.
* @param array $meta Attachment Metadata.
*/
$should_resize = apply_filters( 'wp_smush_resize_uploaded_image', $this->check_should_resize( $id, $meta ), $id, $meta );
/**
* We used this inside Backup::create_backup() and Smush function
* so cache result to avoid to check it again.
*/
Helper::cache_set( $id, $should_resize, 'should_resize' );
return $should_resize;
}
/**
* Checks whether the image should be resized judging by its properties.
*
* @since 3.8.3
*
* @param string $id Attachment ID.
* @param string $meta Attachment Metadata.
*
* @return bool
*/
private function check_should_resize( $id = '', $meta = '' ) {
/**
* Get unfiltered file path if it exists, otherwise we will use filtered attached file ( e.g s3).
* Please check Png2jpg::__construct() for the detail.
*/
$file_path = Helper::get_attached_file( $id, 'check-resize' );
if ( ! empty( $file_path ) ) {
// Skip: if "noresize" is included in the filename, Thanks to Imsanity.
if ( strpos( $file_path, 'noresize' ) !== false ) {
return false;
}
} else {
// Nothing to check.
return false;
}
// Get attachment metadata.
$meta = empty( $meta ) ? wp_get_attachment_metadata( $id ) : $meta;
if ( empty( $meta['width'] ) || empty( $meta['height'] ) ) {
return false;
}
// If GIF is animated, return.
if ( Helper::check_animated_status( $file_path, $id ) ) {
return false;
}
$old_width = $meta['width'];
$old_height = $meta['height'];
$resize_dim = $this->settings->get_setting( 'wp-smush-resize_sizes' );
$max_width = ! empty( $resize_dim['width'] ) ? $resize_dim['width'] : 0;
$max_height = ! empty( $resize_dim['height'] ) ? $resize_dim['height'] : 0;
if ( ( $old_width > $max_width && $max_width > 0 ) || ( $old_height > $max_height && $max_height > 0 ) ) {
return true;
}
return false;
}
/**
* Check whether to resmush image or not.
*
* @since 3.9.6
*
* @usedby Smush\App\Ajax::scan_images()
*
* @param bool $should_resmush Should resmush status.
* @param int $attachment_id Attachment ID.
* @return bool|string resize|TRUE|FALSE
*/
public function should_resmush( $should_resmush, $attachment_id ) {
if ( ! $should_resmush && $this->should_resize( $attachment_id ) ) {
$should_resmush = 'resize';
}
return $should_resmush;
}
/**
* Handles the Auto resizing of new uploaded images
*
* @param int $id Attachment ID.
* @param mixed $meta Attachment Metadata.
*
* @return mixed Updated/Original Metadata if image was resized or not
*/
public function auto_resize( $id, $meta ) {
// Do not perform resize while restoring images/ Editing images.
if ( ! empty( $_REQUEST['do'] ) && ( 'restore' === $_REQUEST['do'] || 'scale' === $_REQUEST['do'] ) ) {
return $meta;
}
// Check if we should resize the image.
if ( ! $this->should_resize( $id, $meta ) ) {
return $meta;
}
$savings = array(
'bytes' => 0,
'size_before' => 0,
'size_after' => 0,
);
// Good to go.
$file_path = Helper::get_attached_file( $id, 'resize' );// S3+.
// Make sure scaled file exits.
if ( ! file_exists( $file_path ) ) {
return;
}
$original_file_size = filesize( $file_path );
$resize = $this->perform_resize( $file_path, $original_file_size, $id, $meta );
// If resize wasn't successful.
if ( ! $resize || $resize['filesize'] >= $original_file_size ) {
update_post_meta( $id, 'wp-smush-resize_savings', $savings );
return $meta;
}
// Else Replace the Original file with resized file.
$replaced = $this->replace_original_image( $file_path, $resize, $meta );
if ( $replaced ) {
// Clear Stat Cache, Else the size obtained is same as the original file size.
clearstatcache();
// Updated File size.
$u_file_size = filesize( $file_path );
$savings['bytes'] = $original_file_size > $u_file_size ? $original_file_size - $u_file_size : 0;
$savings['size_before'] = $original_file_size;
$savings['size_after'] = $u_file_size;
// Store savings in metadata.
update_post_meta( $id, 'wp-smush-resize_savings', $savings );
$meta['width'] = ! empty( $resize['width'] ) ? $resize['width'] : $meta['width'];
$meta['height'] = ! empty( $resize['height'] ) ? $resize['height'] : $meta['height'];
/**
* Called after the image has been successfully resized
* Can be used to update the stored stats
*/
do_action( 'wp_smush_image_resized', $id, $savings );
/**
* The file resized,
* we can clear the temp cache related to this resizing.
*/
Helper::cache_delete( 'should_resize' );
}
return $meta;
}
/**
* Generates the new image for specified width and height,
* Checks if the size of generated image is greater,
*
* @param string $file_path Original File path.
* @param int $original_file_size File size before optimisation.
* @param int $id Attachment ID.
* @param array $meta Attachment Metadata.
* @param bool $unlink Whether to unlink the original image or not.
*
* @return array|bool|false If the image generation was successful
*/
public function perform_resize( $file_path, $original_file_size, $id, $meta = array(), $unlink = true ) {
/**
* Filter the resize image dimensions
*
* @since 2.3
*
* @param array $sizes {
* Array of sizes containing max width and height for all the uploaded images.
*
* @type int $width Maximum Width For resizing
* @type int $height Maximum Height for resizing
* }
*
* @param string $file_path Original Image file path
*
* @param array $upload {
* Array of upload data.
*
* @type string $file Filename of the newly-uploaded file.
* @type string $url URL of the uploaded file.
* @type string $type File type.
* }
*
* @hooked Png2jpg::cache_can_be_converted_status() Save transparent status before resizing the image.
*/
$sizes = apply_filters(
'wp_smush_resize_sizes',
array(
'width' => $this->max_w,
'height' => $this->max_h,
),
$file_path,
$id
);
$data = image_make_intermediate_size( $file_path, $sizes['width'], $sizes['height'] );
// If the image wasn't resized.
if ( empty( $data['file'] ) ) {
if ( $this->try_gd_fallback() ) {
$data = image_make_intermediate_size( $file_path, $sizes['width'], $sizes['height'] );
}
if ( empty( $data['file'] ) ) {
Helper::logger()->resize()->warning( sprintf( 'Cannot resize image [%s(%d)].', Helper::clean_file_path( $file_path ), $id ) );
return false;
}
}
// Check if file size is lesser than original image.
$resize_path = path_join( dirname( $file_path ), $data['file'] );
if ( ! file_exists( $resize_path ) ) {
Helper::logger()->resize()->notice( sprintf( 'The resized image [%s(%d)] does not exist.', Helper::clean_file_path( $resize_path ), Helper::clean_file_path( $file_path ), $id ) );
return false;
}
$data['file_path'] = $resize_path;
$file_size = filesize( $resize_path );
$data['filesize'] = $file_size;
if ( $file_size > $original_file_size ) {
// Don't Unlink for nextgen images.
if ( $unlink ) {
$this->maybe_unlink( $resize_path, $meta );
}
Helper::logger()->resize()->notice( sprintf( 'The resized image [%s](%s) is larger than the original image [%s(%d)](%s).', Helper::clean_file_path( $resize_path ), size_format( $file_size ), Helper::clean_file_path( $file_path ), $id, size_format( $original_file_size ) ) );
}
return $data;
}
/**
* Fix for WP Engine 'width or height exceeds limit' Imagick error.
*
* If unable to resize with Imagick, try to fallback to GD.
*
* @since 3.4.0
*/
private function try_gd_fallback() {
if ( ! function_exists( 'gd_info' ) ) {
return false;
}
return add_filter(
'wp_image_editors',
function( $editors ) {
$editors = array_diff( $editors, array( 'WP_Image_Editor_GD' ) );
array_unshift( $editors, 'WP_Image_Editor_GD' );
return $editors;
}
);
}
/**
* Replace the original file with resized file
*
* @param string $file_path File path.
* @param mixed $resized Resized.
* @param array $meta Meta.
*
* @return bool
*/
private function replace_original_image( $file_path, $resized, $meta = array() ) {
$replaced = copy( $resized['file_path'], $file_path );
$this->maybe_unlink( $resized['file_path'], $meta );
return $replaced;
}
/**
* Return Filename.
*
* @param string $filename Filename.
*
* @return mixed
*/
public function file_name( $filename ) {
if ( empty( $filename ) ) {
return $filename;
}
return $filename . 'tmp';
}
/**
* Do not unlink the resized file if the name is similar to one of the image sizes
*
* @param string $path Image File Path.
* @param array $meta Image Meta.
*
* @return bool
*/
private function maybe_unlink( $path, $meta ) {
if ( empty( $path ) || ! file_exists( $path ) ) {
return true;
}
// Unlink directly if meta value is not specified.
if ( empty( $meta['sizes'] ) ) {
unlink( $path );
}
$unlink = true;
// Check if the file name is similar to one of the image sizes.
$path_parts = pathinfo( $path );
$filename = ! empty( $path_parts['basename'] ) ? $path_parts['basename'] : $path_parts['filename'];
if ( ! empty( $meta['sizes'] ) ) {
foreach ( $meta['sizes'] as $image_size ) {
if ( false === strpos( $image_size['file'], $filename ) ) {
continue;
}
$unlink = false;
break;
}
}
if ( $unlink ) {
unlink( $path );
}
return true;
}
}
PK RE\ / core/modules/background/class-async-request.phpnu [ identifier = $identifier;
add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
}
/**
* Set data used during the request
*
* @param array $data Data.
*
* @return $this
*/
public function data( $data ) {
$this->data = $data;
return $this;
}
/**
* Dispatch the async request
*
* @param int $instance_id
*
* @return array|\WP_Error
*/
public function dispatch( $instance_id ) {
$query_args = $this->get_query_args( $instance_id );
$url = add_query_arg( $query_args, $this->get_query_url() );
$args = $this->get_post_args();
return wp_remote_post( esc_url_raw( $url ), $args );
}
/**
* Get query args
*
* @return array
*/
protected function get_query_args( $instance_id ) {
if ( property_exists( $this, 'query_args' ) ) {
return $this->query_args;
}
return array(
'action' => $this->identifier,
'nonce' => wp_create_nonce( $this->identifier ),
'instance_id' => $instance_id,
);
}
/**
* Get query URL
*
* @return string
*/
protected function get_query_url() {
if ( property_exists( $this, 'query_url' ) ) {
return $this->query_url;
}
return admin_url( 'admin-ajax.php' );
}
/**
* Get process headers.
*
* @return array
*/
protected function get_process_headers() {
$headers = array();
if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
}
return apply_filters( $this->identifier . '_process_headers', $headers );
}
/**
* Get post args
*
* @return array
*/
protected function get_post_args() {
if ( property_exists( $this, 'post_args' ) ) {
return $this->post_args;
}
$post_args = array(
'timeout' => 0.01,
'blocking' => false,
'body' => $this->data,
'cookies' => $_COOKIE,
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
);
$headers = $this->get_process_headers();
if ( ! empty( $headers ) ) {
$post_args['headers'] = $headers;
}
return $post_args;
}
/**
* Maybe handle
*
* Check for correct nonce and pass to handler.
*/
public function maybe_handle() {
// Don't lock up other requests while processing
session_write_close();
check_ajax_referer( $this->identifier, 'nonce' );
$instance_id = empty( $_GET['instance_id'] )
? false
: sanitize_key( $_GET['instance_id'] );
$this->handle( $instance_id );
wp_die();
}
/**
* Handle
*
* Override this method to perform any actions required
* during the async request.
*/
abstract protected function handle( $instance_id );
}
PK UE\^q\ 2 core/modules/background/class-background-utils.phpnu [ options;
$column = 'option_name';
$key_column = 'option_id';
$value_column = 'option_value';
if ( is_multisite() ) {
$table = $wpdb->sitemeta;
$column = 'meta_key';
$key_column = 'meta_id';
$value_column = 'meta_value';
}
return $this->get_value_from_db( $table, $column, $key_column, $option_id, $value_column, $default );
}
public function get_option( $option_id, $default = false ) {
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
$key_column = 'option_id';
$value_column = 'option_value';
return $this->get_value_from_db( $table, $column, $key_column, $option_id, $value_column, $default );
}
private function get_value_from_db( $table, $column, $key_column, $option_id, $value_column, $default ) {
global $wpdb;
$row = $wpdb->get_row( $wpdb->prepare( "
SELECT *
FROM {$table}
WHERE {$column} = %s
ORDER BY {$key_column} ASC
LIMIT 1
", $option_id ) );
if ( empty( $row->$value_column ) || ! is_object( $row ) ) {
return $default;
}
return maybe_unserialize( $row->$value_column );
}
}
PK WE\ժ' ' ; core/modules/background/class-background-process-status.phpnu [ identifier = $identifier;
$this->utils = new Background_Utils();
}
public function get_data() {
$option_value = $this->utils->get_site_option(
$this->get_option_id(),
array()
);
return wp_parse_args(
$option_value,
array(
self::$processing => false,
self::$cancelled => false,
self::$completed => false,
self::$total_items => 0,
self::$processed_items => 0,
self::$failed_items => 0,
)
);
}
public function to_array() {
return $this->get_data();
}
private function set_data( $updated ) {
$data = $this->get_data();
update_site_option( $this->get_option_id(), array_merge( $data, $updated ) );
}
private function get_value( $key ) {
$data = $this->get_data();
return isset( $data[ $key ] )
? $data[ $key ]
: false;
}
private function set_value( $key, $value ) {
$this->mutex( function () use ( $key, $value ) {
$updated_data = array_merge(
$this->get_data(),
array( $key => $value )
);
update_site_option( $this->get_option_id(), $updated_data );
} );
}
private function get_option_id() {
return $this->identifier . '_status';
}
public function is_in_processing() {
return $this->get_value( self::$processing );
}
public function set_in_processing( $in_processing ) {
$this->set_value( self::$processing, $in_processing );
}
public function get_total_items() {
return $this->get_value( self::$total_items );
}
public function set_total_items( $total_items ) {
$this->set_value( self::$total_items, $total_items );
}
public function get_processed_items() {
return $this->get_value( self::$processed_items );
}
public function set_processed_items( $processed_items ) {
$this->set_value( self::$processed_items, $processed_items );
}
public function get_failed_items() {
return $this->get_value( self::$failed_items );
}
public function set_failed_items( $failed_items ) {
$this->set_value( self::$processed_items, $failed_items );
}
public function is_cancelled() {
return $this->get_value( self::$cancelled );
}
public function set_is_cancelled( $is_cancelled ) {
$this->set_value( self::$cancelled, $is_cancelled );
}
public function is_dead() {
return $this->get_value( self::$dead );
}
public function is_completed() {
return $this->get_value( self::$completed );
}
public function set_is_completed( $is_completed ) {
$this->set_value( self::$completed, $is_completed );
}
private function mutex( $operation ) {
$mutex = new Mutex( $this->get_option_id() );
$mutex->execute( $operation );
}
public function start( $total_items ) {
$this->mutex( function () use ( $total_items ) {
$this->set_data( array(
self::$processing => true,
self::$cancelled => false,
self::$dead => false,
self::$completed => false,
self::$total_items => $total_items,
self::$processed_items => 0,
self::$failed_items => 0,
) );
} );
}
public function complete() {
$this->mutex( function () {
$this->set_data( array(
self::$processing => false,
self::$cancelled => false,
self::$dead => false,
self::$completed => true,
) );
} );
}
public function cancel() {
$this->mutex( function () {
$this->set_data( array(
self::$processing => false,
self::$cancelled => true,
self::$dead => false,
self::$completed => false,
) );
} );
}
public function mark_as_dead() {
$this->mutex( function () {
$this->set_data( array(
self::$processing => false,
self::$cancelled => false,
self::$dead => true,
self::$completed => false,
) );
} );
}
public function task_successful() {
$this->mutex( function () {
$this->set_data( array(
self::$processed_items => $this->get_processed_items() + 1,
) );
} );
}
public function task_failed() {
$this->mutex( function () {
$this->set_data( array(
self::$processed_items => $this->get_processed_items() + 1,
self::$failed_items => $this->get_failed_items() + 1,
) );
} );
}
}
PK ZE\HHF HF 4 core/modules/background/class-background-process.phpnu [ cron_hook_identifier = $this->identifier . '_cron';
$this->cron_interval_identifier = $this->identifier . '_cron_interval';
add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
add_action( 'init', function () {
add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
} );
$this->logger_container = new Background_Logger_Container( $this->identifier );
$this->status = new Background_Process_Status( $this->identifier );
$this->utils = new Background_Utils();
$this->server_utils = new Server_Utils();
}
private function generate_unique_id() {
return md5( microtime() . rand() );
}
/**
* Dispatch
*
* @access public
* @return array|\WP_Error
*/
public function dispatch( $instance_id ) {
$this->logger()->info( "Dispatching a new request for instance $instance_id." );
// Schedule the cron healthcheck.
$this->schedule_event();
// Perform remote post.
return parent::dispatch( $instance_id );
}
public function spawn() {
$instance_id = $this->generate_unique_id();
$this->logger()->info( "Spawning a brand new instance (ID: $instance_id) for the process." );
$this->set_active_instance_id( $instance_id );
$this->dispatch( $instance_id );
}
/**
* Update queue
*
* @param array $tasks An array of tasks.
*/
private function update_queue( $tasks ) {
if ( ! empty( $tasks ) ) {
update_site_option( $this->get_queue_key(), $tasks );
}
}
/**
* Delete queue
*/
private function delete_queue() {
delete_site_option( $this->get_queue_key() );
}
/**
* Generate key
*
* Generates a unique key based on microtime. Queue items are
* given a unique key so that they can be merged upon save.
*
* @return string
*/
protected function get_queue_key() {
return $this->identifier . '_queue';
}
/**
* Maybe process queue
*
* Checks whether data exists within the queue and that
* the process is not already running.
*/
public function maybe_handle() {
// Don't lock up other requests while processing
session_write_close();
$this->mutex( function () {
$instance_id = empty( $_GET['instance_id'] )
? false
: wp_unslash( $_GET['instance_id'] );
if ( $this->is_queue_empty() ) {
$this->logger()->warning( "Handler called with instance ID $instance_id but the queue is empty. Killing this instance." );
return;
}
if ( ! $instance_id || ! $this->is_active_instance( $instance_id ) ) {
// We thought the process died, so we spawned a new instance.
// Kill this instance and let the new one continue.
$active_instance_id = $this->get_active_instance_id();
$this->logger()->warning( "Handler called with instance ID $instance_id but the active instance ID is $active_instance_id. Killing $instance_id so $active_instance_id can continue." );
return;
}
if ( ! check_ajax_referer( $this->identifier, 'nonce', false ) ) {
return;
}
$this->handle( $instance_id );
} );
wp_die();
}
/**
* Is queue empty
*
* @return bool
*/
protected function is_queue_empty() {
return empty( $this->get_queue() );
}
/**
* Is process running
*
* Check whether the current process is already running
* in a background process.
*/
protected function is_process_running() {
if ( get_site_transient( $this->get_last_run_transient_key() ) ) {
// Process already running.
return true;
}
return false;
}
protected function update_timestamp( $instance_id ) {
$timestamp = time();
$this->start_time = $timestamp; // Set start time of current process.
set_site_transient(
$this->get_last_run_transient_key(),
$timestamp,
$this->get_instance_expiry_duration_seconds()
);
$human_readable_timestamp = wp_date( 'Y-m-d H:i:s', $timestamp );
$this->logger()->info( "Setting last run timestamp for instance ID $instance_id to $human_readable_timestamp" );
}
/**
* Get queue
*
* @return array Return the first queue from the queue
*/
protected function get_queue() {
$queue = $this->utils->get_site_option( $this->get_queue_key(), array() );
return empty( $queue ) || ! is_array( $queue )
? array()
: $queue;
}
/**
* Handle
*
* Pass each queue item to the task handler, while remaining
* within server memory and time limit constraints.
*/
protected function handle( $instance_id ) {
$this->logger()->info( "Handling instance ID $instance_id." );
$this->update_timestamp( $instance_id );
$queue = $this->get_queue();
$processed_tasks_count = 0;
foreach ( $queue as $key => $value ) {
$this->logger()->info( "Executing task $value." );
$task = $this->task( $value );
if ( $task ) {
$this->status->task_successful();
} else {
$this->status->task_failed();
}
if ( $this->status->is_cancelled() ) {
$this->logger()->info( "While we were busy doing the task $value, the process got cancelled. Clean up and stop." );
return;
}
unset( $queue[ $key ] );
if ( $this->should_update_queue_after_task() ) {
$this->update_queue( $queue );
}
$processed_tasks_count ++;
if ( $this->task_limit_reached( $processed_tasks_count ) ) {
$tasks_per_request = $this->get_tasks_per_request();
$this->logger()->info( "Stopping because we are only supposed to perform $tasks_per_request tasks in a single request and we have reached that limit." );
break;
}
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
$this->logger()->warning( "Time/Memory limits reached, save the queue and dispatch a new request." );
break;
}
}
$this->logger()->info( sprintf( 'Processing time: %d seconds', time() - $this->start_time ) );
if ( empty( $queue ) ) {
$this->complete();
} else {
if ( ! $this->should_update_queue_after_task() ) {
$this->update_queue( $queue );
}
$this->dispatch( $instance_id );
}
}
/**
* Memory exceeded
*
* Ensures the process never exceeds 90%
* of the maximum WordPress memory.
*
* @return bool
*/
protected function memory_exceeded() {
$memory_limit = $this->server_utils->get_memory_limit() * 0.75; // 75% of max memory
$current_memory = $this->server_utils->get_memory_usage();
$return = false;
if ( $current_memory >= $memory_limit ) {
$return = true;
}
return apply_filters( $this->identifier . '_memory_exceeded', $return );
}
/**
* Time exceeded.
*
* Ensures the process never exceeds a sensible time limit.
* A timeout limit of 30s is common on shared hosting.
*
* @return bool
*/
protected function time_exceeded() {
$finish = $this->start_time + $this->get_time_limit();
$return = false;
if ( time() >= $finish ) {
$return = true;
}
return apply_filters( $this->identifier . '_time_exceeded', $return );
}
/**
* Complete.
*
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
protected function complete() {
$this->do_action( 'completed' );
$this->logger()->info( "Process completed." );
$this->cleanup();
$this->status->complete();
}
/**
* Schedule cron healthcheck
*
* @access public
*
* @param mixed $schedules Schedules.
*
* @return mixed
*/
public function schedule_cron_healthcheck( $schedules ) {
$interval = $this->get_cron_interval_seconds();
// Adds every 5 minutes to the existing schedules.
$schedules[ $this->identifier . '_cron_interval' ] = array(
'interval' => $interval,
/* translators: %s: Cron interval in minutes */
'display' => sprintf( __( 'Every %d Minutes', 'wp-smushit' ), $interval / MINUTE_IN_SECONDS ),
);
return $schedules;
}
/**
* Handle cron healthcheck
*
* Restart the background process if not already running
* and data exists in the queue.
*/
public function handle_cron_healthcheck() {
$mutex = new Mutex( $this->identifier . '_cron_healthcheck' );
$mutex->set_break_on_timeout( true )
->set_timeout( 1 ) // We don't want two health checks running
->execute( function () {
$this->logger()->info( "Running scheduled health check." );
if ( $this->is_process_running() ) {
$this->logger()->info( "Health check: Process seems healthy, no action required." );
exit;
}
if ( $this->is_queue_empty() ) {
$this->logger()->info( "Health check: Process not in progress but the queue is empty, no action required." );
$this->clear_scheduled_event();
exit;
}
if ( $this->status->is_cancelled() ) {
$this->logger()->info( "Health check: Process has been cancelled already, no action required." );
$this->clear_scheduled_event();
exit;
}
if ( ! $this->is_revival_limit_reached() ) {
$this->logger()->warning( "Health check: Process instance seems to have died. Spawn a new instance." );
$this->revive_process();
} else {
$this->logger()->warning( "Health check: Process instance seems to have died. Restart disabled, marking the process as dead." );
$this->mark_as_dead();
}
} );
exit;
}
private function revive_process() {
$this->do_action( 'revived' );
$this->increment_revival_count();
$this->spawn();
}
protected function mark_as_dead() {
$this->do_action( 'dead' );
$this->status->mark_as_dead();
$this->cleanup();
}
/**
* Schedule event
*/
protected function schedule_event() {
$hook = $this->cron_hook_identifier;
if ( ! wp_next_scheduled( $hook ) ) {
$interval = $this->cron_interval_identifier;
$next_run = time() + $this->get_cron_interval_seconds();
wp_schedule_event( $next_run, $interval, $hook );
$this->logger()->info( "Scheduling new event with hook $hook to run $interval." );
}
}
/**
* Clear scheduled event
*/
protected function clear_scheduled_event() {
$hook = $this->cron_hook_identifier;
$this->logger()->info( "Cancelling event with hook $hook." );
wp_clear_scheduled_hook( $hook );
}
/**
* Cancel Process
*
* Stop processing queue items, clear cronjob and delete queue.
*/
private function cancel_process() {
$this->cleanup();
$this->logger()->info( "Process cancelled." );
}
public function cancel() {
// Update the cancel flag first
$active_instance_id = $this->get_active_instance_id();
$this->logger()->info( "Starting cancellation (Instance: $active_instance_id)." );
$this->status->cancel();
// Since actual cancellation involves deletion of the queue and the handler
// might be in the middle of updating the queue, we need to use a mutex
$mutex = new Mutex( $this->get_handler_mutex_id() );
$mutex
->set_break_on_timeout( false ) // Since this is a user operation, we must cancel, even if there is a timeout
->set_timeout( $this->get_time_limit() ) // Shouldn't take more time than the time allocated to the process itself
->execute( function () use ( $active_instance_id ) {
// Do this before cleanup, so we still have data available to us
$this->do_action( 'cancelled' );
$this->logger()->info( "Cancelling the process (Instance: $active_instance_id)." );
$this->cancel_process();
$this->logger()->info( "Cancellation completed (Instance: $active_instance_id)." );
} );
}
/**
* Task
*
* Override this method to perform any actions required on each
* queue item. Return the modified item for further processing
* in the next pass through. Or, return false to remove the
* item from the queue.
*
* @param mixed $task Queue item to iterate over.
*
* @return mixed
*/
abstract protected function task( $task );
private function is_active_instance( $instance_id ) {
return $instance_id === $this->get_active_instance_id();
}
/**
* Save the unique ID of the process we are presuming to be dead, so we can prevent it from coming back.
*
* @param $instance_id
*
* @return void
*/
private function set_active_instance_id( $instance_id ) {
update_site_option( $this->get_active_instance_option_id(), $instance_id );
}
private function get_active_instance_id() {
return get_site_option( $this->get_active_instance_option_id(), '' );
}
private function get_active_instance_option_id() {
return $this->identifier . '_active_instance';
}
private function set_process_id( $instance_id ) {
update_site_option( $this->get_process_id_option_key(), $instance_id );
}
public function get_process_id() {
return get_site_option( $this->get_process_id_option_key() );
}
private function delete_process_id() {
delete_site_option( $this->get_process_id_option_key() );
}
private function get_process_id_option_key() {
return $this->identifier . '_process_id';
}
public function set_logger( $logger ) {
$this->logger_container->set_logger( $logger );
}
/**
* @return Background_Logger_Container
*/
private function logger() {
return $this->logger_container;
}
public function get_status() {
return $this->status;
}
/**
* @param $tasks array
*
* @return void
*/
public function start( $tasks ) {
$this->do_action( 'before_start' );
$total_items = count( $tasks );
$this->status->start( $total_items );
$this->update_queue( $tasks );
// Generate ID for the whole process.
$this->set_process_id( $this->generate_unique_id() );
$this->logger()->info( "Starting new process with $total_items tasks" );
// Trigger the started event before dispatching the request to ensure it is called before the completed event.
$this->do_action( 'started' );
$this->spawn();
}
private function mutex( $operation ) {
$mutex = new Mutex( $this->get_handler_mutex_id() );
$mutex->set_break_on_timeout( true ) // Let the previous handler do its thing
->set_timeout( $this->get_lock_duration() )
->execute( $operation );
}
private function get_handler_mutex_id() {
return $this->identifier . '_handler_lock';
}
private function get_time_limit() {
return apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
}
private function get_lock_duration() {
$lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
return apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
}
protected function get_instance_expiry_duration_seconds() {
return MINUTE_IN_SECONDS * 2;
}
private function get_last_run_transient_key() {
return $this->identifier . '_last_run';
}
private function clear_last_run_timestamp() {
delete_site_transient( $this->get_last_run_transient_key() );
}
private function cleanup() {
// Delete options and transients
$this->delete_queue();
delete_site_option( $this->get_active_instance_option_id() );
$this->delete_process_id();
$this->delete_revival_count();
$this->clear_last_run_timestamp();
// Cancel all events
$this->clear_scheduled_event();
}
private function task_limit_reached( $processed_tasks_count ) {
if ( $this->get_tasks_per_request() === self::$tasks_per_request_unlimited ) {
return false;
}
return $processed_tasks_count >= $this->get_tasks_per_request();
}
public function get_tasks_per_request() {
return $this->tasks_per_request ?? self::$tasks_per_request_unlimited;
}
/**
* @param int $tasks_per_request
*/
public function set_tasks_per_request( $tasks_per_request ) {
$this->tasks_per_request = $tasks_per_request;
}
private function do_action( $action ) {
do_action( $this->action_name( $action ), $this->identifier, $this );
}
private function get_cron_interval_seconds() {
$minutes = property_exists( $this, 'cron_interval' )
? $this->cron_interval
: 5;
$interval = apply_filters( $this->identifier . '_cron_interval', $minutes );
return $interval * MINUTE_IN_SECONDS;
}
public function get_identifier() {
return $this->identifier;
}
protected function should_update_queue_after_task() {
return false;
}
private function increment_revival_count() {
$revival_count = $this->get_revival_count();
$this->set_revival_count( $revival_count + 1 );
}
private function set_revival_count( $instance_id ) {
update_site_option( $this->get_revival_count_option_key(), $instance_id );
}
public function get_revival_count() {
return (int) get_site_option( $this->get_revival_count_option_key(), 0 );
}
private function delete_revival_count() {
delete_site_option( $this->get_revival_count_option_key() );
}
private function get_revival_count_option_key() {
return $this->identifier . '_revival_count';
}
protected function get_revival_limit() {
return apply_filters( $this->identifier . '_revival_limit', 5 );
}
protected function is_revival_limit_reached() {
return $this->get_revival_count() >= $this->get_revival_limit();
}
/**
* @param $action
*
* @return string
*/
public function action_name( $action ) {
return "{$this->identifier}_$action";
}
}
PK ]E\ = core/modules/background/class-background-logger-container.phpnu [ identifier = $identifier;
}
public function set_logger( $logger ) {
$this->logger = $logger;
}
public function error( $message ) {
$this->log( $message, 'error' );
}
public function notice( $message ) {
$this->log( $message, 'notice' );
}
public function warning( $message ) {
$this->log( $message, 'warning' );
}
public function info( $message ) {
$this->log( $message, 'info' );
}
private function log( $message, $type ) {
if ( $this->logger && method_exists( $this->logger, $type ) ) {
$this->logger->$type(
$this->prepare_message( $message )
);
}
}
private function prepare_message( $message ) {
$identifier = $this->identifier;
return "Background $identifier: $message";
}
}
PK aE\ B͞ B core/modules/background/class-background-pre-flight-controller.phpnu [ array_utils = new Array_Utils();
// the constructor for Loopback_Request_Tester needs to be called in all requests because it adds some ajax hooks
$this->loopback_tester = new Loopback_Request_Tester();
$this->register_action( 'wp_ajax_smush_start_background_pre_flight_check', array(
$this,
'start_pre_flight_check_ajax',
) );
$this->register_action( 'wp_ajax_smush_get_background_pre_flight_status', array(
$this,
'get_background_pre_flight_status_ajax',
) );
}
public function start_pre_flight_check_ajax() {
check_ajax_referer( 'wp-smush-ajax' );
if ( Helper::is_user_allowed() ) {
$this->start_pre_flight_check();
wp_send_json_success();
} else {
wp_send_json_error();
}
}
public function get_background_pre_flight_status_ajax() {
check_ajax_referer( 'wp-smush-ajax' );
if ( Helper::is_user_allowed() && $this->is_test_performed() ) {
wp_send_json_success( array(
'cron' => $this->is_cron_healthy(),
'loopback' => $this->is_loopback_healthy(),
) );
} else {
wp_send_json_error();
}
}
private function start_pre_flight_check() {
$this->reset_pre_flight_option();
$this->loopback_tester->test();
}
public function is_cron_healthy() {
$common_cron_hooks = array(
'wp_version_check',
'wp_update_plugins',
);
foreach ( $common_cron_hooks as $hook ) {
$next_scheduled_time = wp_next_scheduled( $hook );
if ( ! $next_scheduled_time ) {
continue;
}
$delayed_time = time() - $next_scheduled_time;
// If any of the core cron hooks are delayed by more than 30 minutes, then cron is unhealthy.
return $delayed_time < ( HOUR_IN_SECONDS / 2 );
}
return false;
}
public function is_loopback_healthy() {
return $this->is_item_healthy( 'loopback' );
}
public function set_loopback_healthy() {
$this->set_item_healthy( 'loopback' );
}
public function set_item_healthy( $item ) {
$background_pre_flight = $this->get_pre_flight_option();
$background_pre_flight[ $item ] = time();
$this->update_pre_flight_option( $background_pre_flight );
}
private function is_item_healthy( $item ) {
$background_pre_flight = $this->get_pre_flight_option();
$item_timestamp = (int) $this->array_utils->get_array_value( $background_pre_flight, $item );
$cutoff = time() - DAY_IN_SECONDS;
return $item_timestamp > ( $cutoff );
}
private function reset_pre_flight_option() {
delete_option( self::$background_pre_flight_option );
wp_cache_delete( self::$background_pre_flight_option, 'options' );
}
private function is_test_performed() {
return ! empty( $this->get_pre_flight_option() );
}
/**
* @return false|mixed|null
*/
private function get_pre_flight_option() {
return get_option( self::$background_pre_flight_option, array() );
}
/**
* @param $background_pre_flight
*
* @return void
*/
private function update_pre_flight_option( $background_pre_flight ) {
update_option( self::$background_pre_flight_option, $background_pre_flight, false );
}
}
PK dE\ËM
M
' core/modules/background/class-mutex.phpnu [ key = $key;
}
public function execute( $operation ) {
if ( $this->is_supported() ) {
$acquired = $this->acquire_lock();
if ( $acquired || ! $this->break_on_timeout() ) {
call_user_func( $operation );
}
$this->release_lock();
} else {
call_user_func( $operation );
}
}
private function acquire_lock() {
global $wpdb;
$lock = $wpdb->get_row(
$wpdb->prepare(
'SELECT GET_LOCK(%s,%d) as lock_set',
array(
$this->get_key(),
$this->get_timeout(),
)
)
);
return 1 === intval( $lock->lock_set );
}
private function release_lock() {
global $wpdb;
$wpdb->get_row(
$wpdb->prepare(
'SELECT RELEASE_LOCK(%s) as lock_released',
array( $this->get_key() )
)
);
}
/**
* @return bool
*/
public function break_on_timeout() {
return $this->break_on_timeout;
}
/**
* @param bool $break_on_timeout
*/
public function set_break_on_timeout( $break_on_timeout ) {
$this->break_on_timeout = $break_on_timeout;
return $this;
}
/**
* @return int
*/
public function get_timeout() {
return $this->timeout;
}
/**
* @param int $timeout
*/
public function set_timeout( $timeout ) {
$this->timeout = $timeout;
return $this;
}
/**
* @return string
*/
public function get_key() {
return $this->key;
}
/**
* @param string $key
*/
public function set_key( $key ) {
$this->key = $key;
return $this;
}
private function is_supported() {
return $this->is_mysql_requirement_met();
}
private function get_actual_mysql_version() {
if ( ! $this->mysql_version ) {
global $wpdb;
/**
* MariaDB version prefix 5.5.5- is not stripped when using $wpdb->db_version() to get the DB version:
* https://github.com/php/php-src/issues/7972
*/
$this->mysql_version = $wpdb->get_var( 'SELECT VERSION()' );
}
return $this->mysql_version;
}
private function is_mysql_requirement_met() {
return version_compare( $this->get_actual_mysql_version(), $this->get_required_mysql_version(), '>=' );
}
private function get_required_mysql_version() {
return self::$required_mysql_version;
}
}
PK fE\gW 9 core/modules/background/class-loopback-request-tester.phpnu [ set_loopback_healthy();
}
public function test() {
$this->dispatch( self::$id );
}
}
PK iE\h h core/class-configs.phpnu [ settings = Settings::get_instance();
}
public static function get_instance() {
if ( empty( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
public function __call( $method_name, $arguments ) {
_deprecated_function( esc_html( $method_name ), '3.24.0' );
}
/**
* Gets the local list of configs via Smush endpoint.
*
* @since 3.8.6
*
* @return bool
*/
public function get_callback() {
$stored_configs = get_site_option( 'wp-smush-preset_configs', false );
if ( false === $stored_configs ) {
$stored_configs = array( $this->get_basic_config() );
update_site_option( 'wp-smush-preset_configs', $stored_configs );
}
return $stored_configs;
}
/**
* Updates the local list of configs via Smush endpoint.
*
* @since 3.8.6
*
* @param WP_REST_Request $request Class containing the request data.
*
* @return array|WP_Error
*/
public function post_callback( $request ) {
$data = json_decode( $request->get_body(), true );
if ( ! is_array( $data ) ) {
return new WP_Error( '400', esc_html__( 'Missing configs data', 'wp-smushit' ), array( 'status' => 400 ) );
}
$sanitized_data = $this->sanitize_configs_list( $data );
update_site_option( 'wp-smush-preset_configs', $sanitized_data );
return $sanitized_data;
}
/**
* Checks whether the current user can perform requests to Smush's endpoint.
*
* @since 3.8.6
*
* @return bool
*/
public function permission_callback() {
$capability = is_multisite() ? 'manage_network' : 'manage_options';
return current_user_can( $capability );
}
/**
* Adds the default configuration to the local configs.
*
* @since 3.8.6
*
* TODO: Add get_defaults for Settings class and use it here.
*/
private function get_basic_config() {
$basic_config = array(
'id' => 1,
'name' => __( 'Default config', 'wp-smushit' ),
'description' => __( 'Recommended performance config for every site.', 'wp-smushit' ),
'default' => true,
'config' => array(
'configs' => array(
'settings' => array(
'auto' => true,
'lossy' => Settings::get_level_super_lossy(),
'strip_exif' => true,
'resize' => false,
'detection' => false,
'original' => true,
'backup' => true,
'png_to_jpg' => true,
'background_email' => false,
'nextgen' => false,
's3' => false,
'gutenberg' => false,
'js_builder' => false,
'cdn' => false,
'auto_resizing' => false,
'cdn_dynamic_sizes' => false,
'image_dimensions' => false,
'webp' => true,
'usage' => false,
'accessible_colors' => false,
'keep_data' => true,
'lazy_load' => false,
'background_images' => true,
'rest_api_support' => false,
'webp_mod' => false,
'avif_mod' => false,
'preload_images' => false,
),
),
),
);
$basic_config['config']['strings'] = $this->format_config_to_display( $basic_config['config']['configs'] );
return $basic_config;
}
/**
* Sanitizes the full list of configs.
*
* @since 3.8.6
*
* @param array $configs_list Configs list to sanitize.
* @return array
*/
private function sanitize_configs_list( $configs_list ) {
$sanitized_list = array();
foreach ( $configs_list as $config_data ) {
if ( isset( $config_data['name'] ) ) {
$name = sanitize_text_field( $config_data['name'] );
}
if ( isset( $config_data['description'] ) ) {
$description = sanitize_text_field( $config_data['description'] );
}
$configs = isset( $config_data['config']['configs'] ) ? $config_data['config']['configs'] : array();
$sanitized_data = array(
'id' => filter_var( $config_data['id'], FILTER_VALIDATE_INT ),
'name' => empty( $name ) ? __( 'Undefined', 'wp-smushit' ) : $name,
'description' => empty( $description ) ? '' : $description,
'config' => $this->sanitize_and_format_configs( $configs ),
);
if ( ! empty( $config_data['hub_id'] ) ) {
$sanitized_data['hub_id'] = filter_var( $config_data['hub_id'], FILTER_VALIDATE_INT );
}
if ( isset( $config_data['default'] ) ) {
$sanitized_data['default'] = filter_var( $config_data['default'], FILTER_VALIDATE_BOOLEAN );
}
$sanitized_list[] = $sanitized_data;
}
return $sanitized_list;
}
/**
* Tries to save the uploaded config.
*
* @since 3.8.5
*
* @param array $file The uploaded file.
*
* @return array|WP_Error
*/
public function save_uploaded_config( $file ) {
try {
return $this->decode_and_validate_config_file( $file );
} catch ( Exception $e ) {
return new WP_Error( 'error_saving', $e->getMessage() );
}
}
/**
* Tries to decode and validate the uploaded config file.
*
* @since 3.8.5
*
* @param array $file The uploaded file.
*
* @return array
*
* @throws Exception When there's an error with the uploaded file.
*/
private function decode_and_validate_config_file( $file ) {
if ( ! $file ) {
throw new Exception( __( 'The configs file is required', 'wp-smushit' ) );
} elseif ( ! empty( $file['error'] ) ) {
/* translators: error message */
throw new Exception( sprintf( __( 'Error: %s.', 'wp-smushit' ), $file['error'] ) );
} elseif ( 'application/json' !== $file['type'] ) {
throw new Exception( __( 'The file must be a JSON.', 'wp-smushit' ) );
}
$json_file = file_get_contents( $file['tmp_name'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
if ( ! $json_file ) {
throw new Exception( __( 'There was an error getting the contents of the file.', 'wp-smushit' ) );
}
$configs = json_decode( $json_file, true );
if ( empty( $configs ) || ! is_array( $configs ) ) {
throw new Exception( __( 'There was an error decoding the file.', 'wp-smushit' ) );
}
// Make sure the config has a name and configs.
if ( empty( $configs['name'] ) || empty( $configs['config'] ) ) {
throw new Exception( __( 'The uploaded config must have a name and a set of settings. Please make sure the uploaded file is the correct one.', 'wp-smushit' ) );
}
// Sanitize.
$plugin = isset( $configs['plugin'] ) ? $configs['plugin'] : 0;
$configs = $this->sanitize_configs_list( array( $configs ) );
$configs = $configs[0];
// Restore back plugin ID.
$configs['plugin'] = $plugin;
// Let's re-create this to avoid differences between imported settings coming from other versions.
$configs['config']['strings'] = $this->format_config_to_display( $configs['config']['configs'] );
if ( empty( $configs['config']['configs'] ) ) {
throw new Exception( __( 'The provided configs list isn’t correct. Please make sure the uploaded file is the correct one.', 'wp-smushit' ) );
}
// Don't keep these if they exist.
if ( isset( $configs['hub_id'] ) ) {
unset( $configs['hub_id'] );
}
if ( isset( $configs['default'] ) ) {
unset( $configs['default'] );
}
return $configs;
}
/**
* Applies a config given its ID.
*
* @since 3.8.6
*
* @param string $id The ID of the config to apply.
*
* @return void|WP_Error
*/
public function apply_config_by_id( $id ) {
$stored_configs = get_site_option( 'wp-smush-preset_configs' );
$config = false;
foreach ( $stored_configs as $config_data ) {
if ( (int) $config_data['id'] === (int) $id ) {
$config = $config_data;
break;
}
}
// The config with the given ID doesn't exist.
if ( ! $config ) {
return new WP_Error( '404', __( 'The given config ID does not exist', 'wp-smushit' ) );
}
$this->apply_config( $config['config']['configs'], $config['name'] );
}
/**
* Applies the given config.
*
* @since 3.8.6
*
* @param array $config The config to apply.
*/
public function apply_config( $config, $config_name = '' ) {
$sanitized_config = $this->sanitize_config( $config );
// Update 'networkwide' options in multisites.
if ( is_multisite() && isset( $sanitized_config['networkwide'] ) ) {
update_site_option( 'wp-smush-networkwide', $sanitized_config['networkwide'] );
}
$settings_handler = Settings::get_instance();
// Update image sizes.
if ( isset( $sanitized_config['resize_sizes'] ) ) {
$settings_handler->set_setting( 'wp-smush-resize_sizes', $sanitized_config['resize_sizes'] );
}
// Update settings. We could reuse the `save` method from settings to handle this instead.
if ( ! empty( $sanitized_config['settings'] ) ) {
$stored_settings = $settings_handler->get_setting( 'wp-smush-settings' );
// Keep the keys that are in use in this version.
$new_settings = array_intersect_key( $sanitized_config['settings'], $stored_settings );
if ( $new_settings ) {
foreach ( $this->placeholder_features as $name ) {
$new_settings[ $name ] = false;
}
// Keep the stored settings that aren't present in the incoming one.
$new_settings = array_merge( $stored_settings, $new_settings );
$settings_handler->set_setting( 'wp-smush-settings', $new_settings );
}
}
// Update lazy load.
if ( ! empty( $sanitized_config['lazy_load'] ) ) {
$stored_lazy_load = $settings_handler->get_setting( 'wp-smush-lazy_load' );
// Save the defaults before applying the config if the current settings aren't set.
if ( empty( $stored_lazy_load ) ) {
$settings_handler->init_lazy_load_defaults();
$stored_lazy_load = $settings_handler->get_setting( 'wp-smush-lazy_load' );
}
// Keep the settings that are in use in this version.
foreach ( $sanitized_config['lazy_load'] as $key => $value ) {
if ( is_array( $value ) && is_array( $stored_lazy_load[ $key ] ) ) {
$sanitized_config['lazy_load'][ $key ] = array_intersect_key( $value, $stored_lazy_load[ $key ] );
}
}
// Keep the stored settings that aren't present in the incoming one.
$new_lazy_load = array_replace_recursive( $stored_lazy_load, $sanitized_config['lazy_load'] );
$settings_handler->set_setting( 'wp-smush-lazy_load', $new_lazy_load );
}
do_action( 'wp_smush_config_applied', $config_name );
// Skip onboarding if applying a config.
update_option( 'skip-smush-setup', true );
}
/**
* Gets a new config array based on the current settings.
*
* @since 3.8.5
*
* @return array
*/
public function get_config_from_current() {
$settings = Settings::get_instance();
$stored_settings = $settings->get_setting( 'wp-smush-settings' );
$configs = array( 'settings' => $stored_settings );
if ( $stored_settings['resize'] ) {
$configs['resize_sizes'] = $settings->get_setting( 'wp-smush-resize_sizes' );
}
// Let's store this only for multisites.
if ( is_multisite() ) {
$configs['networkwide'] = get_site_option( 'wp-smush-networkwide' );
}
// There's a site_option that handles this.
unset( $configs['settings']['networkwide'] );
// Looks like unused.
unset( $configs['settings']['api_auth'] );
// These are unique per site. They shouldn't be used.
unset( $configs['settings']['bulk'] );
// Include the lazy load settings only when lazy load is enabled.
if ( ! empty( $configs['settings']['lazy_load'] ) ) {
$lazy_load_settings = $settings->get_setting( 'wp-smush-lazy_load' );
if ( ! empty( $lazy_load_settings ) ) {
// Exclude unique settings.
unset( $lazy_load_settings['animation']['placeholder'] );
unset( $lazy_load_settings['animation']['spinner'] );
unset( $lazy_load_settings['exclude-pages'] );
unset( $lazy_load_settings['exclude-classes'] );
if ( 'fadein' !== $lazy_load_settings['animation']['selected'] ) {
unset( $lazy_load_settings['animation']['fadein'] );
}
$configs['lazy_load'] = $lazy_load_settings;
}
}
// Exclude CDN fields if CDN is disabled.
if ( empty( $configs['settings']['cdn'] ) ) {
foreach ( $settings->get_cdn_fields() as $field ) {
if ( 'cdn' !== $field ) {
unset( $configs['settings'][ $field ] );
}
}
}
return array(
'config' => array(
'configs' => $configs,
'strings' => $this->format_config_to_display( $configs ),
),
);
}
/**
* Sanitizes the given config.
*
* @since 3.8.5
*
* @param array $config Config array to sanitize.
*
* @return array
*/
protected function sanitize_config( $config ) {
$sanitized = array();
if ( isset( $config['networkwide'] ) ) {
if ( ! is_array( $config['networkwide'] ) ) {
$sanitized['networkwide'] = sanitize_text_field( $config['networkwide'] );
} else {
$sanitized['networkwide'] = filter_var(
$config['networkwide'],
FILTER_CALLBACK,
array(
'options' => 'sanitize_text_field',
)
);
}
}
if ( ! empty( $config['settings'] ) ) {
$sanitized['settings'] = filter_var( $config['settings'], FILTER_VALIDATE_BOOLEAN, FILTER_REQUIRE_ARRAY );
if ( isset( $config['settings']['lossy'] ) ) {
$sanitized['settings']['lossy'] = $this->settings->sanitize_lossy_level( $config['settings']['lossy'] );
}
if ( isset( $config['settings'][ Settings::get_next_gen_cdn_key() ] ) ) {
$sanitized['settings'][ Settings::get_next_gen_cdn_key() ] = $this->settings->sanitize_cdn_next_gen_conversion_mode( $config['settings'][ Settings::get_next_gen_cdn_key() ] );
}
}
if ( isset( $config['resize_sizes'] ) ) {
if ( is_bool( $config['resize_sizes'] ) ) {
$sanitized['resize_sizes'] = $config['resize_sizes'];
} else {
$sanitized['resize_sizes'] = array(
'width' => (int) $config['resize_sizes']['width'],
'height' => (int) $config['resize_sizes']['height'],
);
}
}
if ( ! empty( $config['lazy_load'] ) ) {
$args = array(
'format' => array(
'filter' => FILTER_VALIDATE_BOOLEAN,
'flags' => FILTER_REQUIRE_ARRAY + FILTER_NULL_ON_FAILURE,
),
'output' => array(
'filter' => FILTER_VALIDATE_BOOLEAN,
'flags' => FILTER_REQUIRE_ARRAY,
),
'animation' => array(
'filter' => FILTER_CALLBACK,
'options' => 'sanitize_text_field',
'flags' => FILTER_REQUIRE_ARRAY,
),
'include' => array(
'filter' => FILTER_VALIDATE_BOOLEAN,
'flags' => FILTER_REQUIRE_ARRAY,
),
'exclude-pages' => array(
'filter' => FILTER_SANITIZE_URL,
'flags' => FILTER_REQUIRE_ARRAY,
),
'exclude-classes' => array(
'filter' => FILTER_CALLBACK,
'options' => 'sanitize_text_field',
'flags' => FILTER_REQUIRE_ARRAY,
),
'footer' => FILTER_VALIDATE_BOOLEAN,
'native' => FILTER_VALIDATE_BOOLEAN,
'noscript_fallback' => FILTER_VALIDATE_BOOLEAN,
);
$sanitized['lazy_load'] = filter_var_array( $config['lazy_load'], $args, false );
}
return $sanitized;
}
/**
* Formatting methods.
*/
/**
* Formats the given config to be displayed.
* Used when displaying the list of configs and when sending a config to the Hub.
*
* @since 3.8.5
*
* @param array $config The config to format.
*
* @return array Contains an array for each setting. Each with a 'label' and 'value' keys.
*/
protected function format_config_to_display( $config ) {
$lazy_load_fields = Settings::get_instance()->get_lazy_load_fields();
$preload_fields = Settings::get_instance()->get_preload_fields();
$lazy_preload_fields = array_merge( $lazy_load_fields, $preload_fields );
$lazy_preload_module = Settings::get_lazy_preload_module_name();
$settings_data = array(
'bulk_smush' => Settings::get_instance()->get_bulk_fields(),
$lazy_preload_module => $lazy_preload_fields,
'cdn' => Settings::get_instance()->get_cdn_fields(),
'next_gen' => Settings::get_instance()->get_next_gen_fields(),
'integrations' => Settings::get_instance()->get_integrations_fields(),
'settings' => Settings::get_instance()->get_settings_fields(),
);
$display_array = array();
if ( ! empty( $config['settings'] ) ) {
foreach ( $settings_data as $name => $fields ) {
if ( 'next_gen' === $name ) {
$display_array['next_gen'] = $this->get_next_gen_settings_display_value( $config );
continue;
}
if ( $lazy_preload_module === $name ) {
$display_array[ $lazy_preload_module ] = $this->get_lazy_preload_settings_to_display( $config );
continue;
}
// Display the setting inactive when the module is off.
if ( 'cdn' === $name ) {
$display_array[ $name ] = $this->format_boolean_setting_value( $name, $config['settings'][ $name ] );
continue;
}
$display_array[ $name ] = $this->get_settings_display_value( $config, $fields );
}
// Append the resize_sizes to the Bulk Smush display settings.
if ( ! empty( $config['settings']['resize'] ) && ! empty( $config['resize_sizes'] ) ) {
$display_array['bulk_smush'][] = sprintf(
/* translators: 1. Resize-size max width, 2. Resize-size max height */
__( 'Full images max-sizes to resize - Max-width: %1$s. Max height: %2$s', 'wp-smushit' ),
$config['resize_sizes']['width'],
$config['resize_sizes']['height']
);
}
}
// Display only for multisites, if the setting exists.
if ( is_multisite() && isset( $config['networkwide'] ) ) {
$display_array['networkwide'] = $this->get_networkwide_settings_to_display( $config );
}
// Format the values to what's expected in front. A string within an array.
array_walk(
$display_array,
function ( &$value ) {
if ( ! is_string( $value ) ) {
$value = implode( PHP_EOL, $value );
}
$value = array( $value );
}
);
return $display_array;
}
protected function get_next_gen_settings_display_value( $config ) {
return __( 'Inactive', 'wp-smushit' );
}
protected function format_config_description( $field_name, $field_description ) {
return "{$field_name} - {$field_description}";
}
/**
* Formats the given fields that belong to the "settings" option.
*
* @since 3.8.5
*
* @param array $config The config to format.
* @param array $fields The fields to look for.
*
* @return array
*/
protected function get_settings_display_value( $config, $fields ) {
$formatted_rows = array();
$extra_labels = array(
's3' => __( 'Amazon S3', 'wp-smushit' ),
'nextgen' => __( 'NextGen Gallery', 'wp-smushit' ),
'cdn' => __( 'CDN', 'wp-smushit' ),
'keep_data' => __( 'Keep Data On Uninstall', 'wp-smushit' ),
);
foreach ( $fields as $name ) {
if ( isset( $config['settings'][ $name ] ) ) {
$label = Settings::get_instance()->get_setting_data( $name, 'short-label' );
if ( empty( $label ) ) {
$label = ! empty( $extra_labels[ $name ] ) ? $extra_labels[ $name ] : $name;
}
if ( 'lossy' === $name ) {
$formatted_rows[] = $label . ' - ' . $this->settings->get_lossy_level_label( $config['settings'][ $name ] );
continue;
}
if ( Settings::get_next_gen_cdn_key() === $name ) {
$formatted_rows[] = $label . ' - ' . $this->settings->get_cdn_next_gen_conversion_label( $config['settings'][ $name ] );
continue;
}
$formatted_rows[] = $label . ' - ' . $this->format_boolean_setting_value( $name, $config['settings'][ $name ] );
}
}
return $formatted_rows;
}
/**
* Formats the boolean settings that are either 'active' or 'inactive'.
* If the setting belongs to a pro feature and
* this isn't a pro install, we display it as 'inactive'.
*
* @since 3.8.5
*
* @param string $name The setting's name.
* @param boolean $value The setting's value.
* @return string
*/
protected function format_boolean_setting_value( $name, $value ) {
// Display the pro features as 'inactive' for free installs.
if ( in_array( $name, $this->placeholder_features, true ) ) {
$value = false;
}
return $value ? __( 'Active', 'wp-smushit' ) : __( 'Inactive', 'wp-smushit' );
}
protected function get_lazy_preload_settings_to_display( $config ) {
$is_lazy_load_active = ! empty( $config['settings']['lazy_load'] );
if ( ! $is_lazy_load_active ) {
return __( 'Inactive', 'wp-smushit' );
}
$formatted_rows = array();
$formatted_rows[] = __( 'Lazy Load', 'wp-smushit' ) . ' - ' . $this->format_boolean_setting_value( 'lazy_load', $is_lazy_load_active );
if ( $is_lazy_load_active ) {
$formatted_rows = array_merge( $formatted_rows, $this->get_lazy_load_settings_to_display( $config ) );
}
$formatted_rows[] = __( 'Preload Critical Images', 'wp-smushit' ) . ' - ' . $this->format_boolean_setting_value( 'preload_images', false );
return $formatted_rows;
}
/**
* Formats the given lazy_load settings to be displayed.
*
* @since 3.8.5
*
* @param array $config The config to format.
*
* @return array
*/
protected function get_lazy_load_settings_to_display( $config ) {
$formatted_rows = array();
// List of the available lazy load settings for this version and their labels.
$settings_labels = array(
'format' => __( 'Media Types', 'wp-smushit' ),
'output' => __( 'Output Locations', 'wp-smushit' ),
'auto_resizing' => __( 'Auto Resizing', 'wp-smushit' ),
'image_dimensions' => __( 'Add Missing Image Dimensions', 'wp-smushit' ),
'include' => __( 'Included Post Types', 'wp-smushit' ),
'animation' => __( 'Display And Animation', 'wp-smushit' ),
'footer' => __( 'Load Scripts In Footer', 'wp-smushit' ),
'native' => __( 'Native Lazy Load Enabled', 'wp-smushit' ),
'noscript_fallback' => __( 'Noscript Tag', 'wp-smushit' ),
);
foreach ( $settings_labels as $key => $label ) {
// Skip if the setting doesn't exist.
if ( isset( $config['lazy_load'][ $key ] ) ) {
$value = $config['lazy_load'][ $key ];
} elseif ( isset( $config['settings'][ $key ] ) ) {
$value = $config['settings'][ $key ];
} else {
continue;
}
if ( 'format' === $key ) {
$enabled_media_types = array_keys( array_filter( $value ) );
$formatted_rows[] = $this->get_lazy_load_media_types_to_display( $enabled_media_types );
$formatted_rows[] = $this->get_lazy_load_embedded_content_to_display( $enabled_media_types );
continue;
}
$formatted_value = $label . ' - ';
$setting_keys = array(
'auto_resizing',
'image_dimensions',
);
if ( in_array( $key, $setting_keys, true ) ) {
$formatted_value .= $this->format_boolean_setting_value( $key, ! empty( $value ) );
} elseif ( 'animation' === $key ) {
// The special kid.
$formatted_value .= __( 'Selected: ', 'wp-smushit' ) . $value['selected'];
if ( ! empty( $value['fadein'] ) ) {
$formatted_value .= __( '. Fade in duration: ', 'wp-smushit' ) . $value['fadein']['duration'];
$formatted_value .= __( '. Fade in delay: ', 'wp-smushit' ) . $value['fadein']['delay'];
}
} elseif ( in_array( $key, array( 'footer', 'native', 'noscript_fallback' ), true ) ) {
// Enabled/disabled settings.
$formatted_value .= ! empty( $value ) ? __( 'Yes', 'wp-smushit' ) : __( 'No', 'wp-smushit' );
} else {
// Arrays.
if ( in_array( $key, array( 'output', 'include' ), true ) ) {
$value = array_keys( array_filter( $value ) );
}
if ( ! empty( $value ) ) {
$formatted_value .= implode( ', ', $value );
} else {
$formatted_value .= __( 'none', 'wp-smushit' );
}
}
$formatted_rows[] = $formatted_value;
}
return $formatted_rows;
}
private function get_lazy_load_media_types_to_display( $enabled_media_types ) {
$formatted_value = __( 'Media Types', 'wp-smushit' ) . ' - ';
$embed_content_formats = array( 'iframe', 'embed_video' );
$enabled_media_types = array_diff( $enabled_media_types, $embed_content_formats );
if ( empty( $enabled_media_types ) ) {
$formatted_value .= __( 'none', 'wp-smushit' );
} else {
$formatted_value .= implode( ', ', $enabled_media_types );
}
return $formatted_value;
}
private function get_lazy_load_embedded_content_to_display( $enabled_media_types ) {
$formatted_value = __( 'Embedded Content', 'wp-smushit' ) . ' - ';
if ( ! in_array( 'iframe', $enabled_media_types, true ) ) {
return $formatted_value . __( 'No', 'wp-smushit' );
}
if ( ! in_array( 'embed_video', $enabled_media_types, true ) ) {
return $formatted_value .= __( 'Yes', 'wp-smushit' );
}
return $formatted_value . __( 'Replace Video Embed with preview images', 'wp-smushit' );
}
/**
* Formats the 'networkwide' setting to display.
*
* @since 3.8.5
*
* @param array $config The config to format.
*
* @return string
*/
private function get_networkwide_settings_to_display( $config ) {
if ( is_array( $config['networkwide'] ) ) {
return implode( ', ', $config['networkwide'] );
}
return '1' === (string) $config['networkwide'] ? __( 'All', 'wp-smushit' ) : __( 'None', 'wp-smushit' );
}
public function sanitize_and_format_configs( $configs ) {
$configs = $this->normalize_configs( $configs );
return array(
'configs' => $this->sanitize_config( $configs ),
'strings' => $this->format_config_to_display( $configs ),
);
}
private function normalize_configs( $configs ) {
if ( ! isset( $configs['settings'] ) ) {
return $configs;
}
$settings = $configs['settings'];
$settings = $this->maybe_migrate_auto_resize_to_new_settings( $settings );
// Update settings.
$configs['settings'] = $settings;
return $configs;
}
/**
* Migrates the CDN auto_resize setting to the new auto_resizing
* and cdn_dynamic_sizes settings.
*
* @since 3.21.0
*/
private function maybe_migrate_auto_resize_to_new_settings( $settings ) {
if ( isset( $settings['auto_resizing'] ) || isset( $settings['cdn_dynamic_sizes'] ) ) {
return $settings;
}
$is_auto_resizing_active = ! empty( $settings['auto_resize'] );
$settings['auto_resizing'] = $is_auto_resizing_active;
$settings['cdn_dynamic_sizes'] = $is_auto_resizing_active;
return $settings;
}
}
PK nE\5 core/api/class-backoff.phpnu [ set_decider( $this->get_default_decider() );
}
public function run( $callback ) {
$attempt = 0;
$try = true;
$result = null;
$max_attempts = $this->get_max_attempts();
while ( $try ) {
$this->wait( $attempt );
$result = call_user_func( $callback );
$attempt ++;
if ( $attempt >= $max_attempts ) {
$try = false;
} else {
$try = call_user_func( $this->get_decider(), $result );
}
}
return $result;
}
private function wait( $attempt ) {
if ( $attempt == 0 ) {
return;
}
usleep( $this->get_wait_time( $attempt ) * 1000 );
}
/**
* @return mixed
*/
private function get_max_attempts() {
return $this->max_attempts;
}
/**
* @param mixed $max_attempts
*
* @return Backoff
*/
public function set_max_attempts( $max_attempts ) {
$this->max_attempts = max( (int) $max_attempts, 0 );
return $this;
}
/**
* @return mixed
*/
private function get_wait_time( $attempt ) {
$wait_time = $attempt == 1
? $this->wait
: pow( 2, $attempt ) * $this->wait;
return $this->jitter( (int) $wait_time );
}
/**
* @return mixed
*/
private function get_initial_wait() {
return $this->wait;
}
/**
* @param mixed $wait
*
* @return Backoff
*/
public function set_wait( $wait ) {
$this->wait = $wait;
return $this;
}
/**
* @return mixed
*/
private function get_decider() {
return $this->decider;
}
/**
* @param mixed $decider
*
* @return Backoff
*/
public function set_decider( $decider ) {
$this->decider = $decider;
return $this;
}
private function get_default_decider() {
return function ( $result ) {
return is_wp_error( $result );
};
}
private function set_jitter( $useJitter ) {
$this->use_jitter = $useJitter;
}
public function enable_jitter() {
$this->set_jitter( true );
return $this;
}
public function disable_jitter() {
$this->set_jitter( false );
return $this;
}
private function jitter_enabled() {
return $this->use_jitter;
}
private function jitter( $wait_time ) {
if ( ! $this->jitter_enabled() ) {
return $wait_time;
}
$jitter_percentage = mt_rand( 1, 20 );
$add_or_subtract = array_rand( array(
- 1 => - 1,
+ 1 => + 1,
) );
$jitter = ( $wait_time * $jitter_percentage / 100 ) * $add_or_subtract;
return $wait_time + $jitter;
}
}
PK pE\O core/api/class-abstract-api.phpnu [ api_key = $key;
// The Request class needs these to make requests.
if ( empty( $this->version ) || empty( $this->name ) ) {
throw new Exception( __( 'API instances require a version and name properties', 'wp-smushit' ), 404 );
}
$this->request = new Request( $this );
}
}
PK sE\_h_ core/api/class-request.phpnu [ service = $service;
}
/**
* Get the current site URL.
*
* The network_site_url() of the WP installation. (Or network_home_url if not passing an API key).
*
* @since 3.0
*
* @return string
*/
public function get_this_site() {
if ( defined( 'WP_SMUSH_API_DOMAIN' ) && WP_SMUSH_API_DOMAIN ) {
return WP_SMUSH_API_DOMAIN;
}
return network_site_url();
}
/**
* Set request timeout.
*
* @since 3.0
*
* @param int $timeout Request timeout (seconds).
*/
public function set_timeout( $timeout ) {
$this->timeout = $timeout;
}
/**
* Add a new request argument for POST requests.
*
* @since 3.0
*
* @param string $name Argument name.
* @param string $value Argument value.
*/
public function add_post_argument( $name, $value ) {
$this->post_args[ $name ] = $value;
}
/**
* Add a new request argument for GET requests.
*
* @since 3.0
*
* @param string $name Argument name.
* @param string $value Argument value.
*/
public function add_get_argument( $name, $value ) {
$this->get_args[ $name ] = $value;
}
/**
* Add a new request argument for GET requests.
*
* @since 3.0
*
* @param string $name Argument name.
* @param string $value Argument value.
*/
public function add_header_argument( $name, $value ) {
$this->headers[ $name ] = $value;
}
/**
* Make a POST API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function post( $path, $data = array() ) {
try {
$result = $this->request( $path, $data );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Make a GET API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function get( $path, $data = array() ) {
try {
$result = $this->request( $path, $data, 'get' );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Make a HEAD API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function head( $path, $data = array() ) {
try {
$result = $this->request( $path, $data, 'head' );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Make a PATCH API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function patch( $path, $data = array() ) {
try {
$result = $this->request( $path, $data, 'patch' );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Make a DELETE API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function delete( $path, $data = array() ) {
try {
$result = $this->request( $path, $data, 'delete' );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Get API endpoint URL for request.
*
* @since 3.0
*
* @param string $path Endpoint path.
*
* @return string
*/
private function get_api_url( $path = '' ) {
$base = defined( 'WPMUDEV_CUSTOM_API_SERVER' ) && WPMUDEV_CUSTOM_API_SERVER
? WPMUDEV_CUSTOM_API_SERVER
: 'https://wpmudev.com/';
$url = "$base/api/{$this->service->name}/{$this->service->version}/";
$url = trailingslashit( $url . $path );
return $url;
}
/**
* Add authorization header.
*
* @since 3.0
*/
private function sign_request() {
if ( ! empty( $this->service->api_key ) ) {
$this->add_header_argument( 'Authorization', 'Basic ' . $this->service->api_key );
}
}
/**
* Make an API request.
*
* @since 3.0
*
* @param string $path API endpoint route.
* @param array $data Data array.
* @param string $method API method.
*
* @return array|WP_Error
*/
private function request( $path, $data = array(), $method = 'post' ) {
$url = $this->get_api_url( $path );
$this->sign_request();
$url = add_query_arg( $this->get_args, $url );
if ( 'post' !== $method && 'patch' !== $method && 'delete' !== $method ) {
$url = add_query_arg( $data, $url );
}
$args = array(
'user-agent' => WP_SMUSH_UA,
'headers' => $this->headers,
'sslverify' => false,
'method' => strtoupper( $method ),
'timeout' => $this->timeout,
);
if ( ! $args['timeout'] || 2 === $args['timeout'] ) {
$args['blocking'] = false;
}
switch ( strtolower( $method ) ) {
case 'patch':
case 'delete':
case 'post':
if ( is_array( $data ) ) {
$args['body'] = array_merge( $data, $this->post_args );
} else {
$args['body'] = $data;
}
$response = wp_remote_post( $url, $args );
break;
case 'head':
$response = wp_remote_head( $url, $args );
break;
case 'get':
$response = wp_remote_get( $url, $args );
break;
default:
$response = wp_remote_request( $url, $args );
break;
}
// Log error.
if ( is_wp_error( $response ) ) {
Helper::logger()->api()->error( sprintf( 'Error [%s->%s]: %s', $method, $path, $response->get_error_message() ) );
}
return $response;
}
}
PK vE\sGqUB B core/api/class-hub.phpnu [ array_utils = new Array_Utils();
add_filter( 'wdp_register_hub_action', array( $this, 'add_endpoints' ) );
}
/**
* Add Hub endpoints.
*
* Every Hub Endpoint name is build following the structure: 'smush-$endpoint-$action'
*
* @since 3.7.0
* @param array $actions Endpoint action.
* @return array
*/
public function add_endpoints( $actions ) {
foreach ( $this->endpoints as $endpoint ) {
$actions[ "smush_{$endpoint}" ] = array( $this, 'action_' . $endpoint );
}
return $actions;
}
/**
* Retrieve data for endpoint.
*
* @since 3.7.0
* @param array $params Parameters.
* @param string $action Action.
*/
public function action_get_stats( $params, $action ) {
$status = array();
$core = WP_Smush::get_instance()->core();
$settings = Settings::get_instance();
$status['cdn'] = $settings->is_cdn_active();
$status['lossy'] = $settings->get_lossy_level_setting();
$lazy = $settings->get_setting( 'wp-smush-lazy_load' );
$status['lazy'] = array(
'enabled' => $core->mod->lazy->is_active(),
'native' => is_array( $lazy ) && isset( $lazy['native'] ) ? $lazy['native'] : false,
);
$global_stats = $core->get_global_stats();
// Total, Smushed, Unsmushed, Savings.
$status['count_total'] = $this->array_utils->get_array_value( $global_stats, 'count_total' );
$status['count_smushed'] = $this->array_utils->get_array_value( $global_stats, 'count_smushed' );
// Considering the images to be resmushed.
$status['count_unsmushed'] = $this->array_utils->get_array_value( $global_stats, 'count_unsmushed' );
$status['savings'] = $this->get_savings_stats( $global_stats );
$status['dir'] = $this->array_utils->get_array_value( $global_stats, 'savings_dir_smush' );
wp_send_json_success( (object) $status );
}
private function get_savings_stats( $global_stats ) {
// TODO: Is better to update the new change on hub?
$map_stats_keys = array(
'size_before' => 'size_before',
'size_after' => 'size_after',
'percent' => 'savings_percent',
'human' => 'human_bytes',
'bytes' => 'savings_bytes',
'total_images' => 'count_images',
'resize_count' => 'count_resize',
'resize_savings' => 'savings_resize',
'conversion_savings' => 'savings_conversion',
);
$hub_savings_stats = array();
foreach ( $map_stats_keys as $hub_key => $global_stats_key ) {
$hub_savings_stats[ $hub_key ] = $this->array_utils->get_array_value( $global_stats, $global_stats_key );
}
return $hub_savings_stats;
}
/**
* Applies the given config sent by the Hub via the Dashboard plugin.
*
* @since 3.8.5
*
* @param object $config_data The config sent by the Hub.
*/
public function action_import_settings( $config_data ) {
if ( empty( $config_data->configs ) ) {
wp_send_json_error(
array(
'message' => __( 'Missing config data', 'wp-smushit' ),
)
);
}
// The Hub returns an object, we use an array.
$config_array = json_decode( wp_json_encode( $config_data->configs ), true );
$configs_handler = Configs::get_instance();
$configs_handler->apply_config( $config_array );
wp_send_json_success();
}
/**
* Exports the current settings as a config for the Hub.
*
* @since 3.8.5
*/
public function action_export_settings() {
$configs_handler = Configs::get_instance();
$config = $configs_handler->get_config_from_current();
wp_send_json_success( $config['config'] );
}
}
PK xE\:6
core/api/class-smush-api.phpnu [ backoff_sync( function () {
return $this->request->get(
"check/{$this->api_key}",
array(
'api_key' => $this->api_key,
'domain' => $this->request->get_this_site(),
)
);
}, $manual );
}
/**
* Enable CDN for site.
*
* @since 3.0
*
* @param bool $manual If it's a manual check. Overwrites the exponential back off.
*
* @return mixed|WP_Error
*/
public function enable( $manual = false ) {
return $this->backoff_sync( function () {
return $this->request->post(
'cdn',
array(
'api_key' => $this->api_key,
'domain' => $this->request->get_this_site(),
)
);
}, $manual );
}
private function backoff_sync( $operation, $manual ) {
$defaults = array(
'time' => time(),
'fails' => 0,
);
$last_run = (array) get_site_option( 'wp-smush-last_run_sync', $defaults );
if ( ! empty( $last_run['fails'] ) ) {
$backoff = min( pow( 5, $last_run['fails'] ), HOUR_IN_SECONDS ); // Exponential 5, 25, 125, 625, 3125, 3600 max.
if ( $last_run['fails'] && $last_run['time'] > ( time() - $backoff ) && ! $manual ) {
$last_run['time'] = time();
update_site_option( 'wp-smush-last_run_sync', $last_run );
return new WP_Error( 'api-backoff', __( '[WPMUDEV API] Skipped sync due to API error exponential backoff.', 'wp-smushit' ) );
}
}
$response = call_user_func( $operation );
$last_run['time'] = time();
// Clear the API backoff if it's a manual scan or the API call was a success.
if ( $manual || ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) ) {
$last_run['fails'] = 0;
} else {
// For network errors, perform exponential backoff.
$last_run['fails'] = $last_run['fails'] + 1;
}
update_site_option( 'wp-smush-last_run_sync', $last_run );
return $response;
}
}
PK }E\;0 ! core/class-keyword-exclusions.phpnu [ excluded_keywords = $this->sanitize_keywords( $excluded_keywords );
}
/**
* Sanitize keywords.
*
* @param array $keywords Keywords.
*
* @return array
*/
private function sanitize_keywords( $keywords ) {
if ( empty( $keywords ) ) {
return array();
}
$keywords = (array) $keywords;
$sanitized_keywords = array_filter(
$keywords,
function ( $keyword ) {
return is_string( $keyword ) && '' !== trim( $keyword );
}
);
$sanitized_keywords = array_map( 'trim', $sanitized_keywords );
return array_unique( $sanitized_keywords );
}
/**
* Check if excluded keywords are set.
*
* @return bool
*/
public function has_excluded_keywords() {
return ! empty( $this->excluded_keywords );
}
/**
* Get excluded url keywords from excluded keywords.
*
* @return array
*/
public function get_excluded_url_keywords() {
if ( ! $this->excluded_url_keywords ) {
$this->excluded_url_keywords = $this->prepare_excluded_url_keywords();
}
return $this->excluded_url_keywords;
}
/**
* Prepare excluded URL keywords.
*
* @return array
*/
private function prepare_excluded_url_keywords() {
$excluded_url_keywords = array_reduce(
$this->excluded_keywords,
function ( $url_keywords, $keyword ) {
if ( ! str_starts_with( $keyword, self::$file_prefix ) ) {
return $url_keywords;
}
$keyword = ltrim( $keyword, self::$file_prefix . ' \r\t\v\0' );
if ( ! empty( $keyword ) ) {
$url_keywords[] = $keyword;
}
return $url_keywords;
},
array()
);
return array_unique( $excluded_url_keywords );
}
/**
* Get excluded other excluded keywords.
*
* @return array
*/
public function get_common_excluded_keywords() {
if ( ! $this->common_excluded_keywords ) {
$this->common_excluded_keywords = $this->prepare_common_excluded_keywords();
}
return $this->common_excluded_keywords;
}
/**
* Prepare other excluded keywords.
*
* @return array
*/
private function prepare_common_excluded_keywords() {
$common_excluded_keywords = array_filter(
$this->excluded_keywords,
function ( $keyword ) {
$is_id_or_class_name = str_starts_with( $keyword, self::$id_prefix )
|| str_starts_with( $keyword, self::$class_prefix );
$is_url_keyword = str_starts_with( $keyword, self::$file_prefix );
return ! $is_id_or_class_name && ! $is_url_keyword;
}
);
return array_unique( $common_excluded_keywords );
}
/**
* Check if URL has excluded keywords.
*
* @param string $url URL.
*
* @return bool
*/
public function is_url_excluded( $url ) {
return $this->is_string_excluded( $url, $this->get_excluded_url_keywords() );
}
/**
* Check if markup has excluded attribute values.
*
* @param string $markup_html Markup HTML.
*
* @return bool
*/
public function is_markup_excluded( $markup_html ) {
return $this->is_string_excluded( $markup_html );
}
/**
* Check if string has excluded keywords.
*
* @param string $str String.
* @param array $excluded_keywords Excluded keywords (sanitized).
*
* @return bool
*/
private function is_string_excluded( $str, $excluded_keywords = array() ) {
if ( empty( $str ) || ! is_string( $str ) ) {
return false;
}
$common_excluded_keywords = $this->get_common_excluded_keywords();
$excluded_keywords = array_merge( $common_excluded_keywords, (array) $excluded_keywords );
if ( empty( $excluded_keywords ) ) {
return false;
}
foreach ( $excluded_keywords as $excluded_keyword ) {
if ( strpos( $str, $excluded_keyword ) !== false ) {
return true;
}
}
return false;
}
/**
* Check if ID attribute is excluded.
*
* @param string $id_attribute ID attribute.
*
* @return bool
*/
public function is_id_attribute_excluded( $id_attribute ) {
$excluded_ids = $this->get_excluded_ids();
if ( empty( $excluded_ids ) || empty( $id_attribute ) || ! is_string( $id_attribute ) ) {
return false;
}
$element_id = $this->add_id_prefix( $id_attribute );
return in_array( $element_id, $excluded_ids, true );
}
/**
* Check if class attribute is excluded.
*
* @param string $class_attribute class attribute.
*
* @return bool
*/
public function is_class_attribute_excluded( $class_attribute ) {
$excluded_classes = $this->get_excluded_classes();
if ( empty( $excluded_classes ) || empty( $class_attribute ) || ! is_string( $class_attribute ) ) {
return false;
}
$element_classes = explode( ' ', $class_attribute );
foreach ( $element_classes as $element_class ) {
$element_class = $this->add_class_prefix( $element_class );
if ( in_array( $element_class, $excluded_classes, true ) ) {
return true;
}
}
return false;
}
/**
* Get excluded IDs.
*
* @return array
*/
private function get_excluded_classes() {
if ( ! $this->excluded_classes ) {
$this->excluded_classes = $this->prepare_excluded_classes();
}
return $this->excluded_classes;
}
/**
* Prepare excluded ID attributes.
*
* @return array
*/
private function prepare_excluded_classes() {
$excluded_classes = array_filter(
$this->excluded_keywords,
function ( $keyword ) {
return str_starts_with( $keyword, self::$class_prefix );
}
);
return array_unique( $excluded_classes );
}
/**
* Get excluded IDs.
*
* @return array
*/
private function get_excluded_ids() {
if ( ! $this->excluded_ids ) {
$this->excluded_ids = $this->prepare_excluded_ids();
}
return $this->excluded_ids;
}
/**
* Prepare excluded ID attributes.
*
* @return array
*/
private function prepare_excluded_ids() {
$excluded_ids = array_filter(
$this->excluded_keywords,
function ( $keyword ) {
return str_starts_with( $keyword, self::$id_prefix );
}
);
return array_unique( $excluded_ids );
}
/**
* Add ID prefix.
*
* @param string $id_name ID attribute name.
*
* @return string
*/
private function add_id_prefix( $id_name ) {
return self::$id_prefix . $id_name;
}
/**
* Add class prefix.
*
* @param mixed $class_name Class attribute name.
*
* @return string
*/
private function add_class_prefix( $class_name ) {
return self::$class_prefix . $class_name;
}
}
PK E\7/ 2 core/media-library/class-media-library-watcher.phpnu [ array_utils = new Array_Utils();
}
public function init() {
parent::init();
add_action( 'add_attachment', array( $this, 'wait_for_generate_metadata' ) );
add_action( 'admin_init', array( $this, 'watch_image_sizes' ), PHP_INT_MAX );
}
public function wait_for_generate_metadata() {
add_filter( 'wp_generate_attachment_metadata', array( $this, 'trigger_custom_add_attachment' ), 10, 2 );
}
public function trigger_custom_add_attachment( $metadata, $attachment_id ) {
do_action( 'wp_smush_after_attachment_upload', $attachment_id );
remove_filter( 'wp_generate_attachment_metadata', array( $this, 'trigger_custom_add_attachment' ) );
return $metadata;
}
public function watch_image_sizes() {
$skip = get_transient( 'wp_smush_skip_image_sizes_recheck' );
if ( $skip ) {
return;
}
$new_sizes = Helper::fetch_image_sizes();
$new_hash = $this->array_utils->array_hash( $new_sizes );
$old_state = $this->get_image_sizes_state();
$old_sizes = $old_state['sizes'];
$old_hash = $old_state['hash'];
if ( $new_hash !== $old_hash ) {
do_action( 'wp_smush_image_sizes_changed', $old_sizes, $new_sizes );
$this->update_image_sizes_state( $new_sizes, $new_hash );
}
set_transient( 'wp_smush_skip_image_sizes_recheck', true, HOUR_IN_SECONDS );
}
private function get_image_sizes_state() {
$state = get_option( self::$wp_smush_image_sizes_state );
if ( empty( $state ) ) {
$state = array();
}
if ( empty( $state['sizes'] ) || ! is_array( $state['sizes'] ) ) {
$state['sizes'] = array();
}
if ( empty( $state['hash'] ) ) {
$state['hash'] = '';
}
return $state;
}
private function update_image_sizes_state( $sizes, $hash ) {
update_option( self::$wp_smush_image_sizes_state, array(
'sizes' => empty( $sizes ) || ! is_array( $sizes ) ? array() : $sizes,
'hash' => empty( $hash ) ? '' : $hash,
) );
}
}
PK E\k\ \ . core/media-library/class-media-library-row.phpnu [ attachment_id = $attachment_id;
$this->media_item = Media_Item_Cache::get_instance()->get( $this->attachment_id );
$this->global_stats = Global_Stats::get();
$this->optimizer = new Media_Item_Optimizer( $this->media_item );
$this->errors = $this->prepare_errors();
$this->settings = Settings::get_instance();
}
private function prepare_errors() {
$error_list = $this->global_stats->get_error_list();
if (
$error_list->has_id( $this->attachment_id )
|| ( ! $this->media_item->has_wp_metadata() && $this->media_item->is_mime_type_supported() )
) {
return $this->media_item->get_errors();
}
if ( $this->optimizer->has_errors() ) {
$optimization_errors = $this->optimizer->get_errors();
if ( $optimization_errors->get_error_message( 'in_progress' ) ) {
$optimization_errors->remove( 'in_progress' );
}
return $optimization_errors;
}
return new WP_Error();
}
/**
* @return string
*/
public function generate_markup() {
if ( ! $this->media_item->is_image() || ! $this->media_item->is_mime_type_supported() ) {
return esc_html__( 'Not processed', 'wp-smushit' );
}
if ( $this->optimizer->in_progress() || $this->optimizer->restore_in_progress() ) {
return esc_html__( 'File processing is in progress.', 'wp-smushit' );
}
if ( $this->media_item->is_animated() ) {
return $this->generate_markup_for_animated_item();
}
$has_error = $this->errors->has_errors();
if ( $has_error && $this->media_item->size_limit_exceeded() ) {
return $this->generate_markup_for_size_limited_item();
}
// Render ignored after animated/size limited to show upsell even ignored the image.
// And render ignored before media item failed to show Ignored message when the image is ignored.
if ( $this->media_item->is_ignored() ) {
return $this->generate_markup_for_ignored_item();
}
if ( $has_error && $this->media_item->has_errors() ) {
return $this->generate_markup_for_failed_item();
}
if ( $this->is_first_optimization_required() && ! $has_error ) {
return $this->generate_markup_for_unsmushed_item();
}
return $this->generate_markup_for_smushed_item();
}
private function is_first_optimization_required() {
return ! $this->optimizer->is_optimized() && $this->optimizer->should_optimize();
}
private function generate_markup_for_animated_item() {
$error_message = esc_html__( 'Skipped animated file.', 'wp-smushit' );
$utm_link = $this->get_animated_html_utm_link();
return $this->get_html_markup_for_failed_item_with_utm_link( $error_message, $utm_link );
}
protected function get_animated_html_utm_link() {
return $this->get_html_utm_link(
__( 'Upgrade to Serve GIFs faster with CDN.', 'wp-smushit' ),
'smush_bulksmush_library_gif_cdn'
);
}
protected function get_html_utm_link( $utm_message, $utm_campain ) {
$upgrade_url = 'https://wpmudev.com/project/wp-smush-pro/';
$args = array(
'utm_source' => 'smush',
'utm_medium' => 'plugin',
'utm_campaign' => $utm_campain,
);
$utm_link = add_query_arg( $args, $upgrade_url );
return sprintf( '%2$s ', esc_url( $utm_link ), esc_html( $utm_message ) );
}
private function get_html_markup_for_failed_item_with_utm_link( $error_message, $utm_link = '' ) {
if ( $this->media_item->is_ignored() ) {
$links = $this->get_revert_with_utm_link( $utm_link );
} else {
$links = $this->get_ignore_with_utm_link( $utm_link );
}
return $this->get_html_markup_for_failed_item( $error_message, $links );
}
private function get_revert_with_utm_link( $utm_link = '' ) {
$class_names = array();
$links = $utm_link;
if ( ! empty( $utm_link ) ) {
$class_names[] = 'smush-revert-utm';
}
$links .= $this->get_revert_link( $class_names );
return $links;
}
private function get_revert_link( $class_names = array() ) {
$nonce = wp_create_nonce( 'wp-smush-remove-skipped' );
$class_names[] = 'wp-smush-remove-skipped'; // smush-revert-utm
return sprintf(
'%4$s ',
esc_attr( join( ' ', $class_names ) ),
$this->attachment_id,
$nonce,
esc_html__( 'Revert back to previous state', 'wp-smushit' ) . ''
);
}
private function get_ignore_with_utm_link( $utm_link = '' ) {
$class_names = array();
$links = $utm_link;
if ( ! empty( $utm_link ) ) {
$class_names[] = ' smush-ignore-utm';
}
$links .= $this->get_ignore_link( $class_names );
return $links;
}
private function get_ignore_link( $class_names = array() ) {
$class_names[] = 'smush-ignore-image';
return sprintf(
'%s ',
esc_attr( join( ' ', $class_names ) ),
$this->attachment_id,
esc_html__( 'Ignore', 'wp-smushit' )
);
}
private function get_html_markup_for_failed_item( $error_message, $links ) {
$html = $this->get_html_markup_optimization_status_for_failed_item( $error_message );
$html .= $this->get_html_markup_action_links( $links );
return $html;
}
private function get_html_markup_optimization_status_for_failed_item( $error_message ) {
if ( $this->media_item->is_ignored() ) {
$class_name = 'smush-ignored';
} else {
$class_name = 'smush-warning';
}
return $this->get_html_markup_optimization_status( $error_message, $class_name );
}
private function get_html_markup_optimization_status( $message, $class_names = array() ) {
return sprintf( '%s
', join( ' ', (array) $class_names ), $message );
}
private function get_html_markup_action_links( $links, $separator = ' | ' ) {
$links = (array) $links;
$max_links = 4;
if ( count( $links ) > $max_links ) {
$links = array_splice( $links, count( $links ) - $max_links );
}
return sprintf( '%s
', join( $links ) );
}
private function generate_markup_for_size_limited_item() {
$utm_link = $this->get_filesize_limit_utm_link();
if ( $this->media_item->is_ignored() ) {
$error_message = esc_html__( 'Ignored.', 'wp-smushit' );
} else {
$error_message = $this->errors->get_error_message();
}
return $this->get_html_markup_for_failed_item_with_utm_link( $error_message, $utm_link );
}
/**
* Get UTM link for file size limit upsell.
*
* @return string
*/
protected function get_filesize_limit_utm_link() {
return $this->get_html_utm_link(
__( 'Upgrade to Pro to Smush larger images.', 'wp-smushit' ),
'smush_bulksmush_library_filesizelimit'
);
}
private function generate_markup_for_ignored_item() {
return $this->get_html_markup_for_failed_item_with_suggestion_link( esc_html__( 'Ignored.', 'wp-smushit' ) );
}
private function generate_markup_for_failed_item() {
$error_suggestion = $this->get_error_suggestion();
$suggestion_link = $this->get_array_value( $error_suggestion, 'link' );
$suggestion_message = $this->get_array_value( $error_suggestion, 'message' );
$error_message = $this->errors->get_error_message();
if ( $suggestion_message ) {
$error_message = sprintf(
'%s. %s',
rtrim( $error_message, '.' ),
$suggestion_message
);
}
return $this->get_html_markup_for_failed_item_with_suggestion_link( $error_message, $suggestion_link );
}
private function get_error_suggestion() {
$error_suggestion = array(
'message' => '',
'link' => '',
);
if ( ! $this->errors->has_errors() ) {
return $error_suggestion;
}
switch ( $this->errors->get_error_code() ) {
case 'file_not_found':
case 'no_file_meta':
if ( $this->media_item->can_be_restored() ) {
$error_suggestion['message'] = esc_html__( 'We recommend using the restore image function to regenerate the thumbnails.', 'wp-smushit' );
} else {
$error_suggestion['message'] = esc_html__( 'We recommend regenerating the thumbnails.', 'wp-smushit' );
$error_suggestion['link'] = $this->get_html_markup_for_regenerate_doc_link();
}
break;
}
return $error_suggestion;
}
private function get_html_markup_for_regenerate_doc_link() {
return sprintf(
'%s ',
esc_url( $this->get_regenerate_doc_link() ),
$this->attachment_id,
esc_html__( 'Learn more', 'wp-smushit' )
);
}
private function get_regenerate_doc_link() {
return Helper::get_utm_link(
array( 'utm_campaign' => 'smush_pluginlist_docs' ),
'https://wpmudev.com/docs/wpmu-dev-plugins/smush/#restoring-images'
);
}
private function get_html_markup_for_failed_item_with_suggestion_link( $error_message, $suggestion_link = '' ) {
$links = array();
if ( $suggestion_link ) {
$links[] = $suggestion_link;
}
if ( $this->media_item->is_ignored() ) {
$links[] = $this->get_revert_link();
} else {
$resmush_link = $this->get_resmush_link();
if ( $resmush_link ) {
$links[] = $resmush_link;
}
$restore_link = $this->get_restore_link();
if ( $restore_link ) {
$links[] = $restore_link;
}
$links[] = $this->get_ignore_link();
}
return $this->get_html_markup_for_failed_item( $error_message, $links );
}
private function generate_markup_for_unsmushed_item() {
$action_links = array(
$this->get_smush_link(),
$this->get_ignore_link(),
);
$html = $this->get_html_markup_optimization_status( esc_html__( 'Not processed', 'wp-smushit' ) );
$html .= $this->get_html_markup_action_links( $action_links );
return $html;
}
private function generate_markup_for_smushed_item() {
$error_class = $this->errors->has_errors() ? 'smush-warning' : '';
$html = $this->get_html_markup_optimization_status( $this->get_optimization_status(), $error_class );
$html .= $this->get_html_markup_action_links( $this->get_action_links() );
$html .= sprintf( '', $this->attachment_id );
$html .= $this->get_html_markup_detailed_stats();
$html .= '
';
return $html;
}
private function get_optimization_status() {
$error_message = $this->errors->get_error_message();
if ( $error_message ) {
return $error_message;
}
if ( $this->is_no_savings() ) {
return esc_html__( 'Skipped: Image is already optimized.', 'wp-smushit' );
}
return $this->get_optimized_status_text();
}
private function is_no_savings() {
$total_stats = $this->get_total_stats();
return $total_stats->get_size_after() >= $total_stats->get_size_before();
}
private function get_total_stats() {
if ( is_null( $this->total_stats ) ) {
$this->total_stats = $this->prepare_total_stats();
}
return $this->total_stats;
}
private function prepare_total_stats() {
$total_stats = new Media_Item_Stats();
$optimizations = $this->get_applied_optimizations();
if ( empty( $optimizations ) ) {
return $total_stats;
}
$size_before = $this->get_size_before();
$size_after = $this->get_size_after();
$total_stats->from_array(
array(
'size_before' => $size_before,
'size_after' => $size_after,
)
);
return $total_stats;
}
private function get_size_before() {
$optimizations = $this->get_applied_optimizations();
$size_before = max(
array_map(
function ( $optimization ) {
return $optimization->get_stats()->get_size_before();
},
$optimizations
)
);
return $size_before;
}
private function get_size_after() {
$optimizations = $this->get_applied_optimizations();
$size_after = min(
array_map(
function ( $optimization ) {
return $optimization->get_stats()->get_size_after();
},
$optimizations
)
);
return $size_after;
}
private function get_sizes_stats() {
if ( is_null( $this->sizes_stats ) ) {
$this->sizes_stats = $this->prepare_sizes_stats();
}
return $this->sizes_stats;
}
private function prepare_sizes_stats() {
$sizes_stats = array();
foreach ( $this->media_item->get_sizes() as $size ) {
$sizes_stats[ $size->get_key() ] = $this->get_size_stats( $size );
}
return $sizes_stats;
}
private function get_size_stats( $size ) {
$optimizations = $this->get_applied_optimizations();
$size_stats = new Media_Item_Stats();
if ( empty( $optimizations ) ) {
return $size_stats;
}
$size_before = max(
array_map(
function ( $optimization ) use ( $size ) {
return $optimization->get_size_stats( $size->get_key() )->get_size_before();
},
$optimizations
)
);
$size_after = min(
array_map(
function ( $optimization ) use ( $size ) {
return $optimization->get_size_stats( $size->get_key() )->get_size_after();
},
$optimizations
)
);
$size_stats->from_array(
array(
'size_before' => $size_before,
'size_after' => $size_after,
)
);
return $size_stats;
}
/**
* @return Media_Item_Optimization
*/
private function get_primary_optimization() {
$optimizations = $this->get_applied_optimizations();
return array_shift( $optimizations );
}
private function get_applied_optimizations() {
if ( is_null( $this->applied_optimizations ) ) {
$this->applied_optimizations = $this->prepare_applied_optimizations();
}
return $this->applied_optimizations;
}
private function prepare_applied_optimizations() {
$applied_ordered_optimizations = array();
$nextgen_optimization = $this->get_active_nextgen_optimization();
if ( $nextgen_optimization ) {
$applied_ordered_optimizations[] = $nextgen_optimization;
}
$applied_ordered_optimizations = array_merge( $applied_ordered_optimizations, $this->get_classic_optimizations() );
return array_filter(
$applied_ordered_optimizations,
function ( $optimization ) {
return $optimization && $optimization->is_optimized()
&& $optimization->get_stats()->get_bytes() > 0;
}
);
}
private function get_classic_optimizations() {
$ordered_optimizations = $this->get_ordered_optimization_keys();
return array_map( array( $this->optimizer, 'get_optimization' ), $ordered_optimizations );
}
/**
* Get the ordered optimization keys for classic optimizations.
*
* @return array
*/
protected function get_ordered_optimization_keys() {
return array(
Smush_Optimization::get_key(),
Resize_Optimization::get_key(),
);
}
private function get_optimized_status_text() {
$total_stats = $this->get_total_stats();
$sizes_stats = $this->get_sizes_stats();
$count_images = 0;
foreach ( $sizes_stats as $size_stats ) {
if ( ! empty( $size_stats->get_bytes() ) ) {
$count_images++;
}
}
$status_text = '';
if ( 1 < $count_images ) {
$status_text .= sprintf( /* translators: %1$s: bytes savings, %2$s: percentage savings, %3$d: number of images */
esc_html__( '%3$d images reduced by %1$s (%2$s)', 'wp-smushit' ),
$total_stats->get_human_bytes(),
sprintf( '%01.1f%%', $total_stats->get_percent() ),
$count_images
);
} else {
$status_text .= sprintf( /* translators: %1$s: bytes savings, %2$s: percentage savings */
esc_html__( 'Reduced by %1$s (%2$s)', 'wp-smushit' ),
$total_stats->get_human_bytes(),
sprintf( '%01.1f%%', $total_stats->get_percent() )
);
}
// Do we need to show the main image size?
$main_size = $this->media_item->get_scaled_or_full_size();
/**
* @var Media_Item_Stats $main_size_stats
*/
$main_size_stats = $this->get_array_value( $sizes_stats, $main_size->get_key() );
$main_file_size = ( $main_size_stats && $main_size_stats->get_size_after() > 0 )
? $main_size_stats->get_size_after()
: $main_size->get_filesize();
$status_text .= sprintf(
/* translators: 1: tag, 2: Image file size */
esc_html__( '%1$sMain Image size: %2$s', 'wp-smushit' ),
' ',
size_format( $main_file_size, 2 )
);
return $status_text;
}
/**
* @return array
*/
private function get_action_links() {
if ( $this->is_first_optimization_required() ) {
return array( $this->get_smush_link(), $this->get_ignore_link() );
}
$links = array();
$restore_link = $this->get_restore_link();
if ( $restore_link ) {
$links[] = $restore_link;
}
$resmush_link = $this->get_resmush_link();
if ( $resmush_link ) {
$links[] = $resmush_link;
}
if ( ! $this->is_no_savings() ) {
$links[] = $this->get_view_stats_link();
}
// Add ignore button while showing resmush button.
if ( $resmush_link ) {
$links[] = $this->get_ignore_link();
}
return $links;
}
private function get_html_markup_detailed_stats() {
if ( $this->is_no_savings() ) {
return;
}
$primary_optimization = $this->get_primary_optimization();
return sprintf(
'
',
esc_html__( 'Image size', 'wp-smushit' ),
sprintf(
/* translators: %s: Optimization name */
esc_html__( '%s Savings', 'wp-smushit' ),
$primary_optimization->get_name()
),
$this->get_detailed_stats_content()
);
}
private function get_detailed_stats_content() {
$primary_optimization = $this->get_primary_optimization();
$sizes_stats = $this->get_sizes_stats();
$stats_rows = array();
$savings_sizes = array();
// Show Sizes and their compression.
foreach ( $this->media_item->get_sizes() as $size_key => $size ) {
$size_stats = $this->get_array_value( $sizes_stats, $size_key );
if ( $size_stats->is_empty() || empty( $size_stats->get_bytes() ) ) {
continue;
}
$dimensions = "{$size->get_width()}x{$size->get_height()}";
$optimized_file_url = $primary_optimization->get_optimized_file_url( $size->get_file_url() );
if ( empty( $optimized_file_url ) ) {
$optimized_file_url = $size->get_file_url();
}
$stats_rows[ $size_key ] = sprintf(
'
%2$s (%3$s)
%4$s ( %5$s%% )
',
$optimized_file_url ? $optimized_file_url : '#',
strtoupper( $size_key ),
$dimensions,
$size_stats->get_human_bytes(),
$size_stats->get_percent()
);
$savings_sizes[ $size_key ] = $size_stats->get_bytes();
}
uksort(
$stats_rows,
function ( $size_key1, $size_key2 ) use ( $savings_sizes ) {
return $savings_sizes[ $size_key2 ] - $savings_sizes[ $size_key1 ];
}
);
return join( '', $stats_rows );
}
private function get_smush_link() {
return sprintf(
'%s ',
$this->attachment_id,
esc_html__( 'Smush', 'wp-smushit' )
);
}
private function should_reoptimize() {
$reoptimize_list = $this->global_stats->get_reoptimize_list();
$error_list = $this->global_stats->get_error_list();
$should_reoptimize = $reoptimize_list->has_id( $this->attachment_id ) || $error_list->has_id( $this->attachment_id );
if ( $should_reoptimize && $this->optimizer->has_errors() ) {
return $this->optimizer->should_reoptimize();
}
return $should_reoptimize;
}
/**
* @return string|void
*/
private function get_resmush_link() {
if ( ! $this->should_reoptimize() || ! $this->media_item->has_wp_metadata() ) {
return;
}
$next_level_smush_link = $this->get_next_level_smush_link();
if ( ! empty( $next_level_smush_link ) ) {
return $next_level_smush_link;
}
return sprintf(
'%s ',
esc_html__( 'Smush image including original file', 'wp-smushit' ),
$this->attachment_id,
wp_create_nonce( 'wp-smush-resmush-' . $this->attachment_id ),
esc_html__( 'Resmush', 'wp-smushit' )
);
}
/**
* @return string|void
*/
private function get_next_level_smush_link() {
if (
$this->errors->has_errors()
|| $this->is_first_optimization_required()
|| ! $this->is_next_level_smush_required()
) {
return;
}
$anchor_text = $this->get_next_level_smush_anchor_text();
if ( ! $anchor_text ) {
return;
}
return sprintf(
'%s ',
$this->attachment_id,
$anchor_text
);
}
/**
* @return bool
*/
private function is_next_level_smush_required() {
$smush_optimization = $this->get_smush_optimization();
return $smush_optimization && $smush_optimization->is_next_level_available();
}
private function get_next_level_smush_anchor_text() {
$required_level = $this->settings->get_lossy_level_setting();
switch ( $required_level ) {
case Settings::get_level_ultra_lossy():
return esc_html__( 'Ultra Smush', 'wp-smushit' );
case Settings::get_level_super_lossy():
return esc_html__( 'Super Smush', 'wp-smushit' );
default:
return false;
}
}
/**
* @return Smush_Optimization|null
*/
private function get_smush_optimization() {
/**
* @var $smush_optimization Smush_Optimization|null
*/
$smush_optimization = $this->optimizer->get_optimization( Smush_Optimization::get_key() );
return $smush_optimization;
}
/**
* @return string|void
*/
private function get_restore_link() {
if ( ! empty( $this->media_item->can_be_restored() ) ) {
return sprintf(
'%s ',
esc_html__( 'Restore original image', 'wp-smushit' ),
$this->attachment_id,
wp_create_nonce( 'wp-smush-restore-' . $this->attachment_id ),
esc_html__( 'Restore original', 'wp-smushit' )
);
}
return sprintf(
'%s ',
esc_html__( 'No backup image available. Enable Back up original images to restore them in the future.', 'wp-smushit' ),
esc_html__( 'Restore original', 'wp-smushit' )
);
}
private function get_view_stats_link() {
return sprintf(
'%s ',
esc_html__( 'Detailed stats for all the image sizes', 'wp-smushit' ),
' '
);
}
private function get_array_value( $array, $key ) {
return isset( $array[ $key ] ) ? $array[ $key ] : null;
}
/**
* @return Media_Item_Optimization|null
*/
protected function get_active_nextgen_optimization() {
return null;
}
}
PK E\SF = core/media-library/class-background-media-library-scanner.phpnu [ scanner = new Media_Library_Scanner();
$this->logger = Helper::logger();
$this->global_stats = Global_Stats::get();
$identifier = $this->make_identifier();
$this->background_process = new Media_Library_Scan_Background_Process( $identifier, $this->scanner );
$this->background_process->set_logger( Helper::logger() );
$this->register_action( 'wp_ajax_wp_smush_start_background_scan', array( $this, 'start_background_scan' ) );
$this->register_action( 'wp_ajax_wp_smush_cancel_background_scan', array( $this, 'cancel_background_scan' ) );
$this->register_action( 'wp_ajax_wp_smush_get_background_scan_status', array( $this, 'send_status' ) );
$this->register_action( "{$identifier}_completed", array( $this, 'background_process_completed' ) );
$this->register_action( "{$identifier}_dead", array( $this, 'background_process_dead' ) );
add_filter( 'wp_smush_script_data', array( $this, 'localize_media_library_scan_script_data' ) );
}
public function start_background_scan() {
check_ajax_referer( 'wp_smush_media_library_scanner' );
if ( ! Helper::is_user_allowed() ) {
wp_send_json_error();
}
$status = $this->start_background_scan_direct();
if ( is_wp_error( $status ) ) {
wp_send_json_error( array( 'message' => $status->get_error_message() ) );
}
wp_send_json_success( $this->get_scan_status() );
}
public function start_background_scan_direct() {
$in_processing = $this->background_process->get_status()->is_in_processing();
if ( $in_processing ) {
// Already in progress
return new WP_Error( 'in_processing', __( 'Background scan is already in processing.', 'wp-smushit' ) );
}
$this->set_optimize_on_scan_completed( ! empty( $_REQUEST['optimize_on_scan_completed'] ) );
if ( $this->background_process->get_status()->is_dead() ) {
$this->scanner->reduce_slice_size_option();
}
$this->scanner->before_scan_library();
$slice_size = $this->scanner->get_slice_size();
$query = new Media_Item_Query();
$slice_count = $query->get_slice_count( $slice_size );
$tasks = range( 1, $slice_count );
$this->background_process->start( $tasks );
return $this->background_process->get_status()->to_array();
}
public function cancel_background_scan() {
check_ajax_referer( 'wp_smush_media_library_scanner' );
if ( ! Helper::is_user_allowed() ) {
wp_send_json_error();
}
if ( ! $this->background_process->get_status()->is_cancelled() ) {
$this->background_process->cancel();
}
$this->set_optimize_on_scan_completed( false );
wp_send_json_success( $this->get_scan_status() );
}
public function send_status() {
check_ajax_referer( 'wp_smush_media_library_scanner' );
if ( ! Helper::is_user_allowed() ) {
wp_send_json_error();
}
wp_send_json_success( $this->get_scan_status() );
}
public function background_process_completed() {
$this->scanner->after_scan_library();
}
public function background_process_dead() {
$this->global_stats->mark_as_outdated();
}
private function make_identifier() {
$identifier = 'wp_smush_background_scan_process';
if ( is_multisite() ) {
$post_fix = '_' . get_current_blog_id();
$identifier .= $post_fix;
}
return $identifier;
}
public function localize_media_library_scan_script_data( $script_data ) {
$scan_script_data = $this->background_process->get_status()->to_array();
$scan_script_data['nonce'] = wp_create_nonce( 'wp_smush_media_library_scanner' );
$script_data['media_library_scan'] = $scan_script_data;
return $script_data;
}
private function set_optimize_on_scan_completed( $status ) {
$this->optimize_on_scan_completed = $status;
if ( $this->optimize_on_scan_completed ) {
update_option( self::$optimize_on_completed_option_key, 1, false );
} else {
delete_option( self::$optimize_on_completed_option_key );
}
}
public function enabled_optimize_on_scan_completed() {
if ( null === $this->optimize_on_scan_completed ) {
$this->optimize_on_scan_completed = get_option( self::$optimize_on_completed_option_key );
}
return ! empty( $this->optimize_on_scan_completed );
}
private function get_scan_status() {
$is_completed = $this->background_process->get_status()->is_completed();
$is_cancelled = $this->background_process->get_status()->is_cancelled();
$status = $this->background_process->get_status()->to_array();
$status['optimize_on_scan_completed'] = $this->enabled_optimize_on_scan_completed();
// Add global stats on completed/cancelled.
if ( $is_completed || $is_cancelled ) {
$status['global_stats'] = WP_Smush::get_instance()->admin()->get_global_stats_with_bulk_smush_content_and_notice();
}
return $status;
}
public function get_background_process() {
return $this->background_process;
}
}
PK E\1,n n 2 core/media-library/class-media-library-scanner.phpnu [ get_slice_size();
$query = new Media_Item_Query();
$attachment_ids = $query->fetch_slice_ids( $slice, $slice_size );
$slice_data = apply_filters( 'wp_smush_before_scan_library_slice', array(), $slice, $slice_size );
foreach ( $attachment_ids as $attachment_id ) {
$slice_data = apply_filters( 'wp_smush_scan_library_slice_handle_attachment', $slice_data, $attachment_id, $slice, $slice_size );
}
return apply_filters( 'wp_smush_after_scan_library_slice', $slice_data, $slice, $slice_size );
}
public function after_scan_library() {
do_action( 'wp_smush_after_scan_library' );
}
public function get_slice_size() {
$constant_value = $this->get_slice_size_constant();
if ( $constant_value ) {
return $constant_value;
}
$option_value = $this->get_slice_size_option();
if ( $option_value ) {
return $option_value;
}
return $this->calculate_default_slice_size();
}
private function calculate_default_slice_size() {
$query = new Media_Item_Query();
$attachment_count = $query->get_image_attachment_count();
$default_slice_size = (int) ceil( $attachment_count / self::$slice_size_factor );
if ( $default_slice_size > self::$slice_size_max ) {
$default_slice_size = self::$slice_size_max;
} elseif ( $default_slice_size < self::$slice_size_min ) {
$default_slice_size = self::$slice_size_min;
}
return $default_slice_size;
}
public function reduce_slice_size_option() {
$this->set_slice_size( self::$slice_size_min );
}
private function get_slice_size_option() {
$option_value = (int) get_option( self::$slice_size_option_id, 0 );
return max( $option_value, 0 );
}
private function get_slice_size_constant() {
if ( ! defined( 'WP_SMUSH_SCAN_SLICE_SIZE' ) ) {
return 0;
}
$constant_value = (int) WP_SMUSH_SCAN_SLICE_SIZE;
return max( $constant_value, 0 );
}
/**
* @param $value
*
* @return void
*/
private function set_slice_size( $value ) {
update_option( self::$slice_size_option_id, $value );
}
/**
* Get slice_size_option_id.
*
* @return string
*/
public static function get_slice_size_option_id() {
return self::$slice_size_option_id;
}
}
PK E\5 = core/media-library/class-media-library-slice-data-fetcher.phpnu [ is_multisite = $is_multisite;
$this->current_site_id = $current_site_id;
$this->query = new Media_Item_Query();
$this->logger = Helper::logger();
$this->array_utils = new Array_Utils();
$this->register_filter( 'wp_smush_before_scan_library_slice', array( $this, 'prefetch_slice_data' ), 10, 3 );
$this->register_filter( 'wp_smush_before_scan_library_slice', array( $this, 'hook_meta_filters' ), 20, 3 );
$this->register_filter( 'wp_smush_after_scan_library_slice', array( $this, 'unhook_meta_filters' ) );
$this->register_filter( 'wp_smush_after_scan_library_slice', array( $this, 'reset_slice_data' ) );
}
public function hook_meta_filters() {
add_filter( 'get_post_metadata', array( $this, 'maybe_serve_post_meta' ), 10, 3 );
add_filter( 'add_post_meta', array( $this, 'update_post_meta_on_add' ), 10, 3 );
add_filter( 'update_post_meta', array( $this, 'update_post_meta_on_update' ), 10, 4 );
add_action( 'delete_post_meta', array( $this, 'purge_post_meta_on_delete' ), 10, 3 );
}
public function unhook_meta_filters() {
remove_filter( 'get_post_metadata', array( $this, 'maybe_serve_post_meta' ) );
remove_filter( 'add_post_meta', array( $this, 'update_post_meta_on_add' ) );
remove_filter( 'update_post_meta', array( $this, 'update_post_meta_on_update' ) );
remove_action( 'delete_post_meta', array( $this, 'purge_post_meta_on_delete' ) );
}
public function prefetch_slice_data( $slice_data, $slice, $slice_size ) {
$this->prefetch_slice_post_meta( $slice, $slice_size );
$this->prefetch_slice_posts( $slice, $slice_size );
return $slice_data;
}
public function maybe_serve_post_meta( $meta_value, $attachment_id, $meta_key ) {
$slice_post_meta = $this->get_slice_post_meta();
if ( empty( $slice_post_meta ) ) {
return $meta_value;
}
$cache_key = $this->get_post_meta_cache_key( $attachment_id, $meta_key );
$cached_value = '';
if ( isset( $slice_post_meta[ $cache_key ]->meta_value ) ) {
$cached_value = maybe_unserialize( $slice_post_meta[ $cache_key ]->meta_value );
}
return array( $cached_value );
}
public function update_post_meta_on_add( $attachment_id, $meta_key, $meta_value ) {
$this->update_post_meta( $attachment_id, $meta_key, $meta_value );
}
public function update_post_meta_on_update( $meta_id, $attachment_id, $meta_key, $meta_value ) {
$this->update_post_meta( $attachment_id, $meta_key, $meta_value );
}
public function purge_post_meta_on_delete( $meta_ids, $attachment_id, $meta_key ) {
$cache_key = $this->get_post_meta_cache_key( $attachment_id, $meta_key );
$slice_post_meta = $this->get_slice_post_meta();
if ( isset( $slice_post_meta[ $cache_key ] ) ) {
unset( $slice_post_meta[ $cache_key ] );
$this->set_slice_post_meta( $slice_post_meta );
}
}
public function reset_slice_data( $slice_data ) {
$this->set_slice_post_meta( array() );
$this->reset_slice_posts();
return $slice_data;
}
private function prefetch_slice_post_meta( $slice, $slice_size ) {
$fetched_post_meta = $this->query->fetch_slice_post_meta( $slice, $slice_size );
$fetched_post_meta = $this->array_utils->ensure_array( $fetched_post_meta );
$this->set_slice_post_meta( $fetched_post_meta );
}
private function prefetch_slice_posts( $slice, $slice_size ) {
$slice_posts = $this->query->fetch_slice_posts( $slice, $slice_size );
if ( ! empty( $slice_posts ) && is_array( $slice_posts ) ) {
$slice_post_ids = array();
foreach ( $slice_posts as $slice_post_key => $slice_post ) {
$slice_post_ids[] = $slice_post_key;
// Sanitize before adding to cache otherwise the post is going to be sanitized every time it is fetched from the cache
$sanitized_post = sanitize_post( $slice_post, 'raw' );
wp_cache_add( $slice_post_key, $sanitized_post, 'posts' );
}
$this->set_slice_post_ids( $slice_post_ids );
}
}
private function reset_slice_posts() {
foreach ( $this->get_slice_post_ids() as $slice_post_id ) {
wp_cache_delete( $slice_post_id, 'posts' );
}
$this->set_slice_post_ids( array() );
}
/**
* @param $attachment_id
* @param $meta_key
*
* @return string
*/
private function get_post_meta_cache_key( $attachment_id, $meta_key ) {
return "$attachment_id-$meta_key";
}
private function get_slice_post_meta() {
$slice_post_meta = $this->slice_post_meta;
if ( $this->is_multisite ) {
$slice_post_meta = $this->array_utils->get_array_value( $slice_post_meta, $this->current_site_id );
}
return $this->array_utils->ensure_array( $slice_post_meta );
}
private function set_slice_post_meta( $slice_post_meta ) {
if ( $this->is_multisite ) {
$this->slice_post_meta[ $this->current_site_id ] = $slice_post_meta;
} else {
$this->slice_post_meta = $slice_post_meta;
}
}
private function get_slice_post_ids() {
$slice_post_ids = $this->slice_post_ids;
if ( $this->is_multisite ) {
$slice_post_ids = $this->array_utils->get_array_value( $slice_post_ids, $this->current_site_id );
}
return $this->array_utils->ensure_array( $slice_post_ids );
}
private function set_slice_post_ids( $slice_post_ids ) {
if ( $this->is_multisite ) {
$this->slice_post_ids[ $this->current_site_id ] = $slice_post_ids;
} else {
$this->slice_post_ids = $slice_post_ids;
}
}
/**
* @param $attachment_id
* @param $meta_key
* @param $meta_value
*
* @return void
*/
private function update_post_meta( $attachment_id, $meta_key, $meta_value ) {
$cache_key = $this->get_post_meta_cache_key( $attachment_id, $meta_key );
$slice_post_meta = $this->get_slice_post_meta();
if ( empty( $slice_post_meta[ $cache_key ] ) ) {
$slice_post_meta[ $cache_key ] = new \stdClass();
}
$slice_post_meta[ $cache_key ]->meta_value = $meta_value;
$this->set_slice_post_meta( $slice_post_meta );
}
}
PK E\-l l 7 core/media-library/class-ajax-media-library-scanner.phpnu [ scanner = new Media_Library_Scanner();
$this->register_action( 'wp_ajax_wp_smush_before_scan_library', array( $this, 'before_scan_library' ) );
$this->register_action( 'wp_ajax_wp_smush_scan_library_slice', array( $this, 'scan_library_slice' ) );
$this->register_action( 'wp_ajax_wp_smush_after_scan_library', array( $this, 'after_scan_library' ) );
}
public function before_scan_library() {
check_ajax_referer( 'wp_smush_media_library_scanner' );
if ( ! Helper::is_user_allowed() ) {
wp_send_json_error();
}
$this->scanner->before_scan_library();
$slice_size = $this->scanner->get_slice_size();
$parallel_requests = $this->get_parallel_requests();
$query = new Media_Item_Query();
$image_attachment_count = $query->get_image_attachment_count();
$slice_count = $query->get_slice_count( $slice_size );
wp_send_json_success( array(
'image_attachment_count' => $image_attachment_count,
'slice_count' => $slice_count,
'slice_size' => $slice_size,
'parallel_requests' => $parallel_requests,
) );
}
public function scan_library_slice() {
check_ajax_referer( 'wp_smush_media_library_scanner' );
if ( ! Helper::is_user_allowed() ) {
wp_send_json_error();
}
$data = stripslashes_deep( $_POST );
if ( ! isset( $data['slice'] ) ) {
wp_send_json_error();
}
$slice = (int) $data['slice'];
wp_send_json_success( $this->scanner->scan_library_slice( $slice ) );
}
public function after_scan_library() {
check_ajax_referer( 'wp_smush_media_library_scanner' );
if ( ! Helper::is_user_allowed() ) {
wp_send_json_error();
}
$this->scanner->after_scan_library();
wp_send_json_success();
}
public function get_parallel_requests() {
return self::$parallel_requests;
}
}
PK E\>& 7 core/media-library/class-media-library-last-process.phpnu [ array_utils = new Array_Utils();
// Register actions to cache data for displaying stuck notice of background process.
$this->register_action( 'wp_smush_bulk_smush_start', array( $this, 'record_process_start_time' ), 5 );
$this->register_action( 'wp_smush_before_smush_file', array( $this, 'record_bulk_smush_last_processed_attachment' ), 5 );
if ( ! $this->should_track() ) {
return;
}
$scan_background_process = Background_Media_Library_Scanner::get_instance()->get_background_process();
$this->register_action( $scan_background_process->action_name( 'started' ), array( $this, 'record_process_start_time' ), 5 );
$this->register_action( $scan_background_process->action_name( 'dead' ), array( $this, 'record_process_end_time' ), 5 );
$this->register_action( 'wp_smush_after_smush_file', array( $this, 'record_last_processed_attachment_elapsed_time' ), 5 );
$this->register_action( 'wp_ajax_bulk_smush_get_status', array( $this, 'check_bulk_smush_process_stuck_on_ajax_get_status' ), 5 );
}
public function should_run() {
return true;
}
public function should_track() {
return Settings::get_instance()->get( 'usage' );
}
private function get_ajax_nonce( $query_arg = '_ajax_nonce' ) {
$nonce = '';
if ( $query_arg && isset( $_REQUEST[ $query_arg ] ) ) {
$nonce = wp_unslash( $_REQUEST[ $query_arg ] );
} elseif ( isset( $_REQUEST['_ajax_nonce'] ) ) {
$nonce = wp_unslash( $_REQUEST['_ajax_nonce'] );
} elseif ( isset( $_REQUEST['_wpnonce'] ) ) {
$nonce = wp_unslash( $_REQUEST['_wpnonce'] );
}
return $nonce;
}
public function record_bulk_smush_last_processed_attachment( $attachment_id ) {
if ( ! $this->is_bulk_smush_processing() ) {
return;
}
$this->set_last_processed_attachment( $attachment_id );
}
private function is_bulk_smush_processing() {
if ( ! wp_doing_ajax() || empty( $_REQUEST['action'] ) ) {
return false;
}
$bulk_process_actions = array(
'wp_smush_bulk_smush_background_process',
'wp_smushit_bulk',
);
$action = wp_unslash( $_REQUEST['action'] );
foreach ( $bulk_process_actions as $bulk_action ) {
if ( str_starts_with( $action, $bulk_action ) ) {
return true;
}
}
return false;
}
public function check_bulk_smush_process() {
if ( $this->should_check_stuck() && $this->is_process_stuck() ) {
$this->set_first_stuck_attachment();
do_action( 'wp_smush_bulk_smush_stuck', $this );
Helper::logger()->warning(
sprintf(
'The Bulk Smush process has been stuck for %1$s minutes at image %2$d ( %3$s minutes )',
round( $this->get_seconds_since_last_image_processing_started() / 60, 2 ),
$this->get_last_process_attachment_id(),
round( $this->get_last_process_attachment_elapsed_time() / 60, 2 )
)
);
}
}
private function should_check_stuck() {
$first_stuck_attachment = $this->get_process_item( self::$first_stuck_attachment );
return empty( $first_stuck_attachment );
}
public function check_bulk_smush_process_stuck_on_ajax_get_status() {
$nonce = $this->get_ajax_nonce();
// Check capability.
if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'wp-smush-ajax' ) || ! Helper::is_user_allowed( 'manage_options' ) ) {
return;
}
$this->check_bulk_smush_process();
}
private function set_last_processed_attachment( $attachment_id ) {
$this->set_process_item(
self::$last_attachment,
array(
'id' => $attachment_id,
'start_time' => time(),
)
);
}
private function set_first_stuck_attachment() {
$last_process_attachment = $this->get_last_processed_attachment();
$last_process_attachment['elapsed_time'] = $this->get_seconds_since_last_image_processing_started();
$this->set_process_item(
self::$first_stuck_attachment,
$last_process_attachment
);
}
public function is_process_stuck() {
$elapsed_time = $this->get_seconds_since_last_image_processing_started();
return $elapsed_time > self::$process_time_out;
}
public function record_last_processed_attachment_elapsed_time() {
$last_process_attachment = $this->get_last_processed_attachment();
$last_process_attachment['attachment_elapsed_time'] = $this->get_last_process_attachment_elapsed_time();
$this->set_process_item( self::$last_attachment, $last_process_attachment );
}
public function get_last_process_attachment_elapsed_time() {
$last_process_attachment = $this->get_last_processed_attachment();
$attachment_elapsed_time = (int) $this->array_utils->get_array_value( $last_process_attachment, 'attachment_elapsed_time', - 1 );
if ( $attachment_elapsed_time > - 1 ) {
return $attachment_elapsed_time;
}
return $this->get_seconds_since_last_image_processing_started();
}
public function get_seconds_since_last_image_processing_started() {
$last_process_attachment = $this->get_last_processed_attachment();
$start_time = (int) $this->array_utils->get_array_value( $last_process_attachment, 'start_time' );
if ( empty( $start_time ) ) {
return 0;
}
$end_time = time();
return $end_time - $start_time;
}
public function get_last_process_attachment_id() {
$last_process_attachment = $this->get_last_processed_attachment();
return $this->array_utils->get_array_value( $last_process_attachment, 'id', 0 );
}
private function get_last_processed_attachment() {
return $this->get_process_item( self::$last_attachment, array() );
}
public function record_process_start_time() {
$this->reset_process_option();
$this->set_process_start_time();
}
public function record_process_end_time() {
$this->set_process_end_time();
}
private function reset_process_option() {
delete_option( self::$process_key );
wp_cache_delete( self::$process_key, 'options' );
}
private function set_process_start_time() {
$this->set_process_item( self::$start_time, microtime( true ) );
}
private function set_process_end_time() {
$this->set_process_item( self::$end_time, microtime( true ) );
}
public function get_process_elapsed_time() {
$start_time = $this->get_process_start_time();
$end_time = $this->get_process_end_time();
return (int) ( $end_time - $start_time );
}
public function get_process_start_time() {
return $this->get_process_item( self::$start_time );
}
private function get_process_end_time() {
return $this->get_process_item( self::$end_time, time() );
}
private function get_process_item( $item, $default_value = false ) {
$process_option = $this->get_process_option();
return $this->array_utils->get_array_value( $process_option, $item, $default_value );
}
private function set_process_item( $item, $value ) {
( new Mutex( self::$process_key ) )->execute(
function () use ( $item, $value ) {
$process_option = $this->get_process_option();
$process_option[ $item ] = $value;
$this->update_process_option( $process_option );
}
);
}
private function get_process_option() {
$last_process = get_option( self::$process_key, array() );
return $this->array_utils->ensure_array( $last_process );
}
private function update_process_option( $last_process_option ) {
update_option( self::$process_key, $last_process_option, false );
}
}
PK E\' ' B core/media-library/class-media-library-scan-background-process.phpnu [ scanner = $scanner;
}
protected function task( $slice_id ) {
$this->scanner->scan_library_slice( $slice_id );
return true;
}
protected function should_update_queue_after_task() {
return true;
}
protected function get_instance_expiry_duration_seconds() {
$expire_duration = 0;
if ( defined( 'WP_SMUSH_SCAN_EXPIRE_DURATION' ) ) {
$expire_duration = (int) WP_SMUSH_SCAN_EXPIRE_DURATION;
}
return $expire_duration > 0 ? $expire_duration : MINUTE_IN_SECONDS;
}
protected function get_revival_limit() {
$constant_value = $this->get_revival_limit_constant();
return $constant_value ? $constant_value : parent::get_revival_limit();
}
private function get_revival_limit_constant() {
if ( ! defined( 'WP_SMUSH_SCAN_REVIVAL_LIMIT' ) ) {
return 0;
}
$constant_value = (int) WP_SMUSH_SCAN_REVIVAL_LIMIT;
return max( $constant_value, 0 );
}
}
PK E\Q ( ( 0 core/directory/class-directory-ui-controller.phpnu [ register_filter( 'wp_smush_modals', array( $this, 'register_modals' ) );
// Add the directory smush description into Bulk Smush settings.
$this->register_action( 'smush_setting_column_right_outside', array( $this, 'directory_smush_description' ), 25 );
$this->register_action( 'wp_smush_after_page_header', array( $this, 'show_directory_smush_move_notice' ) );
$this->register_filter( 'wp_smush_settings', array( $this, 'add_directory_smush_field' ) );
}
/**
* Check if the directory smush module should run.
*
* @return bool
*/
public function should_run() {
return Settings::get_instance()->is_directory_smush_active();
}
/**
* Register Choose Directory modal and progres dialog.
*/
public function register_modals( $modals ) {
$modals['directory-list'] = array();
$modals['progress-dialog'] = array();
return $modals;
}
/**
* Add Directory Smush field to Bulk Smush settings.
*
* @param array $settings Bulk Smush settings.
* @return array
*/
public function add_directory_smush_field( $settings ) {
$settings['directory_smush'] = array(
'label' => esc_html__( 'Directory Smush', 'wp-smushit' ),
'short_label' => esc_html__( 'Directory Smush', 'wp-smushit' ),
'desc' => esc_html__( 'Select a directory outside your Media Library to automatically Bulk Smush its images.', 'wp-smushit' ),
);
return $settings;
}
/**
* Add Directory Smush description.
*
* @param mixed $setting_key Setting key.
* @return void
*/
public function directory_smush_description( $setting_key ) {
if ( 'directory_smush' !== $setting_key ) {
return;
}
// Reset the bulk limit transient.
Core::should_continue_smush( true, 'dir_sent_count' );
?>
show_header_notice(); ?>
render_scan_result();
}
// Load nonce for the bulk smush.
wp_nonce_field( 'wp_smush_all', 'wp-smush-all' );
?>
core();
$images = $core->mod->dir->get_image_errors( $limit );
$errors = $core->mod->dir->get_image_errors_count();
?>
$image ) :
$tooltip_position = $id > 0 ? 'top' : 'bottom';
?>
50 ) : ?>
admin();
$notice_hidden = $smush_admin->is_notice_dismissed( 'directory-smush-move' );
if ( $notice_hidden ) {
return;
}
?>
*
* @copyright (c) 2017, Incsub (http://incsub.com)
*/
namespace Smush\Core;
use finfo;
use Smush\Core\Media\Media_Item_Cache;
use Smush\Core\Media\Media_Item_Stats;
use Smush\Core\Png2Jpg\Png2Jpg_Optimization;
use WDEV_Logger;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Helper
*/
class Helper {
/**
* Temporary cache.
*
* We use this instead of WP_Object_Cache to avoid save data to memory cache (persistent caching).
*
* And to avoid it take memory space, we also reset the group cache each we get a new key,
* it means one group only has one key.
* It's useful when we want to save result a function.
*
* Leave group is null to set and get the value by unique key.
*
* It's useful to avoid checking something multiple times.
*
* @since 3.9.6
*
* @var array
*/
private static $temp_cache = array();
/**
* WPMUDEV Logger lib.
*
* @access private
*
* @var null|WDEV_Logger
*/
private static $logger;
/**
* Get WDEV_Logger instance.
*
* @return WDEV_Logger
*/
public static function logger() {
if ( null === self::$logger ) {
$swiched_blog = false;
// On MU site, move all log files into the log folder [wp-content/uploads/smush] on the main site.
if ( is_multisite() && ! is_main_site() ) {
switch_to_blog( get_main_site_id() );
$swiched_blog = true;
}
$upload_dir = wp_get_upload_dir();
$log_dir = 'smush';
if ( false !== strpos( $upload_dir['basedir'], WP_CONTENT_DIR ) ) {
$log_dir = str_replace( trailingslashit( WP_CONTENT_DIR ), '', $upload_dir['basedir'] ) . '/smush';
}
if ( $swiched_blog ) {
restore_current_blog();
}
self::$logger = WDEV_Logger::create(
array(
'log_dir' => $log_dir,
'is_private' => true,
'modules' => array(
'smush' => array(
'is_global_module' => true,
),
'cdn' => array(),
'lazy' => array(),
'webp' => array(),
'png2jpg' => array(),
'resize' => array(),
'dir' => array(),
'backup' => array(),
'api' => array(),
'integrations' => array(),
'track' => array(),
),
)
);
}
return self::$logger;
}
/**
* Clean file path.
*
* @param string $file File path.
* @return string
*/
public static function clean_file_path( $file ) {
return str_replace( WP_CONTENT_DIR, '', $file );
}
/**
* Get value from temporary cache.
*
* @param string $key Key name.
* @param string|null $group Group name.
* @param mixed $default Default value, default is NULL.
*
* Uses:
* if( null !== Helper::cache_get( 'your_key', 'your_group' ) ){
* // Do your something with temporary cache value.
* }
* // Maybe setting it with Helper::cache_set.
*
* @since 3.9.6
*
* @return mixed The cached result.
*/
public static function cache_get( $key, $group = null, $default = null ) {
// Add current blog id to support MU site.
$current_blog_id = get_current_blog_id();
// Get cache for current blog.
$temp_cache = array();
if ( isset( self::$temp_cache[ $current_blog_id ] ) ) {
$temp_cache = self::$temp_cache[ $current_blog_id ];
}
/**
* Add a filter to force cache.
* It might be helpful when we debug.
*/
if ( apply_filters( 'wp_smush_force_cache', false, $key, $group, $temp_cache ) ) {
$locked_groups = array(
// Required for cache png2jpg()->can_be_converted() before resizing.
'png2jpg_can_be_converted',
// Required for cache unique file name of png2jpg()->convert_to_jpg().
'convert_to_jpg',
);
if ( ! in_array( $group, $locked_groups, true ) ) {
return null;
}
}
$value = $default;
if ( isset( $group ) ) {
if ( isset( $temp_cache[ $group ][ $key ] ) ) {
$value = $temp_cache[ $group ][ $key ];
} elseif ( isset( $temp_cache[ $group ] ) ) {
// Get a new key, reset group.
unset( $temp_cache[ $group ] );
}
} elseif ( isset( $temp_cache[ $key ] ) ) {
// Get the value by key.
$value = $temp_cache[ $key ];
}
return $value;
}
/**
* Save value to temporary cache.
*
* @since 3.9.6
*
* @param string $key Key name.
* @param mixed $value Data to cache.
* @param string|null $group Group name.
*
* Note, we return the provided value to use it inside some methods.
* @return mixed Returns the provided value.
*/
public static function cache_set( $key, $value, $group = null ) {
// Add current blog id to support MU site.
$current_blog_id = get_current_blog_id();
if ( isset( $group ) ) {
// Reset group and set the value.
self::$temp_cache[ $current_blog_id ][ $group ] = array( $key => $value );
} else {
// Save value by unique key.
self::$temp_cache[ $current_blog_id ][ $key ] = $value;
}
return $value;
}
/**
* Clear cache by group or key.
*
* @since 3.9.6
*
* @param string $cache_key Group name or unique key name.
*/
public static function cache_delete( $cache_key ) {
// Add current blog id to support MU site.
$current_blog_id = get_current_blog_id();
// Delete temp cache by cache key.
if ( isset( $cache_key, self::$temp_cache[ $current_blog_id ][ $cache_key ] ) ) {
unset( self::$temp_cache[ $current_blog_id ][ $cache_key ] );
}
return true;
}
/**
* Get mime type for file.
*
* @since 3.1.0 Moved here as a helper function.
*
* @param string $path Image path.
*
* @return bool|string
*/
public static function get_mime_type( $path ) {
// These mime functions only work on local files/streams.
if ( ! stream_is_local( $path ) ) {
return false;
}
// Get the File mime.
if ( class_exists( 'finfo' ) ) {
$file_info = new finfo( FILEINFO_MIME_TYPE );
} else {
$file_info = false;
}
if ( $file_info ) {
$mime = file_exists( $path ) ? $file_info->file( $path ) : '';
} elseif ( function_exists( 'mime_content_type' ) ) {
$mime = mime_content_type( $path );
} else {
$mime = false;
}
return $mime;
}
/**
* Filter the Posts object as per mime type.
*
* @param array $posts Object of Posts.
*
* @return array Array of post IDs.
*/
public static function filter_by_mime( $posts ) {
if ( empty( $posts ) ) {
return $posts;
}
foreach ( $posts as $post_k => $post ) {
if ( ! isset( $post->post_mime_type ) || ! in_array( $post->post_mime_type, Core::$mime_types, true ) ) {
unset( $posts[ $post_k ] );
} else {
$posts[ $post_k ] = $post->ID;
}
}
return $posts;
}
/**
* Iterate over PNG->JPG Savings to return cummulative savings for an image
*
* @param string $attachment_id Attachment ID.
*
* @return array
*/
public static function get_pngjpg_savings( $attachment_id = '' ) {
$media_item = Media_Item_Cache::get_instance()->get( $attachment_id );
$png2jpg_optimization = new Png2Jpg_Optimization( $media_item );
$stats = $png2jpg_optimization->is_optimized()
? $png2jpg_optimization->get_stats() :
new Media_Item_Stats();
return $stats->to_array();
}
/**
* Get the link to the media library page for the image.
*
* @since 2.9.0
*
* @param int $id Image ID.
* @param string $name Image file name.
* @param bool $src Return only src. Default - return link.
*
* @return string
*/
public static function get_image_media_link( $id, $name, $src = false ) {
$mode = get_user_option( 'media_library_mode' );
if ( 'grid' === $mode ) {
$link = admin_url( "upload.php?item=$id" );
} else {
$link = admin_url( "post.php?post=$id&action=edit" );
}
if ( ! $src ) {
return "$name ";
}
return $link;
}
/**
* Returns current user name to be displayed
*
* @return string
*/
public static function get_user_name() {
$current_user = wp_get_current_user();
return ! empty( $current_user->first_name ) ? $current_user->first_name : $current_user->display_name;
}
/**
* Allows to filter the error message sent to the user
*
* @param string $error Error message.
* @param string $attachment_id Attachment ID.
*
* @return mixed|null|string
*/
public static function filter_error( $error = '', $attachment_id = '' ) {
if ( empty( $error ) ) {
return null;
}
/**
* Replace the 500 server error with a more appropriate error message.
*/
if ( false !== strpos( $error, '500 Internal Server Error' ) ) {
$error = esc_html__( "Couldn't process image due to bad headers. Try re-saving the image in an image editor, then upload it again.", 'wp-smushit' );
} elseif ( strpos( $error, 'timed out' ) ) {
$error = esc_html__( "Timeout error. You can increase the request timeout to make sure Smush has enough time to process larger files. `define('WP_SMUSH_TIMEOUT', 150);`", 'wp-smushit' );
}
/**
* Used internally to modify the error message
*/
return apply_filters( 'wp_smush_error', $error, $attachment_id );
}
/**
* Format metadata from $_POST request.
*
* Post request in WordPress will convert all values
* to string. Make sure image height and width are int.
* This is required only when Async requests are used.
* See - https://wordpress.org/support/topic/smushit-overwrites-image-meta-crop-sizes-as-string-instead-of-int/
*
* @since 2.8.0
*
* @param array $meta Metadata of attachment.
*
* @return array
*/
public static function format_meta_from_post( $meta = array() ) {
// Do not continue in case meta is empty.
if ( empty( $meta ) ) {
return $meta;
}
// If metadata is array proceed.
if ( is_array( $meta ) ) {
// Walk through each items and format.
array_walk_recursive( $meta, array( self::class, 'format_attachment_meta_item' ) );
}
return $meta;
}
/**
* If current item is width or height, make sure it is int.
*
* @since 2.8.0
*
* @param mixed $value Meta item value.
* @param string $key Meta item key.
*/
public static function format_attachment_meta_item( &$value, $key ) {
if ( 'height' === $key || 'width' === $key ) {
$value = (int) $value;
}
/**
* Allows to format single item in meta.
*
* This filter will be used only for Async, post requests.
*
* @param mixed $value Meta item value.
* @param string $key Meta item key.
*/
$value = apply_filters( 'wp_smush_format_attachment_meta_item', $value, $key );
}
/**
* Check to see if file is animated.
*
* @since 3.0 Moved from class-resize.php
* @since 3.9.6 Add a new param $mime_type.
*
* @param string $file_path Image file path.
* @param int $id Attachment ID.
* @param false|string $mime_type Mime type.
*
* @return bool|int
*/
public static function check_animated_status( $file_path, $id, $mime_type = false ) {
$media_item = Media_Item_Cache::get_instance()->get( $id );
return $media_item->is_animated();
}
public static function check_animated_file_contents( $file_path ) {
$filecontents = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$str_loc = 0;
$count = 0;
// There is no point in continuing after we find a 2nd frame.
while ( $count < 2 ) {
$where1 = strpos( $filecontents, "\x00\x21\xF9\x04", $str_loc );
if ( false === $where1 ) {
break;
} else {
$str_loc = $where1 + 1;
$where2 = strpos( $filecontents, "\x00\x2C", $str_loc );
if ( false === $where2 ) {
break;
} else {
if ( $where2 === $where1 + 8 ) {
$count ++;
}
$str_loc = $where2 + 1;
}
}
}
return $count > 1;
}
/**
* Verify the file size limit.
*
* @param int $attachment_id Attachment ID.
*
* Note: We only use this method to verify an image before smushing it,
* we still need to verify the file size of every thumbnail files while smushing them.
*
* @return bool|int Return the file size if the size limit is exceeded, otherwise return FALSE.
*/
public static function size_limit_exceeded( $attachment_id ) {
$original_file_path = self::get_attached_file( $attachment_id, 'original' );
if ( ! file_exists( $original_file_path ) ) {
$original_file_path = self::get_attached_file( $attachment_id );
}
if ( ! file_exists( $original_file_path ) ) {
return false;
}
$max_file_size = WP_SMUSH_MAX_BYTES;
$file_size = filesize( $original_file_path );
return $file_size > $max_file_size ? $file_size : false;
}
/**
* Original File path
*
* @param string $original_file Original file.
*
* @return string File Path
*/
public static function original_file( $original_file = '' ) {
$uploads = wp_get_upload_dir();
$upload_path = $uploads['basedir'];
return path_join( $upload_path, $original_file );
}
/**
* Gets the WPMU DEV API key.
*
* @since 3.8.6
*
* @return string|false
*/
public static function get_wpmudev_apikey() {
// If API key defined manually, get that.
if ( defined( 'WPMUDEV_APIKEY' ) && WPMUDEV_APIKEY ) {
return WPMUDEV_APIKEY;
}
// If dashboard plugin is active, get API key from db.
if ( class_exists( 'WPMUDEV_Dashboard' ) ) {
return get_site_option( 'wpmudev_apikey' );
}
return false;
}
/**
* Get upsell URL.
*
* @since 3.9.1
*
* @param string $utm_campaign Campaing string.
*
* @return string
*/
public static function get_url( $utm_campaign = '' ) {
return self::get_utm_link( array( 'utm_campaign' => $utm_campaign ) );
}
public static function get_utm_link( $args, $url = '' ) {
if ( empty( $url ) ) {
$url = 'https://wpmudev.com/project/wp-smush-pro/';
}
$hash = '';
if ( strpos( $url, '#' ) ) {
list( $url, $hash ) = explode( '#', $url );
$hash = '#' . $hash;
}
$utm_source = 'smush';
$args = wp_parse_args(
$args,
array(
'utm_source' => $utm_source,
'utm_medium' => 'plugin',
)
);
return add_query_arg( $args, $url ) . $hash;
}
/**
* Get Smush page URL.
*
* @param string $page Page URL.
*
* @return string
*/
public static function get_page_url( $page = 'smush-bulk' ) {
if ( is_multisite() && is_network_admin() ) {
return network_admin_url( 'admin.php?page=' . $page );
}
return admin_url( 'admin.php?page=' . $page );
}
/**
* Get the extension of a file.
*
* @param string $file File path or file name.
* @param string $expected_ext The expected extension.
*
* @return bool|string Returns extension of the file, or false if it's not the same as the expected extension.
*/
public static function get_file_ext( $file, $expected_ext = '' ) {
$ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
if ( ! empty( $expected_ext ) ) {
return $expected_ext === $ext ? $ext : false;
} else {
return $ext;
}
}
/**
* Returns TRUE if the current request is REST API but is not media endpoint.
*
* @since 3.9.7
*/
public static function is_non_rest_media() {
static $is_not_rest_media;
if ( null === $is_not_rest_media ) {
$is_not_rest_media = false;
// We need to check if this call originated from Gutenberg and allow only media.
if ( ! empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
$route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] );
// Only allow media routes.
if ( empty( $route ) || '/wp/v2/media' !== $route ) {
// If not - return image metadata.
$is_not_rest_media = true;
}
}
}
return $is_not_rest_media;
}
/**
* Checks if user is allowed to perform the ajax actions.
* As previous we allowed for logged in user, so add a hook filter to allow
* user can custom the capability. It might also helpful when user custom admin menu via Branda.
*
* @since 3.13.0
*
* @param string $capability Capability default is manage_options.
* @return boolean
*/
public static function is_user_allowed( $capability = 'manage_options' ) {
$capability = empty( $capability ) ? 'manage_options' : $capability;
return current_user_can( apply_filters( 'wp_smush_admin_cap', $capability ) );
}
/*------ S3 Compatible Methods ------*/
/**
* Return unfiltered path for Smush or restore.
*
* @since 3.9.6
*
* @param int $attachment_id Attachment ID.
* @param string $type false|original|scaled|smush|backup|resize|check-resize.
* @param bool $unfiltered Whether to get unfiltered path or not.
*
* $type = original|backup => Try to get the original image file path.
* $type = false|smush => Get the file path base on the setting "compress original".
* $type = scaled|resize => Get the full file path, for large jpg it's scaled file not the original file.
*
* @return bool|string
*/
public static function get_raw_attached_file( $attachment_id, $type = 'smush', $unfiltered = false ) {
if ( function_exists( 'wp_get_original_image_path' ) ) {
if ( 'backup' === $type ) {
$type = 'original';
} elseif ( 'resize' === $type || 'check-resize' === $type ) {
$type = 'scaled';
}
// We will get the original file if we are doing for backup or restore, or smush original file.
if ( 'original' === $type || 'scaled' !== $type && Settings::get_instance()->get( 'original' ) ) {
$file_path = wp_get_original_image_path( $attachment_id, $unfiltered );
} else {
$file_path = get_attached_file( $attachment_id, $unfiltered );
}
} else {
$file_path = get_attached_file( $attachment_id, $unfiltered );
}
return $file_path;
}
/**
* Return file path for Smush, restore or checking resize.
*
* Add a hook for third party download the file,
* if it's not available on the server.
*
* @param int $attachment_id Attachment ID.
* @param string $type false|original|smush|backup|resize
* $type = smush|backup => Get the file path and download the attached file if it doesn't exist.
* $type = check-resize => Get the file path ( if it exists ), or filtered file path if it doesn't exist.
* $type = original => Only get the original file path (not scaled file).
* $type = scaled|resize => Get the full file path, for large jpg it's scaled file not the original file.
* $type = false => Get the file path base on the setting "compress original".
*
* @since 3.9.6 Moved S3 to S3 integration.
* Add a hook filter to allow 3rd party to custom the result.
*
* @return bool|string
*/
public static function get_attached_file( $attachment_id, $type = 'smush' ) {
if ( empty( $attachment_id ) ) {
return false;
}
/**
* Add a hook to allow 3rd party to custom the result.
*
* @param null|string $file_path File path or file url(checking resize).
* @param int $attachment_id Attachment ID.
* @param bool $should_download Should download the file if it doesn't exist.
* @param bool $should_real_path Expecting a real file path instead an URL.
* @param string $type false|original|smush|backup|resize|scaled|check-resize.
*
* @usedby Smush\Core\Integrations\S3::get_attached_file
*/
// If the site is using S3, we only need to download the file when doing smush, backup or resizing.
$should_download = in_array( $type, array( 'smush', 'backup', 'resize' ), true );
// But when restoring/smushing we are expecting a real file path.
$should_real_path = 'check-resize' !== $type;
$file_path = apply_filters( 'wp_smush_get_attached_file', null, $attachment_id, $should_download, $should_real_path, $type );
if ( is_null( $file_path ) ) {
$file_path = self::get_raw_attached_file( $attachment_id, $type );
}
return $file_path;
}
/**
* Custom for function wp_update_attachment_metadata
* We use this method to reset our S3 config before updating the metadata.
*
* @param int $attachment_id Attachment ID.
* @param array $meta Metadata.
* @return bool
*/
public static function wp_update_attachment_metadata( $attachment_id, $meta ) {
/**
* Fire before calling wp_update_attachment_metadata.
*
* @param int $attachment_id Attachment ID.
* @param array $meta Metadata.
*
* @hooked Smush\Core\Integrations\S3::release_smush_mode()
* This will help we to upload the attachments, and remove them if it's required.
*/
do_action( 'wp_smush_before_update_attachment_metadata', $attachment_id, $meta );
return wp_update_attachment_metadata( $attachment_id, $meta );
}
/**
* Check if the file exists on the server or cloud (S3).
*
* @since 3.9.6
*
* @param string|int $file File path or File ID.
* @param int|null $attachment_id File ID.
* @param bool $should_download Whether to download the file or not.
* @param bool $force_cache Whether check for result from the cache for full image or not.
*
* @return bool
*/
public static function file_exists( $file, $attachment_id = null, $should_download = false, $force_cache = false ) {
// If file is an attachment id we will reset the arguments.
// Use is_numeric for common case.
if ( $file && is_numeric( $file ) ) {
$attachment_id = $file;
$file = null;
}
// If the file path is not empty we will try to check file_exists first.
if ( empty( $file ) ) {
$file_exists = null;
} else {
$file_exists = file_exists( $file );
if ( $file_exists ) {
return true;
}
}
// Only continue if provided Attachment ID.
if ( $attachment_id < 1 ) {
return false;
}
/**
* Check if there is a cached for full image.
*/
if ( null === $file && ! $force_cache ) {
// Use different key for the download case.
$cache_key = 'helper_file_exists' . intval( $should_download );
$cached_file_exists = self::cache_get( $attachment_id, $cache_key );
if ( null !== $cached_file_exists ) {
return $cached_file_exists;
}
}
/**
* Add a hook to allow 3rd party to custom the result.
*
* @param bool|null $file_exists Current status.
* @param string|null $file Full file path.
* @param int $attachment_id Attachment ID.
* @param bool $should_download Whether to download the file if it's missing on the server or not.
*
* @usedby Smush\Core\Integrations\S3::file_exists_on_s3
*/
$file_exists = apply_filters( 'wp_smush_file_exists', $file_exists, $file, $attachment_id, $should_download );
// If it doesn't check and file is null, we will try to get the attached file from $attachment_id to check.
if ( is_null( $file_exists ) && ! $file ) {
$file = get_attached_file( $attachment_id );
if ( $file ) {
$file_exists = file_exists( $file );
}
}
/**
* Cache the result for full image,
* It also avoid we download again the not found image when enabling S3.
*/
if ( isset( $cache_key ) ) {
return self::cache_set( $attachment_id, $file_exists, $cache_key );
}
return $file_exists;
}
/**
* Check if the file exists, will try to download if it is not on the server (e.g s3).
*
* @since 3.9.6
*
* @param string|int $file File path or File ID.
* @param int|null $attachment_id File ID.
*
* @return bool Returns TRUE if file exists on the server.
*/
public static function exists_or_downloaded( $file, $attachment_id = null ) {
return self::file_exists( $file, $attachment_id, true );
}
/**
* Check if the file is an image, is supported in Smush and exists, and then cache the result.
*
* @since 3.9.6
*
* @param int|null $attachment_id File ID.
*
* @return bool|0 Returns TRUE if file is smushable, FALSE If the image does not exist, and 0 is not an image or is not supported
*/
public static function is_smushable( $attachment_id ) {
if ( empty( $attachment_id ) ) {
return null;// Nothing to check.
}
$is_smushable = self::cache_get( $attachment_id, 'is_smushable' );
if ( ! is_null( $is_smushable ) ) {
return $is_smushable;
}
// Set is_smushable is 0 (not false) to detect is not an image or image not found.
$is_smushable = 0;
$mime = get_post_mime_type( $attachment_id );
if (
apply_filters( 'wp_smush_resmush_mime_supported', in_array( $mime, Core::$mime_types, true ), $mime )
&& wp_attachment_is_image( $attachment_id )
) {
$is_smushable = self::file_exists( $attachment_id );
}
/**
* Cache and returns the result.
* Also added a hook for third-party.
*
* @param bool $is_smushable 0 if is not an image or mime type not supported | TRUE if image exists and otherwise is FALSE.
* @param int $attachment_id Attachment ID.
* @param array $mime_types List supported mime types.
*/
return apply_filters( 'wp_smush_is_smushable', self::cache_set( $attachment_id, $is_smushable, 'is_smushable' ), $attachment_id, Core::$mime_types );
}
/**
* Delete a file path from server and cloud (e.g s3).
*
* @since 3.9.6
*
* @param string|array $file_paths File path or list of file paths to remove.
* @param int $attachment_id Attachment ID.
* @param bool $only_exists_file Whether to call the action wp_smush_after_remove_file even the file doesn't exits or not.
*
* Current we only use this method to delete the file when after converting PNG to JPG or after restore, or when delete the files.
*/
public static function delete_permanently( $file_paths, $attachment_id, $only_exists_file = true ) {
if ( empty( $file_paths ) ) {
return;
}
$file_paths = (array) $file_paths;
$removed = true;
foreach ( $file_paths as $file_path ) {
if ( file_exists( $file_path ) ) {
if ( ! unlink( $file_path ) ) {
$removed = false;
// Log the error.
self::logger()->error( sprintf( 'Cannot delete file [%s(%d)].', self::clean_file_path( $file_path ), $attachment_id ) );
}
}
}
if ( $removed || ! $only_exists_file ) {
/**
* Fires after removing a file on server.
*
* @param int $attachment_id Attachment ID.
* @param string|array $file_paths File path or list of file paths.
* @param bool $removed Unlink status.
*/
do_action( 'wp_smush_after_remove_file', $attachment_id, $file_paths, $removed );
}
}
/*------ End S3 Compatible Methods ------*/
public static function get_image_sizes() {
// Get from cache if available to avoid duplicate looping.
$sizes = wp_cache_get( 'get_image_sizes', 'smush_image_sizes' );
if ( $sizes ) {
return $sizes;
}
return self::fetch_image_sizes();
}
public static function fetch_image_sizes() {
global $_wp_additional_image_sizes;
$additional_sizes = get_intermediate_image_sizes();
$sizes = array();
if ( empty( $additional_sizes ) ) {
return $sizes;
}
// Create the full array with sizes and crop info.
foreach ( $additional_sizes as $_size ) {
if ( in_array( $_size, array( 'thumbnail', 'medium', 'large' ), true ) ) {
$sizes[ $_size ]['width'] = get_option( $_size . '_size_w' );
$sizes[ $_size ]['height'] = get_option( $_size . '_size_h' );
$sizes[ $_size ]['crop'] = (bool) get_option( $_size . '_crop' );
} elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
$sizes[ $_size ] = array(
'width' => $_wp_additional_image_sizes[ $_size ]['width'],
'height' => $_wp_additional_image_sizes[ $_size ]['height'],
'crop' => $_wp_additional_image_sizes[ $_size ]['crop'],
);
}
}
// Medium Large.
if ( ! isset( $sizes['medium_large'] ) || empty( $sizes['medium_large'] ) ) {
$width = (int) get_option( 'medium_large_size_w' );
$height = (int) get_option( 'medium_large_size_h' );
$sizes['medium_large'] = array(
'width' => $width,
'height' => $height,
);
}
// Set cache to avoid this loop next time.
wp_cache_set( 'get_image_sizes', $sizes, 'smush_image_sizes' );
return $sizes;
}
public static function loopback_supported() {
$method_available = class_exists( '\WP_Site_Health' )
&& method_exists( '\WP_Site_Health', 'get_instance' )
&& method_exists( \WP_Site_Health::get_instance(), 'can_perform_loopback' );
if ( $method_available ) {
$loopback = \WP_Site_Health::get_instance()->can_perform_loopback();
return $loopback->status === 'good';
}
return true;
}
public static function get_recheck_images_link() {
if ( is_network_admin() ) {
// Users can't run re-check images on the network admin side at the moment, @see: SMUSH-369.
return '';
}
$recheck_images_link = add_query_arg(
array( 'smush-action' => 'start-scan-media' ),
self::get_page_url( 'smush-bulk' )
);
return $recheck_images_link;
}
}
PK E\ core/class-backup-size.phpnu [ dir = $dir;
}
/**
* @return mixed
*/
public function get_file() {
return $this->file;
}
/**
* @param mixed $file
*/
public function set_file( $file ) {
$this->file = $file;
return $this;
}
/**
* @return mixed
*/
public function get_width() {
return $this->width;
}
/**
* @param mixed $width
*/
public function set_width( $width ) {
$this->width = $width;
return $this;
}
/**
* @return mixed
*/
public function get_height() {
return $this->height;
}
/**
* @param mixed $height
*/
public function set_height( $height ) {
$this->height = $height;
return $this;
}
public function from_array( $array ) {
$this->set_file( (string) $this->get_array_value( $array, 'file' ) );
$this->set_width( (int) $this->get_array_value( $array, 'width' ) );
$this->set_height( (int) $this->get_array_value( $array, 'height' ) );
}
public function to_array() {
return array(
'file' => $this->get_file(),
'width' => $this->get_width(),
'height' => $this->get_height(),
);
}
public function get_file_path() {
$file_name = $this->get_file();
return path_join( $this->dir, $file_name );
}
private function get_array_value( $array, $key ) {
return isset( $array[ $key ] ) ? $array[ $key ] : null;
}
public function file_exists() {
return file_exists( $this->get_file_path() );
}
}
PK E\A core/class-array-utils.phpnu [ $value ) {
if ( is_array( $value ) ) {
$value_hash = $this->array_hash(
$value,
array_merge( $keys, array( $key ) )
);
} else {
$prefix = join( '~', $keys );
$value_hash = crc32( $prefix . $value );
}
$hash += $value_hash;
}
}
return $hash;
}
public function get_array_value( $haystack, $key, $default_value = null ) {
if ( empty( $key ) ) {
return $default_value;
}
if ( ! is_array( $key ) ) {
$key = array( $key );
}
if ( ! is_array( $haystack ) ) {
return $default_value;
}
$value = $haystack;
foreach ( $key as $key_part ) {
$value = isset( $value[ $key_part ] ) ? $value[ $key_part ] : $default_value;
}
return $value;
}
public function put_array_value( &$haystack, $value, $keys ) {
if ( ! is_array( $keys ) ) {
$keys = array( $keys );
}
$pointer = &$haystack;
foreach ( $keys as $key ) {
if ( ! isset( $pointer[ $key ] ) ) {
$pointer = empty( $pointer ) ? array() : $pointer;
$pointer[ $key ] = array();
}
$pointer = &$pointer[ $key ];
}
$pointer = $value;
}
public function ensure_array( $array ) {
return empty( $array ) || ! is_array( $array )
? array()
: $array;
}
/**
* WARNING: This trick works only for arrays in which all the values are valid keys.
* @see https://stackoverflow.com/a/8321701
*
* @param $array scalar[]
*
* @return array Unique array
*/
public function fast_array_unique( $array ) {
if ( ! is_array( $array ) ) {
return array();
}
return array_keys( array_flip( $array ) );
}
}
PK E\ִs8 8 ' core/photon/class-photon-controller.phpnu [ register_filter( 'wp_smush_media_item_size', array( $this, 'only_handle_full_size' ), 10, 2 );
$this->register_action( 'smush_setting_column_right_outside', array( $this, 'render_site_accelerator_notice' ), 20, 2 );
}
public function is_photon_active() {
return has_filter( 'wp_image_editors', 'photon_subsizes_override_image_editors' );
}
public function only_handle_full_size( $size, $key ) {
if ( ! $this->is_photon_active() ) {
return $size;
}
return $key === Media_Item::get_size_key_full()
? $size
: false;
}
public function render_site_accelerator_notice( $name ) {
if ( ! $this->is_photon_active() || 'bulk' !== $name ) {
return;
}
$text = sprintf( /* translators: %1$s - , %2$s - */
esc_html__( "We noticed that your site is configured to completely offload intermediate thumbnail sizes (they don't exist in your Media Library), so Smush can't optimize those images. You can still optimize your %1\$sOriginal Images%2\$s if you want to.", 'wp-smushit' ),
'',
' '
);
?>
*
* @copyright (c) 2018, Incsub (http://incsub.com)
*/
namespace Smush\Core;
use Smush\App\Abstract_Page;
use Smush\Core\CDN\CDN_Controller;
use Smush\Core\Smush\Smusher;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Installer for handling updates and upgrades of the plugin.
*
* @since 2.8.0
*/
class Installer {
/**
* Triggered on Smush deactivation.
*
* @since 3.1.0
*/
public static function smush_deactivated() {
if ( ! class_exists( '\\Smush\\Core\\Modules\\CDN_Controller' ) ) {
$cdn_controller_path = __DIR__ . '/cdn/class-cdn-controller.php';
if ( file_exists( $cdn_controller_path ) ) {
require_once $cdn_controller_path;
}
}
Cron_Controller::get_instance()->unschedule_cron();
Settings::get_instance()->delete_setting( 'wp-smush-cdn_status' );
delete_site_option( 'wp_smush_api_auth' );
}
/**
* Check if an existing install or new.
*
* @since 2.8.0 Moved to this class from wp-smush.php file.
*/
public static function smush_activated() {
if ( ! defined( 'WP_SMUSH_ACTIVATING' ) ) {
define( 'WP_SMUSH_ACTIVATING', true );
}
$version = get_site_option( 'wp-smush-version' );
self::maybe_mark_as_pre_3_22_site( $version );
// Cache activated date time.
$event_name = ! empty( $version ) ? 'plugin_activated' : 'plugin_installed';
self::cache_event_time( $event_name );
if ( ! class_exists( '\\Smush\\Core\\Settings' ) ) {
require_once __DIR__ . '/class-settings.php';
}
Settings::get_instance()->initial_default_site_settings();
// If the version is not saved or if the version is not same as the current version,.
if ( ! $version || WP_SMUSH_VERSION !== $version ) {
global $wpdb;
// Check if there are any existing smush stats.
$results = $wpdb->get_var(
$wpdb->prepare(
"SELECT meta_id FROM {$wpdb->postmeta} WHERE meta_key=%s LIMIT 1",
'wp-smpro-smush-data'
)
); // db call ok; no-cache ok.
if ( $results || $version ) {
update_site_option( 'wp-smush-install-type', 'existing' );
}
// Create directory smush table.
self::directory_smush_table();
// Store the plugin version in db.
update_site_option( 'wp-smush-version', WP_SMUSH_VERSION );
}
}
/**
* Handle plugin upgrades.
*
* @since 2.8.0
*/
public static function upgrade_settings() {
// Avoid executing this over an over in same thread.
if ( defined( 'WP_SMUSH_ACTIVATING' ) || ( defined( 'WP_SMUSH_UPGRADING' ) && WP_SMUSH_UPGRADING ) ) {
return;
}
if ( ! class_exists( '\\Smush\\Core\\Settings' ) ) {
require_once __DIR__ . '/class-settings.php';
}
$version = get_site_option( 'wp-smush-version' );
if ( false === $version ) {
self::smush_activated();
} else {
self::maybe_mark_as_pre_3_22_site( $version );
}
if ( false !== $version && WP_SMUSH_VERSION !== $version ) {
if ( ! defined( 'WP_SMUSH_UPGRADING' ) ) {
define( 'WP_SMUSH_UPGRADING', true );
}
// Cache last updated time.
self::cache_event_time( 'plugin_upgraded' );
if ( version_compare( $version, '3.7.0', '<' ) ) {
self::upgrade_3_7_0();
}
if ( version_compare( $version, '3.8.0', '<' ) ) {
// Delete the flag for hiding the BF modal because it was removed.
delete_site_option( 'wp-smush-hide_blackfriday_modal' );
}
if ( version_compare( $version, '3.8.3', '<' ) ) {
// Delete this unused setting, leftover from old smush.
delete_option( 'wp-smush-transparent_png' );
}
if ( version_compare( $version, '3.9.5', '<' ) ) {
delete_site_option( 'wp-smush-show-black-friday' );
}
if ( version_compare( $version, '3.9.10', '<' ) ) {
self::dir_smush_set_primary_key();
}
if ( version_compare( $version, '3.10.0', '<' ) ) {
self::upgrade_3_10_0();
}
if ( version_compare( $version, '3.10.3', '<' ) ) {
self::upgrade_3_10_3();
}
if ( version_compare( $version, '3.16.0', '<' ) ) {
self::regenerate_preset_configs_before_3_16_0();
} elseif ( version_compare( $version, '3.21.0', '<' ) ) {
self::regenerate_preset_configs();
}
if ( version_compare( $version, '3.21.0', '<' ) ) {
self::upgrade_3_21_0();
}
if ( version_compare( $version, '3.21.0', '<' ) ) {
$hide_new_feature_highlight_modal = apply_filters( 'wpmudev_branding_hide_doc_link', false );
if ( ! $hide_new_feature_highlight_modal ) {
// Add the flag to display the new feature background process modal.
add_site_option( 'wp-smush-show_upgrade_modal', true );
}
// Show new feature hotspot.
self::set_new_feature_hotspot_flag();
}
// Create/upgrade directory smush table.
self::directory_smush_table();
// Store the latest plugin version in db.
update_site_option( 'wp-smush-version', WP_SMUSH_VERSION );
self::reset_smusher_error_counts();
}
}
/**
* Create or upgrade custom table for directory Smush.
*
* After creating or upgrading the custom table, update the path_hash
* column value and structure if upgrading from old version.
*
* @since 2.9.0
*/
public static function directory_smush_table() {
if ( ! class_exists( '\\Smush\\Core\\Modules\\Abstract_Module' ) ) {
require_once __DIR__ . '/modules/class-abstract-module.php';
}
if ( ! class_exists( '\\Smush\\Core\\Modules\\Dir' ) ) {
require_once __DIR__ . '/modules/class-dir.php';
}
// No need to continue on sub sites.
if ( ! Modules\Dir::should_continue() ) {
return;
}
// Create a class object, if doesn't exists.
if ( ! is_object( WP_Smush::get_instance()->core()->mod->dir ) ) {
WP_Smush::get_instance()->core()->mod->dir = new Modules\Dir();
}
// Create/upgrade directory smush table.
WP_Smush::get_instance()->core()->mod->dir->create_table();
}
/**
* Set primary key for directory smush table on upgrade to 3.9.10.
*
* @since 3.9.10
*/
private static function dir_smush_set_primary_key() {
global $wpdb;
// Only call it after creating table smush_dir_images. If the table doesn't exist, returns.
if ( ! Modules\Dir::table_exist() ) {
return;
}
// If the table is already set the primary key, return.
if ( $wpdb->query( $wpdb->prepare( "SHOW INDEXES FROM {$wpdb->base_prefix}smush_dir_images WHERE Key_name = %s;", 'PRIMARY' ) ) ) {
return;
}
// Set column ID as a primary key.
$wpdb->query( "ALTER TABLE {$wpdb->base_prefix}smush_dir_images ADD PRIMARY KEY (id);" );
}
/**
* Check if table needs to be created and create if not exists.
*
* @since 3.8.6
*/
public static function maybe_create_table() {
if ( ! function_exists( 'get_current_screen' ) ) {
return;
}
if ( isset( get_current_screen()->id ) && false === strpos( get_current_screen()->id, 'page_smush' ) ) {
return;
}
self::directory_smush_table();
}
/**
* Upgrade to 3.7.0
*
* @since 3.7.0
*/
private static function upgrade_3_7_0() {
delete_site_option( 'wp-smush-run_recheck' );
// Fix the "None" animation in lazy-load options.
$lazy = Settings::get_instance()->get_setting( 'wp-smush-lazy_load' );
if ( ! $lazy || ! isset( $lazy['animation'] ) || ! isset( $lazy['animation']['selected'] ) ) {
return;
}
if ( '0' === $lazy['animation']['selected'] ) {
$lazy['animation']['selected'] = 'none';
Settings::get_instance()->set_setting( 'wp-smush-lazy_load', $lazy );
}
}
/**
* Upgrade to 3.10.0
*
* @return void
* @since 3.10.0
*/
private static function upgrade_3_10_0() {
// Remove unused options.
delete_site_option( 'wp-smush-hide_pagespeed_suggestion' );
delete_site_option( 'wp-smush-hide_upgrade_notice' );
// Rename the default config.
$stored_configs = get_site_option( 'wp-smush-preset_configs', false );
if ( is_array( $stored_configs ) && isset( $stored_configs[0] ) && isset( $stored_configs[0]['name'] ) && 'Basic config' === $stored_configs[0]['name'] ) {
$stored_configs[0]['name'] = __( 'Default config', 'wp-smushit' );
update_site_option( 'wp-smush-preset_configs', $stored_configs );
}
}
/**
* Upgrade 3.10.3
*
* @return void
* @since 3.10.3
*/
private static function upgrade_3_10_3() {
delete_site_option( 'wp-smush-hide_smush_welcome' );
// Logger options.
delete_site_option( 'wdev_logger_wp-smush-pro' );
delete_site_option( 'wdev_logger_wp-smushit' );
// Clean old cronjob (missing callback).
if ( wp_next_scheduled( 'wdev_logger_clear_logs' ) ) {
wp_clear_scheduled_hook( 'wdev_logger_clear_logs' );
}
}
private static function maybe_mark_as_pre_3_22_site( $version ) {
if ( ! $version || false !== get_site_option( 'wp_smush_pre_3_22_site' ) ) {
return;
}
if ( version_compare( $version, '3.21.1', '>' ) ) {
$version = 0;
}
update_site_option( 'wp_smush_pre_3_22_site', $version );
}
private static function regenerate_preset_configs_before_3_16_0() {
// Update Smush mode for display on Configs page.
$stored_configs = get_site_option( 'wp-smush-preset_configs', array() );
if ( empty( $stored_configs ) || ! is_array( $stored_configs ) ) {
return;
}
$configs_handler = Configs::get_instance();
$new_settings = array(
'background_email' => false,
);
foreach ( $stored_configs as $key => $preset_config ) {
if ( empty( $preset_config['config']['configs']['settings'] ) ) {
continue;
}
$preset_config ['config']['configs']['settings'] = array_merge( $new_settings, $preset_config['config']['configs']['settings'] );
$preset_config ['config'] = $configs_handler->sanitize_and_format_configs( $preset_config['config']['configs'] );
$stored_configs[ $key ] = $preset_config;
}
update_site_option( 'wp-smush-preset_configs', $stored_configs );
}
private static function regenerate_preset_configs() {
// Regenerate preset configs to update Next-Gen Formats.
$stored_configs = get_site_option( 'wp-smush-preset_configs', array() );
if ( empty( $stored_configs ) || ! is_array( $stored_configs ) ) {
return;
}
$configs_handler = Configs::get_instance();
foreach ( $stored_configs as $key => $preset_config ) {
if ( empty( $preset_config['config']['configs'] ) ) {
continue;
}
$preset_config ['config'] = $configs_handler->sanitize_and_format_configs( $preset_config['config']['configs'] );
$stored_configs[ $key ] = $preset_config;
}
update_site_option( 'wp-smush-preset_configs', $stored_configs );
}
private static function upgrade_3_21_0() {
self::migrate_auto_resize_to_new_settings();
self::migrate_auto_resize_to_new_settings_for_sub_sites();
}
private static function migrate_auto_resize_to_new_settings_for_sub_sites() {
if ( ! is_multisite() ) {
return;
}
self::for_each_public_site( function() {
self::migrate_auto_resize_to_new_settings();
} );
}
private static function migrate_auto_resize_to_new_settings() {
$settings = Settings::get_instance();
$is_auto_resizing_active = $settings->get( 'auto_resize' );
if ( ! $is_auto_resizing_active ) {
return;
}
$settings->set( 'auto_resizing', $is_auto_resizing_active );
$settings->set( 'cdn_dynamic_sizes', $is_auto_resizing_active );
$settings->delete( 'auto_resize' );
}
private static function cache_event_time( $event ) {
$option_key = 'wp_smush_event_times';
$event_times = get_site_option( $option_key, array() );
$event_times[ $event ] = time();
update_site_option( $option_key, $event_times );
}
/**
* @return void
*/
private static function reset_smusher_error_counts() {
( new Smusher() )->reset_error_counts();
}
private static function set_new_feature_hotspot_flag() {
add_option( 'wp-smush-show-new-feature-hotspot', true );
self::set_new_feature_hotspot_flag_for_sub_sites();
}
private static function set_new_feature_hotspot_flag_for_sub_sites() {
if ( ! is_multisite() ) {
return;
}
self::for_each_public_site( function() {
add_option( 'wp-smush-show-new-feature-hotspot', true );
} );
}
private static function for_each_public_site( $callback ) {
if ( ! is_multisite() ) {
return;
}
$site_args = array(
'fields' => 'ids',
'public' => 1,
'number' => 250, // Limit to 250 sites to avoid performance issues.
);
$site_ids = get_sites( $site_args );
if ( empty( $site_ids ) ) {
return;
}
foreach ( $site_ids as $site_id ) {
switch_to_blog( $site_id );
call_user_func( $callback );
restore_current_blog();
}
}
}
PK E\
core/class-controller.phpnu [ should_run() ) {
return;
}
foreach ( $this->actions as $action_hook => $actions ) {
foreach ( $actions as $action_args ) {
add_action( $action_hook, $action_args['callback'], $action_args['priority'], $action_args['accepted_args'] );
}
}
foreach ( $this->filters as $filter_hook => $filters ) {
foreach ( $filters as $filter_args ) {
add_filter( $filter_hook, $filter_args['callback'], $filter_args['priority'], $filter_args['accepted_args'] );
}
}
}
public function stop() {
foreach ( $this->actions as $action_hook => $actions ) {
foreach ( $actions as $action_args ) {
remove_action( $action_hook, $action_args['callback'], $action_args['priority'] );
}
}
foreach ( $this->filters as $filter_hook => $filters ) {
foreach ( $filters as $filter_args ) {
remove_action( $filter_hook, $filter_args['callback'], $filter_args['priority'] );
}
}
}
public function register_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
$this->actions[ $hook_name ][] = array(
'callback' => $callback,
'priority' => $priority,
'accepted_args' => $accepted_args,
);
}
public function register_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
$this->filters[ $hook_name ][] = array(
'callback' => $callback,
'priority' => $priority,
'accepted_args' => $accepted_args,
);
}
public function remove_action( $hook_name ) {
if ( isset( $this->actions[ $hook_name ] ) ) {
foreach ( $this->actions[ $hook_name ] as $action_args ) {
remove_action( $hook_name, $action_args['callback'], $action_args['priority'] );
}
}
}
public function remove_filter( $hook_name ) {
if ( isset( $this->filters[ $hook_name ] ) ) {
foreach ( $this->filters[ $hook_name ] as $filter_args ) {
remove_filter( $hook_name, $filter_args['callback'], $filter_args['priority'] );
}
}
}
public function restore_action( $hook_name ) {
if ( isset( $this->actions[ $hook_name ] ) ) {
foreach ( $this->actions[ $hook_name ] as $action_args ) {
add_action( $hook_name, $action_args['callback'], $action_args['priority'], $action_args['accepted_args'] );
}
}
}
public function restore_filter( $hook_name ) {
if ( isset( $this->filters[ $hook_name ] ) ) {
foreach ( $this->filters[ $hook_name ] as $filter_args ) {
add_filter( $hook_name, $filter_args['callback'], $filter_args['priority'], $filter_args['accepted_args'] );
}
}
}
}
PK E\%! ! core/wp-compat.phpnu [
client = new Client();
$this->streaming_enabled = $streaming_enabled;
$this->server_utils = new Server_Utils();
parent::__construct( $streaming_enabled, $extra_headers );
}
public function do_requests( $files_data ) {
$responses = array();
$request_generator = $this->make_request_generator();
$pool = new Pool( $this->client, $request_generator( $files_data ), array(
'concurrency' => count( $files_data ),
'fulfilled' => function ( $response, $size_key ) use ( $files_data, &$responses ) {
$file_data = $files_data[ $size_key ];
// Convert to a response that looks like standard WP HTTP API responses
$response = $this->multi_to_singular_response( $response );
$this->do_action( $response, $file_data );
// Call the actual on complete callback
$responses[ $size_key ] = call_user_func( $this->get_on_complete(), $response, $size_key, $file_data );
},
'rejected' => function ( $reason, $size_key ) use ( $files_data, &$responses ) {
list( $reason_code, $reason_message ) = $this->extract_error_details( $reason );
$file_data = $files_data[ $size_key ];
$response = new WP_Error( $reason_code, $reason_message );
$this->do_action( $response, $file_data );
// Call the actual on complete callback
$responses[ $size_key ] = call_user_func( $this->get_on_complete(), $response, $size_key, $file_data );
},
) );
$pool->promise()->wait();
return $responses;
}
private function extract_error_details( $error ) {
$error_code = '';
$error_message = '';
if ( is_a( $error, '\Exception' ) ) {
$error_code = $error->getCode();
$error_message = $error->getMessage();
} elseif ( is_string( $error ) ) {
$error_code = $error;
$error_message = $error;
}
if ( empty( $error_code ) && ! empty( $error_message ) ) {
$error_message_lowercase = strtolower( $error_message );
if ( $error instanceof ConnectException ) {
$error_code = $this->map_connect_exception_error_code( $error_message_lowercase );
} elseif ( $error instanceof ClientException ) {
$error_code = 'client-error';
} elseif ( $error instanceof ServerException ) {
$error_code = 'server-error';
} elseif ( $error instanceof RequestException ) {
$error_code = 'request-error';
}
}
if ( empty( $error_code ) ) {
$error_code = 'unknown-error';
$error_message = $error_message ? $error_message : 'An unknown error occurred when trying to send the request.';
}
return array( $error_code, $error_message );
}
private function map_connect_exception_error_code( $error_message_lowercase ) {
$error_map = array(
'curl error 35' => 'ssl-error',
'ssl' => 'ssl-error',
'curl error 28' => 'timeout-error',
'timed out' => 'timeout-error',
'curl error 6' => 'host-resolution-error',
'could not resolve host' => 'host-resolution-error',
'curl error 7' => 'connection-failed-error',
'failed to connect' => 'connection-failed-error',
);
foreach ( $error_map as $error_string => $code ) {
if ( false !== strpos( $error_message_lowercase, $error_string ) ) {
return $code;
}
}
return 'connection-error';
}
/**
* @param $guzzle_response Response
*
* @return array
*/
private function multi_to_singular_response( $guzzle_response ) {
return array(
'body' => $guzzle_response->getBody()->getContents(),
'response' => array( 'code' => $guzzle_response->getStatusCode() ),
);
}
/**
* @return \Closure
*/
private function make_request_generator() {
return function ( $files_data ) {
foreach ( $files_data as $size_key => $size_file_data ) {
yield $size_key => function () use ( $size_file_data ) {
list( $file_path ) = $this->get_file_path_and_url( $size_file_data );
return $this->client->postAsync( $this->get_url(), array(
'headers' => $this->get_api_request_headers( $file_path ),
'body' => $this->get_body( $file_path ),
'timeout' => $this->get_timeout(),
'user-agent' => $this->get_user_agent(),
) );
};
}
};
}
private function get_body( $file_path ) {
if ( $this->streaming_enabled ) {
return Utils::streamFor( fopen( $file_path, 'rb' ) );
} else {
return $this->get_full_file_contents( $file_path );
}
}
/**
* @param $response
* @param $file_data
*
* @return void
*/
private function do_action( $response, $file_data ) {
list( $file_path ) = $this->get_file_path_and_url( $file_data );
do_action( 'smush_http_api_debug', $response, array(
'url' => $this->get_url(),
'headers' => $this->get_api_request_headers( $file_path ),
'type' => 'POST',
'data' => "[streamed $file_path]",
'timeout' => $this->get_timeout(),
'user-agent' => $this->get_user_agent(),
) );
}
public function is_supported() {
$curl_version = function_exists( 'curl_version' ) ? curl_version() : array( 'version' => 0 );
$curl_version_supported = version_compare( $curl_version['version'], '7.19.4', '>=' );
$allow_url_fopen_supported = $this->server_utils->is_function_supported( 'allow_url_fopen' );
$php_version_supported = version_compare( PHP_VERSION, '7.2.5', '>=' );
return $php_version_supported && ( $allow_url_fopen_supported || $curl_version_supported );
}
}
PK E\̕v 0 core/smush/class-smush-request-wp-sequential.phpnu [ backoff = new Backoff();
$this->retry_attempts = WP_SMUSH_RETRY_ATTEMPTS;
$this->retry_wait = WP_SMUSH_RETRY_WAIT;
parent::__construct( $streaming_enabled, $extra_headers );
}
public function do_requests( $files_data ) {
$responses = array();
foreach ( $files_data as $size_key => $file_data ) {
$responses[ $size_key ] = $this->do_request( $file_data, $size_key );
}
return $responses;
}
private function get_api_request_args( $file_path ) {
return array(
'headers' => $this->get_api_request_headers( $file_path ),
'body' => $this->get_full_file_contents( $file_path ),
'timeout' => $this->get_timeout(),
'user-agent' => $this->get_user_agent(),
);
}
/**
* @param array $request
*
* @return array|\WP_Error
*/
private function make_request_with_backoff( $request ) {
return $this->backoff->set_wait( $this->retry_wait )
->set_max_attempts( $this->retry_attempts )
->enable_jitter()
->set_decider( array( $this, 'should_retry' ) )
->run( function () use ( $request ) {
return wp_remote_post( $this->get_url(), $request );
} );
}
public function should_retry( $response ) {
return $this->retry_attempts > 0 && (
is_wp_error( $response )
|| 200 !== wp_remote_retrieve_response_code( $response )
);
}
/**
* @param $file_data
* @param $size_key
*
* @return mixed
*/
public function do_request( $file_data, $size_key ) {
list( $file_path ) = $this->get_file_path_and_url( $file_data );
$request = $this->get_api_request_args( $file_path );
$response = $this->make_request_with_backoff( $request );
do_action( 'smush_http_api_debug', $response, $request );
return call_user_func( $this->get_on_complete(), $response, $size_key, $file_data );
}
/**
* @param int $retry_attempts
*/
public function set_retry_attempts( $retry_attempts ) {
$this->retry_attempts = $retry_attempts;
}
public function is_supported() {
return function_exists( 'wp_remote_post' );
}
}
PK E\
% core/smush/class-smush-controller.phpnu [ global_stats = Global_Stats::get();
$this->register_filter( 'wp_smush_optimizations', array(
$this,
'add_smush_optimization',
), self::$smush_optimization_order, 2 );
$this->register_filter( 'wp_smush_global_optimization_stats', array( $this, 'add_png2jpg_global_stats' ) );
$this->register_filter( 'wp_smush_optimization_global_stats_instance', array(
$this,
'create_global_stats_instance',
), 10, 2 );
$this->register_action( 'wp_smush_settings_updated', array(
$this,
'maybe_mark_global_stats_as_outdated',
), 10, 2 );
// Bulk image sizes.
$this->register_action( 'wp_smush_image_sizes_updated', array(
$this,
'mark_global_stats_as_outdated_on_image_sizes_change',
), 10, 2 );
$this->register_action( 'wp_smush_image_sizes_deleted', array( $this->global_stats, 'mark_as_outdated' ) );
$this->register_action( 'wp_smush_image_sizes_added', array( $this->global_stats, 'mark_as_outdated' ) );
}
/**
* @param $optimizations array
* @param $media_item Media_Item
*
* @return array
*/
public function add_smush_optimization( $optimizations, $media_item ) {
$optimization = new Smush_Optimization( $media_item );
$optimizations[ $optimization->get_key() ] = $optimization;
return $optimizations;
}
public function add_png2jpg_global_stats( $stats ) {
$stats[ Smush_Optimization::get_key() ] = new Media_Item_Optimization_Global_Stats_Persistable(
self::$global_stats_option_id,
new Smush_Optimization_Global_Stats()
);
return $stats;
}
public function create_global_stats_instance( $original, $key ) {
if ( $key === Smush_Optimization::get_key() ) {
return new Smush_Optimization_Global_Stats();
}
return $original;
}
public function maybe_mark_global_stats_as_outdated( $old_settings, $settings ) {
$old_lossy_status = ! empty( $old_settings['lossy'] ) ? (int) $old_settings['lossy'] : 0;
$new_lossy_status = ! empty( $settings['lossy'] ) ? (int) $settings['lossy'] : 0;
$lossy_status_changed = $old_lossy_status !== $new_lossy_status;
$old_exif_status = ! empty( $old_settings['strip_exif'] );
$new_exif_status = ! empty( $settings['strip_exif'] );
$exif_status_changed = $old_exif_status !== $new_exif_status;
if ( $lossy_status_changed || $exif_status_changed ) {
$this->global_stats->mark_as_outdated();
}
}
public function mark_global_stats_as_outdated_on_image_sizes_change( $old_image_sizes, $new_image_sizes ) {
$image_sizes_updated = count( $old_image_sizes ) !== count( $new_image_sizes )
|| array_diff( $old_image_sizes, $new_image_sizes );
if ( ! empty( $image_sizes_updated ) ) {
$this->global_stats->mark_as_outdated();
}
}
}
PK E\ " core/smush/class-smush-request.phpnu [ streaming_enabled = $streaming_enabled;
$this->array_utils = new Array_Utils();
$this->file_utils = new File_Utils();
$this->fs = new File_System();
$this->settings = Settings::get_instance();
$this->extra_headers = $extra_headers;
$this->user_agent = WP_SMUSH_UA;
$this->timeout = WP_SMUSH_TIMEOUT;
}
public function get_on_complete() {
return $this->on_complete;
}
public function set_on_complete( $on_complete ) {
$this->on_complete = $on_complete;
return $this;
}
public function get_connect_timeout() {
return $this->connect_timeout;
}
public function get_timeout() {
return $this->timeout;
}
public function get_user_agent() {
return $this->user_agent;
}
public function get_url() {
return defined( 'WP_SMUSH_API_HTTP' ) ? WP_SMUSH_API_HTTP : WP_SMUSH_API;
}
/**
* @return string[]
*/
public function get_api_request_headers( $file_path ) {
$headers = array_merge(
array(
'accept' => 'application/json', // The API returns JSON.
'exif' => $this->settings->get( 'strip_exif' ) ? 'false' : 'true',
),
$this->get_extra_headers()
);
if ( $this->streaming_enabled ) {
$headers['response'] = 'image_url';
} else {
$headers['response'] = 'image_full';
}
$headers['content-type'] = 'application/binary';
$headers['lossy'] = $this->settings->get_lossy_level_setting();
// Check if premium member, add API key.
$api_key = $this->settings->get_api_key();
if ( ! empty( $api_key ) ) {
$headers['apikey'] = $api_key;
$is_large_file = $this->file_utils->is_large_file( $file_path );
if ( $is_large_file ) {
$headers['islarge'] = 1;
}
}
return $headers;
}
public function get_full_file_contents( $file_path ) {
// Temporary increase the limit because we are about to read a full file into memory.
wp_raise_memory_limit( 'image' );
$contents = $this->fs->file_get_contents( $file_path );
return empty( $contents ) ? '' : $contents;
}
/**
* @param $file_data string|array
*
* @return array
*/
protected function get_file_path_and_url( $file_data ) {
if ( is_string( $file_data ) ) {
$file_path = $file_data;
$file_url = '';
} else {
$file_path = $this->array_utils->get_array_value( $file_data, 'path' );
$file_url = $this->array_utils->get_array_value( $file_data, 'url' );
}
return array( $file_path, $file_url );
}
public function get_extra_headers() {
return $this->extra_headers;
}
public function set_extra_headers( $extra_headers ) {
$this->extra_headers = $extra_headers;
return $this;
}
public function do_request( $file_data, $size_key ) {
return false;
}
public function set_streaming_enabled( $streaming_enabled ) {
$this->streaming_enabled = $streaming_enabled;
return $this;
}
/**
* @param $files_data array
*
* @return mixed
*/
abstract public function do_requests( $files_data );
abstract public function is_supported();
}
PK E\l) ) ' core/smush/class-smush-optimization.phpnu [ media_item = $media_item;
$this->settings = Settings::get_instance();
$this->smusher = new Smusher();
}
public static function get_smush_meta_key() {
return self::$smush_meta_key;
}
public static function get_lossy_meta_key() {
return self::$lossy_meta_key;
}
public static function get_key() {
return self::$key;
}
public function get_name() {
return __( 'Smush', 'wp-smushit' );
}
public function get_stats() {
if ( is_null( $this->stats ) ) {
$this->stats = $this->prepare_stats();
}
return $this->stats;
}
public function set_stats( $stats ) {
$this->stats = $stats;
}
private function get_meta_sizes() {
$smush_meta = $this->get_smush_meta();
return empty( $smush_meta['sizes'] )
? array()
: $smush_meta['sizes'];
}
private function get_size_meta( $size_key ) {
$sizes = $this->get_meta_sizes();
$size = empty( $sizes[ $size_key ] )
? array()
: (array) $sizes[ $size_key ];
return empty( $size ) ? array() : $size;
}
private function size_meta_exists( $size_key ) {
return ! empty( $this->get_size_meta( $size_key ) );
}
public function get_size_stats( $size_key ) {
if ( empty( $this->size_stats[ $size_key ] ) ) {
$this->size_stats[ $size_key ] = $this->prepare_size_stats( $size_key );
}
return $this->size_stats[ $size_key ];
}
private function prepare_size_stats( $size_key ) {
$stats = new Media_Item_Stats();
$stats->from_array( $this->get_size_meta( $size_key ) );
return $stats;
}
public function save() {
$meta = $this->make_smush_meta();
if ( ! empty( $meta ) ) {
update_post_meta( $this->media_item->get_id(), self::$smush_meta_key, $meta );
// TODO: the separate lossy meta is only necessary for the backup global stats, if enough time has passed and enough people have moved to the new stats then we can remove it
if ( $this->get_lossy_level() ) {
update_post_meta( $this->media_item->get_id(), self::$lossy_meta_key, 1 );
} else {
delete_post_meta( $this->media_item->get_id(), self::$lossy_meta_key );
}
$this->reset();
}
}
public function is_optimized() {
return ! $this->get_stats()->is_empty();
}
public function should_optimize() {
if ( $this->media_item->is_skipped() || $this->media_item->has_errors() ) {
return false;
}
return ! empty( $this->get_sizes_to_smush() );
}
public function should_reoptimize() {
return $this->should_resmush();
}
public function optimize() {
if ( ! $this->should_optimize() ) {
return false;
}
$media_item = $this->media_item;
$files_data = array_map( function ( $size ) {
return array(
'url' => $size->get_file_url(),
'path' => $size->get_file_path(),
);
}, $this->get_sizes_to_smush() );
$responses = $this->smusher->smush( $files_data );
$success_responses = array_filter( $responses );
if ( count( $success_responses ) !== count( $responses ) ) {
return false;
}
$media_item_stats = $this->create_media_item_stats_instance();
foreach ( $responses as $size_key => $data ) {
$this->update_from_response( $size_key, $data, $media_item_stats );
}
$this->set_stats( $media_item_stats );
if ( $media_item_stats->get_bytes() >= 0 ) {
do_action( 'wp_smush_image_optimised',
$this->media_item->get_id(),
$this->make_smush_meta(),
$this->media_item->get_wp_metadata()
);
}
// Update media item
$media_item->save();
// Update smush meta
$this->save();
return true;
}
private function prepare_stats() {
$smush_meta = $this->get_smush_meta();
$stats = $this->create_media_item_stats_instance();
$stats_data = empty( $smush_meta['stats'] )
? array()
: $smush_meta['stats'];
$stats->from_array( $stats_data );
$stats->set_lossy( (bool) $this->get_lossy_level() );
return $stats;
}
private function get_smush_meta() {
if ( is_null( $this->smush_meta ) ) {
$this->smush_meta = $this->fetch_smush_meta();
}
return $this->smush_meta;
}
private function fetch_smush_meta() {
$post_meta = get_post_meta( $this->media_item->get_id(), self::$smush_meta_key, true );
return empty( $post_meta ) || ! is_array( $post_meta )
? array()
: $post_meta;
}
public function keep_exif() {
if ( is_null( $this->keep_exif ) ) {
$this->keep_exif = $this->prepare_keep_exif();
}
return $this->keep_exif;
}
private function prepare_keep_exif() {
$smush_meta = $this->get_smush_meta();
return isset( $smush_meta['stats']['keep_exif'] )
? (int) $smush_meta['stats']['keep_exif']
: 0;
}
public function set_keep_exif( $keep_exif ) {
$this->keep_exif = (int) $keep_exif;
}
public function get_lossy_level() {
if ( is_null( $this->lossy_level ) ) {
$this->lossy_level = $this->prepare_lossy_level();
}
return $this->lossy_level;
}
private function prepare_lossy_level() {
$smush_meta = $this->get_smush_meta();
return empty( $smush_meta['stats']['lossy'] )
? 0
: (int) $smush_meta['stats']['lossy'];
}
public function set_lossy_level( $lossy ) {
$this->lossy_level = (int) $lossy;
}
public function get_api_version() {
if ( is_null( $this->api_version ) ) {
$this->api_version = $this->prepare_api_version();
}
return $this->api_version;
}
private function prepare_api_version() {
$smush_meta = $this->get_smush_meta();
return empty( $smush_meta['stats']['api_version'] )
? ''
: $smush_meta['stats']['api_version'];
}
public function set_api_version( $api_version ) {
$this->api_version = $api_version;
}
private function make_smush_meta() {
$smush_meta = $this->get_smush_meta();
// Stats
$media_item_stats = $this->get_stats();
if ( ! $media_item_stats->is_empty() ) {
$smush_meta['stats'] = array_merge(
empty( $smush_meta['stats'] ) ? array() : $smush_meta['stats'],
$media_item_stats->to_array(),
array(
'keep_exif' => $this->keep_exif(),
'lossy' => $this->get_lossy_level(),
'api_version' => $this->get_api_version(),
)
);
}
// Sizes
foreach ( $this->size_stats as $size_key => $size_stats ) {
if ( ! $size_stats->is_empty() ) {
$smush_meta['sizes'][ $size_key ] = (object) $size_stats->to_array();
}
}
return $smush_meta;
}
private function should_resmush() {
if ( ! $this->should_optimize() ) {
return false;
}
if ( $this->is_next_level_available() ) {
return true;
}
if ( $this->settings->get( 'strip_exif' ) && $this->keep_exif() ) {
return true;
}
foreach ( $this->get_sizes_to_smush() as $size_key => $size ) {
$is_smushed = $this->size_meta_exists( $size_key ) || $this->is_file_smushed( $size->get_file_path() );
if ( ! $is_smushed ) {
return true;
}
}
return false;
}
public function is_next_level_available() {
$current_lossy_level = $this->get_lossy_level();
$required_lossy_level = $this->settings->get_lossy_level_setting();
return $current_lossy_level < $required_lossy_level;
}
private function is_file_smushed( $file_path ) {
foreach ( $this->media_item->get_sizes() as $size_key => $size ) {
if ( $size->get_file_path() === $file_path && $this->size_meta_exists( $size_key ) ) {
return true;
}
}
return false;
}
/**
* @param $size_key
* @param object $data
* @param $media_item_stats Smush_Media_Item_Stats
*/
private function update_from_response( $size_key, $data, $media_item_stats ) {
$size_stats = $this->get_size_stats( $size_key );
$this->set_api_version( $data->api_version );
$this->set_lossy_level( (int) $data->lossy );
$this->set_keep_exif( empty( $data->keep_exif ) ? 0 : $data->keep_exif );
// Update the size stats
$size_stats->from_array( $this->size_stats_from_response( $size_stats, $data ) );
// Add the size stats to the media item stats
$media_item_stats->add( $size_stats );
// TODO: maybe remove the lossy count from smush stats
$media_item_stats->set_lossy( (bool) $this->get_lossy_level() );
}
/**
* @param $existing_stats Media_Item_Stats
* @param $data
*
* @return array
*/
private function size_stats_from_response( $existing_stats, $data ) {
$size_before = max( $existing_stats->get_size_before(), $data->before_size ); // We want to use the oldest before size
return array(
'size_before' => $size_before,
'size_after' => $data->after_size,
'time' => $data->time,
);
}
/**
* @return WP_Error
*/
public function get_errors() {
return $this->get_smusher()->get_errors();
}
protected function reset() {
foreach ( $this->reset_properties as $property ) {
$this->$property = null;
}
}
public function delete_data() {
delete_post_meta( $this->media_item->get_id(), self::$smush_meta_key );
$this->reset();
}
/**
* @param $size Media_Item_Size
*
* @return bool
*/
public function should_optimize_size( $size ) {
if ( ! $this->should_optimize() ) {
return false;
}
return array_key_exists(
$size->get_key(),
$this->get_sizes_to_smush()
);
}
/**
* @return Media_Item_Size[]
*/
private function get_sizes_to_smush() {
return $this->media_item->get_smushable_sizes();
}
/**
* @return Smusher
*/
public function get_smusher() {
return $this->smusher;
}
/**
* @return Smush_Media_Item_Stats
*/
private function create_media_item_stats_instance() {
return new Smush_Media_Item_Stats();
}
public function get_optimized_sizes_count() {
$count = 0;
$sizes = $this->get_meta_sizes();
foreach ( $sizes as $size ) {
if ( ! empty( $size->bytes ) ) {
$count++;
}
}
return $count;
}
}
PK E\NDڔ + core/smush/class-smush-media-item-stats.phpnu [ lossy;
}
/**
* @param mixed $lossy
*
* @return Smush_Media_Item_Stats
*/
public function set_lossy( $lossy ) {
$this->lossy = $lossy;
return $this;
}
public function to_array() {
$array = parent::to_array();
$array['lossy'] = $this->is_lossy();
return $array;
}
public function from_array( $array ) {
parent::from_array( $array );
$this->set_lossy( ! empty( $array['lossy'] ) );
}
}
PK E\A 4 core/smush/class-smush-optimization-global-stats.phpnu [ set_lossy_count( (int) $this->get_array_value( $array, 'lossy_count' ) );
}
public function to_array() {
$array = parent::to_array();
$array['lossy_count'] = $this->get_lossy_count();
return $array;
}
/**
* @param $attachment_id int
* @param $item_stats Smush_Media_Item_Stats
*
* @return boolean
*/
public function add_item_stats( $attachment_id, $item_stats ) {
$added = parent::add_item_stats( $attachment_id, $item_stats );
if ( $added && $item_stats->is_lossy() ) {
$this->set_lossy_count( $this->get_lossy_count() + 1 );
}
return $added;
}
/**
* @param $attachment_id int
* @param $item_stats Smush_Media_Item_Stats
*
* @return boolean
*/
public function subtract_item_stats( $attachment_id, $item_stats ) {
$subtracted = parent::subtract_item_stats( $attachment_id, $item_stats );
if ( $subtracted && $item_stats->is_lossy() ) {
// Assuming that we added to the lossy count
$this->set_lossy_count( max( $this->get_lossy_count() - 1, 0 ) );
}
return $subtracted;
}
/**
* @param $addend Smush_Optimization_Global_Stats
*
* @return void
*/
public function add( $addend ) {
parent::add( $addend );
$this->set_lossy_count( $this->get_lossy_count() + $addend->get_lossy_count() );
}
/**
* @param $subtrahend Smush_Optimization_Global_Stats
*
* @return void
*/
public function subtract( $subtrahend ) {
parent::subtract( $subtrahend );
$this->set_lossy_count( max( $this->get_lossy_count() - $subtrahend->get_lossy_count(), 0 ) );
}
/**
* @return int
*/
public function get_lossy_count() {
return $this->lossy_count;
}
/**
* @param int $lossy_count
*
* @return Smush_Optimization_Global_Stats
*/
public function set_lossy_count( $lossy_count ) {
$this->lossy_count = $lossy_count;
return $this;
}
/**
* Get key.
*
* @return mixed
*/
public static function get_key() {
return self::$key;
}
/**
* Get lossy_meta_key.
*
* @return mixed
*/
public static function get_lossy_meta_key() {
return self::$lossy_meta_key;
}
/**
* Get smush_meta_key.
*
* @return mixed
*/
public static function get_smush_meta_key() {
return self::$smush_meta_key;
}
}
PK E\uG uG 1 core/smush/class-smush-settings-ui-controller.phpnu [ settings = Settings::get_instance();
$this->register_action( 'smush_setting_column_right_inside', array( $this, 'settings_desc' ), 10, 2 );
$this->register_action( 'smush_setting_column_right_inside', array( $this, 'auto_smush' ), 15, 2 );
$this->register_action( 'smush_setting_column_right_outside', array( $this, 'image_sizes' ), 15, 2 );
$this->register_action( 'smush_setting_column_right_additional', array( $this, 'resize_settings' ), 20 );
$this->register_action( 'smush_setting_column_right_outside', array( $this, 'full_size_options' ), 20, 2 );
$this->register_action( 'smush_setting_column_right_outside', array( $this, 'scale_options' ), 20, 2 );
$this->register_action( 'wp_smush_bulk_smush_settings', array( $this, 'render_basic_settings' ) );
$this->register_action( 'wp_smush_bulk_smush_settings', array( $this, 'render_advanced_settings' ), 20 );
$this->register_action( 'wp_smush_after_advanced_settings', array( $this, 'render_bulk_restore_field' ) );
}
/**
* Show additional descriptions for settings.
*
* @param string $setting_key Setting key.
*/
public function settings_desc( $setting_key = '' ) {
if ( empty( $setting_key ) || ! in_array(
$setting_key,
array( 'original', 'strip_exif', 'png_to_jpg', 'background_email' ),
true
) ) {
return;
}
if ( 'png_to_jpg' === $setting_key ) {
$upgrade_url = Helper::get_utm_link(
array(
'utm_campaign' => 'smush_bulk_smush_advanced_settings_pngtojpg',
)
);
// Pro upsell description.
$desc = sprintf(
/* translators: 1: Open link tag , 2: Close link tag */
esc_html__(
'Enable this feature in Smush to convert non-transparent PNG files to JPEGs, but only if it results in a smaller file size. %1$sUnlock now with Pro%2$s',
'wp-smushit'
),
'',
' '
);
?>
'smush_bulk_smush_BO_email_toggle',
)
);
$bg_email_desc = sprintf(
/* translators: 1: Open link tag , 2: Close link tag */
esc_html__( 'Get the email notification as part of the Background Optimization feature. You don’t have to keep the bulk smush page open when it is in progress. Be notified when Background Optimization completes. %1$sUnlock now with Pro%2$s', 'wp-smushit' ),
'',
' '
);
echo wp_kses_post( $bg_email_desc );
break;
default:
break;
}
?>
settings->get_setting( 'wp-smush-image_sizes' );
$sizes = WP_Smush::get_instance()->core()->image_dimensions();
$all_selected = false === $image_sizes || count( $image_sizes ) === count( $sizes );
?>
settings->get_setting(
'wp-smush-resize_sizes',
array(
'width' => '',
'height' => '',
)
);
// Get max dimensions.
$max_sizes = WP_Smush::get_instance()->core()->get_max_image_dimensions();
$setting_status = $this->settings->get( 'resize' );
?>
',
esc_html( $max_sizes['width'] ),
'×',
esc_html( $max_sizes['height'] ),
''
);
?>
https://gifgifs.com/resizer/'
);
?>
settings->get( 'backup' );
?>
settings->get( 'no_scale' );
?>
/>
get_basic_settings( $bulk_settings );
if ( empty( $basic_settings ) ) {
return;
}
?>
render_bulk_settings( $basic_settings ); ?>
render_bulk_settings( $advanced_settings );
do_action( 'wp_smush_after_advanced_settings' );
?>
settings->can_access_pro_field( $name );
$is_pro_field = $this->settings->is_pro_field( $name );
$is_upsell_field = $this->settings->is_upsell_field( $name );
$is_disabled_field = ( $is_upsell_field || $is_pro_field ) && ! $can_access_pro;
$is_pro_but_not_upsell = $is_pro_field && ! $is_upsell_field;
// Only show pro upsell field on Bulk Smush page to avoid upselly UI.
if ( $is_pro_but_not_upsell && ! $can_access_pro ) {
continue;
}
$value = $this->settings->get( $name );
$value = ( $is_disabled_field || empty( $value ) ) ? false : $value;
// Show settings option.
do_action( 'wp_smush_render_setting_row', $name, $value, $is_disabled_field, $is_upsell_field );
}
}
public function render_bulk_restore_field() {
$backups = new Backups();
$backup_exists = $backups->items_with_backup_exist();
?>
smush_parallel = WP_SMUSH_PARALLEL;
$this->settings = Settings::get_instance();
$this->logger = Helper::logger();
$this->errors = new WP_Error();
$this->warnings = new WP_Error();
$this->fs = new File_System();
$this->upload_dir = new Upload_Dir();
$this->array_utils = new Array_Utils();
$this->product_analytics = Product_Analytics::get_instance();
$this->streaming_enabled = $this->settings->streaming_enabled();
$this->thread_safe_options = new Thread_Safe_Options();
$this->request_multiple = new Smush_Request_Guzzle_Multiple( $this->streaming_enabled );
$this->request_sequential = new Smush_Request_WP_Sequential( $this->streaming_enabled );
}
/**
* @param $files_data string[]|array[]
*
* @return boolean[]|object[]
*/
public function smush( $files_data ) {
$this->set_errors( new WP_Error() );
$this->set_warnings( new WP_Error() );
if (
$this->smush_parallel
&& $this->parallel_available_on_server()
) {
return $this->smush_parallel( $files_data );
} else {
return $this->smush_sequential( $files_data );
}
}
/**
* @param $files_data string[]|array[]
*
* @return boolean[]|object[]
*/
private function smush_parallel( $files_data ) {
$timer = new Timer();
$timer->start();
$retry = array();
$responses = array();
$this->request_multiple
->set_on_complete( function ( $response, $response_size_key, $size_file_data ) use ( &$responses, &$retry ) {
list( $size_file_path ) = $this->get_file_path_and_url( $size_file_data );
$parsed_response = $this->parse_response( $response, $size_file_path );
if ( $this->is_network_error( $parsed_response ) ) {
$retry[ $response_size_key ] = $size_file_data;
$this->add_warnings( $parsed_response, $response_size_key );
} else {
$is_success_response = $this->handle_response( $parsed_response, $response_size_key, $size_file_path );
// If the network request was successful, there are still some cases where it's best to retry
if ( ! $is_success_response && $this->has_error_worth_retrying() ) {
$retry[ $response_size_key ] = $size_file_data;
} else {
$responses[ $response_size_key ] = $is_success_response;
}
}
} )->do_requests( $files_data );
foreach ( $retry as $retry_size_key => $retry_size_file ) {
list( $retry_file_path ) = $this->get_file_path_and_url( $retry_size_file );
// Note that we are not sending a file URL because we want the retry to happen using the traditional approach
// This is designed to prevent issues when a firewall is blocking the callback
$responses[ $retry_size_key ] = $this->smush_file( $retry_file_path, $retry_size_key );
}
$time_elapsed = $timer->end();
$this->maybe_disable_streaming();
$this->maybe_change_http_setting();
$this->maybe_track_image_url_error( $time_elapsed );
$this->maybe_track_network_errors( $time_elapsed );
return $responses;
}
private function maybe_change_http_setting() {
$codes = array_merge( $this->errors->get_error_codes(), $this->warnings->get_error_codes() );
if ( in_array( self::$error_ssl_cert, $codes, true ) ) {
// Switch to http protocol.
$this->settings->set_setting( 'wp-smush-use_http', 1 );
}
}
/**
* @param $files_data string[]|array[]
*
* @return boolean[]|object[]
*/
private function smush_sequential( $files_data ) {
return $this->request_sequential
->set_streaming_enabled( $this->streaming_enabled )
->set_on_complete( function ( $response, $response_size_key, $size_file_data ) {
list( $size_file_path ) = $this->get_file_path_and_url( $size_file_data );
$parsed_response = $this->parse_response( $response, $size_file_path );
return $this->handle_response( $parsed_response, $response_size_key, $size_file_path );
} )->do_requests( $files_data );
}
/**
* @param $file_path string
* @param $size_key string
*
* @return bool|object
*/
public function smush_file( $file_path, $size_key = '', $file_url = '' ) {
return $this->request_sequential
->set_streaming_enabled( false )
->set_on_complete( function ( $response, $size_key, $file_data ) {
list( $file_path ) = $this->get_file_path_and_url( $file_data );
$parsed_response = $this->parse_response( $response, $file_path );
return $this->handle_response( $parsed_response, $size_key, $file_path );
} )
->do_request( $file_path, $size_key );
}
public function set_request_sequential( $request_sequential ) {
$this->request_sequential = $request_sequential;
return $this;
}
public function get_request_sequential() {
return $this->request_sequential;
}
/**
* @param $parsed_response WP_Error|object
* @param $size_key string
* @param $file_path string
*
* @return bool|object
*/
private function handle_response( $parsed_response, $size_key, $file_path ) {
if ( is_wp_error( $parsed_response ) ) {
$this->add_error( $size_key, $parsed_response->get_error_code(), $parsed_response->get_error_message(), $parsed_response->get_error_data() );
return false;
}
$data = $parsed_response;
if ( $data->bytes_saved > 0 ) {
if ( ! empty( $data->image_url ) ) {
$saved_from_image_url = $this->save_from_image_url( $data->image_url, $file_path, $data->image_md5 );
if ( is_wp_error( $saved_from_image_url ) ) {
$this->add_error(
$size_key,
self::$image_not_saved_from_url,
/* translators: %s: Error message. */
sprintf( __( 'Smush was successful but we were unable to save from URL: %s.', 'wp-smushit' ), $saved_from_image_url->get_error_message() ),
array(
'original_code' => $saved_from_image_url->get_error_code(),
'original_message' => $saved_from_image_url->get_error_message(),
)
);
return false;
}
} else {
$optimized_image_saved = $this->save_smushed_image_file( $file_path, $data->image );
if ( ! $optimized_image_saved ) {
$this->add_error(
$size_key,
'image_not_saved',
/* translators: %s: File path. */
sprintf( __( 'Smush was successful but we were unable to save the file due to a file system error: [%s].', 'wp-smushit' ), $this->upload_dir->get_human_readable_path( $file_path ) )
);
return false;
}
}
}
// No need to pass image data any further
if ( isset( $data->image ) ) {
$data->image = null;
}
if ( isset( $data->image_md5 ) ) {
$data->image_md5 = null;
}
// Check for API message and store in db.
if ( ! empty( $data->api_message ) ) {
$this->add_api_message( (array) $data->api_message );
}
return $data;
}
/**
* @param $input_stream resource
* @param $target_file_path
* @param $file_md5
* @param $chunk_size
*
* @return true|WP_Error
*/
protected function save_from_resource( $input_stream, $target_file_path, $file_md5, $chunk_size ) {
if ( ! function_exists( 'wp_tempnam' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$timer = new Timer();
$timer->start();
$error = false;
$temp_name = wp_tempnam();
do {
if ( empty( $temp_name ) ) {
$error = new WP_Error( 'temp-file-creation-error', 'Error creating temporary file' );
break;
}
$output_stream = fopen( $temp_name, "wb" );
do {
$chunk_copied_successfully = stream_copy_to_stream( $input_stream, $output_stream, $chunk_size );
if ( $chunk_copied_successfully === false ) {
break;
}
} while ( ! feof( $input_stream ) );
// Close the input and output streams
fclose( $input_stream );
fclose( $output_stream );
if ( $chunk_copied_successfully === false ) {
$error = new WP_Error( 'temp-file-save-error', 'Error saving temp file' );
break;
}
$hash_equals = hash_equals( $file_md5, md5_file( $temp_name ) );
if ( ! $hash_equals ) {
$error = new WP_Error( 'file-hash-mismatch', 'File hash mismatch' );
break;
}
$target_file_name = basename( $target_file_path );
$type = $this->wp_get_image_mime( $temp_name );
if ( ! str_starts_with( $type, 'image/' ) ) {
$error = new WP_Error(
'invalid-file-type',
sprintf( 'Invalid file type. Calculated type for file named %s at %s is %s', $target_file_name, $temp_name, $type )
);
break;
}
$file_copied = copy( $temp_name, $target_file_path );
if ( ! $file_copied ) {
$error = new WP_Error( 'error-moving-file', 'Error moving file' );
break;
}
$permissions = $this->get_permissions_for_image( $target_file_path );
chmod( $target_file_path, $permissions );
} while ( 0 );
@unlink( $temp_name );
$time = $timer->end();
if ( $error ) {
$this->logger->notice( sprintf( 'File could not be saved: %s', $error->get_error_message() ) );
return $error;
} else {
$this->logger->notice( sprintf( 'File saved successfully in %s seconds', $time ) );
return true;
}
}
public function save_from_image_url( $image_url, $target_file_path, $file_md5, $chunk_size = null ) {
if ( is_null( $chunk_size ) ) {
$chunk_size = self::$default_chunk_size;
}
try {
$client = new Client();
$response = $client->get( $image_url, [
'stream' => true,
] );
$input_stream = $response->getBody()->detach();
return $this->save_from_resource( $input_stream, $target_file_path, $file_md5, $chunk_size );
} catch ( \Exception $exception ) {
$this->logger->error( sprintf( 'Error fetching image from URL: %s', $exception->getMessage() ) );
$code = $exception->getCode();
$code = empty( $code ) ? 'timeout' : $code;
return new WP_Error( $code, 'Error fetching image from URL' );
}
}
protected function save_smushed_image_file( $file_path, $image ) {
$pre = apply_filters( 'wp_smush_pre_image_write', false, $file_path, $image );
if ( $pre !== false ) {
$this->logger->notice( 'Another plugin/theme short circuited the image write operation using the wp_smush_pre_image_write filter.' );
// Assume that the plugin/theme responsible took care of it
return true;
}
$permissions = $this->get_permissions_for_image( $file_path );
// Save the new file
$success = $this->put_smushed_image_file( $file_path, $image );
chmod( $file_path, $permissions );
return $success;
}
private function put_smushed_image_file( $file_path, $image ) {
$temp_file = $file_path . '.tmp';
$success = $this->put_image_using_temp_file( $file_path, $image, $temp_file );
// Clean up
if ( $this->fs->file_exists( $temp_file ) ) {
$this->fs->unlink( $temp_file );
}
return $success;
}
private function put_image_using_temp_file( $file_path, $image, $temp_file ) {
$file_written = file_put_contents( $temp_file, $image );
if ( ! $file_written ) {
return false;
}
$renamed = rename( $temp_file, $file_path );
if ( $renamed ) {
return true;
}
$copied = $this->fs->copy( $temp_file, $file_path );
if ( $copied ) {
return true;
}
return false;
}
private function add_api_message( $api_message = array() ) {
if ( empty( $api_message ) || ! count( $api_message ) || empty( $api_message['timestamp'] ) || empty( $api_message['message'] ) ) {
return;
}
$o_api_message = get_site_option( 'wp-smush-api_message', array() );
if ( array_key_exists( $api_message['timestamp'], $o_api_message ) ) {
return;
}
$message = array();
$message[ $api_message['timestamp'] ] = array(
'message' => sanitize_text_field( $api_message['message'] ),
'type' => sanitize_text_field( $api_message['type'] ),
'status' => 'show',
);
update_site_option( 'wp-smush-api_message', $message );
}
/**
* @param $response
* @param $file_path string
*
* @return object|WP_Error
*/
private function parse_response( $response, $file_path ) {
$error = new WP_Error();
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
if ( strpos( $error_message, 'SSL CA cert' ) !== false ) {
$error->add( self::$error_ssl_cert, $error_message, array(
'original_code' => $response->get_error_code(),
'original_message' => $error_message,
) );
return $error;
} else if ( strpos( $error_message, 'timed out' ) !== false ) {
$error->add(
self::$error_time_out,
esc_html__( "Skipped due to a timeout error. You can increase the request timeout to make sure Smush has enough time to process larger files. define('WP_SMUSH_TIMEOUT', 150);", 'wp-smushit' ),
array(
'original_code' => $response->get_error_code(),
'original_message' => $error_message,
)
);
return $error;
} else {
$error->add(
self::$error_posting_to_api,
/* translators: %s: Error message. */
sprintf( __( 'Error posting to API: %s', 'wp-smushit' ), $error_message ),
array(
'original_code' => $response->get_error_code(),
'original_message' => $error_message,
)
);
return $error;
}
}
$response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $response_code ) {
$non_200_body = wp_remote_retrieve_body( $response );
$non_200_json = $non_200_body ? json_decode( $non_200_body ) : null;
if ( ! empty( $non_200_json->data ) ) {
// We got a pre-formatted error from the API
$error_message = $non_200_json->data;
} else if ( strpos( wp_remote_retrieve_response_message( $response ), 'Gateway Timeout' ) !== false ) {
$error->add(
self::$error_gateway_time_out,
esc_html__( 'The request is taking longer than expected. Please check back in a few moments.', 'wp-smushit' ),
array(
'original_code' => $response_code,
'original_message' => wp_remote_retrieve_response_message( $response ),
)
);
return $error;
}else {
// Make an error from the response message
$error_message = sprintf(
/* translators: 1: Error code, 2: Error message. */
__( 'Error posting to API: %1$s %2$s', 'wp-smushit' ),
$response_code,
wp_remote_retrieve_response_message( $response )
);
}
$error->add( self::$response_code_non_200, $error_message, array(
'original_code' => $response_code,
'original_message' => "Received response code $response_code",
) );
return $error;
}
$json = json_decode( wp_remote_retrieve_body( $response ) );
if ( empty( $json->success ) ) {
$error_message = ! empty( $json->data )
? $json->data
: __( "Image couldn't be smushed", 'wp-smushit' );
$error->add( 'unsuccessful_smush', $error_message );
return $error;
}
if (
empty( $json->data )
|| empty( $json->data->before_size )
|| empty( $json->data->after_size )
) {
$error->add( 'no_data', __( 'Unknown API error', 'wp-smushit' ) );
return $error;
}
$data = $json->data;
$data->bytes_saved = isset( $data->bytes_saved ) ? (int) $data->bytes_saved : 0;
$optimized_image_larger = $data->after_size > $data->before_size;
if ( $optimized_image_larger ) {
$error->add(
'optimized_image_larger',
/* translators: 1: File path, 2: Savings bytes. */
sprintf( 'The smushed image is larger than the original image [%s] (bytes saved %d), keep original image.', $this->upload_dir->get_human_readable_path( $file_path ), $data->bytes_saved )
);
return $error;
}
if ( empty( $data->image_url ) ) {
$image = empty( $data->image ) ? '' : $data->image;
if ( $data->bytes_saved > 0 ) {
// Because of the API response structure, the following should only be done when there are some bytes_saved.
if ( $data->image_md5 !== md5( $image ) ) {
$error_message = __( 'Smush data corrupted, try again.', 'wp-smushit' );
$error->add( 'data_corrupted', $error_message );
return $error;
}
if ( ! empty( $image ) ) {
$data->image = base64_decode( $data->image );
}
}
}
return $data;
}
/**
* @param $response WP_Error|object
*
* @return bool
*/
private function is_network_error( $response ) {
if ( ! is_wp_error( $response ) ) {
return false;
}
$network_error_codes = $this->get_network_error_codes();
foreach ( $response->get_error_codes() as $error_code ) {
if ( in_array( $error_code, $network_error_codes, true ) ) {
return true;
}
}
return false;
}
/**
* @return bool
*/
public function parallel_available_on_server() {
return $this->request_multiple->is_supported();
}
/**
* @param bool $smush_parallel
*
* @return Smusher
*/
public function set_smush_parallel( $smush_parallel ) {
$this->smush_parallel = $smush_parallel;
return $this;
}
public function get_request_multiple() {
return $this->request_multiple;
}
/**
* @param Smush_Request $request_multiple
*
* @return Smusher
*/
public function set_request_multiple( $request_multiple ) {
$this->request_multiple = $request_multiple;
return $this;
}
public function get_errors() {
return $this->errors;
}
/**
* @param $errors WP_Error
*
* @return void
*/
private function set_errors( $errors ) {
$this->errors = $errors;
}
/**
* @param $size_key string
* @param $code string
* @param $message string
*
* @return void
*/
private function add_error( $size_key, $code, $message, $data = array() ) {
$size_key_format = empty( $size_key ) ? '' : "[$size_key] ";
// Log the error
$this->logger->error( $size_key_format . $message );
// Add the error
$this->errors->add( $code, $size_key_format . $message );
if ( ! empty( $data ) ) {
$this->errors->add_data( $data, $code );
}
}
/**
* @param $size_key string
* @param $code string
* @param $message string
*
* @return void
*/
private function add_warning( $size_key, $code, $message, $data = array() ) {
// Log the warning
$this->logger->warning( "[$size_key] $message" );
// Add the warning
$this->warnings->add( $code, "[$size_key] $message" );
if ( ! empty( $data ) ) {
$this->warnings->add_data( $data, $code );
}
}
private function has_warning( $code ) {
return ! empty( $this->warnings->get_error_message( $code ) );
}
/**
* @param $warnings WP_Error
*
* @return void
*/
private function set_warnings( $warnings ) {
$this->warnings = $warnings;
}
public function get_warnings() {
return $this->warnings;
}
/**
* @param $code string
*
* @return bool
*/
private function has_error( $code ) {
return ! empty( $this->errors->get_error_message( $code ) );
}
/**
* @param $file_data string|array
*
* @return array
*/
private function get_file_path_and_url( $file_data ) {
if ( is_string( $file_data ) ) {
$file_path = $file_data;
$file_url = '';
} else {
$file_path = $this->array_utils->get_array_value( $file_data, 'path' );
$file_url = $this->array_utils->get_array_value( $file_data, 'url' );
}
return array( $file_path, $file_url );
}
private function get_permissions_for_image( $file_path ) {
clearstatcache();
$perms = fileperms( $file_path ) & 0777;
// Some servers are having issue with file permission, this should fix it.
if ( empty( $perms ) ) {
// Source: WordPress Core.
$stat = stat( dirname( $file_path ) );
$perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits.
}
return $perms;
}
private function maybe_track_image_url_error( $time_elapsed ) {
if ( $this->has_error( self::$image_not_saved_from_url ) ) {
$this->track_error( $this->errors, self::$image_not_saved_from_url, $time_elapsed );
}
}
private function maybe_disable_streaming() {
// If the constant is defined or disabled, do nothing.
if ( defined( 'WP_SMUSH_USE_STREAMS' ) || ! $this->streaming_enabled ) {
return;
}
$error_counts = $this->thread_safe_options->get_site_option( self::$option_id_smush_error_counts, array() );
$max_occurrences = empty( $error_counts ) ? 0 : max( $error_counts );
if ( $max_occurrences < 3 ) {
$this->count_error_types();
} else {
$this->settings->set( 'disable_streams', WP_SMUSH_VERSION );
}
}
/**
* @return bool
*/
private function has_error_worth_retrying() {
$errors_that_should_be_retried = array(
self::$image_not_saved_from_url,
);
foreach ( $errors_that_should_be_retried as $error_code ) {
if ( $this->has_error( $error_code ) ) {
return true;
}
}
return false;
}
protected function get_type_label() {
return 'Classic';
}
private function add_warnings( $response, $size_key ) {
if ( is_wp_error( $response ) ) {
/**
* @var WP_Error $error
*/
$error = $response;
$this->add_warning( $size_key, $error->get_error_code(), $error->get_error_message(), $error->get_error_data() );
}
}
private function maybe_track_network_errors( $time_elapsed ) {
foreach ( $this->get_network_error_codes() as $error_code ) {
if ( $this->has_warning( $error_code ) ) {
$this->track_error( $this->warnings, $error_code, $time_elapsed );
} elseif ( $this->has_error( $error_code ) ) {
$this->track_error( $this->errors, $error_code, $time_elapsed );
}
}
}
/**
* @param $haystack WP_Error
* @param $error_code string
* @param $time_elapsed
*
* @return void
*/
private function track_error( $haystack, $error_code, $time_elapsed ) {
$error_data = $haystack->get_error_data( $error_code );
$original_code = $this->array_utils->get_array_value( $error_data, 'original_code' );
$original_message = $this->array_utils->get_array_value( $error_data, 'original_message' );
if ( $original_code && $original_message ) {
$this->product_analytics->maybe_track_error(
$error_code,
$original_code,
$original_message,
array(
'Smush Type' => $this->get_type_label() == 'Avif' ? 'AVIF' : $this->get_type_label(),
'Time Elapsed' => $time_elapsed,
)
);
}
}
/**
* @return string[]
*/
private function get_network_error_codes() {
return array(
self::$error_posting_to_api,
self::$error_time_out,
self::$error_ssl_cert,
self::$response_code_non_200,
);
}
/**
* @return void
*/
private function count_error_types() {
$increment_keys = array();
$errors_and_warnings = array_merge( $this->errors->get_error_codes(), $this->warnings->get_error_codes() );
if ( empty( $errors_and_warnings ) ) {
return;
}
foreach ( $errors_and_warnings as $code ) {
$error_data = $this->warnings->get_error_data( $code );
$original_code = $this->array_utils->get_array_value( $error_data, 'original_code' );
$full_code = $code;
if ( $original_code ) {
$full_code .= "_$original_code";
}
$increment_keys[ $full_code ] = $full_code;
}
if ( ! empty( $increment_keys ) ) {
$this->thread_safe_options->increment_values_in_site_option( self::$option_id_smush_error_counts, array_values( $increment_keys ) );
}
}
public function reset_error_counts() {
$this->thread_safe_options->delete_site_option( Smusher::get_smush_error_counts_option_id() );
}
/**
* @param $file
*
* @return string
* @see \wp_get_image_mime()
*/
function wp_get_image_mime( $file ) {
/*
* Use exif_imagetype() to check the mimetype if available or fall back to
* getimagesize() if exif isn't available. If either function throws an Exception
* we assume the file could not be validated.
*/
try {
if ( is_callable( 'exif_imagetype' ) ) {
$imagetype = exif_imagetype( $file );
$mime = ( $imagetype ) ? image_type_to_mime_type( $imagetype ) : false;
} elseif ( function_exists( 'getimagesize' ) ) {
// Don't silence errors when in debug mode, unless running unit tests.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG
&& ! defined( 'WP_RUN_CORE_TESTS' )
) {
// Not using wp_getimagesize() here to avoid an infinite loop.
$imagesize = getimagesize( $file );
} else {
$imagesize = @getimagesize( $file );
}
$mime = ( isset( $imagesize['mime'] ) ) ? $imagesize['mime'] : false;
} else {
$mime = false;
}
if ( false !== $mime ) {
return $mime;
}
$magic = file_get_contents( $file, false, null, 0, 12 );
if ( false === $magic ) {
return false;
}
/*
* Add WebP fallback detection when image library doesn't support WebP.
* Note: detection values come from LibWebP, see
* https://github.com/webmproject/libwebp/blob/master/imageio/image_dec.c#L30
*/
$magic = bin2hex( $magic );
if (
// RIFF.
( str_starts_with( $magic, '52494646' ) ) &&
// WEBP.
( 16 === strpos( $magic, '57454250' ) )
) {
$mime = 'image/webp';
}
/** Custom Code Start */
if ( strpos( $magic, '6674797061766966' ) !== false ) {
$mime = 'image/avif';
}
/** Custom Code End */
} catch ( Exception $e ) {
$mime = false;
}
return $mime;
}
/**
* TODO: remove deprecated errors
*/
public function should_retry_smush( $response ) {
_deprecated_function( __METHOD__, '3.17.0', 'Smusher::get_request_sequential()->should_retry()' );
}
public function curl_multi_exec_available() {
_deprecated_function( __METHOD__, '3.17.0', 'Smusher::get_request_multiple()->is_supported()' );
}
public function set_retry_attempts( $retry_attempts ) {
_deprecated_function( __METHOD__, '3.17.0', 'Smusher::get_request_sequential()->set_retry_attempts()' );
}
public function set_timeout( $timeout ) {
_deprecated_function( __METHOD__, '3.17.0' );
}
/**
* Get option_id_smush_error_counts.
*
* @return string
*/
public static function get_smush_error_counts_option_id() {
return self::$option_id_smush_error_counts;
}
}
PK E\ϥ& & . core/smush/class-smush-request-wp-multiple.phpnu [ server_utils = new Server_Utils();
}
public function do_requests( $files_data ) {
$responses = array();
$requests = $this->prepare_requests( $files_data );
self::request_multiple( $requests, array(
'timeout' => $this->get_timeout(),
'connect_timeout' => $this->get_connect_timeout(),
'user-agent' => $this->get_user_agent(),
'complete' => function ( $response, $size_key ) use ( $files_data, $requests, &$responses ) {
// Convert to a response that looks like standard WP HTTP API responses
$response = $this->multi_to_singular_response( $response );
$request = $requests[ $size_key ];
do_action( 'smush_http_api_debug', $response, $request );
// Call the actual on complete callback
$file_data = $files_data[ $size_key ];
$requests[ $size_key ] = null;
$responses[ $size_key ] = call_user_func( $this->get_on_complete(), $response, $size_key, $file_data );
},
)
);
return $responses;
}
private function multi_to_singular_response( $multi_response ) {
if ( is_a( $multi_response, self::get_requests_exception_class_name() ) ) {
return new WP_Error(
$multi_response->getType(),
$multi_response->getMessage()
);
} else {
return array(
'body' => $multi_response->body,
'response' => array( 'code' => $multi_response->status_code ),
);
}
}
/** \Requests lib are deprecated on WP 6.2.0 */
private static function get_wp_requests_class_name() {
return class_exists( '\WpOrg\Requests\Requests' ) ? '\WpOrg\Requests\Requests' : '\Requests';
}
private static function request_multiple( $requests, $options = array() ) {
$wp_requests_class_name = self::get_wp_requests_class_name();
return $wp_requests_class_name::request_multiple( $requests, $options );
}
private static function get_requests_exception_class_name() {
return class_exists( '\WpOrg\Requests\Exception' ) ? '\WpOrg\Requests\Exception' : '\Requests_Exception';
}
/**
* @param array $files_data
*
* @return array
*/
private function prepare_requests( $files_data ) {
$requests = array();
foreach ( $files_data as $size_key => $file_data ) {
list( $file_path ) = $this->get_file_path_and_url( $file_data );
$requests[ $size_key ] = array(
'url' => $this->get_url(),
'headers' => $this->get_api_request_headers( $file_path ),
'data' => $this->get_full_file_contents( $file_path ),
'type' => 'POST',
);
}
return $requests;
}
public function is_supported() {
$wp_requests_class_name = self::get_wp_requests_class_name();
return $this->server_utils->is_function_supported( 'curl_multi_exec' )
&& method_exists( $wp_requests_class_name, "request_multiple" );
}
}
PK E\U?3 3 core/class-deprecated-hooks.phpnu [ 'old'.
*
* @var array
*/
private $deprecated_action_hooks = array(
'wp_smush_before_smush_file' => 'smush_s3_integration_fetch_file',
'wp_smush_after_remove_file' => 'smush_s3_backup_remove',
);
/**
* Array of deprecated filters hooks we need to handle. Format of 'new' => 'old'.
*
* @var array
*/
private $deprecated_filter_hooks = array(
'wp_smush_backup_exists' => 'smush_backup_exists',
'wp_smush_file_exists' => 'smush_file_exists',
);
/**
* Array of versions on each hook has been deprecated.
*
* @var array
*/
private $deprecated_version = array(
'smush_backup_exists' => '3.9.6',
'smush_s3_integration_fetch_file' => '3.9.6',
'smush_s3_backup_remove' => '3.9.6',
'smush_file_exists' => '3.9.6',
);
/**
* Is action hook.
*
* @var bool
*/
private $is_action;
/**
* Constructor.
*
* Hook into the new hook so we can handle deprecated hooks once fired.
*/
public function __construct() {
$deprecated_hooks = array_merge( array_keys( $this->deprecated_action_hooks ), array_keys( $this->deprecated_filter_hooks ) );
if ( $deprecated_hooks ) {
foreach ( $deprecated_hooks as $new_action ) {
add_filter( $new_action, array( $this, 'maybe_handle_deprecated_hook' ), -1000, 8 );
}
}
}
/**
* Get old hooks to map to new hook.
*
* @param string $new_hook New hook name.
* @return array
*/
private function get_old_hooks( $new_hook ) {
$old_hooks = array();
if ( isset( $this->deprecated_action_hooks[ $new_hook ] ) ) {
$old_hooks = $this->deprecated_action_hooks[ $new_hook ];
$this->is_action = true;
} elseif ( isset( $this->deprecated_filter_hooks[ $new_hook ] ) ) {
$old_hooks = $this->deprecated_filter_hooks[ $new_hook ];
// reset hook type.
$this->is_action = null;
}
return is_array( $old_hooks ) ? $old_hooks : array( $old_hooks );
}
/**
* If the hook is Deprecated, call the old hooks here.
*/
public function maybe_handle_deprecated_hook() {
$new_hook = current_filter();
$new_callback_args = func_get_args();
$return_value = $new_callback_args[0];
$old_hooks = $this->get_old_hooks( $new_hook );
if ( $old_hooks ) {
foreach ( $old_hooks as $old_hook ) {
if ( has_filter( $old_hook ) ) {
$this->display_notice( $old_hook, $new_hook );
$return_value = $this->trigger_hook( $old_hook, $new_callback_args );
}
}
}
return $return_value;
}
/**
* Display a deprecated notice for old hooks.
*
* @param string $old_hook Old hook.
* @param string $new_hook New hook.
*/
protected function display_notice( $old_hook, $new_hook ) {
_deprecated_hook( esc_html( $old_hook ), esc_html( $this->get_deprecated_version( $old_hook ) ), esc_html( $new_hook ) );
}
/**
* Fire off a legacy hook with it's args.
*
* @param string $old_hook Old hook name.
* @param array $new_callback_args New callback args.
* @return mixed|void
*/
protected function trigger_hook( $old_hook, $new_callback_args ) {
if ( $this->is_action ) {
do_action_ref_array( $old_hook, $new_callback_args );
} else {
return apply_filters_ref_array( $old_hook, $new_callback_args );
}
}
/**
* Get deprecated version.
*
* @param string $old_hook Old hook name.
* @return string
*/
protected function get_deprecated_version( $old_hook ) {
return ! empty( $this->deprecated_version[ $old_hook ] ) ? $this->deprecated_version[ $old_hook ] : WP_SMUSH_VERSION;
}
}
PK E\H / core/membership/class-membership-controller.phpnu [ membership = Membership::get_instance();
}
}
PK E\|^ ^ $ core/membership/class-membership.phpnu [ is_pro = false;
}
/**
* Static instance getter
*/
public static function get_instance() {
if ( empty( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* @var boolean
*/
protected $is_pro;
public function get_apikey() {
return false;
}
/**
* Validate the installation.
*
* @param bool $force Force revalidation.
*
* @return void
*/
public function validate_install( $force = false ) {
$this->is_pro = false;
}
/**
* Check if the membership is pro.
*
* @return bool
*/
public function is_pro() {
return $this->is_pro;
}
/**
* Check if the user has access to the hub.
*
* Warning: This method do not support old free users.
*
* @return bool
*/
public function has_access_to_hub() {
if ( $this->is_pro() ) {
return true;
}
if ( class_exists( 'WPMUDEV_Dashboard' ) && method_exists( 'WPMUDEV_Dashboard_Api', 'get_membership_status' ) ) {
// Possible values: full, single, free, expired, paused, unit.
$plan = \WPMUDEV_Dashboard::$api->get_membership_status();
} elseif ( Hub_Connector::has_access() && class_exists( '\WPMUDEV\Hub\Connector\Data' ) ) {
$plan = Data::get()->membership_type();
} else {
return false;
}
return in_array( $plan, array( 'full', 'single', 'free', 'unit' ), true );
}
/**
* Check if access to the Hub access is required to use the API.
*
* @return bool
*/
public function is_api_hub_access_required() {
$is_pre_3_22_site = get_site_option( 'wp_smush_pre_3_22_site' );
if ( $is_pre_3_22_site ) {
return false;
}
return ! $this->has_access_to_hub();
}
}
PK E\}#5 core/class-modules.phpnu [ core()->mod->settings will be typehinted as a call to Settings module.
*
* @package Smush\Core
*/
namespace Smush\Core;
use Smush\Core\Backups\Backups_Controller;
use Smush\Core\Cache\Cache_Controller;
use Smush\Core\Lazy_Load\Lazy_Load_Controller;
use Smush\Core\Lazy_Load\Video_Embed\Video_Thumbnail_Controller;
use Smush\Core\Media\Attachment_Url_Cache_Controller;
use Smush\Core\Media\Media_Item_Controller;
use Smush\Core\Media_Library\Ajax_Media_Library_Scanner;
use Smush\Core\Media_Library\Background_Media_Library_Scanner;
use Smush\Core\Media_Library\Media_Library_Last_Process;
use Smush\Core\Media_Library\Media_Library_Slice_Data_Fetcher;
use Smush\Core\Media_Library\Media_Library_Watcher;
use Smush\Core\Modules\Background\Background_Pre_Flight_Controller;
use Smush\Core\Modules\CDN;
use Smush\Core\Photon\Photon_Controller;
use Smush\Core\Resize\Resize_Controller;
use Smush\Core\Security\Security_Controller;
use Smush\Core\Smush\Smush_Controller;
use Smush\Core\Stats\Global_Stats_Controller;
use Smush\Core\Transform\Transformation_Controller;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Modules
*/
class Modules {
/**
* Directory Smush module.
*
* @var Modules\Dir
*/
public $dir;
/**
* Main Smush module.
*
* @var Modules\Smush
*/
public $smush;
/**
* Backup module.
*
* @var Modules\Backup
*/
public $backup;
/**
* PNG 2 JPG module.
*
* @var Modules\Png2jpg
*/
public $png2jpg;
/**
* Resize module.
*
* @var Modules\Resize
*/
public $resize;
/**
* CDN module.
*
* @var CDN
*/
public $cdn;
/**
* Image lazy load module.
*
* @since 3.2
*
* @var \Smush\Core\Modules\Lazy
*/
public $lazy;
/**
* Webp module.
*
* @var Modules\Webp
*/
public $webp;
/**
* Cache background optimization controller - Bulk_Smush_Controller
*
* @var Modules\Bulk\Background_Bulk_Smush
*/
public $bg_optimization;
/**
* @var Modules\Product_Analytics_Controller
*/
public $product_analytics;
public $backward_compatibility;
public static function get_instance() {
return new self();
}
/**
* Modules constructor.
*/
public function __construct() {
new Deprecated_Hooks();// Handle deprecated hooks.
new Api\Hub(); // Init hub endpoints.
new Modules\Resize_Detection();
new Rest();
if ( is_admin() ) {
$this->dir = new Modules\Dir();
}
$this->smush = $this->get_smush_module();
$this->backup = new Modules\Backup();
$this->resize = new Modules\Resize();
$transformation_controller = new Transformation_Controller();
$transformation_controller->init();
$this->lazy = new Modules\Lazy();
$this->product_analytics = new Modules\Product_Analytics_Controller();
$smush_controller = Smush_Controller::get_instance();
$smush_controller->init();
$resize_controller = new Resize_Controller();
$resize_controller->init();
$backups_controller = new Backups_Controller();
$backups_controller->init();
$library_scanner = new Ajax_Media_Library_Scanner();
$library_scanner->init();
$background_lib_scanner = Background_Media_Library_Scanner::get_instance();
$background_lib_scanner->init();
$media_library_watcher = new Media_Library_Watcher();
$media_library_watcher->init();
$global_stats_controller = new Global_Stats_Controller();
$global_stats_controller->init();
$plugin_settings_watcher = new Plugin_Settings_Watcher();
$plugin_settings_watcher->init();
$animated_status_controller = new Animated_Status_Controller();
$animated_status_controller->init();
$media_library_slice_data_fetcher = new Media_Library_Slice_Data_Fetcher( is_multisite(), get_current_blog_id() );
$media_library_slice_data_fetcher->init();
$media_item_controller = new Media_Item_Controller();
$media_item_controller->init();
$optimization_controller = new Optimization_Controller();
$optimization_controller->init();
$photon_controller = new Photon_Controller();
$photon_controller->init();
$cache_controller = new Cache_Controller();
$cache_controller->init();
$lazy_load_controller = Lazy_Load_Controller::get_instance();
$lazy_load_controller->init();
( new Video_Thumbnail_Controller() )->init();
$background_health = Background_Pre_Flight_Controller::get_instance();
$background_health->init();
$media_lib_last_process = Media_Library_Last_Process::get_instance();
$media_lib_last_process->init();
$cron_controller = Cron_Controller::get_instance();
$cron_controller->init();
$security_controller = Security_Controller::get_instance();
$security_controller->init();
$attachment_url_cache_controller = new Attachment_Url_Cache_Controller();
$attachment_url_cache_controller->init();
$hub_connector = new Hub_Connector();
$hub_connector->init();
}
protected function get_smush_module() {
return new Modules\Smush();
}
}
PK E\Vfk
k
core/class-upload-dir.phpnu [ wp_upload_dir ) ) {
$this->wp_upload_dir = $this->prepare_wp_upload_dir();
}
return $this->wp_upload_dir;
}
/**
* @return mixed
*/
private function get_root_path() {
if ( is_null( $this->root_path ) ) {
$this->root_path = $this->prepare_root_path();
}
return $this->root_path;
}
/**
* @return mixed
*/
public function get_upload_path() {
if ( is_null( $this->upload_path ) ) {
$this->upload_path = $this->prepare_upload_path();
}
return $this->upload_path;
}
/**
* @return string
*/
public function get_upload_rel_path() {
if ( is_null( $this->upload_rel_path ) ) {
$this->upload_rel_path = $this->prepare_upload_rel_path();
}
return $this->upload_rel_path;
}
/**
* @return string
*/
public function get_upload_url() {
if ( is_null( $this->upload_url ) ) {
$this->upload_url = $this->prepare_upload_url();
}
return $this->upload_url;
}
private function prepare_upload_path() {
$upload = $this->get_wp_upload_dir();
return untrailingslashit( $upload['basedir'] );
}
private function prepare_upload_rel_path() {
$root_path = $this->get_root_path();
return str_replace( $root_path, '', $this->get_upload_path() );
}
private function prepare_upload_url() {
$upload = $this->get_wp_upload_dir();
return untrailingslashit( $upload['baseurl'] );
}
private function prepare_wp_upload_dir() {
return wp_upload_dir();
}
protected function prepare_root_path() {
// Is it possible that none of the following conditions are met?
$root_path = '';
// Get the Document root path. There must be a better way to do this.
// For example, /srv/www/site/public_html for /srv/www/site/public_html/wp-content/uploads.
if ( 0 === strpos( $this->get_upload_path(), ABSPATH ) ) {
// Environments like Flywheel have an ABSPATH that's not used in the paths.
$root_path = ABSPATH;
} elseif ( ! empty( $_SERVER['DOCUMENT_ROOT'] ) && 0 === strpos( $this->get_upload_path(), wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) ) {
/**
* This gets called when scanning for uncompressed images.
* When ran from certain contexts, $_SERVER['DOCUMENT_ROOT'] might not be set.
*
* We are removing this part from the path later on.
*/
$root_path = realpath( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
} elseif ( 0 === strpos( $this->get_upload_path(), dirname( WP_CONTENT_DIR ) ) ) {
// We're assuming WP_CONTENT_DIR is only one level deep into the document root.
// This might not be true in customized sites. A bit edgy.
$root_path = dirname( WP_CONTENT_DIR );
}
$root_path = untrailingslashit( $root_path );
/**
* Filters the Document root path used to get relative paths for webp rules.
* Hopefully of help for debugging and SLS.
*
* @since 3.9.0
*/
return apply_filters( 'smush_webp_rules_root_path_base', $root_path );
}
public function get_human_readable_path( $full_path ) {
return str_replace( WP_CONTENT_DIR, '', $full_path );
}
public function is_uploads_url( $url ) {
return str_starts_with( $url, $this->get_upload_url() );
}
}
PK E\:^G G # core/srcset/class-srcset-helper.phpnu [ url_utils = new Url_Utils();
$this->attachment_url_cache = Attachment_Url_Cache::get_instance();
$this->settings = Settings::get_instance();
}
/**
* @return array( $srcset, $sizes )
*/
public function generate_srcset_and_sizes( $src, $attachment_id = 0, $width = 0, $height = 0 ) {
add_filter( 'wp_calculate_image_sizes', array( $this, 'update_image_sizes' ), 10, 2 );
list( $srcset, $sizes ) = $this->_generate_srcset_and_sizes( $src, $attachment_id, $width, $height );
remove_filter( 'wp_calculate_image_sizes', array( $this, 'update_image_sizes' ), 10 );
return array( $srcset, $sizes );
}
private function _generate_srcset_and_sizes( $src, $attachment_id = 0, $width = 0, $height = 0 ) {
/**
* Try to get the attachment URL.
*/
if ( empty( $attachment_id ) ) {
$attachment_id = $this->attachment_url_cache->get_id_for_url( $src );
}
$width = (int) $width;
$height = (int) $height;
if ( ! $width || ! $height ) {
list( $width, $height ) = $this->find_image_dimensions( $src, $attachment_id, $width, $height );
}
if ( empty( $width ) || empty( $height ) ) {
return array( false, false );
}
// This is an image placeholder - do not generate srcset.
if ( $width === $height && $width < Transformation_Controller::get_min_transformable_image_dimension() ) {
return array( false, false );
}
$image_metadata = $attachment_id > 0 ? wp_get_attachment_metadata( $attachment_id ) : array();
$size_array = array( absint( $width ), absint( $height ) );
if ( $this->is_image_metadata_invalid( $image_metadata ) ) {
$image_metadata = array(
'width' => $width,
'height' => $height,
);
// Generate srcset via filter if metadata is invalid.
$srcset = $this->generate_image_srcset_through_filter( $size_array, $src, $image_metadata, $attachment_id );
} else {
$srcset = wp_calculate_image_srcset( $size_array, $src, $image_metadata, $attachment_id );
}
$sizes = wp_calculate_image_sizes( $size_array, $src, $image_metadata, $attachment_id );
return array( $srcset, $sizes );
}
private function generate_image_srcset_through_filter( $size_array, $image_src, $image_meta, $attachment_id ) {
$sources = apply_filters( 'wp_calculate_image_srcset', array(), $size_array, $image_src, $image_meta, $attachment_id );
// Only return a 'srcset' value if there is more than one source.
if ( ! is_array( $sources ) || count( $sources ) < 2 ) {
return false;
}
$srcset = '';
foreach ( $sources as $source ) {
$srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', ';
}
return rtrim( $srcset, ', ' );
}
private function is_image_metadata_invalid( $image_metadata ) {
// Check if required metadata fields are missing or invalid.
$is_missing_sizes = empty( $image_metadata['sizes'] );
$is_missing_dimensions = empty( $image_metadata['width'] ) || empty( $image_metadata['height'] );
$is_missing_file = ! isset( $image_metadata['file'] ) || strlen( $image_metadata['file'] ) < 4;
// Return true if any of the conditions are met.
return $is_missing_sizes || $is_missing_dimensions || $is_missing_file;
}
private function find_image_dimensions( $src_url, $attachment_id, $width_from_attribute, $height_from_attribute ) {
list( $src_width, $src_height ) = $this->get_dimensions_from_url_or_attachment( $src_url, $attachment_id );
// If still missing, return zeros.
if ( $src_width <= 0 || $src_height <= 0 ) {
return array( $width_from_attribute, $height_from_attribute );
}
$image_ratio = $src_width / $src_height;
if ( $width_from_attribute > 0 ) {
return array( $width_from_attribute, $width_from_attribute / $image_ratio );
}
if ( $height_from_attribute > 0 ) {
return array( $height_from_attribute * $image_ratio, $height_from_attribute );
}
return array( $src_width, $src_height );
}
private function get_dimensions_from_url_or_attachment( $src_url, $attachment_id ) {
list( $src_width, $src_height ) = $this->url_utils->get_image_dimensions( $src_url );
if ( empty( $src_width ) || empty( $src_height ) ) {
$image_data = wp_get_attachment_image_src( $attachment_id, 'full' );
if ( is_array( $image_data ) && count( $image_data ) >= 3 ) {
list( , $src_width, $src_height ) = $image_data;
}
}
return array( (int) $src_width, (int) $src_height );
}
private function get_image_metadata( $attachment_id, $image_width, $image_height ) {
$image_metadata = array();
if ( $attachment_id ) {
$image_metadata = wp_get_attachment_metadata( $attachment_id );
}
if ( empty( $image_metadata ) || ! is_array( $image_metadata ) ) {
$image_metadata = array(
'width' => $image_width,
'height' => $image_height,
);
}
return $image_metadata;
}
public function skip_adding_srcset( $src_url, $image_markup ) {
return apply_filters( 'smush_skip_adding_srcset', false, $src_url, $image_markup );
}
public function update_image_sizes( $sizes, $size ) {
$content_width = $this->settings->max_content_width();
$filtered_max_image_width = (int) apply_filters( 'wp_smush_max_image_width', 0, $content_width );
$original_sizes = $sizes;
$image_width = ! empty( $size[0] ) ? $size[0] : 0;
if ( ! empty( $sizes ) && 0 === $filtered_max_image_width ) {
$final_max_width = $content_width;
$final_sizes = $sizes;
} else {
$options = array_filter( array_map( 'absint', array( $image_width, $filtered_max_image_width ) ) );
$final_max_width = ! empty( $options ) ? min( $options ) : $content_width;
$final_sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $final_max_width );
}
return apply_filters( 'wp_smush_image_sizes', $final_sizes, $size, $final_max_width, $original_sizes );
}
}
PK E\Z % core/external/hub-connector/README.mdnu [ # Hub Connector #
Hub Connector module is used in our free plugins to connect the user websites with Hub.
## Requirements:
* PHP: 7.4+
* WordPress: 5.0+
# How to use it #
1. Insert this repository as **sub-module** into the existing project
2. Include the file `connector.php` in your plugin and initialize it by calling ``\WPMUDEV\Hub\Connector::get();``.
3. Set the plugin specific options (see below for more details) using a unique plugin identifier. Identifier can be any unique string.
``\WPMUDEV\Hub\Connector::get()->set_options( 'blc', $options );``
4. Call the action `wpmudev_hub_connector_ui` where you want the Hub connector UI to render.
5. **[IMPORTANT]** Make sure your distributable plugin **NOT** having these files ( via your build script ):
- `test/` ( whole directory)
- `pipeline-scipts/` ( whole directory)
- `.gitignore`
- `bitbucket-pipelines.yml`
- `phpcs.ruleset.xml`
6. Done!
### Options
There are a few plugin specific options you need to set in order for this module to work properly without conflicting with other WPMUDEV plugins.
These are the accepted options:
| Option | Type | Sample | Description |
|--------------|-------|------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
| `screens` | Array | `array( 'toplevel_page_blc' )` | Array of plugin admin screen IDs. |
| `extra_args` | Array | `array( 'auth' = array( 'ref' => 'blc' ), 'register' => array( 'ref' => 'blc' ) )` | Extra arguments to be added to the URLs of authentication with WPMUDEV. See more details about the expected items below. |
#### Extra Arguments
Extra custom arguments can be set to URLs used in Hub Connector auth page. Each sub items should contain array of custom arguments (key and value).
Expected items:
| Key | Type | Description |
|-------------------|-------|-----------------------------------------------|
| `auth` | Array | Custom for default login form action URL. |
| `team_auth` | Array | Arguments for team selection form action URL. |
| `google_auth` | Array | Arguments for Google auth form action URL. |
| `register` | Array | Arguments for registration URL. |
| `forgot_password` | Array | Arguments for forgot password URL. |
## Sample Usage
Hub connector should be loaded unconditionally on every page load and admin screen should be set. Otherwise some hooks may not work.
```
set_options( 'blc', $options );
}
// Now conditionally render Hub connector UI somewhere in your plugin.
if ( $my_condition === true ) {
do_action( 'wpmudev_hub_connector_ui', 'blc' );
}
```
## Rendering Hub Connector ##
```
is_logged_in() ) {
// Render Hub connector UI.
do_action( 'wpmudev_hub_connector_ui', 'blc' );
}
```
## Available Helpers ##
```
is_logged_in();;
```
```
get_api_key();
```
```
membership_type();
```
```
profile_data();
```
## Modifying texts ##
You can modify texts in Hub Connector UI using `wpmudev_hub_connector_localize_text_vars` filter.
```