This commit is contained in:
2024-05-20 15:37:46 +03:00
commit 00b7dbd0b7
10404 changed files with 3285853 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
<?php
/**
* Smush Abstract_API class that handles communications with WPMU DEV API: API class
*
* @since 3.0
* @package Smush\Core\Api
*/
namespace Smush\Core\Api;
use Exception;
use WP_Error;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Abstract_API.
*/
abstract class Abstract_API {
/**
* API key.
*
* @since 3.0
*
* @var string
*/
public $api_key = '';
/**
* API request instance.
*
* @since 3.0
*
* @var Request
*/
protected $request;
/**
* API constructor.
*
* @since 3.0
*
* @param string $key API key.
*
* @throws Exception API Request exception.
*/
public function __construct( $key ) {
$this->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 );
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace Smush\Core\Api;
class Backoff {
private $max_attempts = 5;
private $wait = 1;
private $use_jitter = true;
private $decider;
public function __construct() {
$this->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;
}
}

View File

@@ -0,0 +1,164 @@
<?php
/**
* WPMU DEV Hub endpoints.
*
* Class allows syncing plugin data with the Hub.
*
* @since 3.7.0
* @package Smush\Core\Api
*/
namespace Smush\Core\Api;
use Smush\Core\Array_Utils;
use Smush\Core\Settings;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Hub
*/
class Hub {
/**
* Endpoints array.
*
* @since 3.7.0
* @var array
*/
private $endpoints = array(
'get_stats',
'import_settings',
'export_settings',
);
/**
* @var Array_Utils
*/
private $array_utils;
/**
* Hub constructor.
*
* @since 3.7.0
*/
public function __construct() {
$this->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'] = $core->mod->cdn->is_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 = new \Smush\Core\Configs();
$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 = new \Smush\Core\Configs();
$config = $configs_handler->get_config_from_current();
wp_send_json_success( $config['config'] );
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Smush\Core\Api;
use WP_Error;
class Request_Multiple {
public function do_requests( $requests, $options ) {
$on_complete = ! empty( $options['complete'] )
? $options['complete']
: '__return_false';
self::request_multiple( $requests, array_merge(
$options,
array(
'complete' => function ( $response, $key ) use ( &$requests, $on_complete ) {
// Convert to a response that looks like standard WP HTTP API responses
$response = $this->multi_to_singular_response( $response );
// Call the actual on complete callback
call_user_func( $on_complete, $response, $key );
},
)
) );
}
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';
}
}

View File

@@ -0,0 +1,343 @@
<?php
/**
* API request class: Request
*
* Handles all the internal stuff to form and process a proper API request.
*
* @since 3.0
* @package Smush\Core\Api
*/
namespace Smush\Core\Api;
use Exception;
use WP_Error;
use Smush\Core\Helper;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Request.
*/
class Request {
/**
* API service.
*
* @since 3.0
*
* @var null|API
*/
private $service = null;
/**
* Request max timeout.
*
* @since 3.0
*
* @var int
*/
private $timeout = 15;
/**
* Header arguments.
*
* @since 3.0
*
* @var array
*/
private $headers = array();
/**
* POST arguments.
*
* @since 3.0
*
* @var array
*/
private $post_args = array();
/**
* GET arguments.
*
* @since 3.0
*
* @var array
*/
private $get_args = array();
/**
* Request constructor.
*
* @since 3.0
*
* @param Abstract_API $service API service.
*
* @throws Exception Init exception.
*/
public function __construct( $service ) {
if ( ! $service instanceof Abstract_API ) {
throw new Exception( __( 'Invalid API service.', 'wp-smushit' ), 404 );
}
$this->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;
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* Smush API class that handles communications with WPMU DEV API: API class
*
* @since 3.0
* @package Smush\Core\Api
*/
namespace Smush\Core\Api;
use WP_Error;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Smush_API.
*/
class Smush_API extends Abstract_API {
/**
* Endpoint name.
*
* @since 3.0
*
* @var string
*/
public $name = 'smush';
/**
* Endpoint version.
*
* @since 3.0
*
* @var string
*/
public $version = 'v1';
/**
* Check CDN status (same as verify the is_pro status).
*
* @since 3.0
*
* @param bool $manual If it's a manual check. Only manual on button click.
*
* @return mixed|WP_Error
*/
public function check( $manual = false ) {
return $this->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;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Smush\Core\Backups;
use Smush\Core\Controller;
use Smush\Core\Core;
use Smush\Core\File_System;
use Smush\Core\Helper;
use Smush\Core\Media\Media_Item_Cache;
use Smush\Core\Media\Media_Item_Optimizer;
use WP_Smush;
class Backups_Controller extends Controller {
/**
* @var Media_Item_Cache
*/
private $media_item_cache;
/**
* @var \WDEV_Logger|null
*/
private $logger;
/**
* @var File_System
*/
private $fs;
public function __construct() {
$this->media_item_cache = Media_Item_Cache::get_instance();
$this->logger = Helper::logger();
$this->fs = new File_System();
$this->register_action( 'wp_ajax_smush_restore_image', array( $this, 'handle_restore_ajax' ) );
$this->register_action( 'delete_attachment', array( $this, 'delete_backup_file' ) );
}
public function handle_restore_ajax() {
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.
// TODO: change Helper::is_user_allowed to a non static method
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;
$media_item = Media_Item_Cache::get_instance()->get( $attachment_id );
if ( ! $media_item->is_mime_type_supported() ) {
wp_send_json_error( array(
'error_msg' => $media_item->get_errors()->get_error_message(),
) );
}
$optimizer = new Media_Item_Optimizer( $media_item );
$restored = $optimizer->restore();
if ( ! $restored ) {
wp_send_json_error( array(
'error_msg' => esc_html__( 'Unable to restore image', 'wp-smushit' ),
) );
}
$button_html = WP_Smush::get_instance()->library()->generate_markup( $attachment_id );
$file_path = $media_item->get_main_size()->get_file_path();
$size = $this->fs->file_exists( $file_path )
? $this->fs->filesize( $file_path )
: 0;
if ( $size > 0 ) {
$update_size = size_format( $size );
}
wp_send_json_success( array(
'stats' => $button_html,
'new_size' => isset( $update_size ) ? $update_size : 0,
) );
}
public function delete_backup_file( $attachment_id ) {
$media_item = $this->media_item_cache->get( $attachment_id );
if ( $media_item->is_valid() && $media_item->get_default_backup_size() ) {
// Delete the file
$default_backup_path = $media_item->get_default_backup_size()->get_file_path();
if ( $this->fs->file_exists( $default_backup_path ) ) {
$this->fs->unlink( $default_backup_path );
}
// Delete the meta
$media_item->remove_default_backup_size();
$media_item->save();
} else {
$this->logger->error( sprintf( 'Count not delete webp versions of the media item [%d]', $attachment_id ) );
}
}
}

View File

@@ -0,0 +1,211 @@
<?php
namespace Smush\Core\Backups;
use Smush\Core\File_System;
use Smush\Core\Helper;
use Smush\Core\Media\Media_Item;
use Smush\Core\Media\Media_Item_Optimizer;
use Smush\Core\Settings;
use WDEV_Logger;
class Backups {
/**
* @var WDEV_Logger
*/
private $logger;
/**
* @var Settings|null
*/
private $settings;
/**
* @var File_System
*/
private $fs;
public function __construct() {
$this->logger = Helper::logger()->backup();
$this->settings = Settings::get_instance();
$this->fs = new File_System();
}
public function create_backup_file( $source_image_path ) {
$bak_file_path = $this->generate_unique_bak_file_path( $source_image_path );
$copied = $this->fs->copy( $source_image_path, $bak_file_path );
if ( $copied ) {
return basename( $bak_file_path );
}
return false;
}
private function generate_unique_bak_file_path( $source_image_path ) {
// TODO: why not use the wp_unique_filename method for this?
$path_info = pathinfo( $source_image_path );
$ext = $path_info['extension'];
$bak_ext = ".bak.$ext";
$file_without_ext = trailingslashit( $path_info['dirname'] ) . $path_info['filename'];
$bak_file_path = $file_without_ext . $bak_ext;
if ( ! $this->fs->file_exists( $bak_file_path ) ) {
return $bak_file_path;
}
$count = 1;
$bak_file_path = $file_without_ext . '-' . $count . $bak_ext;
while ( $this->fs->file_exists( $bak_file_path ) ) {
$count ++;
$bak_file_path = $file_without_ext . '-' . $count . $bak_ext;
}
return $bak_file_path;
}
/**
* @param $media_item Media_Item
* @param $optimizer Media_Item_Optimizer
*
* @return bool
*/
public function maybe_create_backup( $media_item, $optimizer ) {
if ( ! $this->settings->is_backup_active() ) {
return false;
}
// Maybe it already exists?
$backup_size = $media_item->get_default_backup_size();
if ( $backup_size && $backup_size->get_file_path() && $this->fs->file_exists( $backup_size->get_file_path() ) ) {
// We have a perfectly viable backup available already
$this->logger->info( sprintf( 'Found an existing backed up file [%s] for attachment [%d].', $backup_size->get_file(), $media_item->get_id() ) );
return false;
}
$size_to_backup = $media_item->get_full_or_scaled_size();
if ( ! $size_to_backup || ! $size_to_backup->file_exists() ) {
$this->logger->warning( sprintf( 'File not found, could not backup up for attachment [%d].', $media_item->get_id() ) );
return false;
}
$create_copy = $optimizer->should_optimize_size( $size_to_backup );
// Create the backup
$file_name = $size_to_backup->get_file_name();
$width = $size_to_backup->get_width();
$height = $size_to_backup->get_height();
if ( $create_copy ) {
$file_name = $this->create_backup_file( $size_to_backup->get_file_path() );
if ( ! $file_name ) {
$this->logger->info( sprintf( 'File operation failed when trying to create backup file [%s] for attachment [%d].', $size_to_backup->get_file_path(), $media_item->get_id() ) );
return false;
}
}
$media_item->add_backup_size( $file_name, $width, $height );
$media_item->save();
return true;
}
/**
* @param $media_item Media_Item
*
* @return bool
*/
public function restore_backup( $media_item ) {
return $this->restore_backup_to_file_path(
$media_item,
$media_item->get_original_image_path() // Directly using the path here because the size object is not available when the file doesn't exist on the disk
);
}
/**
* @param $media_item Media_Item
* @param $file_path
*
* @return bool
*/
public function restore_backup_to_file_path( $media_item, $file_path ) {
$restored = false;
$backup_file_path = '';
$attachment_id = $media_item->get_id();
do {
$backup_size = $media_item->get_default_backup_size();
if ( ! $backup_size ) {
$this->logger->warning( sprintf( 'A restore was attempted for attachment [%d] but we did not find a backup file.', $media_item->get_id() ) );
break;
}
$backup_file_path = $backup_size->get_file_path();
do_action( 'wp_smush_before_restore_backup', $backup_file_path, $attachment_id, $file_path );
if ( ! $this->fs->file_exists( $backup_file_path ) ) {
// Clean up
$media_item->remove_default_backup_size();
$media_item->save();
$this->logger->warning( sprintf( 'A restore was attempted for attachment [%d] but the backup file does not exist.', $media_item->get_id() ) );
break;
}
$is_separate_backup_file = $backup_file_path !== $file_path;
if ( $is_separate_backup_file ) {
$copied = $this->fs->copy( $backup_file_path, $file_path );
if ( $copied ) {
$this->fs->unlink( $backup_file_path );
} else {
$this->logger->warning( sprintf( 'Error copying from [%s] to [%s].', $backup_file_path, $file_path ) );
break;
}
}
wp_generate_attachment_metadata( $attachment_id, $file_path );
/*
* TODO: we might want to follow media_handle_upload which makes an extra update attachment call like this:
* wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
*/
// The metadata has changed because we called wp_generate_attachment_metadata. We need to reset before saving.
$media_item->reset();
$media_item->remove_default_backup_size();
$media_item->save();
$restored = true;
} while ( 0 );
do_action( 'wp_smush_after_restore_backup', $restored, $backup_file_path, $attachment_id, $file_path );
return $restored;
}
/**
* TODO: merge somehow with \Smush\Core\Modules\Backup::get_attachments_with_backups
* @return int
*/
private function count_attachments_with_backups() {
global $wpdb;
$wild = '%';
$backup_key_like = $wild . $wpdb->esc_like( Media_Item::DEFAULT_BACKUP_KEY ) . $wild;
$no_backup_files = $wpdb->get_var(
$wpdb->prepare(
"SELECT count(*)
FROM {$wpdb->postmeta}
WHERE meta_key=%s AND `meta_value` LIKE %s",
Media_Item::BACKUP_SIZES_META_KEY,
$backup_key_like
)
);
return (int) $no_backup_files;
}
public function items_with_backup_exist() {
return $this->count_attachments_with_backups() > 0;
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Smush\Core;
use Smush\Core\Media\Media_Item_Cache;
use Smush\Core\Stats\Global_Stats;
/**
* The method {@see Media_Item::is_animated} is intentionally naive because we don't want to check file contents on the fly.
* This controller is responsible for the expensive file content check at the right times.
*/
class Animated_Status_Controller extends Controller {
private $media_item_cache;
/**
* @var \WDEV_Logger|null
*/
private $logger;
/**
* @var Global_Stats
*/
private $global_stats;
public function __construct() {
$this->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_file', 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 );
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Smush\Core;
class Array_Utils {
public function array_hash( $array, $keys = array() ) {
$hash = 0;
if ( is_array( $array ) ) {
foreach ( $array as $key => $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( $array, $key ) {
return isset( $array[ $key ] ) ? $array[ $key ] : null;
}
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 ) );
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Smush\Core;
use Smush\Core\Modules\Background\Mutex;
class Attachment_Id_List {
private $option_id;
private $ids;
/**
* @var Array_Utils
*/
private $array_utils;
public function __construct( $option_id ) {
$this->option_id = $option_id;
$this->array_utils = new Array_Utils();
}
private function set_ids( $ids ) {
$this->ids = $ids;
}
public function get_ids() {
if ( is_null( $this->ids ) ) {
$this->ids = $this->fetch_ids();
}
return $this->ids;
}
private function fetch_ids() {
wp_cache_delete( $this->option_id, 'options' );
return $this->string_to_array( (string) get_option( $this->option_id ) );
}
public function has_id( $id ) {
return in_array( $id, $this->get_ids() );
}
public function add_id( $attachment_id ) {
$this->mutex( function () use ( $attachment_id ) {
$ids = $this->fetch_ids();
if ( ! in_array( $attachment_id, $ids ) ) {
$ids[] = $attachment_id;
}
$this->_update_ids( $ids );
} );
}
public function add_ids( $attachment_ids ) {
$this->mutex( function () use ( $attachment_ids ) {
$new_ids = array_merge( $this->fetch_ids(), $attachment_ids );
$new_ids = $this->array_utils->fast_array_unique( $new_ids );
$this->_update_ids( $new_ids );
} );
}
public function remove_id( $attachment_id ) {
$this->mutex( function () use ( $attachment_id ) {
$ids = $this->fetch_ids();
$index = array_search( $attachment_id, $ids );
if ( $index !== false ) {
unset( $ids[ $index ] );
}
$this->_update_ids( $ids );
} );
}
public function update_ids( $ids ) {
$this->mutex( function () use ( $ids ) {
$this->_update_ids( $ids );
} );
}
public function delete_ids() {
delete_option( $this->option_id );
$this->set_ids( array() );
}
private function string_to_array( $string ) {
return empty( $string )
? array()
: explode( ',', $string );
}
private function array_to_string( $array ) {
$array = empty( $array ) || ! is_array( $array )
? array()
: $array;
return join( ',', $array );
}
private function mutex( $operation ) {
$option_id = $this->option_id;
( new Mutex( "{$option_id}_mutex" ) )->execute( $operation );
}
private function _update_ids( $ids ) {
update_option(
$this->option_id,
$this->array_to_string( $ids ),
false
);
$this->set_ids( $ids );
}
public function get_count() {
return count( $this->get_ids() );
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace Smush\Core;
class Backup_Size {
private $dir;
private $file;
private $width;
private $height;
public function __construct( $dir ) {
$this->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() );
}
}

View File

@@ -0,0 +1,313 @@
<?php
/**
* Class CLI
*
* @since 3.1
* @package Smush\Core
*/
namespace Smush\Core;
use Smush\Core\Media\Media_Item_Cache;
use Smush\Core\Media\Media_Item_Optimizer;
use WP_CLI;
use WP_CLI_Command;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Reduce image file sizes, improve performance and boost your SEO using the free WPMU DEV Smush API.
*/
class CLI extends WP_CLI_Command {
/**
* Optimize image.
*
* ## OPTIONS
*
* [--type=<type>]
* : Optimize single image, batch or all images.
* ---
* default: all
* options:
* - all
* - single
* - batch
* ---
*
* [--image=<ID>]
* : Attachment ID to compress.
* ---
* default: 0
* ---
*
* ## EXAMPLES
*
* # Smush all images.
* $ wp smush compress
*
* # Smush single image with ID = 10.
* $ wp smush compress --type=single --image=10
*
* # Smush first 5 images.
* $ wp smush compress --type=batch --image=5
*
* @param array $args All the positional arguments.
* @param array $assoc_args All the arguments defined like --key=value or --flag or --no-flag.
*/
public function compress( $args, $assoc_args ) {
$type = $assoc_args['type'];
$image = $assoc_args['image'];
switch ( $type ) {
case 'single':
/* translators: %d - image ID */
$msg = sprintf( __( 'Smushing image ID: %d', 'wp-smushit' ), absint( $image ) );
$this->smush( $msg, array( $image ) );
$this->_list( array() );
break;
case 'batch':
/* translators: %d - number of images */
$msg = sprintf( __( 'Smushing first %d images', 'wp-smushit' ), absint( $image ) );
$this->smush_all( $msg, $image );
break;
case 'all':
default:
$this->smush_all( __( 'Smushing all images', 'wp-smushit' ) );
break;
}
}
/**
* List unoptimized images.
*
* ## OPTIONS
*
* [<count>]
* : Limit number of images to get.
*
* ## EXAMPLES
*
* # Get all unoptimized images.
* $ wp smush list
*
* # Get the first 100 images that are not optimized.
* $ wp smush list 100
*
* @subcommand list
* @when after_wp_load
*
* @param array $args All the positional arguments.
*/
public function _list( $args ) {
if ( ! empty( $args ) ) {
list( $count ) = $args;
} else {
$count = -1;
}
$response = WP_CLI::launch_self(
'post list',
array( '--meta_compare=NOT EXISTS' ),
array(
'post_type' => 'attachment',
'fields' => 'ID, guid, post_mime_type',
'meta_key' => 'wp-smpro-smush-data',
'format' => 'json',
'posts_per_page' => (int) $count,
),
false,
true
);
$images = json_decode( $response->stdout );
if ( empty( $images ) ) {
WP_CLI::success( __( 'No uncompressed images found', 'wp-smushit' ) );
return;
}
WP_CLI::success( __( 'Unsmushed images:', 'wp-smushit' ) );
WP_CLI\Utils\format_items( 'table', $images, array( 'ID', 'guid', 'post_mime_type' ) );
}
/**
* Restore image.
*
* ## OPTIONS
*
* [--id=<ID>]
* : Attachment ID to restore.
* ---
* default: all
* ---
*
* ## EXAMPLES
*
* # Restore all images that have backups.
* $ wp smush restore
*
* # Restore single image with ID = 10.
* $ wp smush restore --id=10
*
* @param array $args All the positional arguments.
* @param array $assoc_args All the arguments defined like --key=value or --flag or --no-flag.
*/
public function restore( $args, $assoc_args ) {
$id = $assoc_args['id'];
if ( 'all' === $id ) {
$this->restore_image();
} else {
$this->restore_image( absint( $id ) );
}
}
/**
* Smush single image.
*
* @since 3.1
*
* @param string $msg Message for progress bar status.
* @param array $images Attachment IDs.
*/
private function smush( $msg = '', $images = array() ) {
$success = false;
$errors = array();
$progress = WP_CLI\Utils\make_progress_bar( $msg, count( $images ) + 1 );
$core = WP_Smush::get_instance()->core();
// We need to initialize the database module (maybe all other modules as well?).
Settings::get_instance()->init();
$unsmushed_attachments = $core->get_unsmushed_attachments();
while ( $images ) {
$progress->tick();
$attachment_id = array_pop( $images );
// Skip if already Smushed.
$should_convert = $core->mod->webp->should_be_converted( $attachment_id );
if ( ! in_array( (int) $attachment_id, $unsmushed_attachments, true ) && ! $should_convert ) {
/* translators: %d - attachment ID */
$errors[] = sprintf( __( 'Image (ID: %d) already compressed', 'wp-smushit' ), $attachment_id );
continue;
}
$status = $core->mod->smush->smush_single( $attachment_id, true );
if ( is_array( $status ) && isset( $status['error'] ) ) {
/* translators: %1$d - attachment ID, %2$s - error. */
$errors[] = sprintf( __( 'Error compressing image (ID: %1$d). %2$s', 'wp-smushit' ), $attachment_id, $status['error'] );
continue;
}
$success = true;
}
$progress->tick();
$progress->finish();
if ( ! empty( $errors ) ) {
foreach ( $errors as $error ) {
WP_CLI::warning( $error );
}
}
if ( $success ) {
WP_CLI::success( __( 'Image compressed', 'wp-smushit' ) );
}
}
/**
* Smush all uncompressed images.
*
* @since 3.1
*
* @param string $msg Message for progress bar status.
* @param int $batch Compress only this number of images.
*/
private function smush_all( $msg, $batch = 0 ) {
$attachments = WP_Smush::get_instance()->core()->get_unsmushed_attachments();
if ( $batch > 0 ) {
$attachments = array_slice( $attachments, 0, $batch );
}
$progress = WP_CLI\Utils\make_progress_bar( $msg, count( $attachments ) );
foreach ( $attachments as $attachment_id ) {
WP_Smush::get_instance()->core()->mod->smush->smush_single( $attachment_id, true );
$progress->tick();
}
$progress->finish();
WP_CLI::success( __( 'All images compressed', 'wp-smushit' ) );
}
/**
* Restore all images.
*
* @since 3.1
*
* @param int $id Image ID to restore. Default: 0 - restores all images.
*/
private function restore_image( $id = 0 ) {
$core = WP_Smush::get_instance()->core();
$attachments = $core->get_smushed_attachments();
if ( empty( $attachments ) ) {
WP_CLI::success( __( 'No images available to restore', 'wp-smushit' ) );
return;
}
if ( 0 !== $id ) {
if ( ! in_array( (string) $id, $attachments, true ) ) {
WP_CLI::warning( __( 'Image with defined ID not found', 'wp-smushit' ) );
return;
}
$attachments = array( $id );
}
$progress = WP_CLI\Utils\make_progress_bar( __( 'Restoring images', 'wp-smushit' ), count( $attachments ) );
$warning = false;
foreach ( $attachments as $attachment_id ) {
if ( ! $core->mod->backup->backup_exists( $attachment_id ) ) {
$warning = true;
$warning_text = sprintf(/* translators: %d - attachment ID */
esc_html__( 'Image %d cannot be restored', 'wp-smushit' ),
(int) $attachment_id
);
WP_CLI::warning( $warning_text );
$progress->tick();
continue;
}
$media_item = Media_Item_Cache::get_instance()->get( $attachment_id );
$optimizer = new Media_Item_Optimizer( $media_item );
$restored = $optimizer->restore();
if ( ! $restored ) {
$warning = true;
}
$progress->tick();
}
$progress->finish();
if ( $warning ) {
WP_CLI::error( __( 'There were issues restoring some images', 'wp-smushit' ) );
} else {
WP_CLI::success( __( 'All images restored', 'wp-smushit' ) );
}
}
}

View File

@@ -0,0 +1,740 @@
<?php
/**
* Configs class.
*
* @since 3.8.5
* @package Smush\Core
*/
namespace Smush\Core;
use Exception;
use WP_Error;
use WP_REST_Request;
use WP_Smush;
/**
* Class Configs
*
* @since 3.8.5
*/
class Configs {
/**
* List of pro features.
*
* @since 3.8.5
*
* @var array
*/
private $pro_features = array( 'png_to_jpg', 's3', 'nextgen', 'cdn', 'webp', 'webp_mod' );
/**
* @var Settings
*/
private $settings;
public function __construct() {
$this->settings = Settings::get_instance();
}
/**
* 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
*/
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::LEVEL_SUPER_LOSSY,
'strip_exif' => true,
'resize' => false,
'detection' => false,
'original' => true,
'backup' => true,
'png_to_jpg' => true,
'nextgen' => false,
's3' => false,
'gutenberg' => false,
'js_builder' => false,
'cdn' => false,
'auto_resize' => false,
'webp' => true,
'usage' => false,
'accessible_colors' => false,
'keep_data' => true,
'lazy_load' => false,
'background_images' => true,
'rest_api_support' => false,
'webp_mod' => 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 isnt 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 ) {
if ( ! WP_Smush::is_pro() ) {
// Disable the pro features before applying them.
foreach ( $this->pro_features as $name ) {
$new_settings[ $name ] = false;
}
}
// Update the flag file when local webp changes.
if ( isset( $new_settings['webp_mod'] ) && $new_settings['webp_mod'] !== $stored_settings['webp_mod'] ) {
WP_Smush::get_instance()->core()->mod->webp->toggle_webp( $new_settings['webp_mod'] );
// Hide the wizard form if Local Webp is configured.
if ( WP_Smush::get_instance()->core()->mod->webp->is_configured() ) {
update_site_option( 'wp-smush-webp_hide_wizard', 1 );
}
}
// Update the CDN status for CDN changes.
if ( isset( $new_settings['cdn'] ) && $new_settings['cdn'] !== $stored_settings['cdn'] ) {
WP_Smush::get_instance()->core()->mod->cdn->toggle_cdn( $new_settings['cdn'] );
}
// 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
*/
private 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['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' => 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.
*/
private function format_config_to_display( $config ) {
$settings_data = array(
'bulk_smush' => Settings::get_instance()->get_bulk_fields(),
'lazy_load' => Settings::get_instance()->get_lazy_load_fields(),
'cdn' => Settings::get_instance()->get_cdn_fields(),
'webp_mod' => array(),
'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 ) {
// Display the setting inactive when the module is off.
if (
'webp_mod' === $name ||
( in_array( $name, array( 'cdn', 'lazy_load' ), true ) && empty( $config['settings'][ $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']
);
}
// Append the lazy laod details to the the Lazy Load display settings.
if ( ! empty( $config['settings']['lazy_load'] ) && ! empty( $config['lazy_load'] ) ) {
$display_array['lazy_load'] = array_merge( $display_array['lazy_load'], $this->get_lazy_load_settings_to_display( $config ) );
}
}
// 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;
}
/**
* 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
*/
private function get_settings_display_value( $config, $fields ) {
$formatted_rows = array();
$extra_labels = array(
's3' => __( 'Amazon S3', 'wp-smushit' ),
'nextgen' => __( 'NextGen Gallery', 'wp-smushit' ),
'lazy_load' => __( 'Lazy Load', '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;
}
$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
*/
private function format_boolean_setting_value( $name, $value ) {
// Display the pro features as 'inactive' for free installs.
if ( ! WP_Smush::is_pro() && in_array( $name, $this->pro_features, true ) ) {
$value = false;
}
return $value ? __( 'Active', 'wp-smushit' ) : __( 'Inactive', 'wp-smushit' );
}
/**
* Formats the given lazy_load settings to be displayed.
*
* @since 3.8.5
*
* @param array $config The config to format.
*
* @return array
*/
private 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' ),
'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' => __( 'Disable Noscript', 'wp-smushit' ),
);
foreach ( $config['lazy_load'] as $key => $value ) {
// Skip if the setting doesn't exist.
if ( ! isset( $settings_labels[ $key ] ) ) {
continue;
}
$formatted_value = $settings_labels[ $key ] . ' - ';
if ( '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' ), true ) ) {
// Enabled/disabled settings.
$formatted_value .= ! empty( $value ) ? __( 'Yes', 'wp-smushit' ) : __( 'No', 'wp-smushit' );
} else {
// Arrays.
if ( in_array( $key, array( 'format', '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;
}
/**
* 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 ) {
return array(
'configs' => $this->sanitize_config( $configs ),
'strings' => $this->format_config_to_display( $configs ),
);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Smush\Core;
abstract class Controller {
private $actions = array();
private $filters = array();
public function init() {
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,
);
}
}

View File

@@ -0,0 +1,567 @@
<?php
/**
* Core class: Core class.
*
* @since 2.9.0
* @package Smush\Core
*/
namespace Smush\Core;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Core
*/
class Core extends Stats {
/**
* Animated status.
*
* @var int
*/
const STATUS_ANIMATED = 2;
/**
* S3 module
*
* @var Integrations\S3
*/
public $s3;
/**
* NextGen module.
*
* @var Integrations\Nextgen
*/
public $nextgen;
/**
* Modules array.
*
* @var Modules
*/
public $mod;
/**
* Allowed mime types of image.
*
* @var array $mime_types
*/
public static $mime_types = array(
'image/jpg',
'image/jpeg',
'image/x-citrix-jpeg',
'image/gif',
'image/png',
'image/x-png',
);
/**
* List of external pages where smush needs to be loaded.
*
* @var array $pages
*/
public static $external_pages = array(
'nggallery-manage-images',
'gallery_page_nggallery-manage-gallery',
'gallery_page_wp-smush-nextgen-bulk',
'nextgen-gallery_page_nggallery-manage-gallery', // Different since NextGen 3.3.6.
'nextgen-gallery_page_wp-smush-nextgen-bulk', // Different since NextGen 3.3.6.
'post',
'post-new',
'page',
'edit-page',
'upload',
);
/**
* Attachment IDs which are smushed.
*
* @var array $smushed_attachments
*/
public $smushed_attachments = array();
/**
* Unsmushed image IDs.
*
* @var array $unsmushed_attachments
*/
public $unsmushed_attachments = array();
/**
* Skipped attachment IDs.
*
* @since 3.0
*
* @var array $skipped_attachments
*/
public $skipped_attachments = array();
/**
* Smushed attachments out of total attachments.
*
* @var int $smushed_count
*/
public $smushed_count = 0;
/**
* Smushed attachments out of total attachments.
*
* @var int $remaining_count
*/
public $remaining_count = 0;
/**
* Images with errors that have been skipped from bulk smushing.
*
* @since 3.0
* @var int $skipped_count
*/
public $skipped_count = 0;
/**
* Super Smushed attachments count.
*
* @var int $super_smushed
*/
public $super_smushed = 0;
/**
* Total count of attachments for smushing.
*
* @var int $total_count
*/
public $total_count = 0;
/**
* Limit for allowed number of images per bulk request.
*
* This is enforced at api level too.
*
* @var int
*/
const MAX_FREE_BULK = 50;
/**
* Initialize modules.
*
* @since 2.9.0
*/
protected function init() {
$this->mod = new Modules();
// Enqueue scripts and initialize variables.
add_action( 'admin_init', array( $this, 'init_settings' ) );
// Load integrations.
add_action( 'init', array( $this, 'load_integrations' ) );
// Big image size threshold (WordPress 5.3+).
add_filter( 'big_image_size_threshold', array( $this, 'big_image_size_threshold' ), 10 );
/**
* Load NextGen Gallery, instantiate the Async class. if hooked too late or early, auto Smush doesn't
* work, also load after settings have been saved on init action.
*/
add_action( 'plugins_loaded', array( $this, 'load_libs' ), 90 );
/**
* Maybe need to load some modules in REST API mode.
* E.g. S3.
*/
add_action( 'rest_api_init', array( $this, 'load_libs_for_rest_api' ), 99 );
}
/**
* Load integrations class.
*
* @since 2.8.0
*/
public function load_integrations() {
new Integrations\Common();
}
/**
* Load plugin modules.
*/
public function load_libs() {
$this->wp_smush_async();
if ( is_admin() ) {
$this->s3 = new Integrations\S3();
}
/**
* Load NextGen integration on admin or custom ajax request.
*
* @since 3.10.0
*/
if ( is_admin() || defined( 'NGG_AJAX_SLUG' ) && ! empty( $_REQUEST[ NGG_AJAX_SLUG ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->nextgen = new Integrations\Nextgen();
}
new Integrations\Gutenberg();
new Integrations\Composer();
new Integrations\Gravity_Forms();
new Integrations\Envira( $this->mod->cdn );
new Integrations\Avada( $this->mod->cdn );
// Register logger to schedule cronjob.
Helper::logger();
}
/**
* Load lib for REST API.
*/
public function load_libs_for_rest_api() {
// Load S3 if there is media REST API.
if ( ! Helper::is_non_rest_media() && ! $this->s3 ) {
$this->s3 = new Integrations\S3();
}
}
/**
* Initialize the Smush Async class.
*/
private function wp_smush_async() {
// Check if Async is disabled.
if ( defined( 'WP_SMUSH_ASYNC' ) && ! WP_SMUSH_ASYNC ) {
return;
}
// Instantiate class.
new Modules\Async\Async();
// Load the Editor Async task only if user logged in or in backend.
if ( is_admin() && is_user_logged_in() ) {
new Modules\Async\Editor();
}
}
/**
* Init settings.
*/
public function init_settings() {
// Initialize Image dimensions.
$this->mod->smush->image_sizes = $this->image_dimensions();
}
/**
* Localize translations.
*/
public function localize() {
global $current_screen;
$handle = 'smush-admin';
$upgrade_url = add_query_arg(
array(
'utm_source' => 'smush',
'utm_medium' => 'plugin',
'utm_campaign' => 'smush_bulksmush_inline_filesizelimit',
),
'https://wpmudev.com/project/wp-smush-pro/'
);
$wp_smush_msgs = array(
'nonce' => wp_create_nonce( 'wp-smush-ajax' ),
'webp_nonce' => wp_create_nonce( 'wp-smush-webp-nonce' ),
'settingsUpdated' => esc_html__( 'Your settings have been updated', 'wp-smushit' ),
'resmush' => esc_html__( 'Super-Smush', 'wp-smushit' ),
'smush_now' => esc_html__( 'Smush Now', 'wp-smushit' ),
'error_in_bulk' => esc_html__( '{{smushed}}/{{total}} images smushed successfully, {{errors}} images were not optimized, find out why and how to resolve the issue(s) below.', 'wp-smushit' ),
'all_failed' => esc_html__( 'All of your images failed to smush. Find out why and how to resolve the issue(s) below.', 'wp-smushit' ),
'all_resmushed' => esc_html__( 'All images are fully optimized.', 'wp-smushit' ),
'all_smushed' => esc_html__( 'All attachments have been smushed. Awesome!', 'wp-smushit' ),
'error_size_limit' => WP_Smush::is_pro() ? '' : sprintf(
/* translators: %1$s - opening a link <a>, %2$s - Close the link </a> */
esc_html__( 'Are you hitting the 5MB "size limit exceeded" warning? %1$sUpgrade to Smush Pro%2$s to optimize unlimited image files up to 256Mb each.', 'wp-smushit' ),
'<a href="' . esc_url( $upgrade_url ) . '" target="_blank">',
'</a>'
),
'processing_cdn_for_free' => esc_html__( 'Want to serve images even faster? Get up to 2x more speed with Smush Pros CDN, which spans 114 servers worldwide.', 'wp-smushit' ),
'processed_cdn_for_free' => esc_html__( 'Let images reach your audience faster no matter where your hosting servers are. Smush Pros global CDN serves images closer to site visitors via 114 worldwide server locations.', 'wp-smushit' ),
'restore' => esc_html__( 'Restoring image...', 'wp-smushit' ),
'smushing' => esc_html__( 'Smushing image...', 'wp-smushit' ),
'btn_ignore' => esc_html__( 'Ignore', 'wp-smushit' ),
'view_detail' => esc_html__( 'View Details', 'wp-smushit' ),
'membership_valid' => esc_html__( 'We successfully verified your membership, all the Pro features should work completely. ', 'wp-smushit' ),
'membership_invalid' => esc_html__( "Your membership couldn't be verified.", 'wp-smushit' ),
'missing_path' => esc_html__( 'Missing file path.', 'wp-smushit' ),
'failed_item_smushed' => esc_html__( 'Images smushed successfully, No further action required', 'wp-smushit' ),
// Used by Directory Smush.
'unfinished_smush_single' => esc_html__( 'image could not be smushed.', 'wp-smushit' ),
'unfinished_smush' => esc_html__( 'images could not be smushed.', 'wp-smushit' ),
'already_optimised' => esc_html__( 'Already Optimized', 'wp-smushit' ),
'ajax_error' => esc_html__( 'Ajax Error', 'wp-smushit' ),
'generic_ajax_error' => esc_html__( 'Something went wrong with the request. Please reload the page and try again.', 'wp-smushit' ),
'all_done' => esc_html__( 'All Done!', 'wp-smushit' ),
'sync_stats' => esc_html__( 'Give us a moment while we sync the stats.', 'wp-smushit' ),
// Progress bar text.
'progress_smushed' => esc_html__( 'images optimized', 'wp-smushit' ),
'bulk_resume' => esc_html__( 'Resume scan', 'wp-smushit' ),
'bulk_stop' => esc_html__( 'Stop current bulk smush process.', 'wp-smushit' ),
// Errors.
'error_ignore' => esc_html__( 'Ignore this image from bulk smushing', 'wp-smushit' ),
// Ignore text.
'ignored' => esc_html__( 'Ignored', 'wp-smushit' ),
'not_processed' => esc_html__( 'Not processed', 'wp-smushit' ),
// Notices.
'noticeDismiss' => esc_html__( 'Dismiss', 'wp-smushit' ),
'noticeDismissTooltip' => esc_html__( 'Dismiss notice', 'wp-smushit' ),
'tutorialsRemoved' => sprintf( /* translators: %1$s - opening a tag, %2$s - closing a tag */
esc_html__( 'The widget has been removed. Smush tutorials can still be found in the %1$sTutorials tab%2$s any time.', 'wp-smushit' ),
'<a href=' . esc_url( menu_page_url( 'smush-tutorials', false ) ) . '>',
'</a>'
),
'smush_cdn_activation_notice' => WP_Smush::is_pro() && ! $this->mod->cdn->is_active() ?
sprintf(
/* translators: %1$s - opening a tag, %2$s - closing a tag */
esc_html__( 'Activate Smush CDN to bulk smush and serve animated GIFs via 114 worldwide locations. %1$sActivate CDN%2$s', 'wp-smushit' ),
'<a href="'. esc_url( network_admin_url( 'admin.php?page=smush-cdn' ) ) .'" />',
'</a>'
) :
''
,
// URLs.
'smush_url' => network_admin_url( 'admin.php?page=smush' ),
'bulk_smush_url' => network_admin_url( 'admin.php?page=smush-bulk' ),
'directory_url' => network_admin_url( 'admin.php?page=smush-directory' ),
'localWebpURL' => network_admin_url( 'admin.php?page=smush-webp' ),
'edit_link' => Helper::get_image_media_link( '{{id}}', null, true ),
'debug_mode' => defined( 'WP_DEBUG' ) && WP_DEBUG,
'cancel' => esc_html__( 'Cancel', 'wp-smushit' ),
'cancelling' => esc_html__( 'Cancelling ...', 'wp-smushit' ),
'recheck_images_link' => Helper::get_recheck_images_link(),
);
wp_localize_script( $handle, 'wp_smush_msgs', $wp_smush_msgs );
$product_analytics = WP_Smush::get_instance()->core()->mod->product_analytics;
wp_localize_script(
$handle,
'wp_smush_mixpanel',
array(
'opt_in' => Settings::get_instance()->get( 'usage' ),
'token' => $product_analytics->get_token(),
'unique_id' => $product_analytics->get_unique_id(),
'super_properties' => $product_analytics->get_super_properties(),
'debug' => defined( 'WP_SMUSH_MIXPANEL_DEBUG' ) && WP_SMUSH_MIXPANEL_DEBUG
&& defined( 'WP_SMUSH_VERSION' ) && strpos( WP_SMUSH_VERSION, 'beta' ),
)
);
if ( 'toplevel_page_smush' === $current_screen->id ) {
$slug = 'dashboard';
} else {
$slug = explode( 'page_smush-', $current_screen->id );
$slug = isset( $slug[1] ) ? $slug[1] : false;
}
// Load the stats on selected screens only.
if ( $slug && isset( WP_Smush::get_instance()->admin()->pages[ $slug ] ) && method_exists( WP_Smush::get_instance()->admin()->pages[ $slug ], 'dashboard_summary_meta_box' ) ) {
// Get resmush list, If we have a resmush list already, localize those IDs.
$resmush_ids = $this->get_resmush_ids();
if ( $resmush_ids ) {
// Get the attachments, and get lossless count.
$this->resmush_ids = $resmush_ids;
}
// Get attachments if all the images are not smushed.
$this->unsmushed_attachments = $this->remaining_count > 0 ? $this->get_unsmushed_attachments() : array();
$this->unsmushed_attachments = ! empty( $this->unsmushed_attachments ) && is_array( $this->unsmushed_attachments ) ? array_values( $this->unsmushed_attachments ) : $this->unsmushed_attachments;
$data = $this->get_global_stats();
} else {
$data = array(
'count_supersmushed' => '',
'count_smushed' => '',
'count_total' => '',
'count_images' => '',
'unsmushed' => '',
'resmush' => '',
'savings_bytes' => '',
'savings_resize' => '',
'savings_conversion' => '',
'savings_supersmush' => '',
'savings_percent' => '',
'percent_grade' => '',
'percent_metric' => '',
'percent_optimized' => '',
);
}
// Check if scanner class is available.
$scanner_ready = isset( $this->mod->dir->scanner );
$data['dir_smush'] = array(
'currentScanStep' => $scanner_ready ? $this->mod->dir->scanner->get_current_scan_step() : 0,
'totalSteps' => $scanner_ready ? $this->mod->dir->scanner->get_scan_steps() : 0,
);
$data['resize_sizes'] = $this->get_max_image_dimensions();
// Convert it into ms.
$data['timeout'] = WP_SMUSH_TIMEOUT * 1000;
wp_localize_script( $handle, 'wp_smushit_data', apply_filters( 'wp_smush_script_data', $data ) );
}
/**
* Check bulk sent count, whether to allow further smushing or not
*
* @param bool $reset To hard reset the transient.
* @param string $key Transient Key - bulk_sent_count/dir_sent_count.
*
* TODO: remove this (and all related code) because the limit has been lifted in 3.12.0
*
* @return bool
*/
public static function check_bulk_limit( $reset = false, $key = 'bulk_sent_count' ) {
$is_pre_3_12_6_site = get_site_option( 'wp_smush_pre_3_12_6_site' );
if ( $is_pre_3_12_6_site ) {
return true;
}
$transient_name = 'wp-smush-' . $key;
// If we JUST need to reset the transient.
if ( $reset ) {
set_transient( $transient_name, 0, 60 );
return false;
}
$bulk_sent_count = (int) get_transient( $transient_name );
// Check if bulk smush limit is less than limit.
if ( ! $bulk_sent_count || $bulk_sent_count < self::MAX_FREE_BULK ) {
$continue = true;
} elseif ( $bulk_sent_count === self::MAX_FREE_BULK ) {
// If user has reached the limit, reset the transient.
$continue = false;
$reset = true;
} else {
$continue = false;
}
// If we need to reset the transient.
if ( $reset ) {
set_transient( $transient_name, 0, 60 );
}
return $continue;
}
/**
* Get registered image sizes with dimension
*
* @return array
*/
public function image_dimensions() {
return Helper::get_image_sizes();
}
/**
* Get the Maximum Width and Height settings for WrodPress
*
* @return array, Array of Max. Width and Height for image.
*/
public function get_max_image_dimensions() {
global $_wp_additional_image_sizes;
$width = 0;
$height = 0;
$limit = 9999; // Post-thumbnail.
$image_sizes = get_intermediate_image_sizes();
// If image sizes are filtered and no image size list is returned.
if ( empty( $image_sizes ) ) {
return array(
'width' => $width,
'height' => $height,
);
}
// Create the full array with sizes and crop info.
foreach ( $image_sizes as $size ) {
if ( in_array( $size, array( 'thumbnail', 'medium', 'medium_large', 'large' ), true ) ) {
$size_width = get_option( "{$size}_size_w" );
$size_height = get_option( "{$size}_size_h" );
} elseif ( isset( $_wp_additional_image_sizes[ $size ] ) ) {
$size_width = $_wp_additional_image_sizes[ $size ]['width'];
$size_height = $_wp_additional_image_sizes[ $size ]['height'];
}
// Skip if no width and height.
if ( ! isset( $size_width, $size_height ) ) {
continue;
}
// If within te limit, check for a max value.
if ( $size_width <= $limit ) {
$width = max( $width, $size_width );
}
if ( $size_height <= $limit ) {
$height = max( $height, $size_height );
}
}
return array(
'width' => $width,
'height' => $height,
);
}
/**
* Update the image smushed count in transient
*
* @param string $key Database key.
*/
public static function update_smush_count( $key = 'bulk_sent_count' ) {
$transient_name = 'wp-smush-' . $key;
$bulk_sent_count = get_transient( $transient_name );
// If bulk sent count is not set.
if ( false === $bulk_sent_count ) {
// Start transient at 0.
set_transient( $transient_name, 1, 200 );
} elseif ( $bulk_sent_count < self::MAX_FREE_BULK ) {
// If lte MAX_FREE_BULK images are sent, increment.
set_transient( $transient_name, $bulk_sent_count + 1, 200 );
}
}
/**
* Set the big image threshold.
*
* @param int $threshold The threshold value in pixels. Default 2560.
*
* @return int|bool New threshold. False if scaling is disabled.
* @since 3.3.2
*
*/
public function big_image_size_threshold( $threshold ) {
if ( Settings::get_instance()->get( 'no_scale' ) ) {
return false;
}
if ( ! $this->mod->resize->is_active() ) {
return $threshold;
}
$resize_sizes = Settings::get_instance()->get_setting( 'wp-smush-resize_sizes' );
if ( ! $resize_sizes || ! is_array( $resize_sizes ) ) {
return $threshold;
}
return $resize_sizes['width'] > $resize_sizes['height'] ? $resize_sizes['width'] : $resize_sizes['height'];
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
* Deprecated hooks.
* It's created based on WC_Deprecated_Hooks.
*
* @since 3.9.6
* @package Deprecated_Hooks
*/
namespace Smush\Core;
defined( 'ABSPATH' ) || exit;
/**
* Handles deprecation notices and triggering of legacy action hooks.
*/
class Deprecated_Hooks {
/**
* Array of deprecated actions hooks we need to handle. Format of 'new' => '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;
}
}

View File

@@ -0,0 +1,275 @@
<?php
/**
* Error_Handler class.
*
* @package Smush\Core
* @version 3.12.0
*/
namespace Smush\Core;
use Smush\Core\Media\Media_Item;
use Smush\Core\Media\Media_Item_Cache;
use Smush\Core\Media\Media_Item_Optimizer;
use Smush\Core\Stats\Global_Stats;
use WP_Error;
if ( ! defined( 'WPINC' ) ) {
die;
}
class Error_Handler {
/**
* Ignore meta key.
*/
const IGNORE_KEY = 'wp-smush-ignore-bulk';
/**
* Error meta key.
*/
const ERROR_KEY = 'wp-smush-error';
/**
* Animated error code.
*/
const ANIMATED_ERROR_CODE = 'animated';
/**
* Handled error codes.
*
* @var array
*/
private static $locked_error_codes = array(
'ignored',
// 'in_progress',
// 'animated',
);
/**
* Skipped error codes.
*
* @var array
*/
private static $skipped_error_codes = array(
'skipped_filter',
'ignored',
'animated',
'size_limit',
'size_pro_limit',
);
/**
* Should regenerate thumbnail error codes.
*
* @var array
*/
private static $regenerate_error_codes = array(
'no_file_meta',
'file_not_found',
);
/**
* Get error message.
*
* @param string $error_code Error code.
* @param int $image_id Attachment ID.
* @return string
*/
public static function get_error_message( $error_code, $image_id = 0 ) {
$error_messages = self::get_default_error_messages();
$error_message = ! empty( $error_messages[ $error_code ] ) ? $error_messages[ $error_code ] : '';
return self::format_error_message( $error_message, $error_code, $image_id );
}
/**
* Get sprintf error message.
*
* @param string $error_message Error message.
* @param string $error_code Error code.
* @param int $image_id Attachment ID.
* @return string
*/
private static function format_error_message( $error_message, $error_code, $image_id ) {
if ( empty( $image_id ) || empty( $error_message ) || false === strpos( $error_message, '%s' ) ) {
return $error_message;
}
switch ( $error_code ) {
case 'size_limit':
case 'size_pro_limit':
$size_exceeded = Helper::size_limit_exceeded( $image_id );
if ( $size_exceeded ) {
$error_message = sprintf( $error_message, size_format( $size_exceeded ) );
} else {
$error_message = null;
}
break;
case 'not_writable':
$file_path = Helper::get_attached_file( $image_id );
$error_message = sprintf( $error_message, Helper::clean_file_path( dirname( $file_path ) ) );
break;
case 'file_not_found':
$file_path = Helper::get_attached_file( $image_id );
$file_not_found = $file_path;
if ( file_exists( $file_path ) ) {
// Try go get the not found thumbnail file.
$all_file_sizes = wp_get_attachment_metadata( $image_id );
$dir_path = dirname( $file_path );
if ( ! empty( $all_file_sizes['sizes'] ) ) {
foreach ( $all_file_sizes['sizes'] as $size => $size_data ) {
$size_file = $dir_path . '/' . $size_data['file'];
if ( ! file_exists( $size_file ) ) {
$file_not_found = $size_file;
break;
}
}
}
}
$error_message = sprintf( $error_message, basename( $file_not_found ) );
break;
}
return $error_message;
}
public static function get_all_failed_images() {
$media_item_error_ids = Global_Stats::get()->get_error_list()->get_ids();
$optimization_error_ids = self::get_last_optimization_error_ids( PHP_INT_MAX );
return array_unique( array_merge( $media_item_error_ids, $optimization_error_ids ) );
}
/**
* Get last optimization errors.
*
* @param int $limit Query limit.
* @return array
*/
private static function get_last_optimization_error_ids( $limit ) {
global $wpdb;
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$query = $wpdb->prepare(
"SELECT DISTINCT post_meta_error.post_id FROM $wpdb->postmeta as post_meta_error
LEFT JOIN $wpdb->postmeta as post_meta_ignore ON post_meta_ignore.post_id = post_meta_error.post_id AND post_meta_ignore.meta_key= %s
WHERE post_meta_ignore.meta_value IS NULL AND post_meta_error.meta_key = %s
ORDER BY post_meta_error.post_id DESC LIMIT %d;",
Media_Item::IGNORED_META_KEY,
Media_Item_Optimizer::ERROR_META_KEY,
$limit
);
/**
* Due to performance, we do not join with table wp_posts to exclude the deleted attachments,
* leave a filter for third-party custom it.
*/
$query = apply_filters( 'wp_smush_query_get_last_optimize_errors', $query );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_col( $query );
}
private static function get_last_media_item_errors( $limit ) {
$error_list_ids = Global_Stats::get()->get_error_list()->get_ids();
$last_errors = array();
if ( empty( $error_list_ids ) ) {
return $last_errors;
}
foreach( $error_list_ids as $attachment_id ) {
$media_item = Media_Item_Cache::get_instance()->get( $attachment_id );
if ( ! $media_item->has_errors() ) {
continue;
}
$last_errors[ $attachment_id ] = self::get_error( $media_item->get_errors(), $media_item );
if ( count( $last_errors ) >= $limit ) {
break;
}
}
return $last_errors;
}
private static function get_last_optimize_errors( $limit ) {
$last_errors_ids = self::get_last_optimization_error_ids( $limit );
$last_errors = array();
if ( empty( $last_errors_ids ) ) {
return $last_errors;
}
foreach( $last_errors_ids as $attachment_id ) {
$media_item = Media_Item_Cache::get_instance()->get( $attachment_id );
$optimizer = new Media_Item_Optimizer($media_item);
if( ! $optimizer->has_errors() ) {
continue;
}
$last_errors[ $attachment_id ] = self::get_error( $optimizer->get_errors(), $media_item );
if ( count( $last_errors ) >= $limit ) {
break;
}
}
return $last_errors;
}
/**
* Get latest errors.
*
* @param int $limit Limit number of errors to return.
* @return array
*/
public static function get_last_errors( $limit = 10 ) {
$last_errors = self::get_last_media_item_errors( $limit );
$no_item_errors = count( $last_errors );
if ( $no_item_errors >= $limit ) {
return $last_errors;
}
$optimize_errors = self::get_last_optimize_errors( $limit - $no_item_errors );
return $last_errors + $optimize_errors;
}
/**
* @return array
*/
public static function get_error( WP_Error $errors, Media_Item $media_item ) {
$thumbnail = $media_item->get_size('thumbnail' );
return array(
'error_code' => $errors->get_error_code(),
'error_message' => $errors->get_error_message(),
'file_name' => $media_item->get_scaled_or_full_size()->get_file_name(),
'thumbnail' => $thumbnail ? $thumbnail->get_file_url() : false,
);
}
/**
* Get error messages.
*
* @return array
*/
private static function get_default_error_messages() {
return apply_filters(
'wp_smush_error_messages',
array(
'missing_id' => esc_html__( 'No attachment ID was received.', 'wp-smushit' ),
'ignored' => esc_html__( 'Skip ignored file.', 'wp-smushit' ),
'animated' => esc_html__( 'Skipped animated file.', 'wp-smushit' ),
'in_progress' => esc_html__( 'File processing is in progress.', 'wp-smushit' ),
'no_file_meta' => esc_html__( 'No file data found in image meta', 'wp-smushit' ),
'skipped_filter' => esc_html__( 'Skipped with wp_smush_image filter', 'wp-smushit' ),
'empty_path' => esc_html__( 'File path is empty', 'wp-smushit' ),
'empty_response' => esc_html__( 'Webp no response was received.', 'wp-smushit' ),
'not_processed' => esc_html__( 'Not processed', 'wp-smushit' ),
/* translators: %s: image size */
'size_limit' => __( 'Skipped (%s), file size limit of 5mb exceeded', 'wp-smushit' ),
/* translators: %s: image size */
'size_pro_limit' => __( 'Skipped (%s), file size limit of 256mb exceeded', 'wp-smushit' ),
/* translators: %s: Directory path */
'not_writable' => __( '%s is not writable', 'wp-smushit' ),
/* translators: %s: File path */
'file_not_found' => __( 'Skipped (%s), File not found.', 'wp-smushit' ),
)
);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Smush\Core;
class File_System {
public function file_get_contents( $path, $use_include_path = false, $context = null, $offset = 0, $length = null ) {
if ( ! $this->is_valid_path( $path ) ) {
return false;
}
$args = array( $path, $use_include_path, $context, $offset );
if ( ! is_null( $length ) ) {
// Even though the default value of $length is 'null', an empty string is returned when 'null' is passed as $length. So, we only include it when a non-null value is provided.
$args[] = $length;
}
return call_user_func_array( 'file_get_contents', $args );
}
public function file_exists( $path ) {
return $this->validate_and_call( 'file_exists', $path );
}
public function unlink( $path ) {
return $this->validate_and_call( 'unlink', $path );
}
public function copy( $source, $destination ) {
return $this->validate_and_call( 'copy', $source, $destination );
}
public function is_file( $file ) {
return $this->validate_and_call( 'is_file', $file );
}
public function is_dir( $path ) {
return $this->validate_and_call( 'is_dir', $path );
}
public function filesize( $file ) {
return $this->validate_and_call( 'filesize', $file );
}
public function getimagesize( $path ) {
return $this->validate_and_call( 'getimagesize', $path );
}
private function validate_and_call( $callback, ...$args ) {
foreach ( $args as $arg ) {
if ( ! $this->is_valid_path( $arg ) ) {
return false;
}
}
return call_user_func_array( $callback, $args );
}
private function is_valid_path( $path ) {
return false === stripos( $path, 'phar://' );
}
}

View File

@@ -0,0 +1,969 @@
<?php
/**
* Helpers class.
*
* @package Smush\Core
* @version 1.0
*
* @author Umesh Kumar <umesh@incsub.com>
*
* @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 WP_Smush;
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(),
),
)
);
}
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 "<a href='$link'>$name</a>";
}
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::is_pro() ? WP_SMUSH_PREMIUM_MAX_BYTES : 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 = '' ) {
$upgrade_url = 'https://wpmudev.com/project/wp-smush-pro/';
return add_query_arg(
array(
'utm_source' => 'smush',
'utm_medium' => 'plugin',
'utm_campaign' => $utm_campaign,
),
$upgrade_url
);
}
/**
* 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;
}
}

View File

@@ -0,0 +1,342 @@
<?php
/**
* Smush installer (update/upgrade procedures): Installer class
*
* @package Smush\Core
* @since 2.8.0
*
* @author Anton Vanyukov <anton@incsub.com>
*
* @copyright (c) 2018, Incsub (http://incsub.com)
*/
namespace Smush\Core;
use Smush\App\Abstract_Page;
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' ) ) {
require_once __DIR__ . '/modules/class-cdn.php';
}
Modules\CDN::unschedule_cron();
Settings::get_instance()->delete_setting( 'wp-smush-cdn_status' );
if ( is_multisite() && is_network_admin() ) {
/**
* Updating the option instead of removing it.
*
* @see https://incsub.atlassian.net/browse/SMUSH-350
*/
update_site_option( 'wp-smush-networkwide', 1 );
}
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_12_6_site( $version );
if ( ! class_exists( '\\Smush\\Core\\Settings' ) ) {
require_once __DIR__ . '/class-settings.php';
}
Settings::get_instance()->init();
$settings = Settings::get_instance()->get();
// 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 || ( isset( $settings['auto'] ) && false !== $settings['auto'] ) ) {
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;
}
$version = get_site_option( 'wp-smush-version' );
if ( false === $version ) {
self::smush_activated();
} else {
self::maybe_mark_as_pre_3_12_6_site( $version );
}
if ( false !== $version && WP_SMUSH_VERSION !== $version ) {
if ( ! defined( 'WP_SMUSH_UPGRADING' ) ) {
define( 'WP_SMUSH_UPGRADING', true );
}
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.0', '<' ) ) {
// Hide the Local WebP wizard if Local WebP is enabled.
if ( Settings::get_instance()->get( 'webp_mod' ) ) {
add_site_option( 'wp-smush-webp_hide_wizard', true );
}
}
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.14.0', '<' ) ) {
self::upgrade_3_14_0();
}
$hide_new_feature_highlight_modal = apply_filters( 'wpmudev_branding_hide_doc_link', false );
if ( ! $hide_new_feature_highlight_modal && version_compare( $version, '3.14.0', '<' ) ) {
// Add the flag to display the new feature background process modal.
add_site_option( 'wp-smush-show_upgrade_modal', true );
}
// 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 );
}
}
/**
* 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
*
* @since 3.10.0
*
* @return void
*/
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 );
}
// Show new features modal for free users.
if ( ! WP_Smush::is_pro() ) {
if ( is_multisite() && ! Abstract_Page::should_render( 'bulk' ) ) {
return;
}
add_site_option( 'wp-smush-show_upgrade_modal', true );
}
}
/**
* Upgrade 3.10.3
*
* @since 3.10.3
*
* @return void
*/
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_12_6_site( $version ) {
if ( ! $version || version_compare( $version, '3.12.0', '<' ) || false !== get_site_option( 'wp_smush_pre_3_12_6_site') ) {
return;
}
if ( version_compare( $version, '3.12.5', '>' ) ) {
$version = 0;
}
update_site_option( 'wp_smush_pre_3_12_6_site', $version );
}
private static function upgrade_3_14_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 = new Configs();
foreach ( $stored_configs as $key => $preset_config ) {
if ( empty( $preset_config['config']['configs']['settings'] ) ) {
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 );
}
}

View File

@@ -0,0 +1,187 @@
<?php
/**
* Class Modules.
*
* Used in Core to type hint the $mod variable. For example, this way any calls to
* \Smush\WP_Smush::get_instance()->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\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_Slice_Data_Fetcher;
use Smush\Core\Media_Library\Media_Library_Watcher;
use Smush\Core\Png2Jpg\Png2Jpg_Controller;
use Smush\Core\Resize\Resize_Controller;
use Smush\Core\S3\S3_Controller;
use Smush\Core\Smush\Smush_Controller;
use Smush\Core\Stats\Global_Stats_Controller;
use Smush\Core\Webp\Webp_Controller;
use Smush\Core\Photon\Photon_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 Modules\CDN
*/
public $cdn;
/**
* Image lazy load module.
*
* @since 3.2
*
* @var 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
*/
public $product_analytics;
public $backward_compatibility;
/**
* 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 = new Modules\Smush();
$this->backup = new Modules\Backup();
$this->png2jpg = new Modules\Png2jpg();
$this->resize = new Modules\Resize();
$page_parser = new Modules\Helpers\Parser();
$page_parser->init();
$this->cdn = new Modules\CDN( $page_parser );
$this->webp = new Modules\WebP();
$this->lazy = new Modules\Lazy( $page_parser );
$this->product_analytics = new Modules\Product_Analytics();
$this->bg_optimization = new Modules\Bulk\Background_Bulk_Smush();
$smush_controller = new Smush_Controller();
$smush_controller->init();
$png2jpg_controller = Png2Jpg_Controller::get_instance();
$png2jpg_controller->init();
$webp_controller = new Webp_Controller();
$webp_controller->init();
$resize_controller = new Resize_Controller();
$resize_controller->init();
$s3_controller = new S3_Controller();
$s3_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();
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Smush\Core;
use Smush\Core\Stats\Global_Stats;
class Optimization_Controller extends Controller {
/**
* @var Global_Stats
*/
private $global_stats;
public function __construct() {
$this->global_stats = Global_Stats::get();
$this->register_action( 'wp_smush_image_sizes_changed', array( $this, 'mark_global_stats_as_outdated' ) );
$this->register_action( 'wp_smush_settings_updated', array(
$this,
'maybe_mark_global_stats_as_outdated',
), 10, 2 );
// TODO: handle auto optimization when media item is uploaded
// TODO: handle bulk smush ajax
}
public function mark_global_stats_as_outdated() {
$this->global_stats->mark_as_outdated();
}
public function maybe_mark_global_stats_as_outdated( $old_settings, $settings ) {
$old_original = ! empty( $old_settings['original'] );
$new_original = ! empty( $settings['original'] );
$original_status_changed = $old_original !== $new_original;
if ( $original_status_changed ) {
$this->mark_global_stats_as_outdated();
}
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Smush\Core;
class Plugin_Settings_Watcher extends Controller {
/**
* @var Settings
*/
private $settings;
/**
* @var Array_Utils
*/
private $array_utils;
public function __construct() {
$this->settings = Settings::get_instance();
$this->array_utils = new Array_Utils();
$this->hook_settings_update_interceptor( array( $this, 'trigger_updated_action' ) );
$this->hook_settings_delete_interceptor( array( $this, 'trigger_deleted_action' ) );
$this->hook_settings_update_interceptor( array(
$this,
'trigger_resize_sizes_updated_action',
), 'wp-smush-resize_sizes' );
$this->hook_settings_update_interceptor( array(
$this,
'trigger_membership_status_change_action',
), 'wp_smush_api_auth' );
}
private function hook_settings_update_interceptor( $callback, $option_id = 'wp-smush-settings' ) {
if ( $this->settings->is_network_enabled() ) {
$this->register_action(
"update_site_option_$option_id",
function ( $option, $settings, $old_settings ) use ( $callback ) {
call_user_func_array( $callback, array( $old_settings, $settings ) );
},
10,
3
);
} else {
$this->register_action( "update_option_$option_id", $callback, 10, 2 );
}
}
private function hook_settings_delete_interceptor( $callback ) {
if ( $this->settings->is_network_enabled() ) {
$this->register_action( "delete_site_option_wp-smush-settings", $callback );
} else {
$this->register_action( "delete_option_wp-smush-settings", $callback );
}
}
public function trigger_updated_action( $old_settings, $settings ) {
do_action( 'wp_smush_settings_updated', $old_settings, $settings );
}
public function trigger_deleted_action() {
do_action( 'wp_smush_settings_deleted' );
}
public function trigger_resize_sizes_updated_action( $old_settings, $settings ) {
do_action( 'wp_smush_resize_sizes_updated', $old_settings, $settings );
}
public function trigger_membership_status_change_action( $old_settings, $settings ) {
$api_key = Helper::get_wpmudev_apikey();
$old_validity = isset( $old_settings[ $api_key ]['validity'] ) ? $old_settings[ $api_key ]['validity'] : false;
$new_validity = isset( $settings[ $api_key ]['validity'] ) ? $settings[ $api_key ]['validity'] : false;
if ( $old_validity !== $new_validity ) {
do_action( 'wp_smush_membership_status_changed' );
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* Smush integration with Rest API: Rest class
*
* @package Smush\Core
* @since 2.8.0
*
* @author Anton Vanyukov <anton@incsub.com>
*
* @copyright (c) 2018, Incsub (http://incsub.com)
*/
namespace Smush\Core;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Singleton class Rest for extending the WordPress REST API interface.
*
* @since 2.8.0
*/
class Rest {
/**
* Rest constructor.
*/
public function __construct() {
// Register smush meta fields and callbacks for the image object in the
// wp-json/wp/v2/media REST API endpoint.
add_action( 'rest_api_init', array( $this, 'register_smush_meta' ) );
// Custom route for handling configs.
add_action( 'rest_api_init', array( $this, 'register_configs_route' ) );
}
/**
* Callback for rest_api_init action.
*
* @since 2.8.0
*/
public function register_smush_meta() {
register_rest_field(
'attachment',
'smush',
array(
'get_callback' => array( $this, 'register_image_stats' ),
'schema' => array(
'description' => __( 'Smush data.', 'wp-smushit' ),
'type' => 'string',
),
)
);
}
/**
* Add image stats to the wp-json/wp/v2/media REST API endpoint.
*
* Will add the stats from wp-smpro-smush-data image meta key to the media REST API endpoint.
* If image is Smushed, the stats from the meta can be queried, if the not - the status of Smushing
* will be displayed as a string in the API.
*
* @since 2.8.0
*
* @link https://developer.wordpress.org/rest-api/reference/media/
*
* @param array $image Image array.
*
* @return array|string
*/
public function register_image_stats( $image ) {
if ( get_transient( 'smush-in-progress-' . $image['id'] ) ) {
return __( 'Smushing in progress', 'wp-smushit' );
}
$wp_smush_data = get_post_meta( $image['id'], Modules\Smush::$smushed_meta_key, true );
if ( empty( $wp_smush_data ) ) {
return __( 'Not processed', 'wp-smushit' );
}
$wp_resize_savings = get_post_meta( $image['id'], 'wp-smush-resize_savings', true );
$conversion_savings = get_post_meta( $image['id'], 'wp-smush-pngjpg_savings', true );
$combined_stats = WP_Smush::get_instance()->core()->combined_stats( $wp_smush_data, $wp_resize_savings );
return WP_Smush::get_instance()->core()->combine_conversion_stats( $combined_stats, $conversion_savings );
}
/**
* Registers the custom route for handling configs.
*
* @since 3.8.6
*/
public function register_configs_route() {
$configs_handler = new Configs();
register_rest_route(
'wp-smush/v1',
'/preset_configs/',
array(
array(
'methods' => 'GET',
'callback' => array( $configs_handler, 'get_callback' ),
'permission_callback' => array( $configs_handler, 'permission_callback' ),
),
array(
'methods' => 'POST',
'callback' => array( $configs_handler, 'post_callback' ),
'permission_callback' => array( $configs_handler, 'permission_callback' ),
),
)
);
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Smush\Core;
class Server_Utils {
/**
* @var string
*/
private $mysql_version;
public function get_server_type() {
if ( empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
return '';
}
$server_software = wp_unslash( $_SERVER['SERVER_SOFTWARE'] );
if ( ! is_array( $server_software ) ) {
$server_software = array( $server_software );
}
$server_software = array_map( 'strtolower', $server_software );
$is_nginx = $this->array_has_needle( $server_software, 'nginx' );
if ( $is_nginx ) {
return 'nginx';
}
$is_apache = $this->array_has_needle( $server_software, 'apache' );
if ( $is_apache ) {
return 'apache';
}
return '';
}
public function get_memory_limit() {
if ( function_exists( 'ini_get' ) ) {
$memory_limit = ini_get( 'memory_limit' );
} else {
// Sensible default.
$memory_limit = '128M';
}
if ( ! $memory_limit || - 1 === $memory_limit ) {
// Unlimited, set to 32GB.
$memory_limit = '32000M';
}
return intval( $memory_limit ) * 1024 * 1024;
}
public function get_memory_usage() {
return memory_get_usage( true );
}
private function array_has_needle( $array, $needle ) {
foreach ( $array as $item ) {
if ( strpos( $item, $needle ) !== false ) {
return true;
}
}
return false;
}
public function get_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;
}
public function get_max_execution_time() {
return (int) ini_get( 'max_execution_time' );
}
public function get_user_agent() {
return ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) : '';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
<?php
namespace Smush\Core;
class Smush_File {
/**
* @var array
*/
private $supported_mime_types = array(
'image/jpg',
'image/jpeg',
'image/x-citrix-jpeg',
'image/gif',
'image/png',
'image/x-png',
);
public function get_supported_mime_types() {
return $this->supported_mime_types;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
<?php
namespace Smush\Core;
class Upload_Dir {
private $wp_upload_dir;
private $root_path;
private $upload_path;
private $upload_rel_path;
private $upload_url;
/**
* @return array
*/
private function get_wp_upload_dir() {
if ( is_null( $this->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() {
if ( ! is_multisite() || is_main_site() ) {
$upload = wp_upload_dir();
} else {
// Use the main site's upload directory for all subsite's webp converted images.
// This makes it easier to have a single rule on the server configs for serving webp in mu.
$blog_id = get_main_site_id();
switch_to_blog( $blog_id );
$upload = wp_upload_dir();
restore_current_blog();
}
return $upload;
}
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 );
}
}

View File

@@ -0,0 +1,166 @@
# WDEV Free Notices Module #
WPMU DEV Free Notices module (short wpmu-free-notices) is used in our free plugins hosted on wordpress.org
It will display,
* A welcome message upon plugin activation that offers the user a 5-day introduction email course for the plugin.
* After 7 days a message asking the user to rate the plugin on wordpress.org.
* After 2 days a giveaway notice asking for email subscription.
# How to use it #
1. Insert this repository as **sub-module** into the existing project
2. Include the file `module.php` in your main plugin file.
3. Call the action `wpmudev_register_notices` with the params mentioned below.
4. Done!
# Upgrading to v2.0
The 2.0 release is backward incompatible with the 1.x versions. To accommodate new functionality and fix WordPress coding standards violations, a lot of the hooks/filters have been refactored.
Make sure to change the following:
1. Update the `do_action` hook name from `wdev-register-plugin` to `wpmudev_register_notices`.
2. Update the params to new format (See example below).
3. Both `wdev-email-message-` and `wdev-rating-message-` filters have been changed to `wdev_email_title_`/`wdev_email_message_` and `wdev_rating_title_`/`wdev_rating_message_`
## IMPORTANT:
DO NOT include this submodule in Pro plugins. These notices are only for wp.org versions.
## Code Example : Registering a plugin (from Smush) ##
```
#!php
<?php
add_action( 'admin_init', 'mycustom_free_notices_init' );
function mycustom_free_notices_init() {
// Load the notices module.
include_once 'external/free-notices/module.php';
// Register the current plugin for notices.
do_action(
'wpmudev_register_notices',
'smush', // Required: plugin id. Get from the below list.
array(
'basename' => WP_SMUSH_BASENAME, // Required: Plugin basename (for backward compat).
'title' => 'Smush', // Plugin title.
'wp_slug' => 'wp-smushit', // Plugin slug on wp.org
'cta_email' => __( 'Get Fast!', 'ga_trans' ), // Email button CTA.
'installed_on' => time(), // Plugin installed time (timestamp). Default to current time.
'screens' => array( // Screen IDs of plugin pages.
'toplevel_page_smush',
'smush_page_smush-bulk',
'smush_page_smush-directory',
),
)
);
}
```
> IMPORTANT: Make sure to initialize this on a hook which is executed in admin-ajax requests too. The recommended hook is `admin_init`
## Plugins and IDs
Only wp.org plugins are listed below.
| Plugin | ID |
|-------------|-------------|
| Smush | smush |
| Hummingbird | hummingbird |
| Defender | defender |
| SmartCrawl | smartcrawl |
| Forminator | forminator |
| Hustle | hustle |
| Snapshot | snapshot |
| Branda | branda |
| Beehive | beehive |
## Testing Notices
To see the notices before the due time, you can fake the current time by appending `&wpmudev_notice_time=CUSTOMTIMESTAMP` to the url on a page where the notice should be visible. Please make sure you are using a timestamp after the due time.
## Optional: Customize the notices via filters ##
```
<?php
// The email message contains 1 variable: plugin-name
add_filter(
'wdev_email_message_smush', // change plugin id.
'custom_email_message'
);
function custom_email_message( $message ) {
$message = 'You installed %s! This is a custom <u>email message</u>';
return $message;
}
```
```
<?php
// The rating message contains 2 variables: user-name, plugin-name
add_filter(
'wdev_rating_message_smush', // Change plugin id.
'custom_rating_message'
);
function custom_rating_message( $message ) {
$message = 'Hi %s, you used %s for a while now! This is a custom <u>rating message</u>';
return $message;
}
```
```
<?php
// To disable or enable a notice type.
add_filter(
'wpmudev_notices_is_disabled',
'custom_rating_message',
10,
2
);
function disable_rating_message( $disabled, $type, $plugin ) {
if ( 'rate' === $type && 'beehive' === $plugin ) {
return true;
}
return $disabled;
}
```
# Development
Do not commit anything directly to `master` branch. The `master` branch should always be production ready. All plugins will be using it as a submodule.
## Build Tasks (npm)
Everything should be handled by npm. Note that you don't need to interact with Gulp in a direct way.
| Command | Action |
|----------------------|--------------------------------------------------------|
| `npm run watch` | Compiles and watch for changes. |
| `npm run compile` | Compile production ready assets. |
| `npm run build` | Build production ready submodule inside `/build/` folder |
## Git Workflow
- Create a new branch from `dev` branch: `git checkout -b branch-name`. Try to give it a descriptive name. For example:
- `release/X.X.X` for next releases
- `new/some-feature` for new features
- `enhance/some-enhancement` for enhancements
- `fix/some-bug` for bug fixing
- Make your commits and push the new branch: `git push -u origin branch-name`
- File the new Pull Request against `dev` branch
- Assign somebody to review your code.
- Once the PR is approved and finished, merge it in `dev` branch.
- Checkout `dev` branch.
- Run `npm run build` and copy all files and folders from the `build` folder.
- Checkout `master` branch and replace all files and folders with copied content from the build folder.
- Commit and push the `master` branch changes.
- Inform all devs to update the submodule.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
body[class*=sui-2-] .sui-wrap .giveaway-banner{padding:20px}@media(min-width: 783px){body[class*=sui-2-] .sui-wrap .giveaway-banner{padding:15px;display:flex;flex-flow:row nowrap}}body[class*=sui-2-] .sui-wrap .giveaway-banner__image{max-width:112px;margin:0 auto 20px}@media(min-width: 783px){body[class*=sui-2-] .sui-wrap .giveaway-banner__image{flex:0 0 auto;margin:15px}}body[class*=sui-2-] .sui-wrap .giveaway-banner__image img{max-width:100%;max-height:100%;display:block;margin:0}@media(max-width: 782px){body[class*=sui-2-] .sui-wrap .giveaway-banner__content{text-align:center}}@media(min-width: 783px){body[class*=sui-2-] .sui-wrap .giveaway-banner__content{margin:15px}}body[class*=sui-2-] .sui-wrap .giveaway-banner__content p{margin-bottom:5px !important;font-size:13px;line-height:22px}body[class*=sui-2-] .sui-wrap .giveaway-banner__content p:last-child{margin:0 !important}body[class*=sui-2-] .sui-wrap .giveaway-banner__content .sui-form-control{height:30px;padding:7px 12px;font-size:12px;line-height:16px}body[class*=sui-2-] .sui-wrap .giveaway-banner p.giveaway-banner__title{margin-bottom:10px !important;padding:0;color:#333;font-size:15px;line-height:30px;font-weight:700}body[class*=sui-2-] .sui-wrap .giveaway-banner p.giveaway-banner__title:last-child{margin:0 !important}body[class*=sui-2-] .sui-wrap .giveaway-banner__dismiss,body[class*=sui-2-] .sui-wrap .giveaway-banner button.giveaway-banner__dismiss{position:absolute;z-index:1;top:16px;right:16px}body[class*=sui-2-] .sui-wrap .giveaway-banner__dismiss:not(:last-child),body[class*=sui-2-] .sui-wrap .giveaway-banner button.giveaway-banner__dismiss:not(:last-child){margin-right:0}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,777 @@
<?php
/**
* Plugin notices handler.
*
* This class will take care of registering, queuing and showing different
* notices across WP pages.
*
* @since 2.0
* @author Incsub (Joel James)
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* @copyright Copyright (c) 2022, Incsub
* @package WPMUDEV\Notices
* @subpackage Handler
*/
namespace WPMUDEV\Notices;
// If this file is called directly, abort.
defined( 'WPINC' ) || die;
if ( ! class_exists( __NAMESPACE__ . '\\Handler' ) ) {
/**
* Class Module
*
* @since 2.0
* @package WPMUDEV\Notices
*/
final class Handler {
/**
* Current version.
*
* @since 2.0
* @var string $version
*/
public $version = '2.0.5';
/**
* Option name to store data.
*
* @since 2.0
* @var string $option_name
*/
protected $option_name = 'wpmudev_notices';
/**
* Registered plugins for the opt-ins.
*
* @since 2.0
* @var array[] $plugins
*/
private $plugins = array();
/**
* WordPress screen IDs to show notices on.
*
* @since 2.0
* @var array[] $screens
*/
private $screens = array();
/**
* Disabled notice types.
*
* @since 2.0
* @var string[] $disabled
*/
private $disabled = array( 'email', 'giveaway' );
/**
* Registered plugin notices data from db.
*
* @since 2.0
* @var array|null $queue
*/
private $stored = null;
/**
* Notice types that are shown on WP Dashboard.
*
* @since 2.0
* @var array $wp_notices
*/
private $wp_notices = array(
'email' => '\WPMUDEV\Notices\Notices\Email',
'rate' => '\WPMUDEV\Notices\Notices\Rating',
);
/**
* Notice type that are shown within plugin pages.
*
* @since 2.0
* @var array $plugin_notices
*/
private $plugin_notices = array(
'giveaway' => '\WPMUDEV\Notices\Notices\Giveaway',
);
/**
* Construct handler class.
*
* @since 2.0
*/
protected function __construct() {
// Register plugins.
add_action( 'wpmudev_register_notices', array( $this, 'register' ), 10, 2 );
// Always setup ajax actions.
add_action( 'wp_ajax_wpmudev_notices_action', array( $this, 'process_action' ) );
// Render admin notices.
add_action( 'load-index.php', array( $this, 'admin_notice' ) );
}
/**
* Initializes and returns the singleton instance.
*
* @since 2.0
*
* @return static
*/
public static function instance() {
static $instance = null;
if ( null === $instance ) {
$instance = new self();
}
return $instance;
}
/**
* Register an active plugin for notices.
*
* ```php
* do_action(
* 'wpmudev_register_notices',
* 'beehive', // Plugin id.
* array(
* 'basename' => plugin_basename( BEEHIVE_PLUGIN_FILE ), // Required: Plugin basename (for backward compat).
* 'title' => 'Beehive', // Required: Plugin title.
* 'wp_slug' => 'beehive-analytics', // Required: wp.org slug of the plugin.
* 'cta_email' => __( 'Get Fast!', 'ga_trans' ), // Email button CTA.
* 'installed_on' => time(), // Optional: Plugin activated time.
* 'screens' => array( // Required: Plugin screen ids.
* 'toplevel_page_beehive',
* 'dashboard_page_beehive-accounts',
* 'dashboard_page_beehive-settings',
* 'dashboard_page_beehive-tutorials',
* 'dashboard_page_beehive-google-analytics',
* 'dashboard_page_beehive-google-tag-manager',
* ),
* )
* );
* ```
*
* @since 2.0
*
* @param array $options Options.
*
* @param string $plugin_id Plugin ID.
*/
public function register( $plugin_id, array $options = array() ) {
// Plugin ID can't be empty.
if ( empty( $plugin_id ) ) {
return;
}
// Add to the plugins list.
$this->plugins[ $plugin_id ] = $options;
// Setup screens.
if ( ! empty( $options['screens'] ) ) {
$this->add_to_screens( $plugin_id, $options['screens'] );
}
}
/**
* Show an admin notice on WP page (not plugin's SUI pages).
*
* @since 2.0
*
* @return void
*/
public function admin_notice() {
if ( is_super_admin() ) {
add_action( 'all_admin_notices', array( $this, 'render_admin_notice' ) );
}
}
/**
* Show a notice on current plugin page.
*
* @since 2.0
*
* @return void
*/
public function plugin_notice() {
if ( is_super_admin() ) {
add_action( 'all_admin_notices', array( $this, 'render_plugin_notice' ) );
}
}
/**
* Render admin notice content.
*
* @since 2.0
*
* @return void
*/
public function render_admin_notice() {
$this->render( false, array_keys( $this->wp_notices ) );
}
/**
* Render a plugin notice content.
*
* @since 2.0
*
* @return void
*/
public function render_plugin_notice() {
// Get current screen.
$screen = get_current_screen();
// Continue only if registered screen.
if ( empty( $screen->id ) || empty( $this->screens[ $screen->id ] ) ) {
return;
}
$this->render(
$this->screens[ $screen->id ],
array_keys( $this->plugin_notices )
);
}
/**
* Process a notice action.
*
* All ajax requests from the notice are processed here.
* After nonce verification the action will be processed if a matching
* method is already defined.
*
* @since 2.0
*/
public function process_action() {
// Check required fields.
if ( ! isset( $_POST['plugin_id'], $_POST['notice_action'], $_POST['notice_type'], $_POST['nonce'] ) ) {
wp_die( esc_html__( 'Required fields are missing.', 'wdev_frash' ) );
}
// Only admins can do this.
if ( ! is_super_admin() ) {
wp_die( esc_html__( 'Access check failed.', 'wdev_frash' ) );
}
// Get request data.
$plugin = sanitize_text_field( wp_unslash( $_POST['plugin_id'] ) );
$action = sanitize_text_field( wp_unslash( $_POST['notice_action'] ) );
$type = sanitize_text_field( wp_unslash( $_POST['notice_type'] ) );
$nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ) );
// Verify nonce.
if ( ! wp_verify_nonce( $nonce, 'wpmudev_notices_action' ) ) {
wp_die( esc_html__( 'Nonce verification failed.', 'wdev_frash' ) );
}
// Initialize the options.
$this->init_option();
// Get the notice class.
$notice = $this->get_notice( $plugin, $type );
// Process action if defined on this class.
if ( method_exists( $this, $action ) ) {
call_user_func( array( $this, $action ), $plugin, $type );
} elseif ( is_object( $notice ) && method_exists( $notice, $action ) ) {
// Process action if defined on the notice class.
call_user_func( array( $notice, $action ), $plugin );
}
/**
* Action hook to do something after a notice action is performed.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
* @param string $type Notice type.
*
* @param string $action Action.
*/
do_action( 'wpmudev_notices_after_notice_action', $action, $plugin, $type );
wp_die();
}
/**
* Remove a notice from the queue.
*
* @since 2.0
*
* @param string $type Notice type.
*
* @param string $plugin Plugin ID.
*
* @return void
*/
public function dismiss_notice( $plugin, $type ) {
// Remove from the queue.
if ( isset( $this->stored['queue'][ $plugin ][ $type ] ) ) {
unset( $this->stored['queue'][ $plugin ][ $type ] );
}
// Setup done list.
if ( ! isset( $this->stored['done'][ $plugin ] ) ) {
$this->stored['done'][ $plugin ] = array();
}
// Mark as done.
$this->stored['done'][ $plugin ][ $type ] = time();
// Update the queue.
$this->update_option();
}
/**
* Getter for the queue data.
*
* @since 2.0
*
* @return array
*/
public function get_option() {
$this->init_option();
return $this->stored;
}
/**
* Update the notices stored data in db.
*
* @since 2.0
*
* @param array $data Option data (optional).
*
* @return bool
*/
public function update_option( $data = false ) {
// If new data is provided use it.
if ( ! empty( $data ) ) {
$this->stored = $data;
}
// Update the data.
return update_site_option( $this->option_name, $this->stored );
}
/**
* Render notice for the current screen.
*
* @since 2.0
*
* @param array $types Notice types to render.
*
* @param string|false $plugin_id Plugin id (false to check all plugins).
*
* @return void|string
*/
protected function render( $plugin_id = false, $types = array() ) {
// Setup queue when required.
$this->setup_queue();
if ( empty( $plugin_id ) ) {
// Get a random notice.
$notice = $this->get_random_notice( $types, $plugin_id );
} else {
// Get a plugin's notice.
$notice = $this->get_plugin_notice( $plugin_id, $types );
}
// Render if notice found.
if ( ! empty( $notice ) && method_exists( $notice, 'render' ) ) {
return call_user_func( array( $notice, 'render' ), $plugin_id );
}
}
/**
* Set screen IDs for the notices.
*
* NOTE: Only one plugin can use one screen id.
*
* @since 2.0
*
* @param array $screens Screen IDs.
*
* @param string $plugin_id Plugin ID.
*
* @return void
*/
protected function add_to_screens( $plugin_id, array $screens ) {
// Set the screens.
if ( ! empty( $screens ) ) {
foreach ( $screens as $screen_id ) {
$this->screens[ $screen_id ] = $plugin_id;
// Remove network suffix for page hook.
if ( is_multisite() ) {
$screen_id = str_replace( '-network', '', $screen_id );
}
// Register screen notice.
add_action( "load-$screen_id", array( $this, 'plugin_notice' ) );
}
}
}
/**
* Setup the notices queue when ready.
*
* To avoid calling db queries we need to do this only before
* a notice is being rendered.
*
* @since 2.0
*
* @return void
*/
protected function setup_queue() {
// Initialize data.
$this->init_option();
// Setup all registered plugins to in queue.
foreach ( $this->plugins as $plugin_id => $options ) {
$this->add_to_queue( $plugin_id, $options );
}
}
/**
* Set the queue for the plugin if required.
*
* We should always schedule all notice types even if they
* are disabled. Then only we can enable it later easily.
* Disabled notices won't be considered when taken from the queue.
*
* @since 2.0
*
* @param array $options Options.
*
* @param string $plugin_id Plugin ID.
*
* @return void
*/
protected function add_to_queue( $plugin_id, array $options ) {
// Store to notice queue if not saved already.
if ( ! isset( $this->stored['plugins'][ $plugin_id ] ) ) {
// Register plugin.
$this->stored['plugins'][ $plugin_id ] = time();
$this->stored['queue'][ $plugin_id ] = array();
// Add notices to queue.
foreach ( $this->get_types() as $type => $class_name ) {
// Notice class.
$notice = $this->get_notice( $plugin_id, $type );
// Schedule notice.
if ( ! empty( $notice ) ) {
$this->stored['queue'][ $plugin_id ][ $type ] = $notice->get_next_schedule( $options['installed_on'] );
}
}
// Upgrade if required.
if ( ! empty( $options['basename'] ) ) {
$this->maybe_upgrade( $plugin_id, $options['basename'] );
}
// Update the stored data.
$this->update_option();
}
}
/**
* Init the notices stored data.
*
* Get from the db only if not already initialized.
*
* @since 2.0
*/
protected function init_option() {
if ( null === $this->stored ) {
$queue = (array) get_site_option( $this->option_name, array() );
$this->stored = wp_parse_args(
$queue,
array(
'plugins' => array(),
'queue' => array(),
'done' => array(),
)
);
}
}
/**
* Get a notice object from the entire due list.
*
* This is usually used for a common WP page where all plugins'
* notices are shown. Eg: WP Dashboard page.
*
* @since 2.0
*
* @param string|bool $plugin_id Plugin ID.
*
* @param array $types Notice types.
*
* @return object|false
*/
protected function get_random_notice( array $types = array(), &$plugin_id = false ) {
if ( ! empty( $this->stored['queue'] ) ) {
// Check all due items.
foreach ( $this->stored['queue'] as $plugin => $notices ) {
if ( ! empty( $notices ) ) {
// Chose one with priority.
$notice = $this->choose_notice( $plugin, $notices, $types );
// Return only if a valid notice is selected.
if ( ! empty( $notice ) ) {
// Set the plugin id.
$plugin_id = $plugin;
return $notice;
}
}
}
}
return false;
}
/**
* Get a notice object for the plugin.
*
* Select one with priority from the due list for the plugin.
*
* @since 2.0
*
* @param array $types Notice types.
*
* @param string $plugin_id Plugin ID.
*
* @return object|false
*/
protected function get_plugin_notice( $plugin_id, array $types = array() ) {
// Choose one notice from the due list.
if ( ! empty( $this->stored['queue'][ $plugin_id ] ) ) {
return $this->choose_notice(
$plugin_id,
$this->stored['queue'][ $plugin_id ],
$types
);
}
return false;
}
/**
* Choose a notice from the due list.
*
* Notice will be selected based on the order it's defined
* in the $types property of this class.
*
* @since 2.0
*
* @param array $notices Notices array.
* @param array $types Notice types.
*
* @param string $plugin_id Plugin ID.
*
* @return object|false
*/
protected function choose_notice( $plugin_id, array $notices, array $types = array() ) {
foreach ( $this->get_types() as $type => $class ) {
// Not in the list.
if ( ! isset( $notices[ $type ] ) ) {
continue;
}
// Not a desired type, skip.
if ( ! empty( $types ) && ! in_array( $type, $types, true ) ) {
continue;
}
// Disabled type, skip.
if ( $this->is_disabled( $type, $plugin_id ) ) {
continue;
}
// Due time reached or passed.
if ( $this->get_current_time() >= (int) $notices[ $type ] ) {
// Get the notice class instance.
$notice = $this->get_notice( $plugin_id, $type );
// Return the notice object.
if ( ! empty( $notice ) && $notice->can_show( $plugin_id ) ) {
return $notice;
}
}
}
return false;
}
/**
* Get the notice type class instance.
*
* @since 2.0
*
* @param string $type Notice type.
*
* @param string $plugin_id Plugin ID.
*
* @return bool|object
*/
protected function get_notice( $plugin_id, $type ) {
$types = $this->get_types();
// If a valid class found for the type.
if (
isset( $types[ $type ] )
&& isset( $this->plugins[ $plugin_id ] )
&& class_exists( $types[ $type ] )
) {
/**
* Notice class name.
*
* @var Notices\Notice $class_name
*/
$class_name = $types[ $type ];
return $class_name::instance( $this->plugins[ $plugin_id ] );
}
return false;
}
/**
* Get available notice types and classes.
*
* @since 2.0
*
* @return array
*/
protected function get_types() {
return array_merge(
$this->wp_notices,
$this->plugin_notices
);
}
/**
* Get current time to use for due date check.
*
* This is used to enable custom time fot testing.
*
* @since 2.0
*
* @return int
*/
protected function get_current_time() {
// Get custom time.
$time = filter_input( INPUT_GET, 'wpmudev_notice_time', FILTER_SANITIZE_SPECIAL_CHARS );
return empty( $time ) ? time() : (int) $time;
}
/**
* Check if a notice type is disabled.
*
* @since 2.0.3
*
* @param string $type Notice type.
* @param string $plugin Plugin ID.
*
* @return bool
*/
protected function is_disabled( $type, $plugin ) {
/**
* Filter to modify disabled notices list.
*
* @param array $disabled Disabled list.
* @param string $plugin Plugin ID.
*/
$disabled = apply_filters( 'wpmudev_notices_disabled_notices', $this->disabled, $plugin );
// Check if notice type is disabled.
$is_disabled = in_array( $type, $disabled, true );
/**
* Filter to enable/disable a notice type.
*
* @param bool $is_disabled Is disabled.
* @param string $type Notice type.
* @param string $plugin Plugin ID.
*/
return apply_filters( 'wpmudev_notices_is_disabled', $is_disabled, $type, $plugin );
}
/**
* Optional upgrade from old version (WDEV Frash).
*
* If old data exist, we need to use it before registering new time.
* Used only when registering for the first time.
*
* @since 2.0
* @deprecated 2.0 We may remove this in future.
*
* @param string $plugin_id Plugin ID.
* @param string $basename Plugin basename (used in old plugins).
*
* @return void
*/
protected function maybe_upgrade( $plugin_id, $basename ) {
// Old settings data.
$deprecated = get_site_option( 'wdev-frash' );
// Old notice exists, upgrade it.
if ( ! empty( $deprecated ) ) {
$deprecated = wp_parse_args(
(array) $deprecated,
array(
'plugins' => array(),
'queue' => array(),
'done' => array(),
)
);
// Not found in old settings.
if ( ! isset( $deprecated['plugins'][ $basename ] ) ) {
return;
}
// Use old registered time.
$this->stored['plugins'][ $plugin_id ] = $deprecated['plugins'][ $basename ];
// Existing plugin, so show giveaway right away.
$this->stored['queue'][ $plugin_id ]['giveaway'] = time();
// Only email and rate types.
foreach ( array( 'email', 'rate' ) as $type ) {
// Old key hash.
$hash = md5( $basename . '-' . $type );
if ( isset( $deprecated['queue'][ $hash ]['show_at'] ) ) {
// Use the existing time.
$this->stored['queue'][ $plugin_id ][ $type ] = $deprecated['queue'][ $hash ]['show_at'];
// Remove from old settings.
unset( $deprecated['queue'][ $hash ] );
} else {
// Check if notice type found in dismissed list.
$dismissed = array_filter(
$deprecated['done'],
function ( $item ) use ( $basename, $type ) {
return $item['plugin'] === $basename && $item['type'] === $type;
}
);
// Already shown and dismissed, remove it.
if ( ! empty( $dismissed[0]['handled_at'] ) ) {
// Remove from queue.
unset( $this->stored['queue'][ $plugin_id ][ $type ] );
// Move to done list.
$this->stored['done'][ $plugin_id ][ $type ] = $dismissed[0]['handled_at'];
}
}
}
// Do not delete it yet for backward compatibility.
update_site_option( 'wdev-frash', $deprecated );
}
}
}
}

View File

@@ -0,0 +1,218 @@
<?php
/**
* Email notice class.
*
* @since 2.0
* @author Incsub (Joel James)
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* @copyright Copyright (c) 2022, Incsub
* @package WPMUDEV\Notices
*/
namespace WPMUDEV\Notices\Notices;
// If this file is called directly, abort.
defined( 'WPINC' ) || die;
use WPMUDEV\Notices\Handler;
if ( ! class_exists( __NAMESPACE__ . '\\Email' ) ) {
/**
* Class Email
*
* @since 2.0
* @package WPMUDEV\Notices
*/
class Email extends Notice {
/**
* Current notice type.
*
* @since 2.0
* @var string $type
*/
protected $type = 'email';
/**
* User id /API Key for Mailchimp subscriber list
*
* @since 1.2
* @var string $mc_user_id
*/
private $mc_user_id = '53a1e972a043d1264ed082a5b';
/**
* Render a notice type content.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return void
*/
public function render( $plugin ) {
$this->enqueue_assets( $plugin );
$admin_email = get_site_option( 'admin_email' );
/* translators: %s - plugin name */
$title = __( "We're happy that you've chosen to install %s!", 'wdev_frash' );
$title = apply_filters( 'wdev_email_title_' . $plugin, $title );
/* translators: %s - plugin name */
$message = __( 'Are you interested in how to make the most of this plugin? How would you like a quick 5 day email crash course with actionable advice on building your membership site? Only the info you want, no subscription!', 'wdev_frash' );
$message = apply_filters( 'wdev_email_message_' . $plugin, $message );
// Plugin title.
$plugin_title = $this->get_option( 'title', __( 'Plugin', 'wdev_frash' ) );
?>
<div class="notice notice-info frash-notice frash-notice-<?php echo esc_attr( $this->type ); ?> hidden">
<?php $this->render_hidden_fields( $plugin ); ?>
<div class="frash-notice-logo <?php echo esc_attr( $plugin ); ?>"><span></span></div>
<div class="frash-notice-message">
<p class="notice-title"><?php printf( esc_html( $title ), esc_html( $plugin_title ) ); ?></p>
<p><?php printf( esc_html( $message ), esc_html( $plugin_title ) ); ?></p>
<div class="frash-notice-cta">
<?php
/**
* Fires before subscribe form renders.
*
* @since 1.3
* @since 2.0.4 Mailchimp ID deprecated.
*
* @param int $mc_list_id Mailchimp list ID (deprecated).
*/
do_action( 'frash_before_subscribe_form_render', '' );
?>
<form action="<?php echo esc_url( $this->api_url( 'mailjet/v1/plugin' ) ); ?>" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank">
<label for="wpmudev-email" class="hidden"><?php esc_html_e( 'Email', 'wdev_frash' ); ?></label>
<input type="email" name="email" class="email" id="wpmudev-email" value="<?php echo esc_attr( $admin_email ); ?>" required="required"/>
<input type="hidden" name="source" id="wpmudev-source" value="<?php echo esc_attr( $plugin ); ?>"/>
<button class="frash-notice-act button-primary" data-msg="<?php esc_attr_e( 'Thanks :)', 'wdev_frash' ); ?>" type="submit">
<?php echo esc_html( $this->get_option( 'cta_email', __( 'Get Fast!', 'wdev_frash' ) ) ); ?>
</button>
<span class="frash-notice-cta-divider">|</span>
<a href="#" class="frash-notice-dismiss" data-msg="<?php esc_attr_e( 'Saving', 'wdev_frash' ); ?>">
<?php esc_html_e( 'No thanks', 'wdev_frash' ); ?>
</a>
<?php
/**
* Fires after subscribe form fields are rendered.
* Use this hook to add additional fields for on the sub form.
*
* Make sure that the additional field has is also present on the
* actual MC subscribe form.
*
* @since 1.3
* @since 2.0.4 Mailchimp ID deprecated.
*
* @param int $mc_list_id Mailchimp list ID (deprecated).
*/
do_action( 'frash_subscribe_form_fields', '' );
?>
</form>
<?php
/**
* Fires after subscribe form is rendered
*
* @since 1.3
* @since 2.0.4 Mailchimp ID deprecated.
*
* @param int $mc_list_id Mailchimp list ID (deprecated).
*/
do_action( 'frash_before_subscribe_form_render', '' );
?>
</div>
</div>
</div>
<?php
}
/**
* Render a notice type content.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return void
*/
protected function render_hidden_fields( $plugin ) {
$wp_url = $this->get_option( 'wp_slug' );
if ( false === strpos( $wp_url, '://' ) ) {
$wp_url = 'https://wordpress.org/plugins/' . trim( $wp_url, '/' );
}
?>
<input type="hidden" name="type" value="<?php echo esc_attr( $this->type ); ?>"/>
<input type="hidden" name="plugin_id" value="<?php echo esc_attr( $plugin ); ?>"/>
<input type="hidden" name="url_wp" value="<?php echo esc_attr( $wp_url ); ?>"/>
<?php wp_nonce_field( 'wpmudev_notices_action', 'notice_nonce' ); ?>
<?php
}
/**
* Enqueue assets for a notice if required.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return void
*/
protected function enqueue_assets( $plugin ) {
$handle = 'wpmudev-notices-dashboard';
wp_enqueue_style(
$handle,
$this->assets_url( 'css/dashboard-notices.min.css' ),
array(),
Handler::instance()->version
);
wp_enqueue_script(
$handle,
$this->assets_url( 'js/dashboard-notices.min.js' ),
array(),
Handler::instance()->version,
true
);
}
/**
* Check if current notice is allowed for the plugin.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return bool
*/
public function can_show( $plugin ) {
// Show only on dashboard.
return 'dashboard' === $this->screen_id();
}
/**
* Parse options for the notice.
*
* @since 2.0
*
* @param array $options Plugin options.
*
* @return array
*/
protected function parse_options( array $options ) {
return wp_parse_args(
$options,
array(
'title' => __( 'Plugin', 'wdev_frash' ),
'wp_slug' => '',
'cta_email' => __( 'Get Fast!', 'wdev_frash' ),
)
);
}
}
}

View File

@@ -0,0 +1,273 @@
<?php
/**
* Giveaway notice class.
*
* @since 2.0
* @author Incsub (Joel James)
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* @copyright Copyright (c) 2022, Incsub
* @package WPMUDEV\Notices\Notices
*/
namespace WPMUDEV\Notices\Notices;
// If this file is called directly, abort.
defined( 'WPINC' ) || die;
use WPMUDEV\Notices\Handler;
if ( ! class_exists( __NAMESPACE__ . '\\Giveaway' ) ) {
/**
* Class Giveaway
*
* @since 2.0
* @package WPMUDEV\Notices
*/
class Giveaway extends Notice {
/**
* Current notice type.
*
* @since 2.0
* @var string $type
*/
protected $type = 'giveaway';
/**
* Show after 2 days
*
* @since 2.0
* @var string $type
*/
protected $time = DAY_IN_SECONDS * 2; // After 2 days.
/**
* Allowed plugin IDs for the notice.
*
* @since 2.0
* @var string[] $allowed_plugins
*/
protected $allowed_plugins = array(
'defender',
'smartcrawl',
'forminator',
'hustle',
'branda',
'beehive',
);
/**
* Initializes and returns the notice instance.
*
* @since 2.0
*
* @param array $options Plugin options.
*/
protected function __construct( array $options ) {
parent::__construct( $options );
// Dismiss all plugin notices once giveaway is dismissed.
add_action( 'wpmudev_notices_after_notice_action', array( $this, 'maybe_dismiss_all' ), 10, 3 );
}
/**
* Render a notice type content.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return void
*/
public function render( $plugin ) {
$this->enqueue_assets( $plugin );
echo '<div id="wpmudev-plugin-notices" class="wpmudev-plugin-notices sui-wrap"></div>';
}
/**
* Enqueue assets for a notice if required.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return void
*/
protected function enqueue_assets( $plugin ) {
$script_handle = 'wpmudev-notices-giveaway';
wp_enqueue_style(
$script_handle,
$this->assets_url( 'css/giveaway-banner.min.css' ),
array(),
Handler::instance()->version
);
wp_enqueue_script(
$script_handle,
$this->assets_url( 'js/giveaway-banner.min.js' ),
array( 'wp-element', 'wp-i18n' ),
Handler::instance()->version,
true
);
// Script vars.
wp_localize_script(
$script_handle,
'wpmudevNoticeGiveaway',
array(
'pluginId' => $plugin,
'apiUrl' => $this->api_url( 'giveaway/v1/plugin' ),
'images' => array(
'form' => $this->assets_url( 'images/giveaway/form/' . $plugin . '.png' ),
'form2x' => $this->assets_url( 'images/giveaway/form/' . $plugin . '@2x.png' ),
'success' => $this->assets_url( 'images/giveaway/success/common.png' ),
'success2x' => $this->assets_url( 'images/giveaway/success/common@2x.png' ),
),
'nonce' => wp_create_nonce( 'wpmudev_notices_action' ),
)
);
}
/**
* Check if current notice is allowed for the plugin.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return bool
*/
public function can_show( $plugin ) {
// Should be a valid plugin and not on dashboard.
$allowed_plugins = in_array( $plugin, $this->allowed_plugins, true );
// Check if WPMUDEV Dashboard plugin is active.
$dash_installed = class_exists( '\WPMUDEV_Dashboard' );
return $allowed_plugins && ! $dash_installed && ! $this->is_dismissed();
}
/**
* Check if any of the plugins has already dismissed the notice.
*
* @since 2.0.1
*
* @return bool
*/
private function is_dismissed() {
$option = Handler::instance()->get_option();
if ( ! empty( $option['done'] ) ) {
foreach ( $option['done'] as $notices ) {
// Remove from the queue.
if ( isset( $notices[ $this->type ] ) ) {
// Make sure to dismiss all.
$this->dismiss_all();
return true;
}
}
}
return false;
}
/**
* Parse options for the notice.
*
* @since 2.0
*
* @param array $options Plugin options.
*
* @return array
*/
protected function parse_options( array $options ) {
return wp_parse_args(
$options,
array(
'installed_on' => time() + $this->time,
)
);
}
/**
* Remove a notice from the queue.
*
* If a giveaway notice is dismissed permanently, we need to hide
* all plugins' giveaway notices.
*
* @since 2.0.1
*
* @param string $plugin Plugin ID.
* @param string $type Notice type.
*
* @param string $action Action.
*
* @return void
*/
public function maybe_dismiss_all( $action, $plugin, $type ) {
// Not a giveaway notice.
if ( $this->type === $type && 'dismiss_notice' === $action ) {
$this->dismiss_all();
}
}
/**
* Mark all plugins' giveaway notices as done.
*
* @since 2.0.1
*
* @return void
*/
private function dismiss_all() {
$option = Handler::instance()->get_option();
if ( ! empty( $option['queue'] ) ) {
foreach ( $option['queue'] as $plugin_id => $notices ) {
// Remove from the queue.
if ( isset( $notices[ $this->type ] ) ) {
unset( $option['queue'][ $plugin_id ][ $this->type ] );
// Add to done list.
if ( ! isset( $option['done'][ $plugin_id ] ) ) {
$option['done'][ $plugin_id ] = array();
}
$option['done'][ $plugin_id ][ $this->type ] = time();
}
}
// Update the queue.
Handler::instance()->update_option( $option );
}
}
/**
* Extend a notice to future time.
*
* If notice not found in queue, it will be added.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return void
*/
public function extend_notice( $plugin ) {
$option = Handler::instance()->get_option();
if (
isset( $option['plugins'][ $plugin ] ) // Only if already registered.
&& ! isset( $option['done'][ $plugin ][ $this->type ] ) // Should not be in done list.
) {
// Extend to future.
$option['queue'][ $plugin ][ $this->type ] = $this->get_next_schedule(
false,
DAY_IN_SECONDS * 30 // Extend 30 days.
);
// Update queue.
Handler::instance()->update_option( $option );
}
}
}
}

View File

@@ -0,0 +1,235 @@
<?php
/**
* Plugin notice base class.
*
* @since 2.0
* @author Incsub (Joel James)
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* @copyright Copyright (c) 2022, Incsub
* @package WPMUDEV\Notices\Notices
*/
namespace WPMUDEV\Notices\Notices;
// If this file is called directly, abort.
defined( 'WPINC' ) || die;
use WPMUDEV\Notices\Handler;
if ( ! class_exists( __NAMESPACE__ . '\\Notice' ) ) {
/**
* Class Notice
*
* @since 2.0
* @package WPMUDEV\Notices
*/
abstract class Notice {
/**
* Current notice type (Should override in sub class).
*
* @var string $type
*
* @since 2.0
*/
protected $type;
/**
* Time to start showing notice.
*
* This will be added to the current time.
*
* @var string $type
*
* @since 2.0
*/
protected $time = 0; // Right now.
/**
* Current plugin's notice options.
*
* @var string $plugin_id
*
* @since 2.0
*/
protected $options = array();
/**
* Initializes and returns the notice instance.
*
* @param array $options Plugin options.
*
* @since 2.0
*/
protected function __construct( array $options ) {
// Set options.
$this->options = $this->parse_options( $options );
}
/**
* Initializes and returns the singleton instance.
*
* @param array $options Plugin options.
*
* @since 2.0
*
* @return static
*/
public static function instance( array $options = array() ) {
static $instance = null;
if ( null === $instance ) {
$called_class = get_called_class();
$instance = new $called_class( $options );
}
return $instance;
}
/**
* Render a notice type content.
*
* @param string $plugin Plugin ID.
*
* @since 2.0
*
* @return void
*/
abstract public function render( $plugin );
/**
* Check if current notice is allowed for the plugin.
*
* @param string $plugin Plugin ID.
*
* @since 2.0
*
* @return bool
*/
public function can_show( $plugin ) {
return true;
}
/**
* Enqueue assets for a notice if required.
*
* @param string $plugin Plugin ID.
*
* @since 2.0
*
* @return void
*/
protected function enqueue_assets( $plugin ) {
// Override to enqueue.
}
/**
* Parse options for the notice.
*
* @param array $options Plugin options.
*
* @since 2.0
*
* @return array
*/
protected function parse_options( array $options ) {
return $options;
}
/**
* Get a notice option value.
*
* @param string $key Option name.
* @param mixed $default Default value.
*
* @since 2.0
*
* @return array
*/
protected function get_option( $key, $default = '' ) {
if ( isset( $this->options[ $key ] ) ) {
return $this->options[ $key ];
}
return $default;
}
/**
* Get next scheduled time to show notice.
*
* @param int $time Current time.
* @param int $extend How many days to extend.
*
* @since 2.0
*
* @return int
*/
public function get_next_schedule( $time = false, $extend = false ) {
// Use current time.
if ( ! is_int( $time ) ) {
$time = time();
}
// Use extension time.
if ( ! is_int( $extend ) ) {
$extend = $this->time;
}
return $time + $extend;
}
/**
* Get full url to an asset.
*
* @param string $path Path to append.
*
* @since 2.0
*
* @return string
*/
protected function assets_url( $path ) {
return plugin_dir_url( WPMUDEV_NOTICES_FILE ) . 'assets/' . $path;
}
/**
* Get the full url to the API endpoint.
*
* @param string $endpoint API endpoint.
*
* @since 2.0
*
* @return string
*/
protected function api_url( $endpoint ) {
$base = 'https://wpmudev.com/';
// Support custom API base.
if ( defined( 'WPMUDEV_CUSTOM_API_SERVER' ) && ! empty( WPMUDEV_CUSTOM_API_SERVER ) ) {
$base = trailingslashit( WPMUDEV_CUSTOM_API_SERVER );
}
// Append endpoint.
return $base . 'api/' . $endpoint;
}
/**
* Get current screen id.
*
* @since 2.0
*
* @return string
*/
protected function screen_id() {
// Screen not defined yet.
if ( ! function_exists( 'get_current_screen' ) ) {
return '';
}
// Get current screen.
$screen = get_current_screen();
// Return current screen id.
return empty( $screen->id ) ? '' : $screen->id;
}
}
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* Rating notice class.
*
* Rating notice is almost same as email notice. Only the notice
* content is different.
*
* @since 2.0
* @author Incsub (Joel James)
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* @copyright Copyright (c) 2022, Incsub
* @package WPMUDEV\Notices\Notices
*/
namespace WPMUDEV\Notices\Notices;
// If this file is called directly, abort.
defined( 'WPINC' ) || die;
if ( ! class_exists( __NAMESPACE__ . '\\Rating' ) ) {
/**
* Class Rating
*
* @since 2.0
* @package WPMUDEV\Notices
*/
class Rating extends Email {
/**
* Current notice type.
*
* @since 2.0
* @var string $type
*/
protected $type = 'rate';
/**
* Show after 1 week.
*
* @since 2.0
* @var string $time
*/
protected $time = WEEK_IN_SECONDS; // After 1 week.
/**
* Render a notice type content.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return void
*/
public function render( $plugin ) {
$this->enqueue_assets( $plugin );
/* translators: %s - plugin name */
$title = __( 'Enjoying %s? Wed love to hear your feedback!', 'wdev_frash' );
$title = apply_filters( 'wdev_rating_title_' . $plugin, $title );
/* translators: %s - plugin name */
$message = __( 'Youve been using %s for over a week now, and wed love to hear about your experience! Weve spent countless hours developing it for you, and your feedback is important to us. Wed really appreciate your rating.', 'wdev_frash' );
$message = apply_filters( 'wdev_rating_message_' . $plugin, $message );
// Plugin title.
$plugin_title = $this->get_option( 'title', __( 'Plugin', 'wdev_frash' ) );
?>
<div class="notice notice-info frash-notice frash-notice-<?php echo esc_attr( $this->type ); ?> hidden">
<?php $this->render_hidden_fields( $plugin ); // Render hidden fields. ?>
<div class="frash-notice-logo <?php echo esc_attr( $plugin ); ?>"><span></span></div>
<div class="frash-notice-message">
<p class="notice-title"><?php printf( esc_html( $title ), esc_html( $plugin_title ) ); ?></p>
<p><?php printf( esc_html( $message ), esc_html( $plugin_title ) ); ?></p>
<div class="frash-notice-actions">
<a href="#" class="frash-notice-act frash-stars" data-msg="<?php esc_attr_e( 'Thanks :)', 'wdev_frash' ); ?>">
<span>★</span><span>★</span><span>★</span><span>★</span><span>★</span>
</a>
<span class="frash-notice-cta-divider">|</span>
<a href="#" class="frash-notice-dismiss" data-msg="<?php esc_attr_e( 'Saving', 'wdev_frash' ); ?>">
<?php esc_html_e( 'Dismiss', 'wdev_frash' ); ?>
</a>
</div>
</div>
</div>
<?php
}
/**
* Check if current notice is allowed for the plugin.
*
* @since 2.0
*
* @param string $plugin Plugin ID.
*
* @return bool
*/
public function can_show( $plugin ) {
// Mailchimp list id is required.
$wp_slug = $this->get_option( 'wp_slug' );
// Show only on dashboard.
return 'dashboard' === $this->screen_id() && ! empty( $wp_slug );
}
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* WPMUDEV Notices - Notices module for WPMUDEV free plugins.
*
* Used by wordpress.org hosted plugins to show email optins, rating notice
* and giveaway notices.
*
* @since 2.0
* @author Incsub (Philipp Stracker, Joel James)
* @package WPMUDEV\Notices
*/
if ( ! class_exists( 'WPMUDEV\Notices\Handler' ) ) {
// Base file.
if ( ! defined( 'WPMUDEV_NOTICES_FILE' ) ) {
define( 'WPMUDEV_NOTICES_FILE', __FILE__ );
}
// Include main module.
require_once 'classes/class-handler.php';
// Include notices.
require_once 'classes/notices/class-notice.php';
require_once 'classes/notices/class-email.php';
require_once 'classes/notices/class-rating.php';
require_once 'classes/notices/class-giveaway.php';
// Initialize notices.
WPMUDEV\Notices\Handler::instance();
}

View File

@@ -0,0 +1,61 @@
# Usage Examples
## Minimum requirement
```
<?php
require_once 'recommended-plugins-notice/notice.php';
do_action(
'wpmudev-recommended-plugins-register-notice',
plugin_basename(__FILE__), // Plugin basename
'My Plugin Name', // Plugin Name
array(
'top_level_page_screen_id' // Screen IDs
),
);
```
# Development Mode
## Always ON
This code below will always show the notice on every page.
```
<?php
require_once 'recommended-plugins-notice/notice.php';
add_filter( 'wpmudev-recommended-plugins-is-displayable', '__return_true' );
add_filter(
'wpmudev-recommended-plugin-active-registered',
function () {
$active = new WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin( 'basename' );
$active->selector = array( 'after', '.sui-wrap .sui-header' );
$active->name = 'Sample';
return $active;
}
);
```
## Custom time trigger
Default of notice to be displayed in plugin page(s) is **14** days after its registered.
You can decrease or even increase this because why not.
```
<?php
add_filter(
'wpmudev-recommended-plugins-notice-display-seconds-after-registered',
function ( $time_trigger ) {
// 1 minute trigger
$time_trigger = 1 * MINUTE_IN_SECONDS;
return $time_trigger;
}
);
```
## Un-dismiss
Accidentally or purposed-ly dismiss the notice for whatever reason ? this below code can undo that.
```
<?php
add_action(
'wpmudev-recommended-plugins-before-display',
function () {
WPMUDEV_Recommended_Plugins_Notice::get_instance()->un_dismiss();
}
);
```

View File

@@ -0,0 +1,134 @@
@font-face {
font-family: 'wpmudev-close-icon';
src: url('../fonts/wpmudev-close-icon.eot?v4kr59');
src: url('../fonts/wpmudev-close-icon.eot?v4kr59#iefix') format('embedded-opentype'),
url('../fonts/wpmudev-close-icon.woff2?v4kr59') format('woff2'),
url('../fonts/wpmudev-close-icon.ttf?v4kr59') format('truetype'),
url('../fonts/wpmudev-close-icon.woff?v4kr59') format('woff'),
url('../fonts/wpmudev-close-icon.svg?v4kr59#wpmudev-close-icon') format('svg');
font-weight: normal;
font-style: normal;
}
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'wpmudev-close-icon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-close:before {
content: "\29";
color: #AAAAAA;
-webkit-transition: ease-in-out .2s;
transition: ease-in-out .2s;
}
.wpmudev-recommended-plugins {
position: relative;
border-radius: 4px;
background-color: #FFFFFF;
padding: 25px 30px 30px;
-webkit-box-shadow: 0 2px 0 #E6E6E6;
box-shadow: 0 2px 0 #E6E6E6;
margin-bottom: 30px;
-webkit-transition: ease-in-out .2s;
transition: ease-in-out .2s;
}
.wpmudev-recommended-plugins .wpmudev-recommended-plugins-dismiss {
position: absolute;
top: 20px;
right: 20px;
}
.wpmudev-recommended-plugins .wpmudev-recommended-plugins-dismiss a {
display: flex;
width: 30px;
height: 30px;
justify-content: center;
align-items: center;
}
.wpmudev-recommended-plugins .wpmudev-recommended-plugins-dismiss:hover .icon-close:before {
color: #888888;
}
div.wpmudev-recommended-plugins p.wpmudev-notice-status {
font-size: 13px;
color: #888888;
margin-bottom: 25px !important;
line-height: 22px;
}
.wpmudev-recommended-plugins h3.wpmudev-plugin-name {
font-family: "Roboto", Arial, sans-serif;
font-size: 13px;
line-height: 22px;
margin-bottom: 2px;
-webkit-transition: ease-in-out .2s;
transition: ease-in-out .2s;
}
.wpmudev-recommended-plugins p.wpmudev-plugin-description {
font-family: "Roboto", Arial, sans-serif;
color: #888888;
font-size: 13px;
letter-spacing: -0.25px;
line-height: 18px;
margin: 0;
}
.wpmudev-recommended-plugin-blocks {
display: flex;
}
.wpmudev-recommended-plugin-block {
width: 50%;
min-height: 60px;
}
.wpmudev-recommended-plugin-block-image {
position: absolute;
}
.wpmudev-recommended-plugin-block-detail {
margin: 0 15px 0 75px;
}
.wpmudev-recommended-plugin-block-detail {
vertical-align: middle;
}
.wpmudev-recommended-plugin-block-image img {
border-radius: 4px;
width: 60px;
-webkit-transition: ease-in-out .2s;
transition: ease-in-out .2s;
}
.wpmudev-recommended-plugin-block:hover img {
opacity: .8;
}
.wpmudev-recommended-plugin-block:hover h3.wpmudev-plugin-name {
color: #666666;
}
/* Don't display the notice on screens smaller than 782px */
@media only screen and (max-width: 782px) {
.wpmudev-recommended-plugins {
display: none;
}
}
/* Color accessible close icon */
.sui-wrap.sui-color-accessible .wpmudev-recommended-plugins .wpmudev-recommended-plugins-dismiss .icon-close:before {
color: #000000;
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="wpmudev-close-icon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#x29;" glyph-name="close" d="M620.102 448.032l234.834 234.834c7.407 7.444 11.987 17.708 11.987 29.041s-4.579 21.597-11.988 29.042l-50.879 50.097c-7.444 7.407-17.708 11.987-29.041 11.987s-21.597-4.579-29.042-11.988l-234.832-234.832-234.834 234.834c-7.377 7.168-17.458 11.588-28.572 11.588s-21.195-4.42-28.581-11.598l-50.558-50.401c-7.315-7.428-11.832-17.628-11.832-28.885s4.517-21.457 11.836-28.89l234.828-234.828-234.834-234.834c-7.407-7.444-11.987-17.708-11.987-29.041s4.579-21.597 11.988-29.042l50.566-50.097c7.427-7.445 17.696-12.051 29.041-12.051s21.615 4.606 29.041 12.050l234.835 234.835 234.834-234.834c7.444-7.407 17.708-11.987 29.041-11.987s21.597 4.579 29.042 11.988l50.409 50.409c7.407 7.444 11.987 17.708 11.987 29.041s-4.579 21.597-11.988 29.042z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,45 @@
(function ($, window) {
var notice = $('.wpmudev-recommended-plugins'),
selectorEl = notice.data('selectorEl'),
selectorFn = notice.data('selectorFn');
// customize placement
if ('' !== selectorEl && '' !== selectorFn) {
try {
$.fn[selectorFn].call($(selectorEl), notice);
} catch (e) {
}
}
notice.show();
$('.wpmudev-recommended-plugins .dismiss').on('click', function (e) {
var pointer = $(this).data('pointer'),
action = $(this).data('action');
e.preventDefault();
if (window.ajaxurl) {
$.ajax({
url: window.ajaxurl,
method: 'POST',
data: {
pointer: pointer,
action: action,
},
}).always(function () {
// ALWAYS CLOSE WHATEVER AJAX RESULT
removeNotice();
});
} else {
// ALWAYS CLOSE EVEN AJAX NOT POSSIBLE
removeNotice();
}
return false;
});
function removeNotice() {
$('.wpmudev-recommended-plugins').remove();
}
}(jQuery, window));

View File

@@ -0,0 +1,757 @@
<?php
/**
* WPMUDEV - Recommended Plugins Notice
*
* @author WPMUDEV (https://wpmudev.com)
* @license GPLv2
* @package WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin
*/
if ( ! class_exists( 'WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin' ) ) {
/**
* Class WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin
*
* Hold registered plugin as object
*/
class WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin { //phpcs:ignore
/**
* Plugin basename
* plugin-dir/plugin-name.php
*
* @var string
*/
protected $basename = '';
/**
* Plugin nice name
*
* `My Plugin`
*
* @var string
*/
public $name = '';
/**
* Screens which notice should be displayed
*
* @var array
*/
public $screens = array();
/**
* Time the plugin registered to notice
*
* @var int
*/
public $registered_at = 0;
/**
* Element selector which notice should be append-ed
*
* @var array
*/
public $selector = array();
/**
* Current active screen being displayed
*
* @var string
*/
protected $active_screen = '';
/**
* WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin constructor.
*
* @param string $basename Plugin basename.
*/
public function __construct( $basename ) {
$this->basename = $basename;
}
/**
* Get plugin basename
*
* @return string
*/
public function get_basename() {
return $this->basename;
}
/**
* Build object properties from array
*
* @param array $data Notice data.
*/
public function from_array( $data ) {
if ( is_array( $data ) ) {
if ( isset( $data['registered_at'] ) ) {
$this->registered_at = (int) $data['registered_at'];
}
}
}
/**
* Export to array
*
* @return array
*/
public function to_array() {
return array(
'registered_at' => $this->registered_at,
);
}
/**
* Check if screen is listen on plugin screen
*
* @param string $screen_id Screen ID.
*
* @return bool
*/
public function is_my_screen( $screen_id ) {
foreach ( $this->screens as $screen ) {
if ( $screen_id === $screen ) {
$this->active_screen = $screen_id;
return true;
}
}
return false;
}
/**
* Get where notice should be moved
*
* @return array
*/
public function get_selector() {
$selector = $this->selector;
$active_screen = $this->active_screen;
/**
* Filter selector which notice should be moved to
*
* @param array $selector
* @param string $active_screen
*
* @return array
*/
return apply_filters( "wpmudev-recommended-plugin-$this->basename-notice-selector", $selector, $active_screen );
}
/**
* Check whether now is the time to display
*
* @return bool
*/
public function is_time_to_display_notice() {
$active_screen = $this->active_screen;
$seconds_after_registered = 14 * DAY_IN_SECONDS;
/**
* Filter how many seconds after registered before notice displayed
*
* This filter is globally used.
*
* @param int $seconds_after_registered
* @param string $active_screen
*
* @return string
*/
$seconds_after_registered = apply_filters( "wpmudev-recommended-plugins-notice-display-seconds-after-registered", $seconds_after_registered, $active_screen );
/**
* Filter how many seconds after registered before notice displayed
*
* This filter is for plugin based, overriding global value.
*
* @param int $seconds_after_registered
* @param string $active_screen
*
* @return string
*/
$seconds_after_registered = apply_filters( "wpmudev-recommended-plugin-{$this->basename}-notice-display-seconds-after-registered", $seconds_after_registered, $active_screen );
$now = time();
if ( $now >= ( $this->registered_at + $seconds_after_registered ) ) {
return true;
}
return false;
}
/**
* Get pre text on displayed notice
*
* @return string
*/
public function get_pre_text_notice() {
$pre_text_notice = sprintf( /* translators: %s - plugin name */
__( 'Enjoying %s? Try out a few of our other popular free plugins...', 'wpmudev_recommended_plugins_notice' ),
$this->name
);
/**
* Filter pre text on displayed notice
*
* @param string $pre_text_notice
*
* @return string
*/
return apply_filters( "wpmudev-recommended-plugin-$this->basename-pre-text-notice", $pre_text_notice );
}
}
}
if ( ! class_exists( 'WPMUDEV_Recommended_Plugins_Notice' ) ) {
/**
* Class WPMUDEV_Recommended_Plugins_Notice
*
* @internal
*/
class WPMUDEV_Recommended_Plugins_Notice {//phpcs:ignore
/**
* Class instance.
*
* @var WPMUDEV_Recommended_Plugins_Notice
*/
private static $instance = null;
/**
* Collection of recommended plugins
*
* @var array
*/
protected $recommended_plugins = array();
/**
* Version
*/
const VERSION = '1.0.0';
/**
* Pointer name
*/
const POINTER_NAME = 'wpmudev_recommended_plugins';
/**
* Registered plugins
*/
const OPTION_NAME = 'wpmudev_recommended_plugins_registered';
/**
* Collection of registered plugins to use this notice
*
* @var WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin[]
*/
protected $registered_plugins = array();
/**
* Active registered plugin on this screen
*
* @var null
*/
protected $active_registered_plugin = null;
/**
* WPMUDEV_Recommended_Plugins_Notice constructor.
*/
public function __construct() {
// Only do things when its on admin screen.
if ( is_admin() ) {
$this->init_recommended_plugins();
$this->parse_saved_registered_plugins();
add_action( 'wpmudev-recommended-plugins-register-notice', array( $this, 'register' ), 10, 4 );
add_action( 'all_admin_notices', array( $this, 'display' ), 6 );
}
}
/**
* Init recommended plugins
*
* @return void
*/
private function init_recommended_plugins() {
$recommended_plugins = array(
array(
'name' => 'Smush Image Compression',
'desc' => __( 'Resize, optimize and compress all of your images to the max.', 'wpmudev_recommended_plugins_notice' ),
'image' => trailingslashit( plugin_dir_url( __FILE__ ) ) . '/assets/images/plugins-smush.png',
'free_slug' => 'wp-smushit/wp-smush.php',
'pro_slug' => 'wp-smush-pro/wp-smush.php',
'install_link' => 'https://wordpress.org/plugins/wp-smushit/',
),
array(
'name' => 'Hummingbird Performance',
'desc' => __( 'Add powerful caching and optimize your assets.', 'wpmudev_recommended_plugins_notice' ),
'image' => trailingslashit( plugin_dir_url( __FILE__ ) ) . '/assets/images/plugins-hummingbird.png',
'free_slug' => 'hummingbird-performance/wp-hummingbird.php',
'pro_slug' => 'wp-hummingbird/wp-hummingbird.php',
'install_link' => 'https://wordpress.org/plugins/hummingbird-performance/',
),
array(
'name' => 'Defender Security',
'desc' => __( 'Secure and protect your site from malicious hackers and bots.', 'wpmudev_recommended_plugins_notice' ),
'image' => trailingslashit( plugin_dir_url( __FILE__ ) ) . '/assets/images/plugins-defender.png',
'free_slug' => 'defender-security/wp-defender.php',
'pro_slug' => 'wp-defender/wp-defender.php',
'install_link' => 'https://wordpress.org/plugins/defender-security/',
),
array(
'name' => 'SmartCrawl SEO',
'desc' => __( 'Configure your markup for optimal page and social ranking.', 'wpmudev_recommended_plugins_notice' ),
'image' => trailingslashit( plugin_dir_url( __FILE__ ) ) . '/assets/images/plugins-smartcrawl.png',
'free_slug' => 'smartcrawl-seo/wpmu-dev-seo.php',
'pro_slug' => 'wpmu-dev-seo/wpmu-dev-seo.php',
'install_link' => 'https://wordpress.org/plugins/smartcrawl-seo/',
),
array(
'name' => 'Forminator Forms, Polls & Quizzes',
'desc' => __( 'Create dynamic forms easily and quickly with our form builder.', 'wpmudev_recommended_plugins_notice' ),
'image' => trailingslashit( plugin_dir_url( __FILE__ ) ) . '/assets/images/plugins-forminator.png',
'free_slug' => 'forminator/forminator.php',
'pro_slug' => '',
'install_link' => 'https://wordpress.org/plugins/forminator/',
),
array(
'name' => 'Hustle Marketing',
'desc' => __( 'Generate leads with pop-ups, slide-ins and email opt-ins.', 'wpmudev_recommended_plugins_notice' ),
'image' => trailingslashit( plugin_dir_url( __FILE__ ) ) . '/assets/images/plugins-hustle.png',
'free_slug' => 'wordpress-popup/popover.php',
'pro_slug' => 'hustle/opt-in.php',
'install_link' => 'https://wordpress.org/plugins/wordpress-popup/',
),
);
$recommended_plugins = apply_filters( 'wpmudev-all-recommended-plugins', $recommended_plugins );
$this->recommended_plugins = $recommended_plugins;
}
/**
* Get recommended plugins to be displayed on notice
*
* This function will only return recommended plugins that `not installed` yet
*
* @param int $min Minimum plugins to be displayed.
* @param int $max Maximum plugins to be displayed.
*
* @return array
*/
protected function get_recommended_plugins_for_notice( $min = 2, $max = 2 ) {
$recommended_plugins_for_notice = array();
$recommended_plugins = $this->recommended_plugins;
foreach ( $recommended_plugins as $recommended_plugin ) {
if ( $this->is_plugin_installed( $recommended_plugin ) ) {
continue;
}
$recommended_plugins_for_notice[] = $recommended_plugin;
// Stop when we reached max.
if ( count( $recommended_plugins_for_notice ) >= $max ) {
break;
}
}
// Not enough!
if ( count( $recommended_plugins_for_notice ) < $min ) {
$recommended_plugins_for_notice = array();
}
/**
* Filter recommended plugins to be displayed on notice
*
* @param array $recommended_plugins_for_notice recommended plugins to be displayed
* @param array $recommended_plugins all recommended plugins
* @param int $min minimum plugins to be displayed
* @param int $max maximum plugins to be displayed
*
* @return array
*/
return apply_filters( 'wpmudev-recommended-plugins', $recommended_plugins_for_notice, $recommended_plugins, $min, $max );
}
/**
* Check whether plugin is installed
*
* @uses get_plugins()
*
* @param array $plugin_data Plugin data.
*
* @return bool
*/
protected function is_plugin_installed( $plugin_data ) {
$is_installed = false;
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$installed_plugins = get_plugins();
// Check the free one.
if ( isset( $plugin_data['free_slug'] ) && ! empty( $plugin_data['free_slug'] ) ) {
if ( isset( $installed_plugins[ $plugin_data['free_slug'] ] ) ) {
$is_installed = true;
}
}
// Check the pro one.
if ( ! $is_installed ) {
if ( isset( $plugin_data['pro_slug'] ) && ! empty( $plugin_data['pro_slug'] ) ) {
if ( isset( $installed_plugins[ $plugin_data['pro_slug'] ] ) ) {
$is_installed = true;
}
}
}
/**
* Filter is_installed status of recommended plugin
*
* @param bool $is_installed
* @param array $plugin_data plugin to be check
* @param array $installed_plugins current installed plugins
*
* @return bool
*/
return apply_filters( 'wpmudev-recommended-plugin-is-installed', $is_installed, $plugin_data, $installed_plugins );
}
/**
* Display the notice
*
* @return void
*/
public function display() {
/**
* Fires before displaying notice
*
* This action fired before any check done.
*/
do_action( 'wpmudev-recommended-plugins-before-display' );
$is_displayable = $this->is_displayable();
/**
* Filter whether notice is displayable
*
* @param bool $is_displayable
*
* @return bool
*/
$is_displayable = apply_filters( 'wpmudev-recommended-plugins-is-displayable', $is_displayable );
if ( ! $is_displayable ) {
return;
}
$active_registered_plugin = $this->active_registered_plugin;
/**
* Filter whether notice is displayable
*
* @param bool $is_displayable
*
* @return bool
*/
$active_registered_plugin = apply_filters( 'wpmudev-recommended-plugin-active-registered', $active_registered_plugin );
if ( ! $active_registered_plugin instanceof WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin ) {
return;
}
$recommended_plugins = $this->get_recommended_plugins_for_notice();
// no plugins to be recommended.
if ( empty( $recommended_plugins ) ) {
return;
}
wp_register_style( 'wpmudev-recommended-plugins-css', trailingslashit( plugin_dir_url( __FILE__ ) ) . 'assets/css/notice.css', array(), self::VERSION );
wp_enqueue_style( 'wpmudev-recommended-plugins-css' );
wp_register_script( 'wpmudev-recommended-plugins-js', trailingslashit( plugin_dir_url( __FILE__ ) ) . 'assets/js/notice.js', array( 'jquery' ), self::VERSION, true );
wp_enqueue_script( 'wpmudev-recommended-plugins-js' );
$dismissed_text = __( 'Dismiss', 'wpmudev_recommended_plugins_notice' );
/**
* Filter dismissed text
*
* @param string $dismissed_text
*
* @return string
*/
$dismissed_text = apply_filters( 'wpmudev-recommended-plugins-dismissed-text', $dismissed_text );
// placement customizer.
$selector = $active_registered_plugin->get_selector();
$data_selector_el = '';
$data_selector_fn = '';
if ( is_array( $selector ) ) {
if ( isset( $selector[0] ) ) {
$data_selector_fn = (string) $selector[0];
}
if ( isset( $selector[1] ) ) {
$data_selector_el = (string) $selector[1];
}
}
?>
<div class="wpmudev-recommended-plugins" style="display: none"
data-selector-el="<?php echo esc_attr( $data_selector_el ); ?>"
data-selector-fn="<?php echo esc_attr( $data_selector_fn ); ?>">
<p class="wpmudev-notice-status"><?php echo wp_kses_post( $active_registered_plugin->get_pre_text_notice() ); ?></p>
<div class="wpmudev-recommended-plugin-blocks">
<?php foreach ( $recommended_plugins as $recommended_plugin ) : ?>
<div class="wpmudev-recommended-plugin-block">
<a href="<?php echo esc_attr( $recommended_plugin['install_link'] ); ?>" target="_blank">
<div class="wpmudev-recommended-plugin-block-image">
<img src="<?php echo esc_url( $recommended_plugin['image'] ); ?>" alt="<?php echo esc_attr( $recommended_plugin['name'] ); ?>"/>
</div>
<div class="wpmudev-recommended-plugin-block-detail">
<h3 class="wpmudev-plugin-name"><?php echo esc_html( $recommended_plugin['name'] ); ?></h3>
<p class="wpmudev-plugin-description"><?php echo esc_html( $recommended_plugin['desc'] ); ?></p>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
<div class="wpmudev-recommended-plugins-dismiss">
<a class="dismiss"
href="#"
aria-label="Dismiss"
data-action="dismiss-wp-pointer"
data-pointer="<?php echo esc_attr( self::POINTER_NAME ); ?>">
<i class="icon-close" aria-hidden="true"></i>
</a>
</div>
</div>
<?php
/**
* Fires after displaying notice
*
* This action fired after notice markup sent if any check above fails, this action won't fire.
*/
do_action( 'wpmudev-recommended-plugins-after-display' );
}
/**
* Check if notice can be displayed
*
* @uses current_user_can()
*
* @return bool
*/
public function is_displayable() {
/**
* CHECK #1 whether dismissed previously ?
*
* @uses dismissed_wp_pointers use builtin dismissed_wp_pointers to hold data, to avoid add more rows on WP
*/
$dismissed_wp_pointers = get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true );
$dismissed_wp_pointers = explode( ',', (string) $dismissed_wp_pointers );
$dismissed = ( is_array( $dismissed_wp_pointers ) && in_array( self::POINTER_NAME, $dismissed_wp_pointers, true ) );
/**
* Filter flag of dismissed status
*
* @param bool $dismissed
*
* @return bool
*/
$dismissed = apply_filters( 'wpmudev-recommended-plugins-dismissed', $dismissed, $dismissed_wp_pointers );
if ( $dismissed ) {
return false;
}
/**
* CHECK #2 Cap check
* default is for user that capable of `install_plugins`, which make sense because this notice will suggest user to install plugin
*/
$capability_to_check = 'install_plugins';
/**
* Filter user capability which will be shown this notice
*
* @param string $capability_to_check
*
* @return string
*/
$capability_to_check = apply_filters( 'wpmudev-recommended-plugins-capability', $capability_to_check );
if ( ! current_user_can( $capability_to_check ) ) {
return false;
}
/**
* CHECK #3 is_wpmudev_member
*
* This guy is a pro!!
* - wpmudev dash activated and or membership type = full
*/
if ( function_exists( 'is_wpmudev_member' ) ) {
if ( is_wpmudev_member() ) {
return false;
}
}
/**
* CHECK #4 only display on screens that defined by plugin
*/
$current_screen = get_current_screen();
if ( ! $current_screen instanceof WP_Screen || ! isset( $current_screen->id ) ) {
return false;
}
$active_registered_plugin = null;
foreach ( $this->registered_plugins as $registered_plugin ) {
if ( $registered_plugin->is_my_screen( $current_screen->id ) ) {
$active_registered_plugin = $registered_plugin;
break;
}
}
if ( is_null( $active_registered_plugin ) ) {
return false;
}
/**
* CHECK #5 time after register to display notice
*/
if ( ! $active_registered_plugin->is_time_to_display_notice() ) {
return false;
}
$this->active_registered_plugin = $active_registered_plugin;
return true;
}
/**
* Register plugin for notice to be added
*
* @param string $plugin_basename Plugin basename.
* @param string $plugin_name Plugin name.
* @param array $screens Screens.
* @param array $selector [jqueryFn, jQuerySelector].
*/
public function register( $plugin_basename, $plugin_name, $screens = array(), $selector = array() ) {
// Invalid register.
if ( empty( $plugin_basename ) || empty( $plugin_name ) ) {
return;
}
if ( ! isset( $this->registered_plugins[ $plugin_basename ] ) ) {
$registered_plugin_object = new WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin( $plugin_basename );
$registered_plugin_object->registered_at = time();
$this->registered_plugins[ $plugin_basename ] = $registered_plugin_object;
$this->save_registered_plugins();
}
$registered_plugin_object = $this->registered_plugins[ $plugin_basename ];
$registered_plugin_object->name = $plugin_name;
$registered_plugin_object->screens = $screens;
$registered_plugin_object->selector = $selector;
$this->registered_plugins[ $plugin_basename ] = $registered_plugin_object;
}
/**
* Parse registered plugins from storage
*/
public function parse_saved_registered_plugins() {
/**
* Fired before saved registered plugins being parse
*/
do_action( 'wpmudev-recommended-plugins-before-parse-saved-registered-plugins' );
$registered_plugins = get_site_option( self::OPTION_NAME, array() );
if ( is_array( $registered_plugins ) ) {
foreach ( $registered_plugins as $plugin_basename => $registered_plugin ) {
$registered_plugin_object = new WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin( $plugin_basename );
$registered_plugin_object->from_array( $registered_plugin );
$this->registered_plugins[ $plugin_basename ] = $registered_plugin_object;
}
}
}
/**
* Save registered plugins to storage
*/
public function save_registered_plugins() {
$registered_plugins = array();
foreach ( $this->registered_plugins as $registered_plugin ) {
$registered_plugins[ $registered_plugin->get_basename() ] = $registered_plugin->to_array();
}
update_site_option( self::OPTION_NAME, $registered_plugins );
}
/**
* Un-dismiss the notice
*
* @internal
*/
public function un_dismiss() {
$dismissed_wp_pointers = get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true );
$dismissed_wp_pointers = explode( ',', (string) $dismissed_wp_pointers );
$dismissed = ( is_array( $dismissed_wp_pointers ) && in_array( self::POINTER_NAME, $dismissed_wp_pointers, true ) );
if ( $dismissed ) {
foreach ( $dismissed_wp_pointers as $key => $dismissed_wp_pointer ) {
if ( self::POINTER_NAME === $dismissed_wp_pointer ) {
unset( $dismissed_wp_pointers[ $key ] );
}
}
$dismissed_wp_pointers = implode( ',', $dismissed_wp_pointers );
update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed_wp_pointers );
}
}
/**
* Reset saved registered plugins
*
* @internal
*/
public function reset_registered_plugins() {
delete_site_option( self::OPTION_NAME );
}
/**
* Get class instance.
*
* @return WPMUDEV_Recommended_Plugins_Notice
*/
public static function get_instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
}
$GLOBALS['WPMUDEV_Recommended_Plugins_Notice'] = WPMUDEV_Recommended_Plugins_Notice::get_instance();
}

View File

@@ -0,0 +1,158 @@
# WDEV Logger #
WPMU DEV Logger - A simple logger module.
It's created based on Hummingbird\Core\Logger.
This logger lib will handle the old messages based on the expected size.
This means, it will try to get rid of the old messages if the file size is larger than the max size of the log file.
# How to use it #
1. Insert this repository as **sub-module** into the existing project
2. Include the file `wdev-logger.php` in your main plugin file.
3. Register a logger instance via method: WDEV_Logger::create
## Code Example ##
```
#!php
<?php
// Load the WDEV Logger module.
include_once 'lib/wdev-logger/wdev-logger.php';
$logger = WDEV_Logger::create(array(
'max_log_size' => 10,//10MB
'expected_log_size_in_percent' => 0.7,//70%
'log_dir' => 'uploads/your_plugin_name',
'add_subsite_dir' => true,//For MU site, @see self::get_log_directory(),
'is_private' => false,
'modules' => array(
'foo' => array(
'is_private' => true,//-log.php,
'log_dir' => 'uploads/specific/log_dir',
),
//uploads/your_plugin_name/bazz-debug.log
'baz' => array(
'name' => 'bazz',
'max_log_size' => 5,//5MB,
),
//It's not required to register a main module, by default it will be index ($logger->index()->...)
'main_module' => array(
'is_global_module' => true,
'log_level' => LOG_DEBUG,// @see self::$log_level
)
)
), $your_plugin_key_or_null);
// We will use this name ($your_plugin_key_or_null) to save the settings, we can leave it empty to use the folder plugin name.
// @see WDEV_Logger::get_option_key() for more detail.
$logger->foo()->error('Log an error.');// result: [...DATE...] Error: Log an error. File path: [uploads/specific/log_dir/foo-log.php]
$logger->foo()->warning('Log a warning');
$logger->foo()->notice('Log a notice');
$logger->foo()->info('Log info');
//Main Module.
$logger->main_module()->error('Log an error for main module');
//Or
$logger->error('Log an error');
//Delete log file.
$logger->foo()->delete();//Delete the log file of module Foo.
$logger->delete();//Delete log file for the main module.
/**
* By default, Logger only log when the user enable WP_DEBUG_LOG, but we can overwrite it via setting
* or via methods.
*
* 1. Log mode:
* define('WP_DEBUG_LOG', LOG_DEBUG );
*
* LOG_ERR or 3 => Only log Error type.
* LOG_WARNING or 4 => Only log Warning type.
* LOG_NOTICE or 5 => Only log Notice type.
* LOG_INFO or 6 => Only log Info type.
* LOG_DEBUG or 7 => Log Error, Warning and Notice type.
* self::WPMUDEV_DEBUG_LEVEL or 10 or TRUE => for all message types.
*/
// or set log mode via set_log_level();
$logger->set_log_level( LOG_DEBUG );// It will set for global module, and other modules can inherit from global module.
// We can do like this to only activate it for the main module.
$logger->main_module()->set_log_level( true );// While setting the log level via method, we will convert true to WPMUDEV_DEBUG_LEVEL.
/**
* 2. Debug mode.
* define('WP_DEBUG', LOG_DEBUG );
*
* We use this config to enable option to log the backtrace:
* LOG_ERR or 3 => Only for Error type.
* LOG_WARNING or 4 => Only for Warning type.
* LOG_NOTICE or 5 => Only for Notice type.
* LOG_INFO or 6 => Only for Info type.
* LOG_DEBUG or 7 => For Error, Warning and Notice type.
* self::WPMUDEV_DEBUG_LEVEL or 10 => for all message types.
*
* Or via method.
*/
$logger->set_debug_level( LOG_DEBUG );
/**
* Add a new module.
*/
$logger->add_module('new-module', array(
'is_private' => true,
'log_level' => LOG_DEBUG
));
/**
* Update a module (it will also inherit from the old module option)
* @see WDEV_Logger::update_module()
*/
$logger->update_module('exist-module', array(
'is_private' => true,
'log_level' => LOG_DEBUG,
'max_log_size' => 5,
));
/**
* Get the download link
*/
$download_link_foo_log = $logger->foo()->get_download_link();
/**
* Delete the log file via ajax.
*/
add_action( 'admin_footer', 'wpmudev_logger_ajax' ); // Write our JS below here
function wpmudev_logger_ajax() {
?>
<script type="text/javascript" >
jQuery(document).ready(function($) {
var data = {
'action': 'wdev_logger_action',
'log_action': 'delete',
'log_module': 'foo',
<?php echo WDEV_Logger::NONCE_NAME;?>: '<?php echo wp_create_nonce('action_[your_option_key]');?>'
};
jQuery.post(ajaxurl, data, function(response) {
console.log(response);
});
});
</script> <?php
}
}
/**
* Cleanup.
*/
$logger->cleanup();
// End.
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
<?php
/**
* Abstract class for an integration module: class Abstract_Integration
*
* @since 2.9.0
* @package Smush\Core\Modules\Integrations
*/
namespace Smush\Core\Integrations;
use Smush\Core\Settings;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Abstract_Integration
*/
abstract class Abstract_Integration {
/**
* Module slug.
*
* @var string $module
*/
protected $module;
/**
* Module class - free module by default, can be pro.
*
* @var string $class Accepts: 'free', 'pro'.
*/
protected $class = 'free';
/**
* Module status.
*
* @var bool $enabled
*/
protected $enabled = false;
/**
* Settings class instance for easier access.
*
* @since 3.0
*
* @var Settings
*/
protected $settings;
/**
* Abstract_Integration constructor.
*/
public function __construct() {
$this->settings = Settings::get_instance();
// Filters the setting variable to add module setting title and description.
add_filter( 'wp_smush_settings', array( $this, 'register' ) );
// Disable setting.
add_filter( 'wp_smush_integration_status_' . $this->module, array( $this, 'setting_status' ) );
// Show submit button if one of the integrations available.
add_filter( 'wp_smush_integration_show_submit', array( $this, 'enable_submit_button' ) );
}
/**
* Update setting status - disable module functionality if not enabled.
*
* @since 2.8.1
*
* @return bool
*/
public function setting_status() {
return ! $this->enabled;
}
/**
* Whether to enable the submit button or not.
*
* @since 3.9.8
*
* @param bool $enabled Current status.
*
* @return bool
*/
public function enable_submit_button( $enabled ) {
return $enabled || $this->enabled;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Avada integration module.
*
* @since 3.3.0
* @package Smush\Core\Integrations
*/
namespace Smush\Core\Integrations;
use Smush\Core\Modules\CDN;
use Smush\Core\Modules\Helpers\Parser;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Avada
*/
class Avada {
/**
* CDN module instance.
*
* @var CDN $cdn
*/
private $cdn;
/**
* Avada constructor.
*
* @since 3.3.0
*
* @param CDN $cdn CDN module.
*/
public function __construct( CDN $cdn ) {
if ( $cdn->is_active() ) {
$this->cdn = $cdn;
add_filter( 'smush_cdn_bg_image_tag', array( $this, 'replace_cdn_links' ) );
if ( defined( 'FUSION_BUILDER_PLUGIN_DIR' ) ) {
add_filter( 'smush_after_process_background_images', array( $this, 'smush_cdn_image_replaced' ), 10, 3 );
}
}
}
/**
* Replace all the image src with cdn link.
*
* @param string $content Content of the current post.
* @param string $image Backround Image tag without src.
* @param string $img_src Image src.
* @return string
*/
public function smush_cdn_image_replaced( $content, $image, $img_src ) {
if ( $this->cdn->is_supported_path( $img_src ) ) {
$new_src = $this->cdn->generate_cdn_url( $img_src );
if ( $new_src ) {
$content = str_replace( $img_src, $new_src, $content );
}
}
return $content;
}
/**
* Replace images from data-bg-url with CDN links.
*
* @since 3.3.0
*
* @param string $img Image.
*
* @return string
*/
public function replace_cdn_links( $img ) {
$image_src = Parser::get_attribute( $img, 'data-bg-url' );
if ( $image_src ) {
// Store the original source to be used later on.
$original_src = $image_src;
// Replace the data-bg-url of the image with CDN link.
if ( $this->cdn->is_supported_path( $image_src ) ) {
$image_src = $this->cdn->generate_cdn_url( $image_src );
if ( $image_src ) {
$img = preg_replace( '#(data-bg-url=["|\'])' . $original_src . '(["|\'])#i', '\1' . $image_src . '\2', $img, 1 );
}
}
}
return $img;
}
}

View File

@@ -0,0 +1,676 @@
<?php
/**
* Smush integration with various plugins: Common class
*
* @package Smush\Core\Integrations
* @since 2.8.0
*
* @author Anton Vanyukov <anton@incsub.com>
*
* @copyright (c) 2018, Incsub (http://incsub.com)
*/
namespace Smush\Core\Integrations;
use Smush\Core\Helper;
use Smush\Core\Modules\Helpers\Parser;
use Smush\Core\Modules\Smush;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Singleton class class Common
*
* @since 2.8.0
*/
class Common {
/**
* Common constructor.
*/
public function __construct() {
if ( is_admin() ) {
// AJAX Thumbnail Rebuild integration.
add_filter( 'wp_smush_media_image', array( $this, 'skip_images' ), 10, 2 );
// Optimise WP retina 2x images.
add_action( 'wr2x_retina_file_added', array( $this, 'smush_retina_image' ), 20, 3 );
// Remove any pre_get_posts_filters added by WP Media Folder plugin.
add_action( 'wp_smush_remove_filters', array( $this, 'remove_filters' ) );
}
// WPML integration.
add_action( 'wpml_updated_attached_file', array( $this, 'wpml_undo_ignore_attachment' ) );
add_action( 'wpml_after_duplicate_attachment', array( $this, 'wpml_ignore_duplicate_attachment' ), 10, 2 );
add_action( 'wpml_after_copy_attached_file_postmeta', array( $this, 'wpml_ignore_duplicate_attachment' ), 10, 2 );
// ReCaptcha lazy load.
add_filter( 'smush_skip_iframe_from_lazy_load', array( $this, 'exclude_recaptcha_iframe' ), 10, 2 );
// Compatibility modules for lazy loading.
add_filter( 'smush_skip_image_from_lazy_load', array( $this, 'lazy_load_compat' ), 10, 3 );
// Soliloquy slider CDN support.
add_filter( 'soliloquy_image_src', array( $this, 'soliloquy_image_src' ) );
// Translate Press integration.
add_filter( 'smush_skip_image_from_lazy_load', array( $this, 'trp_translation_editor' ) );
// Jetpack CDN compatibility.
add_filter( 'smush_cdn_skip_image', array( $this, 'jetpack_cdn_compat' ), 10, 2 );
// WP Maintenance Plugin integration.
add_action( 'template_redirect', array( $this, 'wp_maintenance_mode' ) );
// WooCommerce's product gallery thumbnail CDN support.
add_filter( 'woocommerce_single_product_image_thumbnail_html', array( $this, 'woocommerce_cdn_gallery_thumbnails' ) );
// Buddyboss theme and its platform plugin integration.
add_filter( 'wp_smush_cdn_before_process_src', array( $this, 'buddyboss_platform_modify_image_src' ), 10, 2 );
// GiveWP donation form load lazyload images in iframe.
add_action( 'give_donation_form_top', array( $this, 'givewp_skip_image_lazy_load' ), 0 );
// Thumbnail regeneration handler.
add_filter( 'wp_generate_attachment_metadata', array( $this, 'maybe_handle_thumbnail_generation' ) );
}
/**
* Remove any pre_get_posts_filters added by WP Media Folder plugin.
*/
public function remove_filters() {
// Remove any filters added b WP media Folder plugin to get the all attachments.
if ( class_exists( 'Wp_Media_Folder' ) ) {
global $wp_media_folder;
if ( is_object( $wp_media_folder ) ) {
remove_filter( 'pre_get_posts', array( $wp_media_folder, 'wpmf_pre_get_posts1' ) );
remove_filter( 'pre_get_posts', array( $wp_media_folder, 'wpmf_pre_get_posts' ), 0, 1 );
}
}
global $wpml_query_filter;
// If WPML is not installed, return.
if ( ! is_object( $wpml_query_filter ) ) {
return;
}
// Remove language filter and let all the images be smushed at once.
if ( has_filter( 'posts_join', array( $wpml_query_filter, 'posts_join_filter' ) ) ) {
remove_filter( 'posts_join', array( $wpml_query_filter, 'posts_join_filter' ), 10, 2 );
remove_filter( 'posts_where', array( $wpml_query_filter, 'posts_where_filter' ), 10, 2 );
}
}
/**************************************
*
* AJAX Thumbnail Rebuild
*
* @since 2.8
*/
/**
* AJAX Thumbnail Rebuild integration.
*
* If this is a thumbnail regeneration - only continue for selected thumbs
* (no need to regenerate everything else).
*
* @since 2.8.0
*
* @param string $smush_image Image size.
* @param string $size_key Thumbnail size.
*
* @return bool
*/
public function skip_images( $smush_image, $size_key ) {
if ( empty( $_POST['regen'] ) || ! is_array( $_POST['regen'] ) ) { // Input var ok.
return $smush_image;
}
$smush_sizes = wp_unslash( $_POST['regen'] ); // Input var ok.
if ( in_array( $size_key, $smush_sizes, true ) ) {
return $smush_image;
}
// Do not regenerate other thumbnails for regenerate action.
return false;
}
/**************************************
*
* WP Retina 2x
*/
/**
* Smush Retina images for WP Retina 2x, Update Stats.
*
* @param int $id Attachment ID.
* @param string $retina_file Retina image.
* @param string $image_size Image size.
*/
public function smush_retina_image( $id, $retina_file, $image_size ) {
$smush = WP_Smush::get_instance()->core()->mod->smush;
/**
* Allows to Enable/Disable WP Retina 2x Integration
*/
$smush_retina_images = apply_filters( 'smush_retina_images', true );
// Check if Smush retina images is enabled.
if ( ! $smush_retina_images ) {
return;
}
// Check for Empty fields.
if ( empty( $id ) || empty( $retina_file ) || empty( $image_size ) ) {
return;
}
// Do not smush if auto smush is turned off.
if ( ! $smush->should_auto_smush( $id ) ) {
return;
}
/**
* Allows to skip a image from smushing
*
* @param bool $smush_image Smush image or not
* @param string $image_size Size of image being smushed
* @param string $retina_file Retina file path.
* @param int $id Attachment ID.
*
* @since 3.9.6 Add two parameters for the filter.
*/
$smush_image = apply_filters( 'wp_smush_media_image', true, $image_size, $retina_file, $id );
if ( ! $smush_image ) {
return;
}
$stats = $smush->do_smushit( $retina_file );
// If we squeezed out something, Update stats.
if ( ! is_wp_error( $stats ) && ! empty( $stats['data'] ) && isset( $stats['data'] ) && $stats['data']->bytes_saved > 0 ) {
$image_size = $image_size . '@2x';
$this->update_smush_stats_single( $id, $stats, $image_size );
}
}
/**
* Updates the smush stats for a single image size.
*
* @param int $id Attachment ID.
* @param array $smush_stats Smush stats.
* @param string $image_size Image size.
*/
private function update_smush_stats_single( $id, $smush_stats, $image_size = '' ) {
// Return, if we don't have image id or stats for it.
if ( empty( $id ) || empty( $smush_stats ) || empty( $image_size ) ) {
return;
}
$smush = WP_Smush::get_instance()->core()->mod->smush;
$data = $smush_stats['data'];
// Get existing Stats.
$stats = get_post_meta( $id, Smush::$smushed_meta_key, true );
// Update existing Stats.
if ( ! empty( $stats ) ) {
// Update stats for each size.
if ( isset( $stats['sizes'] ) ) {
// if stats for a particular size doesn't exists.
if ( empty( $stats['sizes'][ $image_size ] ) ) {
// Update size wise details.
$stats['sizes'][ $image_size ] = (object) $smush->array_fill_placeholders( $smush->get_size_signature(), (array) $data );
} else {
// Update compression percent and bytes saved for each size.
$stats['sizes'][ $image_size ]->bytes = $stats['sizes'][ $image_size ]->bytes + $data->bytes_saved;
$stats['sizes'][ $image_size ]->percent = $stats['sizes'][ $image_size ]->percent + $data->compression;
}
}
} else {
// Create new stats.
$stats = array(
'stats' => array_merge(
$smush->get_size_signature(),
array(
'api_version' => - 1,
'lossy' => - 1,
)
),
'sizes' => array(),
);
$stats['stats']['api_version'] = $data->api_version;
$stats['stats']['lossy'] = $data->lossy;
$stats['stats']['keep_exif'] = ! empty( $data->keep_exif ) ? $data->keep_exif : 0;
// Update size wise details.
$stats['sizes'][ $image_size ] = (object) $smush->array_fill_placeholders( $smush->get_size_signature(), (array) $data );
}
// Calculate the total compression.
$stats = WP_Smush::get_instance()->core()->total_compression( $stats );
update_post_meta( $id, Smush::$smushed_meta_key, $stats );
}
/**************************************
*
* WPML
*
* @since 3.0
*/
/**
* Ignore WPML duplicated images from Smush.
*
* If WPML is duplicating images, we need to mark them as ignored for Smushing
* because the image is same for all those duplicated attachment posts. This is
* required to avoid wrong Smush stats.
*
* @param int $attachment_id Original attachment ID.
* @param int $duplicated_attachment_id Duplicated attachment ID.
*
* @since 3.9.4
*/
public function wpml_ignore_duplicate_attachment( $attachment_id, $duplicated_attachment_id ) {
// Ignore the image from Smush if duplicate.
update_post_meta( $duplicated_attachment_id, 'wp-smush-ignore-bulk', true );
}
/**
* Remove an image from the ignored list.
*
* When a new image is added instead of duplicate, we need to remove it
* from the ignored list to make it available for Smushing.
*
* @param int $attachment_id Attachment ID.
*
* @since 3.9.4
*/
public function wpml_undo_ignore_attachment( $attachment_id ) {
// Delete ignore flag.
delete_post_meta( $attachment_id, 'wp-smush-ignore-bulk' );
}
/**
* Skip ReCaptcha iframes from lazy loading.
*
* @since 3.4.2
*
* @param bool $skip Should skip? Default: false.
* @param string $src Iframe url.
*
* @return bool
*/
public function exclude_recaptcha_iframe( $skip, $src ) {
return false !== strpos( $src, 'recaptcha/api' );
}
/**************************************
*
* Soliloquy slider
*
* @since 3.6.2
*/
/**
* Replace slider image links with CDN links.
*
* @param string $src Image source.
*
* @return string
*/
public function soliloquy_image_src( $src ) {
$cdn = WP_Smush::get_instance()->core()->mod->cdn;
if ( ! $cdn->get_status() || empty( $src ) ) {
return $src;
}
if ( $cdn->is_supported_path( $src ) ) {
return $cdn->generate_cdn_url( $src );
}
return $src;
}
/**************************************
*
* Translate Press
*
* @since 3.6.3
*/
/**
* Disables "Lazy Load" on Translate Press translate editor
*
* @param bool $skip Should skip? Default: false.
*
* @return bool
*/
public function trp_translation_editor( $skip ) {
if ( ! class_exists( '\TRP_Translate_Press' ) || ! isset( $_GET['trp-edit-translation'] ) ) {
return $skip;
}
return true;
}
/**************************************
*
* Jetpack
*
* @since 3.7.1
*/
/**
* Skips the url from the srcset from our CDN when it's already served by Jetpack's CDN.
*
* @since 3.7.1
*
* @param bool $skip Should skip? Default: false.
* @param string $url Source.
*
* @return bool
*/
public function jetpack_cdn_compat( $skip, $url ) {
if ( ! class_exists( '\Jetpack' ) ) {
return $skip;
}
if ( method_exists( '\Jetpack', 'is_module_active' ) && ! \Jetpack::is_module_active( 'photon' ) ) {
return $skip;
}
$parsed_url = wp_parse_url( $url );
// The image already comes from Jetpack's CDN.
if ( preg_match( '#^i[\d]{1}.wp.com$#i', $parsed_url['host'] ) ) {
return true;
}
return $skip;
}
/**************************************
*
* WP Maintenance Plugin
*
* @since 3.8.0
*/
/**
* Disable page parsing when "Maintenance" is enabled
*
* @since 3.8.0
*/
public function wp_maintenance_mode() {
if ( ! class_exists( '\MTNC' ) ) {
return;
}
global $mt_options;
if ( ! is_user_logged_in() && ! empty( $mt_options['state'] ) ) {
add_filter( 'wp_smush_should_skip_parse', '__return_true' );
}
}
/**************************************
*
* WooCommerce
*
* @since 3.9.0
*/
/**
* Replaces the product's gallery thumbnail URL with the CDN URL.
*
* WC uses a <div data-thumbnail=""> attribute to get the thumbnail
* img src which is then added via JS. Our regex for parsing the page
* doesn't check for this div and attribute (and it shouldn't, it becomes too slow).
*
* We can remove this if we ever use the filter "wp_get_attachment_image_src"
* to replace the images' src URL with the CDN one.
*
* @since 3.9.0
*
* @param string $html The thumbnail markup.
* @return string
*/
public function woocommerce_cdn_gallery_thumbnails( $html ) {
$cdn = WP_Smush::get_instance()->core()->mod->cdn;
// Replace only when the CDN is active.
if ( ! $cdn->get_status() ) {
return $html;
}
preg_match_all( '/<(div)\b(?>\s+(?:data-thumb=[\'"](?P<thumb>[^\'"]*)[\'"])|[^\s>]+|\s+)*>/is', $html, $matches );
if ( ! $matches || ! is_array( $matches ) ) {
return $html;
}
foreach ( $matches as $key => $url ) {
// Only use the match for the thumbnail URL if it's supported.
if ( 'thumb' !== $key || empty( $url[0] ) || ! $cdn->is_supported_path( $url[0] ) ) {
continue;
}
// Replace the data-thumb attribute of the div with the CDN link.
$cdn_url = $cdn->generate_cdn_url( $url[0] );
if ( $cdn_url ) {
$html = str_replace( $url[0], $cdn_url, $html );
}
}
return $html;
}
/**************************************
*
* Various modules
*
* @since 3.5
*/
/**
* Lazy loading compatibility checks.
*
* @since 3.5.0
*
* @param bool $skip Should skip? Default: false.
* @param string $src Image url.
* @param string $image Image.
*
* @return bool
*/
public function lazy_load_compat( $skip, $src, $image ) {
// Avoid conflicts if attributes are set (another plugin, for example).
if ( false !== strpos( $image, 'data-src' ) ) {
return true;
}
// Compatibility with Essential Grid lazy loading.
if ( false !== strpos( $image, 'data-lazysrc' ) ) {
return true;
}
// Compatibility with JetPack lazy loading.
if ( false !== strpos( $image, 'jetpack-lazy-image' ) ) {
return true;
}
// Compatibility with Slider Revolution's lazy loading.
if ( false !== strpos( $image, '/revslider/' ) && false !== strpos( $image, 'data-lazyload' ) ) {
return true;
}
return $skip;
}
/**
* CDN compatibility with Buddyboss platform
*
* @param string $src Image source.
* @param string $image Actual image element.
*
* @return string Original or modified image source.
*/
public function buddyboss_platform_modify_image_src( $src, $image ) {
if ( ! defined( 'BP_PLATFORM_VERSION' ) ) {
return $src;
}
/**
* Compatibility with buddyboss theme and it's platform plugin.
*
* Buddyboss platform plugin uses the placeholder image as it's main src.
* And process_src() method below uses the same placeholder.png to create
* the srcset when "Automatic resizing" options is enabled for CDN.
* ---------
* Replacing the placeholder with actual image source as early as possible.
* Checks:
* 1. The image source contains buddyboss-platform in its string
* 2. The image source contains placeholder.png and is crucial because there are other
* images as well which doesn't uses placeholder.
*/
if ( false !== strpos( $src, 'buddyboss-platform' ) && false !== strpos( $src, 'placeholder.png' ) ) {
$new_src = Parser::get_attribute( $image, 'data-src' );
if ( ! empty( $new_src ) ) {
$src = $new_src;
}
}
return $src;
}
/**
* Skip images from lazy loading on GiveWP forms.
*
* @since 3.8.8
*/
public function givewp_skip_image_lazy_load() {
add_filter( 'wp_smush_should_skip_parse', '__return_true' );
}
/**
* Add method to handle thumbnail generation.
*
* We use this trick to call self::thumbnail_regenerate_handler()
* to avoid it called several times while calling wp_generate_attachment_metadata().
* 1. wp_generate_attachment_metadata -> wp_create_image_subsizes -> wp_update_attachment_metadata().
* 2. wp_generate_attachment_metadata -> _wp_make_subsizes -> wp_update_attachment_metadata().
* 3. After calling wp_generate_attachment_metadata() => We should only add our filter here.
*
* @param array $metadata Image metadata.
* @return array The provided metadata.
*/
public function maybe_handle_thumbnail_generation( $metadata ) {
/**
* Add filter to handle thumbnail generation.
* We use a big priority because it seems WP has an issue for this case,
* after we remove this filter, all registered filters after this priority of this hook will not call.
*/
add_filter( 'wp_update_attachment_metadata', array( $this, 'thumbnail_regenerate_handler' ), 99999, 2 ); // S3 is using priority 110.
return $metadata;
}
/**
* Callback for 'wp_update_attachment_metadata' WordPress hook used by Smush to detect
* regenerated thumbnails and mark them as pending for (re)smush.
*
* @since 3.9.2
*
* @param array $new_meta New metadata.
* @param int $attachment_id The attachment ID.
*
* @since 3.9.6 Disable this filter while async uploading,
* and update compatible with S3, and only call it after generated metadata.
*
* @return array
*/
public function thumbnail_regenerate_handler( $new_meta, $attachment_id ) {
// Remove the filter as we are no longer need it.
remove_filter( 'wp_update_attachment_metadata', array( $this, 'thumbnail_regenerate_handler' ), 99999 );
/**
* Skip if there is WP uploading a new image,
* or the attachment is not an image or does not have thumbnails.
*/
if (
empty( $new_meta['sizes'] )
// Async uploading.
|| isset( $_POST['post_id'] ) || isset( $_FILES['async-upload'] )
// Smushed it, don't need to check it again.
|| did_action( 'wp_smush_before_smush_file' )
// Disable when restoring.
|| did_action( 'wp_smush_before_restore_backup' )
// Only support Image.
|| ! Helper::is_smushable( $attachment_id )
) {
return $new_meta;
}
// Skip if the attachment has an active smush operation or in being restored by Smush or ignored.
if ( get_transient( 'smush-in-progress-' . $attachment_id ) || get_transient( 'wp-smush-restore-' . $attachment_id ) || get_post_meta( $attachment_id, 'wp-smush-ignore-bulk', true ) ) {
return $new_meta;
}
$smush_meta = get_post_meta( $attachment_id, Smush::$smushed_meta_key, true );
// Skip attachments without Smush meta key.
if ( empty( $smush_meta ) ) {
return $new_meta;
}
$size_increased = false;
/**
* Get attached file
* If there is generating the image, S3 also downloaded it,
* so we don't need to download it if it doesn't exist.
*/
$attached_file = Helper::get_attached_file( $attachment_id, 'original' );// S3+.
// If the main file does not exist, there is not generating the thumbnail, return.
if ( ! file_exists( $attached_file ) ) {
return $new_meta;
}
// We need only the last $new_meta['sizes'] element of each subsequent call
// to wp_update_attachment_metadata() made by wp_create_image_subsizes().
$size = array_keys( $new_meta['sizes'] )[ count( $new_meta['sizes'] ) - 1 ];
$file_dir = dirname( $attached_file );
$file_name = $file_dir . '/' . $new_meta['sizes'][ $size ]['file'];
$actual_size = is_file( $file_name ) ? filesize( $file_name ) : false;
$stored_size = isset( $smush_meta['sizes'][ $size ]->size_after ) ? $smush_meta['sizes'][ $size ]->size_after : false;
// Only do the comparison if we have both the actual and the database stored size.
if ( $actual_size && $stored_size ) {
$size_increased = $actual_size > 1.01 * $stored_size;// Not much we can do if save less than 1%.
}
// File size increased? Let's remove all
// Smush related meta keys for this attachment.
if ( $size_increased ) {
/**
* When regenerate an image, we only generate the sub-sizes,
* so we don't need to delete the saving data of PNG2JPG.
* And similar for resizing, we also added filter for big_image_size_threshold,
* and we don't use the resizing meta for any conditional, so it's ok to keep it.
*/
// Remove stats and update cache.
WP_Smush::get_instance()->core()->remove_stats( $attachment_id );
}
return $new_meta;
}
}

View File

@@ -0,0 +1,218 @@
<?php
/**
* Smush integration with WPBakery Page Builder: Composer class
*
* @package Smush\Core\Integrations
* @since 3.2.1
*
* @author Anton Vanyukov <anton@incsub.com>
*
* @copyright (c) 2018, Incsub (http://incsub.com)
*/
namespace Smush\Core\Integrations;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Composer for WPBakery Page Builder integration.
*
* @since 3.2.1
*/
class Composer extends Abstract_Integration {
/**
* Composer constructor.
*
* @since 3.2.1
*/
public function __construct() {
$this->module = 'js_builder';
$this->class = 'free';
$this->check_for_js_builder();
parent::__construct();
// Hook at the end of setting row to output a error div.
add_action( 'smush_setting_column_right_inside', array( $this, 'additional_notice' ) );
if ( $this->settings->get( 'js_builder' ) ) {
add_filter( 'image_make_intermediate_size', array( $this, 'process_image_resize' ) );
// CDN link image handler for ajax based loading.
add_filter( 'wp_get_attachment_image_src', array( $this, 'cdn_attachment_image_src' ) );
}
}
/**************************************
*
* OVERWRITE PARENT CLASS FUNCTIONALITY
*/
/**
* Filters the setting variable to add NextGen setting title and description
*
* @since 3.2.1
*
* @param array $settings Settings.
*
* @return mixed
*/
public function register( $settings ) {
$settings[ $this->module ] = 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' ),
);
return $settings;
}
/**
* Show additional notice if the required plugins are not installed.
*
* @since 3.2.1
*
* @param string $name Setting name.
*/
public function additional_notice( $name ) {
if ( $this->module === $name && ! $this->enabled ) {
?>
<div class="sui-toggle-content">
<div class="sui-notice">
<div class="sui-notice-content">
<div class="sui-notice-message">
<i class="sui-notice-icon sui-icon-info" aria-hidden="true"></i>
<p><?php esc_html_e( 'To use this feature you need be using WPBakery Page Builder.', 'wp-smushit' ); ?></p>
</div>
</div>
</div>
</div>
<?php
}
}
/**************************************
*
* PUBLIC CLASSES
*/
/**
* Check if the file source is a registered attachment and if not - Smush it.
*
* TODO: with little adjustments this can be used for all page builders.
*
* @since 3.2.1
*
* @param string $image_src Image src.
*
* @return string
*/
public function process_image_resize( $image_src ) {
$vc_editable = filter_input( INPUT_GET, 'vc_editable', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
$vc_action = filter_input( INPUT_POST, 'action', FILTER_SANITIZE_SPECIAL_CHARS );
global $pagename, $vc_manager;
/**
* There are three types of situations:
* 1. $vc_editable and $vc_action will be set in the frontend page builder
* 2. $pagename in the backend.
* 3. $vc_manager is a fallback (could possibly cause issues).
*/
if ( ( ! $vc_editable || 'vc_load_shortcode' !== $vc_action ) && ( ! isset( $pagename ) || 'page-builder' !== $pagename ) && ( ! $vc_manager || ! is_object( $vc_manager ) ) ) {
return $image_src;
}
// Save the original image source.
$vc_image = $image_src;
// Remove the [width]x[height] params from URL.
$size = array();
if ( preg_match( '/(\d+)x(\d+)\.(?:' . implode( '|', array( 'gif', 'jpg', 'jpeg', 'png' ) ) . '){1}$/i', $image_src, $size ) ) {
$image_src = str_replace( '-' . $size[1] . 'x' . $size[2], '', $image_src );
}
// Convert image src to URL.
$upload_dir = wp_get_upload_dir();
$image_url = str_replace( $upload_dir['path'], $upload_dir['url'], $image_src );
// Try to get the attachment ID.
$attachment_id = attachment_url_to_postid( $image_url );
if ( ! wp_attachment_is_image( $attachment_id ) ) {
return $vc_image;
}
$image = image_get_intermediate_size( $attachment_id, array( $size[1], $size[2] ) );
if ( $image ) {
return $vc_image;
}
// Smush image. TODO: should we update the stats?
WP_Smush::get_instance()->core()->mod->smush->do_smushit( $vc_image );
return $vc_image;
}
/**
* Replace the image src with cdn link for all the Ajax requests.
*
* @since 3.9.10
*
* @see SMUSH-206
*
* @param array|false $image {
* Array of image data, or boolean false if no image is available.
*
* @type string $0 Image source URL.
* @type int $1 Image width in pixels.
* @type int $2 Image height in pixels.
* @type bool $3 Whether the image is a resized image.
* }
*
* @return mixed
*/
public function cdn_attachment_image_src( $image ) {
if ( ! wp_doing_ajax() ) {
return $image;
}
$cdn = WP_Smush::get_instance()->core()->mod->cdn;
if ( ! $cdn->get_status() ) {
return $image;
}
if ( is_array( $image ) && ! empty( $image[0] ) ) {
$image[0] = $cdn->generate_cdn_url( $image[0] );
}
return $image;
}
/**************************************
*
* PRIVATE CLASSES
*/
/**
* Should only be active when WPBakery Page Builder is installed.
*
* @since 3.2.1
*
* @see https://kb.wpbakery.com/docs/inner-api/vc_disable_frontend
*/
private function check_for_js_builder() {
// This function exists since WPBakery 4.0 (02.03.2014) and is listed
// on their API docs. It should be stable enough to rely on it.
$this->enabled = defined( 'WPB_VC_VERSION' ) && function_exists( 'vc_disable_frontend' );
}
}

View File

@@ -0,0 +1,170 @@
<?php
/**
* Integration with Envira Gallery
*
* @since 3.3.0
* @package Smush\Core\Integrations
*/
namespace Smush\Core\Integrations;
use Smush\Core\Modules\CDN;
use Smush\Core\Modules\Helpers\Parser;
use Smush\Core\Settings;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Envira
*/
class Envira {
/**
* CDN module instance.
*
* @var CDN $cdn
*/
private $cdn;
/**
* Envira constructor.
*
* @since 3.3.0
*
* @param CDN $cdn CDN module.
*/
public function __construct( CDN $cdn ) {
if ( is_admin() ) {
return;
}
if ( Settings::get_instance()->get( 'lazy_load' ) ) {
add_filter( 'smush_skip_image_from_lazy_load', array( $this, 'skip_lazy_load' ), 10, 3 );
add_filter( 'envira_gallery_indexable_images', array( $this, 'add_no_lazyload_class' ) );
}
if ( $cdn->is_active() ) {
$this->cdn = $cdn;
add_filter( 'smush_cdn_image_tag', array( $this, 'replace_cdn_links' ) );
}
}
/**
* Do not lazy load images from Envira Gallery.
*
* @since 3.3.0
*
* @param bool $lazy_load Should skip? Default: false.
* @param string $src Image url.
* @param string $img Image.
*
* @return bool
*/
public function skip_lazy_load( $lazy_load, $src, $img ) {
$classes = Parser::get_attribute( $img, 'class' );
return false !== strpos( $classes, 'envira-lazy' );
}
/**
* Replace images from data-envira-src and data-envira-srcset with CDN links.
*
* @since 3.3.0
*
* @param string $img Image.
*
* @return string
*/
public function replace_cdn_links( $img ) {
$image_src = Parser::get_attribute( $img, 'data-envira-src' );
if ( $image_src ) {
// Store the original source to be used later on.
$original_src = $image_src;
// Replace the data-envira-src of the image with CDN link.
$image_src = $this->convert_url_to_cdn( $image_src );
if ( $image_src ) {
$img = preg_replace( '#(data-envira-src=["|\'])' . $original_src . '(["|\'])#i', '\1' . $image_src . '\2', $img, 1 );
}
}
$image_srcset = Parser::get_attribute( $img, 'data-envira-srcset' );
if ( $image_srcset ) {
// Do not add our own srcset attributes.
add_filter( 'smush_skip_adding_srcset', '__return_true' );
// Store the original source to be used later on.
$original_src = $image_srcset;
$replace = false;
$images = Parser::get_links_from_content( $image_srcset );
if ( isset( $images[0] ) && is_array( $images[0] ) ) {
foreach ( $images[0] as $image ) {
// Replace the data-envira-srcset of the image with CDN link.
$image_src = $this->convert_url_to_cdn( $image );
if ( $image_src ) {
$replace = true;
$image_srcset = preg_replace( '#' . $image . '#i', '\1' . $image_src . '\2', $image_srcset, 1 );
}
}
}
if ( $replace ) {
$img = preg_replace( '#(data-envira-srcset=["|\'])' . $original_src . '(["|\'])#i', '\1' . $image_srcset . '\2', $img, 1 );
}
}
return $img;
}
/**
* Galleries in Envira will use a noscript tag with images. Smush can't filter the DOM tree, so we will add
* a no-lazyload class to every image.
*
* @since 3.5.0
*
* @param string $images String of img tags that will go inside nocscript element.
*
* @return string
*/
public function add_no_lazyload_class( $images ) {
$parsed = ( new Parser() )->get_images_from_content( $images );
if ( empty( $parsed ) ) {
return $images;
}
foreach ( $parsed[0] as $image ) {
$original = $image;
$class = Parser::get_attribute( $image, 'class' );
if ( ! $class ) {
Parser::add_attribute( $image, 'class', 'no-lazyload' );
} else {
Parser::add_attribute( $image, 'class', $class . ' no-lazyload' );
}
$images = str_replace( $original, $image, $images );
}
return $images;
}
/**
* Convert URL to CDN link.
*
* @since 3.3.0
*
* @param string $url Image URL.
*
* @return bool|string
*/
private function convert_url_to_cdn( $url ) {
if ( ! $this->cdn->is_supported_path( $url ) ) {
return false;
}
return $this->cdn->generate_cdn_url( $url );
}
}

View File

@@ -0,0 +1,221 @@
<?php
/**
* Integration with Gravity Forms: Gravity_Forms class
*
* This integration will automatically compress images on Gravity Forms upload.
*
* @since 3.9.10
*
* @package Smush\Core\Integrations
*/
namespace Smush\Core\Integrations;
use GFFormsModel;
use Smush\Core\Core;
use Smush\Core\Helper;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Gravity_Forms for Gravity Forms integration.
*
* This integration will automatically compress images on Gravity Forms upload.
*
* @since 3.9.10
*
* @see Abstract_Integration
*/
class Gravity_Forms extends Abstract_Integration {
/**
* Gravity_Forms constructor.
*
* @since 3.9.10
*/
public function __construct() {
$this->module = 'gform';
$this->class = 'free';
$this->enabled = defined( 'GF_SUPPORTED_WP_VERSION' ) && class_exists( 'GFForms' );
parent::__construct();
// Hook at the end of setting row to output an error div.
add_action( 'smush_setting_column_right_inside', array( $this, 'additional_notice' ) );
// Return if Gravity Form integration or auto compression is not enabled.
if ( ! $this->enabled || ! $this->settings->get( 'gform' ) || ! $this->settings->get( 'auto' ) ) {
return;
}
// Track gravity form submission and validate if there is any image uploaded in Image or File Upload fields.
add_action( 'gform_after_submission', array( $this, 'smush_gform_after_submission' ), 10, 2 );
}
/*
* ************************************
*
* OVERWRITE PARENT CLASS FUNCTIONALITY
*/
/**
* Filters the setting variable to add Gravity Form setting title and description
*
* @since 3.9.10
*
* @param array $settings Settings.
*
* @return array
*/
public function register( $settings ) {
$settings[ $this->module ] = 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' ),
);
return $settings;
}
/**
* Show additional notice if the required plugins are not installed.
*
* @since 3.9.10
*
* @param string $name Setting name.
*/
public function additional_notice( $name ) {
if ( $this->module === $name && ! $this->enabled ) {
?>
<div class="sui-toggle-content">
<div class="sui-notice">
<div class="sui-notice-content">
<div class="sui-notice-message">
<i class="sui-notice-icon sui-icon-info" aria-hidden="true"></i>
<p><?php esc_html_e( 'To use this feature you need be using Gravity Forms.', 'wp-smushit' ); ?></p>
</div>
</div>
</div>
</div>
<?php
}
}
/**
* Processing automatic image smush on Gravity Forms upload.
*
* @since 3.9.10
*
* @param Object $entry Entry Object.
* @param Object $form Form Object.
*/
public function smush_gform_after_submission( $entry, $form ) {
$fields = $form['fields'];
foreach ( $fields as $field ) {
if ( 'fileupload' !== $field->type && 'post_image' !== $field->type ) {
continue;
}
if ( ! function_exists( 'rgar' ) ) {
continue;
}
$uploaded_files = rgar( $entry, $field->id );
$uploaded_files = $this->smush_parse_files( $uploaded_files, $field );
if ( ! is_array( $uploaded_files ) || empty( $uploaded_files ) ) {
continue;
}
foreach ( $uploaded_files as $_file ) {
$dir = $this->get_gform_upload_dir( $form['id'] );
if ( ! $dir || ! isset( $dir['url'] ) || ! isset( $dir['path'] ) ) {
continue;
}
$file = str_replace( $dir['url'], $dir['path'], $_file );
// Get mime type from file path.
$mime = Helper::get_mime_type( $file );
// If image file not exist or image type not supported.
if ( ! file_exists( $file ) || ! in_array( $mime, Core::$mime_types, true ) ) {
continue;
}
WP_Smush::get_instance()->core()->mod->smush->do_smushit( $file );
}
}
}
/**
* Get upload directory url and path.
*
* @since 3.9.10
*
* @param int $form_id Form ID.
* @return bool|array
*/
public function get_gform_upload_dir( $form_id ) {
if ( ! class_exists( 'GFFormsModel' ) ) {
return false;
}
$dir = GFFormsModel::get_file_upload_path( $form_id, 'PLACEHOLDER' );
$dir['path'] = dirname( $dir['path'] );
$dir['url'] = dirname( $dir['url'] );
return $dir;
}
/**
* Parsing uploaded files.
*
* @since 3.9.10
*
* @param mixed $files File path.
* @param Object $field Form field object.
*
* @return array
*/
public function smush_parse_files( $files, $field ) {
if ( empty( $files ) ) {
return array();
}
if ( $this->smush_is_json( $files ) ) {
$files = json_decode( $files );
} elseif ( 'post_image' === $field->get_input_type() ) {
$file_bits = explode( '|:|', $files );
$files = array( $file_bits[0] );
} else {
$files = array( $files );
}
return $files;
}
/**
* Check entry files in JSON format.
*
* @since 3.9.10
*
* @param String $string File string.
*
* @return bool
*/
public function smush_is_json( $string ) {
// Duplicate contents of GFCommon::is_json() here to supports versions of GF older than GF 2.5.
if ( is_string( $string ) && in_array( substr( $string, 0, 1 ), array( '{', '[' ) ) && is_array( json_decode( $string, ARRAY_A ) ) ) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,168 @@
<?php
/**
* Smush integration with Gutenberg editor: Gutenberg class
*
* @package Smush\Core\Integrations
* @since 2.8.1
*
* @author Anton Vanyukov <anton@incsub.com>
*
* @copyright (c) 2018, Incsub (http://incsub.com)
*/
namespace Smush\Core\Integrations;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Gutenberg for Gutenberg integration.
*
* @since 2.8.1
*/
class Gutenberg extends Abstract_Integration {
/**
* Gutenberg constructor.
*
* @since 2.8.1
*/
public function __construct() {
$this->module = 'gutenberg';
$this->class = 'free';
$this->check_for_gutenberg();
parent::__construct();
if ( ! $this->enabled ) {
// Disable setting if Gutenberg is not active.
add_filter( 'wp_smush_integration_status_' . $this->module, '__return_true' );
// Hook at the end of setting row to output an error div.
add_action( 'smush_setting_column_right_inside', array( $this, 'integration_error' ) );
return;
}
// Register gutenberg block assets.
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_gb' ) );
}
/**************************************
*
* OVERWRITE PARENT CLASS FUNCTIONALITY
*/
/**
* Filters the setting variable to add Gutenberg setting title and description.
*
* @since 2.8.1
*
* @param array $settings Settings array.
*
* @return mixed
*/
public function register( $settings ) {
$settings[ $this->module ] = 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'
),
);
return $settings;
}
/**************************************
*
* PUBLIC CLASSES
*/
/**
* Prints the message for Gutenberg setup.
*
* @since 2.8.1
*
* @param string $setting_key Settings key.
*/
public function integration_error( $setting_key ) {
// Return if not Gutenberg integration.
if ( $this->module !== $setting_key ) {
return;
}
?>
<div class="sui-toggle-content">
<div class="sui-notice">
<div class="sui-notice-content">
<div class="sui-notice-message">
<i class="sui-notice-icon sui-icon-info" aria-hidden="true"></i>
<p><?php esc_html_e( 'To use this feature you need to install and activate the Gutenberg plugin.', 'wp-smushit' ); ?></p>
</div>
</div>
</div>
</div>
<?php
}
/**
* Enqueue Gutenberg block assets for backend editor.
*
* `wp-blocks`: includes block type registration and related functions.
* `wp-element`: includes the WordPress Element abstraction for describing the structure of your blocks.
* `wp-i18n`: To internationalize the block's text.
*
* @since 2.8.1
*/
public function enqueue_gb() {
$enabled = $this->settings->get( $this->module );
if ( ! $enabled ) {
return;
}
// Gutenberg block scripts.
wp_enqueue_script(
'smush-gutenberg',
WP_SMUSH_URL . 'app/assets/js/smush-blocks.min.js',
array( 'wp-blocks', 'wp-i18n', 'wp-element' ),
WP_SMUSH_VERSION,
true
);
}
/**************************************
*
* PRIVATE CLASSES
*/
/**
* Make sure we only enqueue when Gutenberg is active.
*
* For WordPress pre 5.0 - only when Gutenberg plugin is installed.
* For WordPress 5.0+ - only when Classic Editor is NOT installed.
*
* @since 3.0.2
*/
private function check_for_gutenberg() {
global $wp_version;
if ( ! function_exists( 'is_plugin_active' ) ) {
/* @noinspection PhpIncludeInspection */
include_once ABSPATH . 'wp-admin/includes/plugin.php';
}
// Check if WordPress 5.0 or higher.
$is_wp5point0 = version_compare( $wp_version, '4.9.9', '>' );
if ( $is_wp5point0 ) {
$this->enabled = ! is_plugin_active( 'classic-editor/classic-editor.php' );
} else {
$this->enabled = is_plugin_active( 'gutenberg/gutenberg.php' );
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Smush\Core\Integrations;
use Smush\Core\Helper;
/**
* @method identify( int $user_id )
* @method register( string $property, mixed $value )
* @method registerAll( array $properties )
* @method track( string $event, array $properties = array() )
*/
class Mixpanel {
private $mixpanel;
public function __construct( $project_token ) {
$this->mixpanel = class_exists( '\Mixpanel' ) && method_exists( '\Mixpanel', 'getInstance' )
? \Mixpanel::getInstance( $project_token, array(
'error_callback' => array( $this, 'handle_error' ),
) )
: null;
}
public function handle_error( $code, $data ) {
Helper::logger()->error( "$code: $data" );
}
public function __call( $name, $arguments ) {
if ( method_exists( $this->mixpanel, $name ) ) {
return call_user_func_array(
array( $this->mixpanel, $name ),
$arguments
);
}
return null;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,866 @@
<?php
/**
* Adds the Bulk Page and Smush Column to NextGen Gallery
*
* @package Smush\Core\Integrations\NextGen
* @version 1.0
*
* @author Umesh Kumar <umesh@incsub.com>
*
* @copyright (c) 2016, Incsub (http://incsub.com)
*/
namespace Smush\Core\Integrations\NextGen;
use C_Component_Registry;
use C_Gallery_Storage;
use nggdb;
use Smush\App\Media_Library;
use Smush\Core\Core;
use Smush\Core\Helper;
use Smush\Core\Integrations\NextGen;
use Smush\Core\Settings;
use stdClass;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Admin
*/
class Admin extends NextGen {
/**
* Total image count.
*
* @var int $total_count
*/
public $total_count = 0;
/**
* Count of images ( Attachments ), Does not includes additional sizes that might have been created.
*
* @var int $smushed_count
*/
public $smushed_count = 0;
/**
* Includes the count of different sizes an image might have
*
* @var int $image_count
*/
public $image_count = 0;
/**
* Remaining count.
*
* @var int $remaining_count
*/
public $remaining_count = 0;
/**
* Super Smushed.
*
* @var int $super_smushed
*/
public $super_smushed = 0;
/**
* Smushed images.
*
* @var array $smushed
*/
public $smushed = array();
/**
* Stores all lossless smushed IDs.
*
* @var array $resmush_ids
*/
public $resmush_ids = array();
/**
* Stats class object.
*
* @var Stats
*/
public $ng_stats;
protected $settings;
/**
* Admin constructor.
*
* @param Stats $stats Class object.
*/
public function __construct( Stats $stats ) {
$this->ng_stats = $stats;
$this->settings = Settings::get_instance();
// Update the number of columns.
add_filter( 'ngg_manage_images_number_of_columns', array( $this, 'wp_smush_manage_images_number_of_columns' ) );
// Update resmush list, if a NextGen image is deleted.
add_action( 'ngg_delete_picture', array( $this, 'update_resmush_list' ) );
// Update Stats, if a NextGen image is deleted.
add_action( 'ngg_delete_picture', array( $this, 'update_nextgen_stats' ) );
// Update Stats, Lists - if a NextGen Gallery is deleted.
add_action( 'ngg_delete_gallery', array( $this->ng_stats, 'update_stats_cache' ) );
// Update the Super Smush count, after the smushing.
add_action( 'wp_smush_image_optimised_nextgen', array( $this, 'update_lists_after_optimizing' ), '', 2 );
// Reset smush data after restoring the image.
add_action( 'ngg_recovered_image', array( $this, 'reset_smushdata' ) );
add_action( 'wp_ajax_nextgen_get_stats', array( $this, 'ajax_get_stats' ) );
add_filter( 'wp_smush_nextgen_scan_stats', array( $this, 'scan_images' ) );
}
/**
* Returns a column name for WP Smush.
*
* @param array $columns Current columns.
*
* @return array|string
*/
public function wp_smush_image_column_name( $columns ) {
// Latest next gen takes string, while the earlier WP Smush plugin shows there use to be a array.
if ( is_array( $columns ) ) {
$columns['wp_smush_image'] = esc_html__( 'Smush', 'wp-smushit' );
} else {
$columns = esc_html__( 'Smush', 'wp-smushit' );
}
return $columns;
}
/**
* Returns Smush option / Stats, depending if image is already smushed or not.
*
* @param string $column_name Column name.
* @param object|int $id Image object or ID.
*
* @return array|bool|string|void
*/
public function wp_smush_column_options( $column_name, $id ) {
// NExtGen Doesn't returns Column name, weird? yeah, right, it is proper because hook is called for the particular column.
if ( 'wp_smush_image' === $column_name || '' === $column_name ) {
// We're not using our in-house function Smush\Core\Integrations\Nextgen::get_nextgen_image_from_id()
// as we're already instializing the nextgen gallery object, we need $storage instance later.
// Registry Object for NextGen Gallery.
$registry = C_Component_Registry::get_instance();
/**
* Gallery Storage Object.
*
* @var C_Gallery_Storage $storage
*/
$storage = $registry->get_utility( 'I_Gallery_Storage' );
// We'll get the image object in $id itself, else fetch it using Gallery Storage.
if ( is_object( $id ) ) {
$image = $id;
} else {
// get an image object.
$image = $storage->object->_image_mapper->find( $id );
}
// Check if it is supported image format, get image type to do that get the absolute path.
$file_path = $storage->get_image_abspath( $image, 'full' );
// Get image type from file path.
$image_type = $this->get_file_type( $file_path );
// If image type not supported.
if ( ! $image_type || ! in_array( $image_type, Core::$mime_types, true ) ) {
return;
}
$image->meta_data = $this->get_combined_stats( $image->meta_data );
// Check Image metadata, if smushed, print the stats or super smush button.
if ( ! empty( $image->meta_data['wp_smush'] ) ) {
// Echo the smush stats.
return $this->show_stats( $image->pid, $image->meta_data['wp_smush'], $image_type );
}
// Print the status of image, if Not smushed.
return $this->set_status( $image->pid );
}
}
/**
* Localize Translations And Stats
*/
public function localize() {
$handle = 'smush-admin';
$upgrade_url = add_query_arg(
array(
'utm_source' => 'smush',
'utm_medium' => 'plugin',
'utm_campaign' => 'smush_bulksmush_issues_filesizelimit_notice',
),
'https://wpmudev.com/project/wp-smush-pro/'
);
if ( WP_Smush::is_pro() ) {
$error_in_bulk = esc_html__( '{{smushed}}/{{total}} images were successfully compressed, {{errors}} encountered issues.', 'wp-smushit' );
} else {
$error_in_bulk = sprintf(
/* translators: %1$s - opening link tag, %2$s - Close the link </a> */
esc_html__( '{{smushed}}/{{total}} images were successfully compressed, {{errors}} encountered issues. Are you hitting the 5MB "size limit exceeded" warning? %1$sUpgrade to Smush Pro%2$s to optimize unlimited image files.', 'wp-smushit' ),
'<a href="' . esc_url( $upgrade_url ) . '" target="_blank">',
'</a>'
);
}
$wp_smush_msgs = array(
'nonce' => wp_create_nonce( 'wp-smush-ajax' ),
'resmush' => esc_html__( 'Super-Smush', 'wp-smushit' ),
'smush_now' => esc_html__( 'Smush Now', 'wp-smushit' ),
'error_in_bulk' => $error_in_bulk,
'all_resmushed' => esc_html__( 'All images are fully optimized.', 'wp-smushit' ),
'restore' => esc_html__( 'Restoring image...', 'wp-smushit' ),
'smushing' => esc_html__( 'Smushing image...', 'wp-smushit' ),
);
wp_localize_script( $handle, 'wp_smush_msgs', $wp_smush_msgs );
$data = $this->ng_stats->get_global_stats();
wp_localize_script( $handle, 'wp_smushit_data', $data );
}
/**
* Increase the count of columns for Nextgen Gallery Manage page.
*
* @param int $count Current columns count.
*
* @return int
*/
public function wp_smush_manage_images_number_of_columns( $count ) {
$count ++;
// Add column Heading.
add_filter( "ngg_manage_images_column_{$count}_header", array( $this, 'wp_smush_image_column_name' ) );
// Add Column data.
add_filter( "ngg_manage_images_column_{$count}_content", array( $this, 'wp_smush_column_options' ), 10, 2 );
return $count;
}
/**
* Set send button status
*
* @param int $pid ID.
*
* @return string
*/
private function set_status( $pid ) {
// the status.
$status_txt = __( 'Not processed', 'wp-smushit' );
// we need to show the smush button.
$show_button = true;
// the button text.
$button_txt = __( 'Smush', 'wp-smushit' );
// If we are not showing smush button, append progress bar, else it is already there.
if ( ! $show_button ) {
$status_txt .= Media_Library::progress_bar();
}
return $this->column_html( $pid, $status_txt, $button_txt, $show_button );
}
/**
* Print the column html
*
* @param string $pid Media id.
* @param string $status_txt Status text.
* @param string $button_txt Button label.
* @param boolean $show_button Whether to shoe the button.
* @param bool $smushed Image compressed or not.
*
* @return string|void
*/
public function column_html( $pid, $status_txt = '', $button_txt = '', $show_button = true, $smushed = false ) {
$class = $smushed ? '' : ' sui-hidden';
$html = '<p class="smush-status' . $class . '">' . $status_txt . '</p>';
// if we aren't showing the button.
if ( ! $show_button ) {
return $html;
}
$html .= '<div class="sui-smush-media smush-status-links">';
$html .= wp_nonce_field( 'wp_smush_nextgen', '_wp_smush_nonce', '', false );
$html .= '<button class="button button-primary wp-smush-nextgen-send" data-id="' . $pid . '">
<span>' . $button_txt . '</span>
</button>';
$html .= '</div>';
return $html;
}
/**
* Updates the resmush list for NextGen gallery, remove the given id
*
* @param int $attachment_id Attachment ID.
*/
public function update_resmush_list( $attachment_id ) {
if ( $this->ng_stats->get_reoptimize_list()->has_id( $attachment_id ) ) {
return $this->ng_stats->get_reoptimize_list()->remove_id( $attachment_id );
}
return $this->ng_stats->get_reoptimize_list()->add_id( $attachment_id );
}
/**
* Fetch the stats for the given attachment id, and subtract them from Global stats
*
* @param int $attachment_id Attachment ID.
*
* @return bool
*/
public function update_nextgen_stats( $attachment_id ) {
if ( empty( $attachment_id ) ) {
return false;
}
$image_id = absint( (int) $attachment_id );
// Get the absolute path for original image.
$image = $this->get_nextgen_image_from_id( $image_id );
// Image Metadata.
$metadata = ! empty( $image ) ? $image->meta_data : '';
$smush_stats = ! empty( $metadata['wp_smush'] ) ? $metadata['wp_smush'] : '';
if ( empty( $smush_stats ) ) {
return false;
}
$nextgen_stats = get_option( 'wp_smush_stats_nextgen', false );
if ( ! $nextgen_stats ) {
return false;
}
if ( ! empty( $nextgen_stats['size_before'] ) && ! empty( $nextgen_stats['size_after'] ) && $nextgen_stats['size_before'] > 0 && $nextgen_stats['size_after'] > 0 && $nextgen_stats['size_before'] >= $smush_stats['stats']['size_before'] ) {
$nextgen_stats['size_before'] = $nextgen_stats['size_before'] - $smush_stats['stats']['size_before'];
$nextgen_stats['size_after'] = $nextgen_stats['size_after'] - $smush_stats['stats']['size_after'];
$nextgen_stats['bytes'] = $nextgen_stats['size_before'] - $nextgen_stats['size_after'];
if ( 0 === $nextgen_stats['bytes'] && 0 === $nextgen_stats['size_before'] ) {
$nextgen_stats['percent'] = 0;
} else {
$nextgen_stats['percent'] = ( $nextgen_stats['bytes'] / $nextgen_stats['size_before'] ) * 100;
}
$nextgen_stats['human'] = size_format( $nextgen_stats['bytes'], 1 );
}
// Update Stats.
update_option( 'wp_smush_stats_nextgen', $nextgen_stats, false );
// Remove from Super Smush list.
$this->ng_stats->get_supper_smushed_list()->remove_id( $image_id );
}
/**
* Update the Super Smush count for NextGen Gallery
*
* @param int $image_id Image ID.
* @param array $stats Stats.
*/
public function update_lists_after_optimizing( $image_id, $stats ) {
if ( isset( $stats['stats']['lossy'] ) && 1 === (int) $stats['stats']['lossy'] ) {
$this->ng_stats->get_supper_smushed_list()->add_id( $image_id );
}
$this->update_resmush_list( $image_id );
}
/**
* Initialize NextGen Gallery Stats
*/
public function setup_image_counts() {
$this->total_count = $this->ng_stats->total_count();
$this->smushed_count = $this->ng_stats->get_smushed_count();
$this->image_count = $this->ng_stats->get_smushed_image_count();
$this->resmush_ids = $this->ng_stats->get_reoptimize_list()->get_ids();
$this->super_smushed = $this->ng_stats->get_supper_smushed_count();
$this->remaining_count = $this->ng_stats->get_remaining_count();
}
/**
* Get the image count for nextgen images
*
* @param array $images Array of attachments to get the image count for.
* @param bool $exclude_resmush_ids Whether to exclude resmush ids or not.
*
* @return int
*/
public function get_image_count( $images = array(), $exclude_resmush_ids = true ) {
if ( empty( $images ) || ! is_array( $images ) ) {
return 0;
}
$image_count = 0;
// $image in here is expected to be metadata array
foreach ( $images as $image_k => $image ) {
// Get image object if not there already.
if ( ! is_array( $image ) ) {
$image = $this->get_nextgen_image_from_id( $image );
// Get the meta.
$image = ! empty( $image->meta_data ) ? $image->meta_data : '';
}
// If there are no smush stats, skip.
if ( empty( $image['wp_smush'] ) ) {
continue;
}
// If resmush ids needs to be excluded.
if ( $exclude_resmush_ids && ( ! empty( $this->resmush_ids ) && in_array( $image_k, $this->resmush_ids ) ) ) {
continue;
}
// Get the image count.
if ( ! empty( $image['wp_smush']['sizes'] ) ) {
$image_count += count( $image['wp_smush']['sizes'] );
}
}
return $image_count;
}
/**
* Combine the resizing stats and smush stats , One time operation - performed during the image optimization
*
* @param array $metadata Image metadata.
*
* @return mixed
*/
private function get_combined_stats( $metadata ) {
if ( empty( $metadata ) ) {
return $metadata;
}
$smush_stats = ! empty( $metadata['wp_smush'] ) ? $metadata['wp_smush'] : '';
$resize_savings = ! empty( $metadata['wp_smush_resize_savings'] ) ? $metadata['wp_smush_resize_savings'] : '';
if ( empty( $resize_savings ) || empty( $smush_stats ) ) {
return $metadata;
}
$smush_stats['stats']['bytes'] = ! empty( $resize_savings['bytes'] ) ? $smush_stats['stats']['bytes'] + $resize_savings['bytes'] : $smush_stats['stats']['bytes'];
$smush_stats['stats']['size_before'] = ! empty( $resize_savings['size_before'] ) ? $smush_stats['stats']['size_before'] + $resize_savings['size_before'] : $smush_stats['stats']['size_before'];
$smush_stats['stats']['size_after'] = ! empty( $resize_savings['size_after'] ) ? $smush_stats['stats']['size_after'] + $resize_savings['size_after'] : $smush_stats['stats']['size_after'];
$smush_stats['stats']['percent'] = ! empty( $resize_savings['size_before'] ) ? ( $smush_stats['stats']['bytes'] / $smush_stats['stats']['size_before'] ) * 100 : $smush_stats['stats']['percent'];
// Round off.
$smush_stats['stats']['percent'] = round( $smush_stats['stats']['percent'], 2 );
if ( ! empty( $smush_stats['sizes']['full'] ) ) {
// Full Image.
$smush_stats['sizes']['full']['bytes'] = ! empty( $resize_savings['bytes'] ) ? $smush_stats['sizes']['full']['bytes'] + $resize_savings['bytes'] : $smush_stats['sizes']['full']['bytes'];
$smush_stats['sizes']['full']['size_before'] = ! empty( $resize_savings['size_before'] ) ? $smush_stats['sizes']['full']['size_before'] + $resize_savings['size_before'] : $smush_stats['sizes']['full']['size_before'];
$smush_stats['sizes']['full']['size_after'] = ! empty( $resize_savings['size_after'] ) ? $smush_stats['sizes']['full']['size_after'] + $resize_savings['size_after'] : $smush_stats['sizes']['full']['size_after'];
$smush_stats['sizes']['full']['percent'] = ! empty( $smush_stats['sizes']['full']['bytes'] ) && $smush_stats['sizes']['full']['size_before'] > 0 ? ( $smush_stats['sizes']['full']['bytes'] / $smush_stats['sizes']['full']['size_before'] ) * 100 : $smush_stats['sizes']['full']['percent'];
$smush_stats['sizes']['full']['percent'] = round( $smush_stats['sizes']['full']['percent'], 2 );
} else {
$smush_stats['sizes']['full'] = $resize_savings;
}
$metadata['wp_smush'] = $smush_stats;
return $metadata;
}
/**
* Reset smush data after restoring the image.
*
* @since 3.10.0
*
* @param stdClass $image Image object for NextGen gallery.
* @param false|string $attachment_file_path The full file path, if it's provided we will reset the dimension.
*/
public function reset_smushdata( $image, $attachment_file_path = false ) {
if ( empty( $image->meta_data['wp_smush'] ) && empty( $image->meta_data['wp_smush_resize_savings'] ) ) {
return;
}
$this->ng_stats->subtract_image_stats( $image );
// Remove the Meta, And send json success.
$image->meta_data['wp_smush'] = '';
// Remove resized data.
if ( ! empty( $image->meta_data['wp_smush_resize_savings'] ) ) {
$image->meta_data['wp_smush_resize_savings'] = '';
if ( $attachment_file_path && file_exists( $attachment_file_path ) ) {
// Update the dimension.
list( $width, $height ) = getimagesize( $attachment_file_path );
if ( $width ) {
$image->meta_data['width'] = $width;
$image->meta_data['full']['width'] = $width;
}
if ( $height ) {
$image->meta_data['height'] = $height;
$image->meta_data['full']['height'] = $height;
}
}
}
// Update metadata.
nggdb::update_image_meta( $image->pid, $image->meta_data );
/**
* Called after the image has been successfully restored
*
* @since 3.7.0
*
* @param int $image_id ID of the restored image.
*/
do_action( 'wp_smush_image_nextgen_restored', $image->pid );
}
public function ajax_get_stats() {
check_ajax_referer( 'wp-smush-ajax', '_nonce' );
// Check capability.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_send_json_error(
array(
'notice' => esc_html__( "You don't have permission to do this.", 'wp-smushit' ),
'noticeType' => 'error',
)
);
}
$stats = $this->get_global_stats_with_bulk_smush_content_and_notice();
wp_send_json_success( $stats );
}
private function get_global_stats_with_bulk_smush_content_and_notice() {
$stats = $this->get_global_stats_with_bulk_smush_content();
$remaining_count = $stats['remaining_count'];
if ( $remaining_count > 0 ) {
$stats['noticeType'] = 'warning';
$stats['notice'] = sprintf(
/* translators: %1$d - number of images, %2$s - opening a tag, %3$s - closing a tag */
esc_html__( 'Image check complete, you have %1$d images that need smushing. %2$sBulk smush now!%3$s', 'wp-smushit' ),
$remaining_count,
'<a href="#" class="wp-smush-trigger-nextgen-bulk">',
'</a>'
);
} else {
$stats['notice'] = esc_html__( 'Yay! All images are optimized as per your current settings.', 'wp-smushit' );
$stats['noticeType'] = 'success';
}
return $stats;
}
private function get_global_stats_with_bulk_smush_content() {
$stats = $this->ng_stats->get_global_stats();
$remaining_count = $stats['remaining_count'];
$reoptimize_count = $stats['count_resmush'];
$optimize_count = $stats['count_unsmushed'];
if ( $remaining_count > 0 ) {
ob_start();
WP_Smush::get_instance()->admin()->print_pending_bulk_smush_content(
$remaining_count,
$reoptimize_count,
$optimize_count
);
$content = ob_get_clean();
$stats['content'] = $content;
}
return $stats;
}
public function scan_images() {
$resmush_list = array();
$attachments = $this->ng_stats->get_ngg_images();
// Check if any of the smushed image needs to be resmushed.
if ( ! empty( $attachments ) && is_array( $attachments ) ) {
foreach ( $attachments as $attachment_k => $metadata ) {
$smush_data = ! empty( $metadata['wp_smush'] ) ? $metadata['wp_smush'] : array();
if ( $this->should_resmush( $smush_data ) ) {
$resmush_list[] = $attachment_k;
}
}// End of Foreach Loop
// Store the resmush list in Options table.
$this->ng_stats->get_reoptimize_list()->update_ids( $resmush_list );
}
// Delete resmush list if empty.
if ( empty( $resmush_list ) ) {
$this->ng_stats->get_reoptimize_list()->delete_ids();
}
return $this->get_global_stats_with_bulk_smush_content_and_notice();
}
private function should_resmush( $smush_data ) {
if ( empty( $smush_data['stats'] ) ) {
return false;
}
return $this->lossy_optimization_required( $smush_data )
|| $this->strip_exif_optimization_required( $smush_data )
|| $this->original_optimization_required( $smush_data );
}
private function lossy_optimization_required( $smush_data ) {
$required_lossy_level = $this->settings->get_lossy_level_setting();
$current_lossy_level = ! empty( $smush_data['stats']['lossy'] ) ? (int) $smush_data['stats']['lossy'] : 0;
return $current_lossy_level < $required_lossy_level;
}
private function strip_exif_optimization_required( $smush_data ) {
return $this->settings->get( 'strip_exif' ) && ! empty( $smush_data['stats']['keep_exif'] ) && ( 1 === (int) $smush_data['stats']['keep_exif'] );
}
private function original_optimization_required( $smush_data ) {
return $this->settings->get( 'original' ) && empty( $smush_data['sizes']['full'] );
}
/**
* Display the smush stats for the image
*
* @param int $pid Image Id stored in nextgen table.
* @param bool|array $wp_smush_data Stats, stored after smushing the image.
* @param string $image_type Used for determining if not gif, to show the Super Smush button.
*
* @uses Admin::column_html(), WP_Smush::get_restore_link(), WP_Smush::get_resmush_link()
*
* @return bool|array|string
*/
public function show_stats( $pid, $wp_smush_data = false, $image_type = '' ) {
if ( empty( $wp_smush_data ) ) {
return false;
}
$button_txt = '';
$show_button = false;
$show_resmush = false;
$bytes = isset( $wp_smush_data['stats']['bytes'] ) ? $wp_smush_data['stats']['bytes'] : 0;
$bytes_readable = ! empty( $bytes ) ? size_format( $bytes, 1 ) : '';
$percent = isset( $wp_smush_data['stats']['percent'] ) ? $wp_smush_data['stats']['percent'] : 0;
$percent = $percent < 0 ? 0 : $percent;
$status_txt = '';
if ( isset( $wp_smush_data['stats']['size_before'] ) && $wp_smush_data['stats']['size_before'] == 0 && ! empty( $wp_smush_data['sizes'] ) ) {
$status_txt = __( 'Already Optimized', 'wp-smushit' );
} else {
if ( 0 === (int) $bytes || 0 === (int) $percent ) {
$status_txt = __( 'Already Optimized', 'wp-smushit' );
// Add resmush option if needed.
$show_resmush = $this->should_resmush( $wp_smush_data );
if ( $show_resmush ) {
$status_txt .= '<div class="sui-smush-media smush-status-links">';
$status_txt .= $this->get_resmsuh_link( $pid );
$status_txt .= '</div>';
}
} elseif ( ! empty( $percent ) && ! empty( $bytes_readable ) ) {
$status_txt = sprintf( /* translators: %1$s: reduced by bytes, %2$s: size format */
__( 'Reduced by %1$s (%2$01.1f%%)', 'wp-smushit' ),
$bytes_readable,
number_format_i18n( $percent, 2 )
);
$status_txt .= '<div class="sui-smush-media smush-status-links">';
$show_resmush = $this->should_resmush( $wp_smush_data );
if ( $show_resmush ) {
$status_txt .= $this->get_resmsuh_link( $pid );
}
// Restore Image: Check if we need to show the restore image option.
$show_restore = $this->show_restore_option( $pid, $wp_smush_data );
if ( $show_restore ) {
if ( $show_resmush ) {
// Show Separator.
$status_txt .= ' | ';
}
$status_txt .= $this->get_restore_link( $pid );
}
// Show detailed stats if available.
if ( ! empty( $wp_smush_data['sizes'] ) ) {
if ( $show_resmush || $show_restore ) {
// Show Separator.
$status_txt .= ' | ';
} else {
// Show the link in next line.
$status_txt .= '<br />';
}
// Detailed Stats Link.
$status_txt .= '<a href="#" class="smush-stats-details">' . esc_html__( 'Smush stats', 'wp-smushit' ) . ' [<span class="stats-toggle">+</span>]</a>';
// Get metadata For the image
// Registry Object for NextGen Gallery.
$registry = C_Component_Registry::get_instance();
/**
* Gallery Storage Object.
*
* @var C_Gallery_Storage $storage
*/
$storage = $registry->get_utility( 'I_Gallery_Storage' );
// get an array of sizes available for the $image.
$sizes = $storage->get_image_sizes();
$image = $storage->object->_image_mapper->find( $pid );
$full_image = $storage->get_image_abspath( $image, 'full' );
// Stats.
$stats = $this->get_detailed_stats( $pid, $wp_smush_data, array( 'sizes' => $sizes ), $full_image );
$status_txt .= $stats;
$status_txt .= '</div>';
}
}
}
// If show button is true for some reason, column html can print out the button for us.
return $this->column_html( $pid, $status_txt, $button_txt, $show_button, true );
}
/**
* Returns the Stats for a image formatted into a nice table
*
* @param int $image_id Image ID.
* @param array $wp_smush_data Smush data.
* @param array $attachment_metadata Attachment metadata.
* @param string $full_image Full sized image.
*
* @return string
*/
private function get_detailed_stats( $image_id, $wp_smush_data, $attachment_metadata, $full_image ) {
$stats = '<div id="smush-stats-' . $image_id . '" class="smush-stats-wrapper hidden">
<table class="wp-smush-stats-holder">
<thead>
<tr>
<th><strong>' . esc_html__( 'Image size', 'wp-smushit' ) . '</strong></th>
<th><strong>' . esc_html__( 'Savings', 'wp-smushit' ) . '</strong></th>
</tr>
</thead>
<tbody>';
$size_stats = $wp_smush_data['sizes'];
// Reorder Sizes as per the maximum savings.
uasort( $size_stats, array( $this, 'cmp' ) );
// Show Sizes and their compression.
foreach ( $size_stats as $size_key => $size_value ) {
$size_value = ! is_object( $size_value ) ? (object) $size_value : $size_value;
if ( $size_value->bytes > 0 ) {
$stats .= '<tr>
<td>' . strtoupper( $size_key ) . '</td>
<td>' . size_format( $size_value->bytes, 1 );
}
// Add percentage if set.
if ( isset( $size_value->percent ) && $size_value->percent > 0 ) {
$stats .= " ( $size_value->percent% )";
}
$stats .= '</td>
</tr>';
}
$stats .= '</tbody>
</table>
</div>';
return $stats;
}
/**
* Compare Values
*
* @param object|array $a First object.
* @param object|array $b Second object.
*
* @return int
*/
public function cmp( $a, $b ) {
if ( is_object( $a ) ) {
// Check and typecast $b if required.
$b = is_object( $b ) ? $b : (object) $b;
return $b->bytes - $a->bytes ;
} elseif ( is_array( $a ) ) {
$b = is_array( $b ) ? $b : (array) $b;
return $b['bytes'] - $a['bytes'];
}
}
/**
* Generates a Resmush link for a image.
*
* @param int $image_id Attachment ID.
* @param string $type Type of attachment.
*
* @return bool|string
*/
private function get_resmsuh_link( $image_id ) {
if ( empty( $image_id ) ) {
return false;
}
$class = 'wp-smush-action wp-smush-title sui-tooltip sui-tooltip-constrained wp-smush-nextgen-resmush';
return sprintf(
'<a href="#" data-tooltip="%s" data-id="%d" data-nonce="%s" class="%s">%s</a>',
esc_html__( 'Smush image including original file', 'wp-smushit' ),
$image_id,
wp_create_nonce( 'wp-smush-resmush-' . $image_id ),
$class,
esc_html__( 'Resmush', 'wp-smushit' )
);
}
/**
* Returns a restore link for given image id
*
* @param int $image_id Attachment ID.
* @param string $type Attachment type.
*
* @return bool|string
*/
private function get_restore_link( $image_id ) {
if ( empty( $image_id ) ) {
return false;
}
$class = 'wp-smush-action wp-smush-title sui-tooltip wp-smush-nextgen-restore';
return sprintf(
'<a href="#" data-tooltip="%s" data-id="%d" data-nonce="%s" class="%s">%s</a>',
esc_html__( 'Restore original image', 'wp-smushit' ),
$image_id,
wp_create_nonce( 'wp-smush-restore-' . $image_id ),
$class,
esc_html__( 'Restore', 'wp-smushit' )
);
}
}

View File

@@ -0,0 +1,686 @@
<?php
/**
* Handles all the stats related functions
*
* @package Smush\Core\Integrations\NextGen
* @version 1.0
*
* @author Umesh Kumar <umesh@incsub.com>
*
* @copyright (c) 2016, Incsub (http://incsub.com)
*/
namespace Smush\Core\Integrations\NextGen;
use C_Component_Registry;
use C_Gallery_Storage;
use C_NextGen_Serializable;
use Exception;
use Ngg_Serializable;
use Smush\Core\Attachment_Id_List;
use Smush\Core\Integrations\NextGen;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class
*
* TODO refactor stats by using the new core stats to clean the code.
*/
class Stats extends NextGen {
const REOPTIMIZE_LIST_OPTION_ID = 'wp-smush-nextgen-reoptimize-list';
const SUPPER_SMUSHED_LIST_OPTION_ID = 'wp-smush-nextgen-super-smushed-list';
const SMUSH_STATS_OPTION_ID = 'wp_smush_stats_nextgen';
/**
* Contains the total Stats, for displaying it on bulk page
*
* @var array
*/
public $stats = array();
/**
* PRO user status.
*
* @var bool
*/
private $is_pro_user;
/**
* @var Attachment_Id_List
*/
private $reoptimize_list;
/**
* @var Attachment_Id_List
*/
private $supper_smushed_list;
/**
* @var null|array
*/
private $global_stats;
/**
* @var null|array
*/
private $unsmushed_images;
/**
* @var null|int.
*/
private $remaining_count;
/**
* @var int
*/
private $percent_optimized = 0;
/**
* Stats constructor.
*/
public function __construct() {
parent::__construct();
$this->is_pro_user = WP_Smush::is_pro();
$this->reoptimize_list = new Attachment_Id_List( self::REOPTIMIZE_LIST_OPTION_ID );
$this->supper_smushed_list = new Attachment_Id_List( self::SUPPER_SMUSHED_LIST_OPTION_ID );
// Clear stats cache when an image is restored.
add_action( 'wp_smush_image_nextgen_restored', array( $this, 'clear_cache' ) );
// Add the resizing stats to Global stats.
add_action( 'wp_smush_image_nextgen_resized', array( $this, 'update_stats' ), '', 2 );
// Get the stats for single image, update the global stats.
add_action( 'wp_smush_nextgen_image_stats', array( $this, 'update_stats' ), '', 2 );
}
/**
* Get the images id for nextgen gallery
*
* @param bool $force_refresh Optional. Whether to force the cache to be refreshed.
* Default false.
*
* @param bool $return_ids Whether to return the ids array, set to false by default.
*
* @return int|mixed Returns the images ids or the count
*/
public static function total_count( $force_refresh = false, $return_ids = false ) {
// Check for the wp_smush_images in the 'nextgen' group.
$attachment_ids = wp_cache_get( 'wp_smush_images', 'nextgen' );
// If nothing is found, build the object.
if ( true === $force_refresh || false === $attachment_ids ) {
// Get the nextgen image IDs.
$attachment_ids = self::get_nextgen_attachments();
if ( ! is_wp_error( $attachment_ids ) ) {
// In this case we don't need a timed cache expiration.
wp_cache_set( 'wp_smush_images', $attachment_ids, 'nextgen' );
}
}
return $return_ids ? $attachment_ids : count( $attachment_ids );
}
/**
* Returns the ngg images list(id and meta ) or count
*
* @param string $type Whether to return smushed images or unsmushed images.
* @param bool|false $count Return count only.
* @param bool|false $force_update True/false to update the cache or not.
*
* @return bool|mixed Returns assoc array of image ids and meta or Image count
*
* @throws Exception Exception.
*/
public function get_ngg_images( $type = 'smushed', $count = false, $force_update = false ) {
global $wpdb;
$limit = apply_filters( 'wp_smush_nextgen_query_limit', 1000 );
$offset = 0;
// Check type of images being queried.
if ( ! in_array( $type, array( 'smushed', 'unsmushed' ), true ) ) {
return false;
}
// Check for the wp_smush_images_smushed in the 'nextgen' group.
$images = wp_cache_get( 'wp_smush_images_' . $type, 'nextgen' );
// If nothing is found, build the object.
if ( ! $images || $force_update ) {
// Query Attachments for meta key.
$attachments = $wpdb->get_results( $wpdb->prepare( "SELECT pid, meta_data FROM {$wpdb->nggpictures} LIMIT %d, %d", $offset, $limit ) ); // Db call ok.
while ( ! empty( $attachments ) ) {
foreach ( $attachments as $attachment ) {
// Check if it has `wp_smush` key.
if ( class_exists( 'Ngg_Serializable' ) ) {
$meta = ( new Ngg_Serializable() )->unserialize( $attachment->meta_data );
} elseif ( class_exists( 'C_NextGen_Serializable' ) && method_exists( 'C_NextGen_Serializable', 'unserialize' ) ) {
$meta = C_NextGen_Serializable::unserialize( $attachment->meta_data );
} else {
// If you can't parse it without NextGen - don't parse at all.
continue;
}
// Store pid in image meta.
if ( is_array( $meta ) && empty( $meta['pid'] ) ) {
$meta['pid'] = $attachment->pid;
} elseif ( is_object( $meta ) && empty( $meta->pid ) ) {
$meta->pid = $attachment->pid;
}
// Check meta for wp_smush.
if ( ! is_array( $meta ) || empty( $meta['wp_smush'] ) ) {
$unsmushed_images[ $attachment->pid ] = $meta;
continue;
}
$smushed_images[ $attachment->pid ] = $meta;
}
// Set the offset.
$offset += $limit;
$attachments = $wpdb->get_results( $wpdb->prepare( "SELECT pid, meta_data FROM {$wpdb->nggpictures} LIMIT %d, %d", $offset, $limit ) ); // Db call ok.
}
if ( ! empty( $smushed_images ) ) {
wp_cache_set( 'wp_smush_images_smushed', $smushed_images, 'nextgen', 300 );
}
if ( ! empty( $unsmushed_images ) ) {
wp_cache_set( 'wp_smush_images_unsmushed', $unsmushed_images, 'nextgen', 300 );
}
}
if ( 'smushed' === $type ) {
$smushed_images = ! empty( $smushed_images ) ? $smushed_images : $images;
if ( ! $smushed_images ) {
return 0;
}
return $count ? count( $smushed_images ) : $smushed_images;
}
$unsmushed_images = ! empty( $unsmushed_images ) ? $unsmushed_images : $images;
if ( ! $unsmushed_images ) {
return 0;
}
return $count ? count( $unsmushed_images ) : $unsmushed_images;
}
/**
* Updated the global smush stats for NextGen gallery
*
* @param int $image_id Image ID.
* @param array $stats Compression stats fo respective image.
*/
public function update_stats( $image_id, $stats ) {
$stats = ! empty( $stats['stats'] ) ? $stats['stats'] : '';
$smush_stats = $this->get_cache_smush_stats();
if ( ! empty( $stats ) ) {
// Human Readable.
$smush_stats['human'] = ! empty( $smush_stats['bytes'] ) ? size_format( $smush_stats['bytes'], 1 ) : '';
// Size of images before the compression.
$smush_stats['size_before'] = ! empty( $smush_stats['size_before'] ) ? ( $smush_stats['size_before'] + $stats['size_before'] ) : $stats['size_before'];
// Size of image after compression.
$smush_stats['size_after'] = ! empty( $smush_stats['size_after'] ) ? ( $smush_stats['size_after'] + $stats['size_after'] ) : $stats['size_after'];
$smush_stats['bytes'] = ! empty( $smush_stats['size_before'] ) && ! empty( $smush_stats['size_after'] ) ? ( $smush_stats['size_before'] - $smush_stats['size_after'] ) : 0;
// Compression Percentage.
$smush_stats['percent'] = ! empty( $smush_stats['size_before'] ) && ! empty( $smush_stats['size_after'] ) && $smush_stats['size_before'] > 0 ? ( $smush_stats['bytes'] / $smush_stats['size_before'] ) * 100 : $stats['percent'];
}
update_option( self::SMUSH_STATS_OPTION_ID, $smush_stats, false );
$this->clear_cache();
}
/**
* Clears the object cache for NextGen stats.
*
* @since 3.7.0
*/
public function clear_cache() {
wp_cache_delete( 'wp_smush_images_smushed', 'nextgen' );
wp_cache_delete( 'wp_smush_images_unsmushed', 'nextgen' );
wp_cache_delete( 'wp_smush_images', 'nextgen' );
}
/**
* Get the attachment stats for a image
*
* @param object|array|int $id Attachment ID.
*
* @return array
*/
private function get_attachment_stats( $image ) {
// We'll get the image object in $image itself, else fetch it using Gallery Storage.
if ( is_numeric( $image ) ) {
// Registry Object for NextGen Gallery.
$registry = C_Component_Registry::get_instance();
// Gallery Storage Object.
$storage = $registry->get_utility( 'I_Gallery_Storage' );
// get an image object.
$image = $storage->object->_image_mapper->find( $image );
}
$smush_savings = $this->get_image_smush_savings( $image );
$resize_savings = $this->get_image_resize_savings( $image );
return $this->recalculate_stats( 'add', $smush_savings, $resize_savings );
}
/**
* Get the Nextgen Smush stats
*
* @return bool|mixed|void
*/
public function get_smush_stats() {
$smushed_stats = array(
'bytes' => 0,
'size_before' => 0,
'size_after' => 0,
'percent' => 0,
);
// Clear up the stats.
if ( 0 == $this->total_count() || $this->get_smushed_count() < 1 ) {
delete_option( self::SMUSH_STATS_OPTION_ID );
}
// Check for the wp_smush_images in the 'nextgen' group.
$stats = $this->get_cache_smush_stats();
$size_before = (int) $this->get_array_value( $stats, 'size_before' );
if ( empty( $size_before ) ) {
return $smushed_stats;
}
$size_after = (int) $this->get_array_value( $stats, 'size_after' );
$stats['bytes'] = $size_before - $size_after;
$stats['bytes'] = $stats['bytes'] > 0 ? $stats['bytes'] : 0;
$stats['percent'] = ( $stats['bytes'] / $stats['size_before'] ) * 100;
// Round off precentage.
$stats['percent'] = ! empty( $stats['percent'] ) ? round( $stats['percent'], 1 ) : 0;
$stats['human'] = size_format( $stats['bytes'], $stats['bytes'] >= 1024 ? 1 : 0 );
$smushed_stats = array_merge( $smushed_stats, $stats );
// Gotta remove the stats for re-smush ids.
if ( $this->get_reoptimize_list()->get_count() ) {
$resmush_stats = $this->get_stats_for_ids( $this->get_reoptimize_list()->get_ids() );
// Recalculate stats, Remove stats for resmush ids.
$smushed_stats = $this->recalculate_stats( 'sub', $smushed_stats, $resmush_stats );
}
return $smushed_stats;
}
/**
* Get the combined stats for given Ids
*
* @param array $ids Image IDs.
*
* @return array|bool Array of Stats for the given ids
*/
public function get_stats_for_ids( $ids = array() ) {
// Return if we don't have an array or no ids.
if ( ! is_array( $ids ) || empty( $ids ) ) {
return false;
}
// Initialize the Stats array.
$stats = array(
'size_before' => 0,
'size_after' => 0,
);
// Calculate the stats, Expensive Operation.
foreach ( $ids as $id ) {
$image_stats = $this->get_attachment_stats( $id );
$stats = $this->recalculate_stats( 'add', $stats, $image_stats );
}
return $stats;
}
/**
* Add/Subtract the values from 2nd array to First array
* This function is very specific to current requirement of stats re-calculation
*
* @param string $op 'add', 'sub' Add or Subtract the values.
* @param array $a1 First array.
* @param array $a2 Second array.
*
* @return array Return $a1
*/
private function recalculate_stats( $op = 'add', $a1 = array(), $a2 = array() ) {
// If the first array itself is not set, return.
if ( empty( $a1 ) ) {
return $a1;
}
// Iterate over keys in first array, and add/subtract the values.
foreach ( $a1 as $k => $v ) {
// If the key is not set in 2nd array, skip.
if ( empty( $a2[ $k ] ) || ! in_array( $k, array( 'size_before', 'size_after' ) ) ) {
continue;
}
// Else perform the operation, Considers the value to be integer, doesn't performs a check.
if ( 'sub' === $op ) {
// Subtract the value.
$a1[ $k ] -= $a2[ $k ];
} elseif ( 'add' === $op ) {
// Add the value.
$a1[ $k ] += $a2[ $k ];
}
}
// Recalculate percentage and human savings.
$a1['bytes'] = $a1['size_before'] - $a1['size_after'];
$a1['percent'] = $a1['bytes'] > 0 ? round( ( $a1['bytes'] / $a1['size_before'] ) * 100, 1 ) : 0;
$a1['human'] = $a1['bytes'] > 0 ? size_format( $a1['bytes'], 1 ) : 0;
return $a1;
}
/**
* Get Super smushed images from the given images array
*
* @param array $images Array of images containing metadata.
*
* @return array Array containing ids of supersmushed images
*/
private function get_super_smushed_images( $images = array() ) {
if ( empty( $images ) ) {
return array();
}
$super_smushed = array();
// Iterate Over all the images.
foreach ( $images as $image_k => $image ) {
if ( empty( $image ) || ! is_array( $image ) || ! isset( $image['wp_smush'] ) ) {
continue;
}
// Check for lossy compression.
if ( ! empty( $image['wp_smush']['stats'] ) && ! empty( $image['wp_smush']['stats']['lossy'] ) ) {
$super_smushed[] = $image_k;
}
}
return $super_smushed;
}
/**
* Recalculate stats for the given smushed ids and update the cache
* Update Super smushed image ids
*
* @throws Exception Exception.
*/
public function update_stats_cache() {
// Get the Image ids.
$smushed_images = $this->get_ngg_images( 'smushed' );
$super_smushed = array(
'ids' => array(),
'timestamp' => '',
);
$stats = $this->get_stats_for_ids( $smushed_images );
$lossy = $this->get_super_smushed_images( $smushed_images );
if ( empty( $stats['bytes'] ) && ! empty( $stats['size_before'] ) ) {
$stats['bytes'] = $stats['size_before'] - $stats['size_after'];
}
$stats['human'] = size_format( ! empty( $stats['bytes'] ) ? $stats['bytes'] : 0 );
if ( ! empty( $stats['size_before'] ) ) {
$stats['percent'] = ( $stats['bytes'] / $stats['size_before'] ) * 100;
$stats['percent'] = round( $stats['percent'], 2 );
}
// Update Re-smush list.
if ( is_array( WP_Smush::get_instance()->core()->nextgen->ng_admin->resmush_ids ) && is_array( $smushed_images ) ) {
$resmush_ids = array_intersect( WP_Smush::get_instance()->core()->nextgen->ng_admin->resmush_ids, array_keys( $smushed_images ) );
}
// If we have resmush ids, add it to db.
if ( ! empty( $resmush_ids ) ) {
// Update re-smush images to db.
$this->get_reoptimize_list()->update_ids( $resmush_ids );
}
// Update Super smushed images in db.
$this->get_supper_smushed_list()->update_ids( $lossy );
// Update Stats Cache.
update_option( self::SMUSH_STATS_OPTION_ID, $stats, false );
}
public function get_reoptimize_list() {
return $this->reoptimize_list;
}
public function get_supper_smushed_list() {
return $this->supper_smushed_list;
}
public function get_supper_smushed_count() {
return count( $this->get_supper_smushed() );
}
private function get_supper_smushed() {
$super_smushed = $this->get_supper_smushed_list()->get_ids();
// If we have images to be resmushed, Update supersmush list.
$resmush_ids = $this->get_reoptimize_list()->get_ids();
if ( ! empty( $resmush_ids ) && ! empty( $super_smushed ) ) {
$super_smushed = array_diff( $super_smushed, $resmush_ids );
}
// If supersmushed images are more than total, clean it up.
if ( count( $super_smushed ) > self::total_count() ) {
$super_smushed = $this->cleanup_super_smush_data();
}
return (array) $super_smushed;
}
/**
* Cleanup Super-smush images array against the all ids in gallery
*
* @return array|mixed|void
*/
private function cleanup_super_smush_data() {
$supper_smushed_list = $this->get_supper_smushed_list();
$super_smushed = $supper_smushed_list->get_ids();
$ids = self::total_count( false, true );
if ( ! empty( $super_smushed ) && is_array( $ids ) ) {
$super_smushed = array_intersect( $super_smushed, $ids );
}
$supper_smushed_list->update_ids( $super_smushed );
}
public function get_global_stats() {
if ( $this->global_stats ) {
return $this->global_stats;
}
$stats = $this->get_smush_stats();
$human_bytes = $this->get_array_value( $stats, 'human' );
if ( empty( $human_bytes ) ) {
$human_bytes = '0 B';
}
$this->global_stats = array(
'count_supersmushed' => $this->get_supper_smushed_count(),
'count_smushed' => $this->get_smushed_count(),
'count_total' => $this->total_count(),
'count_images' => $this->get_smushed_image_count(),
'count_resize' => 0,
'count_skipped' => 0,
'unsmushed' => $this->get_unsmushed_images(),
'count_unsmushed' => count( $this->get_unsmushed_images() ),
'resmush' => $this->get_reoptimize_list()->get_ids(),
'count_resmush' => $this->get_reoptimize_list()->get_count(),
'size_before' => $this->get_array_value( $stats, 'size_before' ),
'size_after' => $this->get_array_value( $stats, 'size_after' ),
'savings_bytes' => $this->get_array_value( $stats, 'bytes' ),
'human_bytes' => $human_bytes,
'savings_resize' => 0,
'savings_resize_human' => 0,
'savings_conversion' => 0,
'savings_dir_smush' => 0,
'savings_percent' => $this->get_array_value( $stats, 'percent' ),
'percent_grade' => $this->get_grade_class(),
'percent_metric' => $this->get_percent_metric(),
'percent_optimized' => $this->get_percent_optimized(),
'remaining_count' => $this->get_remaining_count(),
);
return $this->global_stats;
}
public function get_smushed_image_count() {
$ng_smushed_images = $this->get_ngg_images( 'smushed' );
if ( empty( $ng_smushed_images ) ) {
return 0;
}
$image_count = 0;
// $image in here is expected to be metadata array
foreach ( $ng_smushed_images as $pid => $image ) {
// If there are no smush stats, skip.
if ( empty( $image['wp_smush'] ) || $this->get_reoptimize_list()->has_id( $pid ) ) {
continue;
}
// Get the image count.
if ( ! empty( $image['wp_smush']['sizes'] ) ) {
$image_count += count( $image['wp_smush']['sizes'] );
}
}
return $image_count;
}
public function get_smushed_count() {
return $this->total_count() - $this->get_remaining_count();
}
public function get_unsmushed_images() {
if ( null !== $this->unsmushed_images ) {
return $this->unsmushed_images;
}
$ng_unsmushed_images = $this->get_ngg_images( 'unsmushed' );
if ( ! $ng_unsmushed_images ) {
return array();
}
$this->unsmushed_images = array_keys( $ng_unsmushed_images );
return $this->unsmushed_images;
}
public function get_remaining_count() {
if ( null === $this->remaining_count ) {
$unsmushed_images = $this->get_unsmushed_images();
$resmush_ids = $this->get_reoptimize_list()->get_ids();
$remaining_images = array_unique( array_merge( $resmush_ids, $unsmushed_images ) );
$this->remaining_count = count( $remaining_images );
}
return $this->remaining_count;
}
private function get_percent_optimized() {
$smushed_count = $this->get_smushed_count();
if ( $smushed_count < 1 ) {
return $this->percent_optimized;
}
$total_optimizable_count = $this->total_count();
$remaining_count = $this->get_remaining_count();
$this->percent_optimized = floor( ( $total_optimizable_count - $remaining_count ) * 100 / $total_optimizable_count );
if ( $this->percent_optimized > 100 ) {
$this->percent_optimized = 100;
} elseif ( $this->percent_optimized < 0 ) {
$this->percent_optimized = 0;
}
return $this->percent_optimized;
}
private function get_percent_metric() {
$percent_optimized = $this->get_percent_optimized();
return 0.0 === (float) $percent_optimized ? 100 : $percent_optimized;
}
private function get_grade_class() {
$percent_optimized = $this->get_percent_optimized();
if ( 0 === $percent_optimized ) {
return 'sui-grade-dismissed';
}
$grade = 'sui-grade-f';
if ( $percent_optimized >= 60 && $percent_optimized < 90 ) {
$grade = 'sui-grade-c';
} elseif ( $percent_optimized >= 90 ) {
$grade = 'sui-grade-a';
}
return $grade;
}
public function get_array_value( $array, $key ) {
return isset( $array[ $key ] ) ? $array[ $key ] : null;
}
public function subtract_image_stats( $image ) {
$stats = $this->get_cache_smush_stats();
$stats = $this->recalculate_stats( 'sub', $stats, $this->get_attachment_stats( $image ) );
$this->update_smush_stats( $stats );
}
private function get_image_smush_savings( $image ) {
$image = (array) $image;
if ( ! empty( $image['meta_data']['wp_smush']['stats'] ) ) {
return $image['meta_data']['wp_smush']['stats'];
}
if ( ! empty( $image['wp_smush']['stats'] ) ) {
return $image['wp_smush']['stats'];
}
return array();
}
private function get_image_resize_savings( $image ) {
$image = (array) $image;
if ( ! empty( $image['meta_data']['wp_smush_resize_savings'] ) ) {
return $image['meta_data']['wp_smush_resize_savings'];
}
if ( ! empty( $image['wp_smush_resize_savings'] ) ) {
return $image['wp_smush_resize_savings'];
}
return array();
}
private function update_smush_stats( $stats ) {
return update_option( self::SMUSH_STATS_OPTION_ID, $stats );
}
private function get_cache_smush_stats() {
return get_option( self::SMUSH_STATS_OPTION_ID, array() );
}
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* Extend NextGen Mixin class to smush dynamic images.
*
* @package Smush\Core\Integrations\NextGen
*/
namespace Smush\Core\Integrations\NextGen;
use C_Component_Registry;
use C_Gallery_Storage;
use C_Image;
use Mixin;
use nggdb;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Thumbs
*/
class Thumbs extends Mixin {
/**
* Overrides the NextGen Gallery function, to smush the dynamic images and thumbnails created by gallery
*
* @param int|object|C_Image $image Image object.
* @param string $size Size.
* @param array|null $params Optional. Parameters array.
* @param bool $skip_defaults Optional. Skip defaults.
*
* @return bool|object
*/
function generate_image_size( $image, $size, $params = null, $skip_defaults = false ) {
$smush = WP_Smush::get_instance()->core()->mod->smush;
$image_id = ! empty( $image->pid ) ? $image->pid : '';
// Get image from storage object if we don't have it already.
if ( empty( $image_id ) ) {
// Get metadata For the image.
// Registry Object for NextGen Gallery.
$registry = C_Component_Registry::get_instance();
/**
* Gallery Storage Object.
*
* @var C_Gallery_Storage $storage
*/
$storage = $registry->get_utility( 'I_Gallery_Storage' );
$image_id = $storage->object->_get_image_id( $image );
}
// Call the actual function to generate the image, and pass the image to smush.
$success = $this->call_parent( 'generate_image_size', $image, $size, $params, $skip_defaults );
if ( $success ) {
$filename = $success->fileName;
// Smush it, if it exists.
if ( file_exists( $filename ) ) {
$response = $smush->do_smushit( $filename );
// If the image was smushed.
if ( ! is_wp_error( $response ) && ! empty( $response['data'] ) && $response['data']->bytes_saved > 0 ) {
// Check for existing stats.
if ( ! empty( $image->meta_data ) && ! empty( $image->meta_data['wp_smush'] ) ) {
$stats = $image->meta_data['wp_smush'];
} else {
// Initialize stats array.
$stats = array(
'stats' => array_merge(
$smush->get_size_signature(),
array(
'api_version' => - 1,
'lossy' => - 1,
'keep_exif' => false,
)
),
'sizes' => array(),
);
$stats['bytes'] = $response['data']->bytes_saved;
$stats['percent'] = $response['data']->compression;
$stats['size_after'] = $response['data']->after_size;
$stats['size_before'] = $response['data']->before_size;
$stats['time'] = $response['data']->time;
}
$stats['sizes'][ $size ] = (object) $smush->array_fill_placeholders( $smush->get_size_signature(), (array) $response['data'] );
if ( isset( $image->metadata ) ) {
$image->meta_data['wp_smush'] = $stats;
nggdb::update_image_meta( $image->pid, $image->meta_data );
}
// Allows To get the stats for each image, after the image is smushed.
do_action( 'wp_smush_nextgen_image_stats', $image_id, $stats );
}
}
}
return $success;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Smush\Core\Media_Library;
use Smush\Core\Controller;
use Smush\Core\Helper;
use Smush\Core\Media\Media_Item_Query;
class Ajax_Media_Library_Scanner extends Controller {
const PARALLEL_REQUESTS = 5;
/**
* @var Media_Library_Scanner
*/
private $scanner;
public function __construct() {
$this->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;
}
}

View File

@@ -0,0 +1,201 @@
<?php
namespace Smush\Core\Media_Library;
use Smush\Core\Controller;
use Smush\Core\Helper;
use Smush\Core\Media\Media_Item_Query;
use Smush\Core\Stats\Global_Stats;
use WP_Smush;
class Background_Media_Library_Scanner extends Controller {
const OPTIMIZE_ON_COMPLETED_OPTION_KEY = 'wp_smush_run_optimize_on_scan_completed';
/**
* @var Media_Library_Scanner
*/
private $scanner;
/**
* @var Media_Library_Scan_Background_Process
*/
private $background_process;
private $logger;
/**
* @var bool
*/
private $optimize_on_scan_completed;
/**
* @var Global_Stats
*/
private $global_stats;
/**
* Static instance
*
* @var self
*/
private static $instance;
public static function get_instance() {
if ( empty( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->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();
}
$in_processing = $this->background_process->get_status()->is_in_processing();
if ( $in_processing ) {
// Already in progress
wp_send_json_error( array( 'message' => __( 'Background scan is already in processing.', 'wp-smushit' ) ) );
}
if ( ! Helper::loopback_supported() ) {
$this->logger->error( 'Loopback check failed. Not starting a new background process.' );
wp_send_json_error( array(
'message' => sprintf(
esc_html__( 'Your site seems to have an issue with loopback requests. Please try again and if the problem persists find out more %s.', 'wp-smushit' ),
sprintf( '<a target="_blank" href="https://wpmudev.com/docs/wpmu-dev-plugins/smush/#background-processing">%s</a>', esc_html__( 'here', 'wp-smushit' ) )
),
) );
} else {
$this->logger->notice( 'Loopback check successful.' );
}
$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 );
wp_send_json_success( $this->get_scan_status() );
}
public function cancel_background_scan() {
check_ajax_referer( 'wp_smush_media_library_scanner' );
if ( ! Helper::is_user_allowed() ) {
wp_send_json_error();
}
$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();
if ( $this->enabled_optimize_on_scan_completed() ) {
$bg_optimization = WP_Smush::get_instance()->core()->mod->bg_optimization;
$bg_optimization->start_bulk_smush_direct();
}
}
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 );
}
}
private 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();
}
if ( $is_completed ) {
$bg_optimization = WP_Smush::get_instance()->core()->mod->bg_optimization;
$status['enabled_background_process'] = $bg_optimization->should_use_background();
}
return $status;
}
public function get_background_process() {
return $this->background_process;
}
}

View File

@@ -0,0 +1,629 @@
<?php
namespace Smush\Core\Media_Library;
use Smush\Core\Helper;
use Smush\Core\Media\Media_Item;
use Smush\Core\Media\Media_Item_Cache;
use Smush\Core\Media\Media_Item_Optimizer;
use Smush\Core\Media\Media_Item_Stats;
use Smush\Core\Settings;
use Smush\Core\Smush\Smush_Optimization;
use Smush\Core\Stats\Global_Stats;
use WP_Error;
use WP_Smush;
class Media_Library_Row {
/**
* @var int
*/
private $attachment_id;
/**
* @var WP_Error
*/
private $errors;
/**
* @var Media_Item_Optimizer
*/
private $optimizer;
/**
* @var Media_Item
*/
private $media_item;
/**
* @var Global_Stats
*/
private $global_stats;
/**
* @var Settings
*/
private $settings;
public function __construct( $attachment_id ) {
$this->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() ) {
return $this->optimizer->get_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( $this->optimizer->get_total_stats() );
}
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 );
}
private function get_animated_html_utm_link() {
if ( WP_Smush::is_pro() ) {
return $this->get_animated_cdn_notice_with_config_link();
}
return $this->get_html_utm_link(
__( 'Upgrade to Serve GIFs faster with CDN.', 'wp-smushit' ),
'smush_bulksmush_library_gif_cdn'
);
}
private function get_animated_cdn_notice_with_config_link() {
$cdn = WP_Smush::get_instance()->core()->mod->cdn;
if ( $cdn->get_status() ) {
return '<span class="smush-cdn-notice">' . esc_html__( 'GIFs are serving from global CDN', 'wp-smushit' ) . '</span>';
}
$cdn_link = Helper::get_page_url( 'smush-cdn' );
return '<span class="smush-cdn-notice">' . sprintf(
/* translators: %1$s : Open a link %2$s Close the link */
esc_html__( '%1$sEnable CDN%2$s to serve GIFs closer and faster to visitors', 'wp-smushit' ),
'<a href="' . esc_url( $cdn_link ) . '" target="_blank">',
'</a>'
) . '</span>';
}
private 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( '<a class="smush-upgrade-link" href="%1$s" target="_blank">%2$s</a>', 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(
'<a href="#" class="%1$s" data-id="%2$d" data-nonce="%3$s">%4$s</a>',
esc_attr( join( ' ', $class_names ) ),
$this->attachment_id,
$nonce,
esc_html__( 'Revert back to previous state', 'wp-smushit' ) . '</a>'
);
}
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(
'<a href="#" class="%s" data-id="%d">%s</a>',
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( '<p class="smush-status %s">%s</p>', 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( '<div class="sui-smush-media smush-status-links">%s</div>', join( $separator, $links ) );
}
private function generate_markup_for_size_limited_item() {
$utm_link = '';
if ( ! WP_Smush::is_pro() ) {
$utm_link = $this->get_html_utm_link(
__( 'Upgrade to Pro to Smush larger images.', 'wp-smushit' ),
'smush_bulksmush_library_filesizelimit'
);
}
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 );
}
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->backup_file_exists() ) {
$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(
'<a target="_blank" href="%s" class="wp-smush-learnmore" data-id="%d">%s</a>',
esc_url( $this->get_regenerate_doc_link() ),
$this->attachment_id,
esc_html__( 'Learn more', 'wp-smushit' )
);
}
private function get_regenerate_doc_link() {
$doc = 'https://wpmudev.com/docs/wpmu-dev-plugins/smush/';
if ( ! WP_Smush::is_pro() ) {
$doc = 'https://wpmudev.com/docs/wpmu-dev-plugins/smush/?utm_source=smush&utm_medium=plugin&utm_campaign=smush_pluginlist_docs';
}
$doc .= '#restoring-images';
return $doc;
}
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( Media_Item_Stats $total_stats ) {
$error_class = $this->errors->has_errors() ? 'smush-warning' : '';
$html = $this->get_html_markup_optimization_status( $this->get_optimization_status( $total_stats ), $error_class );
$html .= $this->get_html_markup_action_links( $this->get_action_links( $total_stats ) );
$html .= $this->get_html_markup_detailed_stats( $total_stats );
return $html;
}
private function get_optimization_status( Media_Item_Stats $total_stats ) {
$error_message = $this->errors->get_error_message();
if ( $error_message ) {
return $error_message;
}
$no_savings = $total_stats->get_size_after() >= $total_stats->get_size_before();
if ( $no_savings ) {
return esc_html__( 'Skipped: Image is already optimized.', 'wp-smushit' );
}
return $this->get_savings_status_text( $total_stats );
}
private function get_savings_status_text( $total_stats ) {
$count_images = $this->optimizer->get_optimized_sizes_count();
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?
$status_text .= sprintf(
/* translators: 1: <br/> tag, 2: Image file size */
esc_html__( '%1$sMain Image size: %2$s', 'wp-smushit' ),
'<br />',
size_format( $this->media_item->get_scaled_or_full_size()->get_filesize(), 2 )
);
return $status_text;
}
/**
* @return array
*/
private function get_action_links( Media_Item_Stats $total_stats ) {
if ( $this->is_first_optimization_required() ) {
return array( $this->get_smush_link(), $this->get_ignore_link() );
}
$links = array();
$resmush_link = $this->get_resmush_link();
if ( $resmush_link ) {
$links[] = $resmush_link;
// Add ignore button while showing resmush button.
$links[] = $this->get_ignore_link();
}
$no_savings = $total_stats->get_size_after() >= $total_stats->get_size_before();
if ( $no_savings ) {
return $links;
}
$restore_link = $this->get_restore_link();
if ( $restore_link ) {
$links[] = $restore_link;
}
$links[] = $this->get_view_stats_link();
return $links;
}
/**
* @return string|void
*/
private function get_html_markup_detailed_stats( Media_Item_Stats $total_stats ) {
$no_savings = $total_stats->get_size_after() >= $total_stats->get_size_before();
if ( $no_savings ) {
return;
}
return sprintf(
'<div id="smush-stats-%d" class="sui-smush-media smush-stats-wrapper hidden">
<table class="wp-smush-stats-holder">
<thead>
<tr>
<th class="smush-stats-header">%s</th>
<th class="smush-stats-header">%s</th>
</tr>
</thead>
<tbody>%s</tbody>
</table>
</div>',
$this->attachment_id,
esc_html__( 'Image size', 'wp-smushit' ),
esc_html__( 'Savings', 'wp-smushit' ),
$this->get_detailed_stats_content()
);
}
private function get_detailed_stats_content() {
$stats_rows = array();
$savings_sizes = array();
// Show Sizes and their compression.
foreach ( $this->media_item->get_sizes() as $size_key => $size ) {
$total_size_stats = $this->optimizer->get_total_size_stats( $size_key );
if ( $total_size_stats->is_empty() ) {
continue;
}
$dimensions = "{$size->get_width()}x{$size->get_height()}";
$stats_rows[ $size_key ] = sprintf(
'<tr>
<td>%s<br/>(%s)</td>
<td>%s ( %s%% )</td>
</tr>',
strtoupper( $size_key ),
$dimensions,
$total_size_stats->get_human_bytes(),
$total_size_stats->get_percent()
);
$savings_sizes[ $size_key ] = $total_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(
'<a href="#" class="wp-smush-send" data-id="%d">%s</a>',
$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();
return $reoptimize_list->has_id( $this->attachment_id ) || $error_list->has_id( $this->attachment_id );
}
/**
* @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(
'<a href="#" data-tooltip="%s" data-id="%d" data-nonce="%s" class="wp-smush-action wp-smush-title sui-tooltip sui-tooltip-constrained wp-smush-resmush">%s</a>',
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(
'<a href="#" class="wp-smush-send" data-id="%d">%s</a>',
$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::LEVEL_ULTRA_LOSSY:
return esc_html__( 'Ultra Smush', 'wp-smushit' );
case Settings::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::KEY );
return $smush_optimization;
}
/**
* @return string|void
*/
private function get_restore_link() {
if ( ! $this->media_item->backup_file_exists() ) {
return;
}
return sprintf(
'<a href="#" data-tooltip="%s" data-id="%d" data-nonce="%s" class="wp-smush-action wp-smush-title sui-tooltip wp-smush-restore">%s</a>',
esc_html__( 'Restore original image', 'wp-smushit' ),
$this->attachment_id,
wp_create_nonce( 'wp-smush-restore-' . $this->attachment_id ),
esc_html__( 'Restore', 'wp-smushit' )
);
}
private function get_view_stats_link() {
return sprintf(
'<a href="#" class="wp-smush-action smush-stats-details wp-smush-title sui-tooltip sui-tooltip-top-right" data-tooltip="%s">%s</a>',
esc_html__( 'Detailed stats for all the image sizes', 'wp-smushit' ),
esc_html__( 'View Stats', 'wp-smushit' )
);
}
private function get_array_value( $array, $key ) {
return isset( $array[ $key ] ) ? $array[ $key ] : null;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Smush\Core\Media_Library;
use Smush\Core\Modules\Background\Background_Process;
class Media_Library_Scan_Background_Process extends Background_Process {
/**
* @var Media_Library_Scanner
*/
private $scanner;
public function __construct( $identifier, $scanner ) {
parent::__construct( $identifier );
$this->scanner = $scanner;
}
protected function task( $slice_id ) {
$this->scanner->scan_library_slice( $slice_id );
return true;
}
protected function attempt_restart_during_health_check() {
return false;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Smush\Core\Media_Library;
use Smush\Core\Controller;
use Smush\Core\Media\Media_Item_Query;
/**
* An un-opinionated scanner.
* All it does is traverse attachments, the real work is supposed to be done by other controllers through actions and filters.
* Supposed to handle parallel requests, each request handling a 'slice' of the total media items.
*/
class Media_Library_Scanner {
const SLICE_SIZE_DEFAULT = 2500;
const SLICE_SIZE_MIN = 500;
const SLICE_SIZE_OPTION_ID = 'wp_smush_scan_slice_size';
public function before_scan_library() {
do_action( 'wp_smush_before_scan_library' );
}
public function scan_library_slice( $slice ) {
$slice_size = $this->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 self::SLICE_SIZE_DEFAULT;
}
public function reduce_slice_size_option() {
update_option( self::SLICE_SIZE_OPTION_ID, 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 );
}
}

View File

@@ -0,0 +1,196 @@
<?php
namespace Smush\Core\Media_Library;
use Smush\Core\Array_Utils;
use Smush\Core\Controller;
use Smush\Core\Helper;
use Smush\Core\Media\Media_Item_Query;
class Media_Library_Slice_Data_Fetcher extends Controller {
private $slice_post_meta = array();
private $slice_post_ids = array();
private $query;
/**
* @var \WDEV_Logger|null
*/
private $logger;
private $is_multisite;
private $current_site_id;
/**
* @var Array_Utils
*/
private $array_utils;
public function __construct( $is_multisite = false, $current_site_id = 0 ) {
$this->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 );
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Smush\Core\Media_Library;
use Smush\Core\Array_Utils;
use Smush\Core\Controller;
use Smush\Core\Helper;
class Media_Library_Watcher extends Controller {
const WP_SMUSH_IMAGE_SIZES_STATE = 'wp_smush_image_sizes_state';
/**
* @var Array_Utils
*/
private $array_utils;
public function __construct() {
$this->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_action( '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_action( '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,
) );
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Smush\Core\Media;
use WP_Smush;
/**
* TODO: maybe reset the media item when:
* - a new size is added
*/
class Media_Item_Cache {
const CACHE_GROUP = 'wp-smushit';
/**
* Static instance
*
* @var self
*/
private static $instance;
/**
* @var Media_Item[]
*/
private $media_items;
/**
* Static instance getter
*/
public static function get_instance() {
if ( empty( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
public function has( $id ) {
$media_item = $this->get_from_cache( $id );
return ! empty( $media_item );
}
/**
* @param $id
*
* @return Media_Item|null
*/
public function get( $id ) {
$media_item = $this->get_from_cache( $id );
if ( ! $media_item ) {
$media_item = new Media_Item( $id );
$this->save_to_cache( $id, $media_item );
}
return $media_item;
}
/**
* @param $id
*
* @return Media_Item|null
*/
private function get_from_cache( $id ) {
return $this->get_array_value(
$this->media_items,
$this->make_key( $id )
);
}
private function make_key( $id ) {
$membership_type_postfix = WP_Smush::is_pro() ? 'pro' : 'free';
return "wp-smush-$membership_type_postfix-media-item-$id";
}
private function save_to_cache( $id, $media_item ) {
$this->media_items[ $this->make_key( $id ) ] = $media_item;
}
public function remove( $id ) {
unset( $this->media_items[ $this->make_key( $id ) ] );
}
private function get_array_value( $array, $key ) {
return $array && isset( $array[ $key ] )
? $array[ $key ]
: null;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Smush\Core\Media;
use Smush\Core\Controller;
use Smush\Core\Error_Handler;
use Smush\Core\Helper;
use Smush\Core\Stats\Global_Stats;
use WP_Smush;
/**
* Performs operations on the media item
*/
class Media_Item_Controller extends Controller {
public function __construct() {
$this->register_action( 'wp_ajax_ignore_bulk_image', array( $this, 'ignore_bulk_image' ) );
$this->register_action( 'wp_ajax_remove_from_skip_list', array( $this, 'remove_from_skip_list' ) );
$this->register_action( 'wp_ajax_wp_smush_ignore_all_failed_items', array(
$this,
'ignore_all_failed_items',
) );
}
public function remove_from_skip_list() {
check_ajax_referer( 'wp-smush-remove-skipped' );
if ( ! Helper::is_user_allowed( 'upload_files' ) ) {
wp_send_json_error( array(
'error_message' => esc_html__( "You don't have permission to work with uploaded files.", 'wp-smushit' ),
), 403 );
}
if ( ! isset( $_POST['id'] ) ) {
wp_send_json_error();
}
$attachment_id = absint( $_POST['id'] );
$changed = $this->change_attachment_ignored_status( $attachment_id, false );
if ( ! $changed ) {
wp_send_json_error();
}
wp_send_json_success(
array(
'html' => WP_Smush::get_instance()->library()->generate_markup( $attachment_id ),
)
);
}
public function ignore_bulk_image() {
check_ajax_referer( 'wp-smush-ajax' );
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' ),
), 403 );
}
if ( ! isset( $_POST['id'] ) ) {
wp_send_json_error();
}
$attachment_id = absint( $_POST['id'] );
$changed = $this->change_attachment_ignored_status( $attachment_id, true );
if ( ! $changed ) {
wp_send_json_error();
}
wp_send_json_success( array(
'html' => WP_Smush::get_instance()->library()->generate_markup( $attachment_id ),
) );
}
public function ignore_all_failed_items() {
check_ajax_referer( 'wp-smush-ajax' );
if ( ! Helper::is_user_allowed() ) {
wp_send_json_error( array(
'message' => __( "You don't have permission to do this.", 'wp-smushit' ),
), 403 );
}
$failed_images = Error_Handler::get_all_failed_images();
if ( empty( $failed_images ) ) {
wp_send_json_error( array( 'message' => __( 'Not found any failed items.', 'wp-smushit' ) ) );
}
foreach ( $failed_images as $failed_image_id ) {
$this->change_attachment_ignored_status( $failed_image_id, true );
}
wp_send_json_success();
}
private function change_attachment_ignored_status( $attachment_id, $new_status ) {
$media_item = Media_Item_Cache::get_instance()->get( $attachment_id );
if ( ! $media_item->is_mime_type_supported() ) {
return false;
}
$media_item->set_ignored( $new_status );
$media_item->save();
do_action( 'wp_smush_attachment_ignored_status_changed', $attachment_id, $new_status );
return true;
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace Smush\Core\Media;
use Smush\Core\Array_Utils;
class Media_Item_Optimization_Global_Stats extends Media_Item_Stats {
/**
* @var int How many media *items* are included in this instance.
*/
private $count = 0;
/**
* @var int[] Ids of the attachments included in this instance.
*/
private $attachment_ids = array();
private $array_utils;
public function __construct() {
$this->array_utils = new Array_Utils();
}
public function to_array() {
$array = parent::to_array();
$array['count'] = $this->get_count();
$array['attachment_ids'] = join( ',', $this->get_attachment_ids() );
return $array;
}
public function from_array( $array ) {
parent::from_array( $array );
$this->set_count( (int) $this->get_array_value( $array, 'count' ) );
$attachment_ids = $this->get_array_value( $array, 'attachment_ids' );
$attachment_ids = empty( $attachment_ids ) ? array() : explode( ',', $attachment_ids );
$this->set_attachment_ids( $attachment_ids );
}
/**
* @param $attachment_id int
* @param $item_stats Media_Item_Stats
*
* @return boolean
*/
public function add_item_stats( $attachment_id, $item_stats ) {
if ( $this->has_attachment_id( $attachment_id ) ) {
return false;
} else {
parent::add( $item_stats );
$this->set_count( $this->get_count() + 1 );
$this->add_attachment_id( $attachment_id );
return true;
}
}
/**
* @param $attachment_id int
* @param $item_stats Media_Item_Stats
*
* @return boolean
*/
public function subtract_item_stats( $attachment_id, $item_stats ) {
if ( $this->has_attachment_id( $attachment_id ) ) {
parent::subtract( $item_stats );
$this->set_count( $this->get_count() - 1 );
$this->remove_attachment_id( $attachment_id );
return true;
} else {
return false;
}
}
/**
* @param $addend Media_Item_Optimization_Global_Stats
*
* @return void
*/
public function add( $addend ) {
parent::add( $addend );
$this->set_count( $this->get_count() + $addend->get_count() );
$this->set_attachment_ids(
$this->array_utils->fast_array_unique( array_merge(
$this->get_attachment_ids(),
$addend->get_attachment_ids()
) )
);
}
/**
* @param $subtrahend Media_Item_Optimization_Global_Stats
*
* @return void
*/
public function subtract( $subtrahend ) {
parent::subtract( $subtrahend );
$this->set_count( max( $this->get_count() - $subtrahend->get_count(), 0 ) );
$this->set_attachment_ids(
array_diff(
$this->get_attachment_ids(),
$subtrahend->get_attachment_ids()
)
);
}
/**
* @return mixed
*/
public function get_count() {
return $this->count;
}
/**
* @param mixed $count
*
* @return Media_Item_Optimization_Global_Stats
*/
public function set_count( $count ) {
$this->count = $count;
return $this;
}
private function add_attachment_id( $attachment_id ) {
$this->attachment_ids[] = $attachment_id;
}
private function remove_attachment_id( $attachment_id ) {
$attachment_ids = $this->get_attachment_ids();
$index = array_search( $attachment_id, $attachment_ids );
if ( $index !== false ) {
unset( $attachment_ids[ $index ] );
$this->set_attachment_ids( $attachment_ids );
}
}
public function has_attachment_id( $attachment_id ) {
return in_array( $attachment_id, $this->get_attachment_ids() );
}
private function get_attachment_ids() {
$attachment_ids = $this->attachment_ids;
return empty( $attachment_ids ) || ! is_array( $attachment_ids )
? array()
: $attachment_ids;
}
private function set_attachment_ids( $attachment_ids ) {
$this->attachment_ids = empty( $attachment_ids ) || ! is_array( $attachment_ids )
? array()
: $attachment_ids;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Smush\Core\Media;
use WP_Error;
abstract class Media_Item_Optimization {
/**
* @param $media_item Media_Item
*/
abstract public function __construct( $media_item );
abstract public function get_key();
/**
* @return Media_Item_Stats
*/
abstract public function get_stats();
/**
* @return Media_Item_Stats
*/
abstract public function get_size_stats( $size_key );
abstract public function get_optimized_sizes_count();
abstract public function save();
abstract public function is_optimized();
abstract public function should_optimize();
abstract public function should_reoptimize();
/**
* @param $size Media_Item_Size
*/
abstract public function should_optimize_size( $size );
/**
* @return mixed
*/
abstract public function delete_data();
/**
* @return boolean
*/
abstract public function optimize();
public function can_restore() {
return false;
}
public function restore() {
return false;
}
public function has_errors() {
$wp_error = $this->get_errors();
return $wp_error
&& is_a( $wp_error, '\WP_Error' )
&& $wp_error->has_errors();
}
/**
* @return WP_Error
*/
abstract public function get_errors();
}

View File

@@ -0,0 +1,450 @@
<?php
namespace Smush\Core\Media;
use Smush\Core\Backups\Backups;
use Smush\Core\Helper;
use Smush\Core\Smush\Smush_Optimization;
use Smush\Core\Stats\Global_Stats;
use WDEV_Logger;
use WP_Error;
class Media_Item_Optimizer {
const ERROR_META_KEY = 'wp-smush-optimization-errors';
/**
* @var Media_Item_Optimization[]
*/
private $optimizations;
/**
* @var Media_Item
*/
private $media_item;
/**
* @var Backups
*/
private $backups;
/**
* @var WDEV_Logger
*/
private $logger;
/**
* @var Global_Stats
*/
private $global_stats;
/**
* @var WP_Error
*/
private $errors;
public function __construct( $media_item ) {
$this->media_item = $media_item;
$this->backups = new Backups();
$this->logger = Helper::logger();
$this->global_stats = Global_Stats::get();
}
/**
* @return Media_Item_Optimization[]
*/
public function get_optimizations() {
if ( is_null( $this->optimizations ) ) {
$this->optimizations = $this->initialize_optimizations();
}
return $this->optimizations;
}
public function set_optimizations( $optimizations ) {
$this->optimizations = $optimizations;
}
private function initialize_optimizations() {
return apply_filters( 'wp_smush_optimizations', array(), $this->media_item );
}
/**
* TODO: check the uses for this method to make sure they are prepared to receive null
*
* @param $key
*
* @return Media_Item_Optimization|null
*/
public function get_optimization( $key ) {
return $this->get_array_value( $this->get_optimizations(), $key );
}
/**
* @param $key
*
* @return Media_Item_Stats
*/
public function get_stats( $key ) {
$optimization = $this->get_optimization( $key );
if ( $optimization ) {
return $optimization->get_stats();
}
return new Media_Item_Stats();
}
public function get_total_stats() {
$total_stats = new Media_Item_Stats();
foreach ( $this->get_optimizations() as $optimization ) {
$total_stats->add( $optimization->get_stats() );
}
return $total_stats;
}
/**
* @param $optimization_key
* @param $size_key
*
* @return Media_Item_Stats
*/
public function get_size_stats( $optimization_key, $size_key ) {
$optimization = $this->get_optimization( $optimization_key );
if ( $optimization ) {
return $optimization->get_size_stats( $size_key );
}
return new Media_Item_Stats();
}
public function get_total_size_stats( $size_key ) {
$total_stats = new Media_Item_Stats();
foreach ( $this->get_optimizations() as $optimization ) {
$total_stats->add( $optimization->get_size_stats( $size_key ) );
}
return $total_stats;
}
public function get_optimized_sizes_count() {
$size_count = 0;
foreach ( $this->get_optimizations() as $optimization ) {
$optimized_sizes_count = $optimization->get_optimized_sizes_count();
if ( $optimized_sizes_count > $size_count ) {
$size_count = $optimized_sizes_count;
}
}
return $size_count;
}
/**
* Whether the media item was optimized at some point. It may need to be reoptimized.
*
* @return bool
*/
public function is_optimized() {
foreach ( $this->get_optimizations() as $optimization ) {
if ( $optimization->is_optimized() ) {
return true;
}
}
return false;
}
public function should_optimize() {
foreach ( $this->get_optimizations() as $optimization ) {
if ( $optimization->should_optimize() ) {
return true;
}
}
return false;
}
public function should_reoptimize() {
$should_reoptimize = false;
foreach ( $this->get_optimizations() as $optimization ) {
if ( $optimization->should_reoptimize() ) {
$should_reoptimize = true;
}
}
return apply_filters( 'wp_smush_should_resmush', $should_reoptimize, $this->media_item->get_id() );
}
public function optimize() {
if ( $this->restore_in_progress() ) {
$this->logger->log( 'Prevented auto-smush during restore.' );
return false;
}
if ( $this->in_progress() ) {
$this->handle_error( 'in_progress', 'Smush already in progress' );
return false;
}
$media_item = $this->media_item;
do_action(
'wp_smush_before_smush_file',
$media_item->get_id(),
$media_item->get_wp_metadata()
);
if ( $media_item->has_errors() || $media_item->is_skipped() ) {
$this->adjust_global_stats_lists();
return false;
}
$this->set_in_progress_transient();
$this->backups->maybe_create_backup( $media_item, $this );
$optimized = $this->run_optimizations();
do_action(
'wp_smush_after_smush_file',
$media_item->get_id(),
$media_item->get_wp_metadata(),
$optimized ? array() : $this->get_errors()
);
if ( $optimized ) {
do_action(
'wp_smush_after_smush_successful',
$media_item->get_id(),
$media_item->get_wp_metadata()
);
$this->delete_previous_optimization_errors();
} else {
$this->handle_optimization_errors();
}
$this->delete_in_progress_transient();
return $optimized;
}
public function restore() {
if ( $this->in_progress() || $this->restore_in_progress() ) {
return false;
}
$this->set_restore_in_progress_transient();
$restoration_attempted = false;
$restored = false;
// First, allow one of the optimizations to handle the restoration process
foreach ( $this->get_optimizations() as $optimization ) {
if ( $optimization->can_restore() ) {
$restoration_attempted = true;
$restored = $optimization->restore();
break;
}
}
if ( ! $restoration_attempted ) {
// Try the standard restoration
$restored = $this->backups->restore_backup( $this->media_item );
}
if ( $restored ) {
// Before deleting all data subtract the stats
$this->global_stats->subtract_item_stats( $this->media_item );
$this->global_stats->subtract_optimized_images_count( $this->get_optimized_sizes_count() );
// Delete all the optimization data
$this->delete_data();
// Delete optimization errors.
$this->delete_previous_optimization_errors();
// Once all data has been deleted, adjust the lists
$this->global_stats->adjust_lists_for_media_item( $this->media_item );
}
$this->delete_restore_in_progress_transient();
return $restored;
}
public function save() {
foreach ( $this->get_optimizations() as $optimization ) {
$optimization->save();
}
}
private function get_array_value( $array, $key ) {
return $array && isset( $array[ $key ] )
? $array[ $key ]
: null;
}
/**
* @param Media_Item_Size $full_size
*
* @return boolean
*/
public function should_optimize_size( $full_size ) {
$should_optimize_size = false;
foreach ( $this->get_optimizations() as $optimization ) {
if ( $optimization->should_optimize_size( $full_size ) ) {
$should_optimize_size = true;
break;
}
}
return $should_optimize_size;
}
public function delete_data() {
foreach ( $this->get_optimizations() as $optimization ) {
$optimization->delete_data();
}
}
/**
* @return bool
*/
private function run_optimizations() {
$all_optimized = true;
foreach ( $this->get_optimizations() as $optimization ) {
if ( $optimization->should_optimize() ) {
$current_optimized = $optimization->optimize();
$all_optimized = $all_optimized && $current_optimized;
}
}
return $all_optimized;
}
private function adjust_global_stats_lists() {
$this->global_stats->adjust_lists_for_media_item( $this->media_item );
}
private function set_in_progress_transient() {
set_transient( $this->in_progress_transient_key(), 1, HOUR_IN_SECONDS );
}
private function delete_in_progress_transient() {
delete_transient( $this->in_progress_transient_key() );
}
public function in_progress() {
return (bool) get_transient( $this->in_progress_transient_key() );
}
private function in_progress_transient_key() {
return 'smush-in-progress-' . $this->media_item->get_id();
}
private function set_restore_in_progress_transient() {
set_transient( $this->restore_in_progress_transient_key(), 1, HOUR_IN_SECONDS );
}
private function delete_restore_in_progress_transient() {
delete_transient( $this->restore_in_progress_transient_key() );
}
public function restore_in_progress() {
return (bool) get_transient( $this->restore_in_progress_transient_key() );
}
private function restore_in_progress_transient_key() {
return 'wp-smush-restore-' . $this->media_item->get_id();
}
/**
* @param $code
* @param $error_message
*
* @return void
*/
private function handle_error( $code, $error_message ) {
$this->logger->error( $error_message );
$this->set_errors( new WP_Error( $code, $error_message ) );
$this->update_errors_meta();
}
public function get_errors() {
if ( is_null( $this->errors ) ) {
$this->errors = $this->fetch_errors_from_meta();
}
return $this->errors;
}
private function set_errors( $errors ) {
$this->errors = $errors;
}
public function has_errors() {
return $this->get_errors()->has_errors();
}
private function set_optimization_errors() {
$errors = new WP_Error();
// Add optimization errors
foreach ( $this->get_optimizations() as $optimization ) {
if ( $optimization->has_errors() ) {
$errors->merge_from( $optimization->get_errors() );
}
}
$this->set_errors( $errors );
}
private function fetch_errors_from_meta() {
$wp_error = new WP_Error();
$errors = get_post_meta( $this->media_item->get_id(), self::ERROR_META_KEY, true );
if ( empty( $errors ) || ! is_array( $errors ) ) {
return $wp_error;
}
foreach ( $errors as $error_code => $error_message ) {
if ( empty( $error_message ) ) {
continue;
}
if ( is_array( $error_message ) ) {
foreach ( $error_message as $error ) {
$wp_error->add( $error_code, $error );
}
} else {
$wp_error->add( $error_code, $error_message );
}
}
return $wp_error;
}
private function update_errors_meta() {
$errors_array = array();
foreach ( $this->errors->get_error_codes() as $error_code ) {
$errors_array[ $error_code ] = $this->errors->get_error_messages( $error_code );
}
if ( ! empty( $errors_array ) ) {
update_post_meta( $this->media_item->get_id(), self::ERROR_META_KEY, $errors_array );
}
}
/**
* @return void
*/
private function handle_optimization_errors() {
$this->set_optimization_errors();
$this->update_errors_meta();
}
private function delete_previous_optimization_errors() {
if ( $this->has_errors() ) {
delete_post_meta( $this->media_item->get_id(), self::ERROR_META_KEY );
$this->set_errors( null );
}
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace Smush\Core\Media;
use Smush\Core\Smush\Smush_Optimization;
use Smush\Core\Smush_File;
class Media_Item_Query {
public function fetch( $offset = 0, $limit = - 1 ) {
global $wpdb;
$query = $this->make_query( 'ID', $offset, $limit );
return $wpdb->get_col( $query );
}
public function fetch_slice_post_meta( $slice, $slice_size ) {
global $wpdb;
$offset = $this->get_offset( $slice, $slice_size );
$limit = (int) $slice_size;
$ids_query = $this->make_query( 'ID', $offset, $limit );
$query = "SELECT CONCAT(post_id, '-', meta_key), post_id, meta_key, meta_value FROM $wpdb->postmeta WHERE post_id IN (SELECT * FROM ($ids_query) AS slice_ids);";
return $wpdb->get_results( $query, OBJECT_K );
}
public function fetch_slice_posts( $slice, $slice_size ) {
global $wpdb;
$offset = $this->get_offset( $slice, $slice_size );
$limit = (int) $slice_size;
$posts_query = $this->make_query( '*', $offset, $limit );
return $wpdb->get_results( $posts_query, OBJECT_K );
}
public function fetch_slice_ids( $slice, $slice_size ) {
$offset = $this->get_offset( $slice, $slice_size );
$limit = (int) $slice_size;
return $this->fetch( $offset, $limit );
}
public function get_slice_count( $slice_size ) {
if ( empty( $slice_size ) ) {
return 0;
}
$image_attachment_count = $this->get_image_attachment_count();
return (int) ceil( $image_attachment_count / $slice_size );
}
public function get_image_attachment_count() {
global $wpdb;
$query = $this->make_query( 'COUNT(*)' );
return (int) $wpdb->get_var( $query );
}
/**
* @param $select
* @param $offset
* @param $limit
*
* @return string|null
*/
private function make_query( $select, $offset = 0, $limit = - 1 ) {
global $wpdb;
$mime_types = ( new Smush_File() )->get_supported_mime_types();
$placeholders = implode( ',', array_fill( 0, count( $mime_types ), '%s' ) );
$column = $select;
$query = "SELECT %s FROM $wpdb->posts WHERE post_type = 'attachment' AND post_mime_type IN (%s)";
$query = sprintf( $query, $column, $placeholders );
$args = $mime_types;
if ( $limit > 0 ) {
$query = "$query LIMIT %d";
$args[] = $limit;
if ( $offset >= 0 ) {
$query = "$query OFFSET %d";
$args[] = $offset;
}
}
return $wpdb->prepare( $query, $args );
}
public function get_lossy_count() {
global $wpdb;
$query = $wpdb->prepare( "SELECT COUNT(DISTINCT post_id) FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = 1", Smush_Optimization::LOSSY_META_KEY );
return $wpdb->get_var( $query );
}
public function get_smushed_count() {
global $wpdb;
$query = $wpdb->prepare(
"SELECT COUNT(DISTINCT post_meta_optimized.post_id) FROM $wpdb->postmeta as post_meta_optimized
LEFT JOIN $wpdb->postmeta as post_meta_ignored ON post_meta_optimized.post_id = post_meta_ignored.post_id AND post_meta_ignored.meta_key= %s
WHERE post_meta_optimized.meta_key = %s AND post_meta_ignored.meta_value IS NULL",
Media_Item::IGNORED_META_KEY,
Smush_Optimization::SMUSH_META_KEY
);
return $wpdb->get_var( $query );
}
public function get_ignored_count() {
global $wpdb;
$query = $wpdb->prepare( "SELECT COUNT(DISTINCT post_id) FROM $wpdb->postmeta WHERE meta_key = %s", Media_Item::IGNORED_META_KEY );
return $wpdb->get_var( $query );
}
/**
* @param $slice
* @param $slice_size
*
* @return float|int
*/
private function get_offset( $slice, $slice_size ) {
$slice = (int) $slice;
$slice_size = (int) $slice_size;
return ( $slice - 1 ) * $slice_size;
}
}

View File

@@ -0,0 +1,263 @@
<?php
namespace Smush\Core\Media;
use Smush\Core\File_System;
use Smush\Core\Settings;
use WP_Smush;
class Media_Item_Size {
/**
* @var string
*/
private $key;
/**
* @var string
*/
private $file_name;
/**
* @var int
*/
private $width;
/**
* @var int
*/
private $height;
/**
* @var string
*/
private $mime_type;
/**
* @var int
*/
private $filesize;
/**
* @var int
*/
private $attachment_id;
/**
* @var Settings
*/
private $settings;
/**
* @var array
*/
private $wp_metadata;
/**
* @var int
*/
private $size_limit;
/**
* @var string
*/
private $dir;
/**
* @var string
*/
private $base_url;
/**
* @var string
*/
private $extension;
/**
* @var File_System
*/
private $fs;
/**
* @param $key string
* @param $attachment_id int
* @param $wp_size_metadata array
*/
public function __construct( $key, $attachment_id, $dir, $base_url, $wp_size_metadata ) {
$this->key = $key;
$this->attachment_id = $attachment_id;
$this->dir = $dir;
$this->base_url = $base_url;
$this->wp_metadata = $wp_size_metadata;
$this->fs = new File_System();
$this->size_limit = WP_Smush::is_pro()
? WP_SMUSH_PREMIUM_MAX_BYTES
: WP_SMUSH_MAX_BYTES;
$this->settings = Settings::get_instance();
$this->from_array( $wp_size_metadata );
}
/**
* @param $size_data array Typically an item from 'sizes' array returned by wp_get_attachment_metadata
*
* @return void
*/
private function from_array( $size_data ) {
$this->set_file_name( (string) $this->get_array_value( $size_data, 'file' ) );
$this->set_width( (int) $this->get_array_value( $size_data, 'width' ) );
$this->set_height( (int) $this->get_array_value( $size_data, 'height' ) );
$this->set_mime_type( (string) $this->get_array_value( $size_data, 'mime-type' ) );
$this->set_filesize( (int) $this->get_array_value( $size_data, 'filesize' ) );
}
private function get_array_value( $array, $key ) {
return isset( $array[ $key ] ) ? $array[ $key ] : null;
}
public function get_file_name_without_extension() {
return mb_substr( $this->get_file_name(), 0, mb_strlen( $this->get_file_name() ) - mb_strlen( '.' . $this->get_extension() ) );
}
public function get_file_name() {
return $this->file_name;
}
public function set_file_name( $file_name ) {
$this->file_name = $file_name;
}
/**
* @return string
*/
public function get_file_path() {
return path_join( $this->dir, $this->get_file_name() );
}
public function get_file_url() {
$base_url = $this->base_url;
$file_name = $this->get_file_name();
return "$base_url$file_name";
}
/**
* @return int
*/
public function get_width() {
return $this->width;
}
/**
* @param int $width
*/
public function set_width( $width ) {
$this->width = $width;
}
/**
* @return int
*/
public function get_height() {
return $this->height;
}
/**
* @param int $height
*/
public function set_height( $height ) {
$this->height = $height;
}
/**
* @return string
*/
public function get_mime_type() {
return $this->mime_type;
}
/**
* @param string $mime_type
*/
public function set_mime_type( $mime_type ) {
$this->mime_type = $mime_type;
}
/**
* @return int
*/
public function get_filesize() {
return $this->filesize;
}
/**
* @param int $filesize
*/
public function set_filesize( $filesize ) {
$this->filesize = $filesize;
}
/**
* @return string
*/
public function get_key() {
return $this->key;
}
public function has_wp_metadata() {
return ! empty( $this->wp_metadata );
}
public function is_smushable() {
return $this->is_size_selected_in_settings() &&
$this->media_image_filter();
}
public function exceeds_size_limit() {
return $this->get_filesize() > $this->size_limit;
}
private function media_image_filter() {
return apply_filters( 'wp_smush_media_image', true, $this->get_key(), $this->get_file_path(), $this->get_attachment_id() );
}
public function file_exists() {
return $this->fs->file_exists( $this->get_file_path() );
}
private function is_size_selected_in_settings() {
if ( $this->get_key() === 'full' ) {
return $this->settings->get( 'original' );
}
$selected = $this->settings->get_setting( 'wp-smush-image_sizes' );
if ( empty( $selected ) || ! is_array( $selected ) ) {
return true;
}
return in_array( $this->get_key(), $selected );
}
/**
* @return int
*/
public function get_size_limit() {
return $this->size_limit;
}
/**
* @param int $size_limit
*/
public function set_size_limit( $size_limit ) {
$this->size_limit = $size_limit;
}
public function get_dir() {
return $this->dir;
}
public function get_extension() {
if ( is_null( $this->extension ) ) {
$this->extension = $this->prepare_extension();
}
return $this->extension;
}
public function prepare_extension() {
return pathinfo( $this->get_file_path(), PATHINFO_EXTENSION );
}
/**
* @return int
*/
public function get_attachment_id() {
return $this->attachment_id;
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace Smush\Core\Media;
class Media_Item_Stats {
/**
* @var int
*/
private $size_before = 0;
/**
* @var int
*/
private $size_after = 0;
/**
* @var float
*/
private $time = 0.0;
/**
* @return float
*/
public function get_percent() {
return $this->calculate_percentage(
$this->get_size_before(),
$this->get_size_after()
);
}
public function get_human_bytes() {
$bytes = $this->get_bytes();
return size_format(
$bytes,
$bytes >= 1024 ? 1 : 0
);
}
/**
* @return int
*/
public function get_bytes() {
$size_before = $this->get_size_before();
$size_after = $this->get_size_after();
return $size_before > $size_after
? $size_before - $size_after
: 0;
}
/**
* @return int
*/
public function get_size_before() {
return $this->size_before;
}
/**
* @param int $size_before
*/
public function set_size_before( $size_before ) {
$this->size_before = (int) $size_before;
}
/**
* @return int
*/
public function get_size_after() {
return $this->size_after;
}
/**
* @param int $size_after
*/
public function set_size_after( $size_after ) {
$this->size_after = (int) $size_after;
}
/**
* @return float
*/
public function get_time() {
return $this->time;
}
/**
* @param float $time
*/
public function set_time( $time ) {
$this->time = (float) $time;
}
public function from_array( $array ) {
$this->set_time( (float) $this->get_array_value( $array, 'time' ) );
$this->set_size_before( (int) $this->get_array_value( $array, 'size_before' ) );
$this->set_size_after( (int) $this->get_array_value( $array, 'size_after' ) );
}
public function is_empty() {
return empty( $this->get_size_before() ) && empty( $this->get_size_after() );
}
public function to_array() {
return array(
'time' => $this->get_time(),
'bytes' => $this->get_bytes(),
'percent' => $this->get_percent(),
'size_before' => $this->get_size_before(),
'size_after' => $this->get_size_after(),
);
}
protected function get_array_value( $array, $key ) {
return isset( $array[ $key ] ) ? $array[ $key ] : null;
}
/**
* Add values from the passed stats object to the current object
*
* @param $addend Media_Item_Stats
*
* @return void
*/
public function add( $addend ) {
$new_size_before = $this->get_size_before() + $addend->get_size_before();
$new_size_after = $this->get_size_after() + $addend->get_size_after();
$new_time = $this->get_time() + $addend->get_time();
// Update with new values
$this->set_time( $new_time );
$this->set_size_before( $new_size_before );
$this->set_size_after( $new_size_after );
}
/**
* @param $subtrahend Media_Item_Stats
*
* @return void
*/
public function subtract( $subtrahend ) {
$new_size_before = $this->get_size_before() - $subtrahend->get_size_before();
$new_size_after = $this->get_size_after() - $subtrahend->get_size_after();
$new_time = $this->get_time() - $subtrahend->get_time();
// Update with new values
$this->set_time( max( $new_time, 0 ) );
$this->set_size_before( max( $new_size_before, 0 ) );
$this->set_size_after( max( $new_size_after, 0 ) );
}
/**
* @param $to_check Media_Item_Stats
*
* @return boolean
*/
public function equals( $to_check ) {
return $this->get_size_before() === $to_check->get_size_before()
&& $this->get_size_after() === $to_check->get_size_after()
&& $this->get_time() === $to_check->get_time();
}
private function calculate_percentage( $size_before, $size_after ) {
$savings = $size_before - $size_after;
if ( $savings > 0 && $size_before > 0 ) {
$percentage = ( $savings / $size_before ) * 100;
return $percentage > 0
? round( $percentage, 2 )
: $percentage;
}
return 0;
}
/**
* @param $to_copy Media_Item_Stats
*
* @return void
*/
public function copy( $to_copy ) {
$this->from_array( $to_copy->to_array() );
}
}

Some files were not shown because too many files have changed in this diff Show More