first
This commit is contained in:
@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
namespace Smush\Core\Stats;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Smush\Core\Controller;
|
||||
use Smush\Core\Helper;
|
||||
use Smush\Core\Media\Media_Item_Cache;
|
||||
use Smush\Core\Media\Media_Item_Optimization_Global_Stats;
|
||||
use Smush\Core\Media\Media_Item_Optimizer;
|
||||
use Smush\Core\Settings;
|
||||
|
||||
class Global_Stats_Controller extends Controller {
|
||||
const IMAGE_ATTACHMENT_COUNT_KEY = 'image_attachment_count';
|
||||
const OPTIMIZED_IMAGES_COUNT_KEY = 'optimized_images_count';
|
||||
const OPTIMIZE_IDS_KEY = 'optimize_attachment_ids';
|
||||
const REOPTIMIZE_IDS_KEY = 'reoptimize_attachment_ids';
|
||||
const ERROR_IDS_KEY = 'error_attachment_ids';
|
||||
const IGNORE_IDS_KEY = 'ignore_attachment_ids';
|
||||
const ANIMATED_IDS_KEY = 'animated_attachment_ids';
|
||||
/**
|
||||
* @var Global_Stats
|
||||
*/
|
||||
private $global_stats;
|
||||
/**
|
||||
* @var Media_Item_Cache
|
||||
*/
|
||||
private $media_item_cache;
|
||||
/**
|
||||
* @var Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
public function __construct() {
|
||||
$this->global_stats = Global_Stats::get();
|
||||
$this->media_item_cache = Media_Item_Cache::get_instance();
|
||||
$this->settings = Settings::get_instance();
|
||||
|
||||
$this->register_media_library_scan_processes();
|
||||
|
||||
$this->register_action( 'wp_smush_after_attachment_upload', array( $this, 'adjust_on_attachment_upload' ) );
|
||||
$this->register_action( 'delete_attachment', array( $this, 'adjust_on_attachment_deletion' ) );
|
||||
$this->register_action( 'wp_smush_before_smush_file', array( $this, 'adjust_before_optimization' ), 10, 3 );
|
||||
$this->register_action( 'wp_smush_after_smush_file', array( $this, 'adjust_after_optimization' ), 10, 3 );
|
||||
$this->register_action( 'wp_smush_plugin_activated', array( $this, 'maybe_mark_as_outdated' ) );
|
||||
$this->register_action( 'wp_smush_attachment_ignored_status_changed', array(
|
||||
$this,
|
||||
'adjust_global_stats_for_attachment',
|
||||
) );
|
||||
$this->register_action( 'wp_smush_attachment_animated_status_changed', array(
|
||||
$this,
|
||||
'adjust_global_stats_for_attachment',
|
||||
) );
|
||||
$this->register_action( 'wp_smush_membership_status_changed', array(
|
||||
$this->global_stats,
|
||||
'mark_as_outdated',
|
||||
), 10, 2 );
|
||||
$this->register_action( 'wp_ajax_wp_smush_get_global_stats', array( $this, 'ajax_get_global_stats' ) );
|
||||
}
|
||||
|
||||
public function register_scan_process( $before_scan, $handle_attachment, $after_slice ) {
|
||||
$this->register_filter( 'wp_smush_before_scan_library', $before_scan );
|
||||
$this->register_filter( 'wp_smush_scan_library_slice_handle_attachment', $handle_attachment, 10, 2 );
|
||||
$this->register_filter( 'wp_smush_after_scan_library_slice', $after_slice );
|
||||
}
|
||||
|
||||
public function reset_global_stats() {
|
||||
foreach ( $this->global_stats->get_persistable_stats_for_optimizations() as $optimization_global_stats ) {
|
||||
$optimization_global_stats->reset();
|
||||
}
|
||||
}
|
||||
|
||||
public function accumulate_slice_stats( $slice_data, $attachment_id ) {
|
||||
$media_item = $this->media_item_cache->get( $attachment_id );
|
||||
if ( ! $media_item->is_valid() ) {
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
foreach ( $optimizer->get_optimizations() as $optimization ) {
|
||||
$key = $this->slice_stats_key( $optimization->get_key() );
|
||||
if ( empty( $slice_data[ $key ] ) ) {
|
||||
$slice_data[ $key ] = $this->global_stats->create_global_stats_object( $optimization->get_key() );
|
||||
}
|
||||
|
||||
$slice_stats = $slice_data[ $key ];
|
||||
if ( $optimization->is_optimized() ) {
|
||||
$item_stats = $optimization->get_stats();
|
||||
$slice_stats->add_item_stats( $attachment_id, $item_stats );
|
||||
}
|
||||
}
|
||||
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $slice_data Media_Item_Optimization_Global_Stats[]
|
||||
*
|
||||
* @return Media_Item_Optimization_Global_Stats[]
|
||||
*/
|
||||
public function save_slice_stats( $slice_data ) {
|
||||
foreach ( $this->global_stats->get_persistable_stats_for_optimizations() as $optimization_key => $optimization_global_stats ) {
|
||||
$key = $this->slice_stats_key( $optimization_key );
|
||||
if ( empty( $slice_data[ $key ] ) ) {
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
$slice_stats = $slice_data[ $key ];
|
||||
|
||||
$optimization_global_stats->add( $slice_stats );
|
||||
}
|
||||
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $optimization_key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function slice_stats_key( $optimization_key ) {
|
||||
return 'slice_stats_' . $optimization_key;
|
||||
}
|
||||
|
||||
public function reset_counts() {
|
||||
$this->global_stats->delete_global_stats_option();
|
||||
}
|
||||
|
||||
public function accumulate_counts( $slice_data, $attachment_id ) {
|
||||
$media_item = $this->media_item_cache->get( $attachment_id );
|
||||
if ( ! $media_item->is_valid() ) {
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
// Attachment count
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
$slice_data = $this->accumulate_count( $slice_data, self::IMAGE_ATTACHMENT_COUNT_KEY, 1 );
|
||||
$slice_data = $this->accumulate_count( $slice_data, self::OPTIMIZED_IMAGES_COUNT_KEY, $optimizer->get_optimized_sizes_count() );
|
||||
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
public function save_counts( $slice_data ) {
|
||||
$this->global_stats->add_image_attachment_count( (int) $this->get_array_value( $slice_data, self::IMAGE_ATTACHMENT_COUNT_KEY ) );
|
||||
$this->global_stats->add_optimized_images_count( (int) $this->get_array_value( $slice_data, self::OPTIMIZED_IMAGES_COUNT_KEY ) );
|
||||
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
private function accumulate_count( $slice_data, $key, $addend ) {
|
||||
if ( empty( $slice_data[ $key ] ) ) {
|
||||
$slice_data[ $key ] = 0;
|
||||
}
|
||||
$slice_data[ $key ] += $addend;
|
||||
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
private function get_array_value( $array, $key ) {
|
||||
return $array && isset( $array[ $key ] )
|
||||
? $array[ $key ]
|
||||
: null;
|
||||
}
|
||||
|
||||
public function reset_lists() {
|
||||
$this->global_stats->get_optimize_list()->delete_ids();
|
||||
$this->global_stats->get_reoptimize_list()->delete_ids();
|
||||
$this->global_stats->get_error_list()->delete_ids();
|
||||
$this->global_stats->get_ignore_list()->delete_ids();
|
||||
$this->global_stats->get_animated_list()->delete_ids();
|
||||
}
|
||||
|
||||
/**
|
||||
* Also:
|
||||
* @see Global_Stats::adjust_lists_for_media_item()
|
||||
*/
|
||||
public function accumulate_attachment_ids( $slice_data, $attachment_id ) {
|
||||
$media_item = $this->media_item_cache->get( $attachment_id );
|
||||
if ( ! $media_item->is_valid() ) {
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
if ( $media_item->is_ignored() ) {
|
||||
$this->add_to_list( $slice_data, self::IGNORE_IDS_KEY, $attachment_id );
|
||||
} elseif ( $media_item->is_animated() ) {
|
||||
$this->add_to_list( $slice_data, self::ANIMATED_IDS_KEY, $attachment_id );
|
||||
} elseif ( $media_item->has_errors() ) {
|
||||
$this->add_to_list( $slice_data, self::ERROR_IDS_KEY, $attachment_id );
|
||||
} else {
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
if ( $optimizer->is_optimized() ) {
|
||||
if ( $optimizer->should_reoptimize() ) {
|
||||
$this->add_to_list( $slice_data, self::REOPTIMIZE_IDS_KEY, $attachment_id );
|
||||
}
|
||||
} else {
|
||||
if ( $optimizer->should_optimize() ) {
|
||||
$this->add_to_list( $slice_data, self::OPTIMIZE_IDS_KEY, $attachment_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
private function add_to_list( &$slice_data, $key, $attachment_id ) {
|
||||
if ( empty( $slice_data[ $key ] ) ) {
|
||||
$slice_data[ $key ] = array();
|
||||
}
|
||||
|
||||
$slice_data[ $key ][] = $attachment_id;
|
||||
}
|
||||
|
||||
public function save_optimization_lists( $slice_data ) {
|
||||
$slice_error_ids = empty( $slice_data[ self::ERROR_IDS_KEY ] ) ? array() : $slice_data[ self::ERROR_IDS_KEY ];
|
||||
if ( $slice_error_ids ) {
|
||||
$this->global_stats->get_error_list()->add_ids( $slice_error_ids );
|
||||
}
|
||||
|
||||
$slice_ignore_ids = empty( $slice_data[ self::IGNORE_IDS_KEY ] ) ? array() : $slice_data[ self::IGNORE_IDS_KEY ];
|
||||
if ( $slice_ignore_ids ) {
|
||||
$this->global_stats->get_ignore_list()->add_ids( $slice_ignore_ids );
|
||||
}
|
||||
|
||||
$slice_animated_ids = empty( $slice_data[ self::ANIMATED_IDS_KEY ] ) ? array() : $slice_data[ self::ANIMATED_IDS_KEY ];
|
||||
if ( $slice_animated_ids ) {
|
||||
$this->global_stats->get_animated_list()->add_ids( $slice_animated_ids );
|
||||
}
|
||||
|
||||
$slice_reoptimize_ids = empty( $slice_data[ self::REOPTIMIZE_IDS_KEY ] ) ? array() : $slice_data[ self::REOPTIMIZE_IDS_KEY ];
|
||||
if ( $slice_reoptimize_ids ) {
|
||||
$this->global_stats->get_reoptimize_list()->add_ids( $slice_reoptimize_ids );
|
||||
}
|
||||
|
||||
$slice_optimize_ids = empty( $slice_data[ self::OPTIMIZE_IDS_KEY ] ) ? array() : $slice_data[ self::OPTIMIZE_IDS_KEY ];
|
||||
if ( $slice_optimize_ids ) {
|
||||
$this->global_stats->get_optimize_list()->add_ids( $slice_optimize_ids );
|
||||
}
|
||||
|
||||
return $slice_data;
|
||||
}
|
||||
|
||||
public function adjust_on_attachment_deletion( $attachment_id ) {
|
||||
$media_item = $this->media_item_cache->get( $attachment_id );
|
||||
if ( ! $media_item->is_valid() || ! $media_item->is_mime_type_supported() ) {
|
||||
return;
|
||||
}
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
|
||||
// Counts
|
||||
$this->global_stats->subtract_image_attachment_count( 1 );
|
||||
if ( $optimizer->is_optimized() ) {
|
||||
$this->global_stats->subtract_optimized_images_count( $optimizer->get_optimized_sizes_count() );
|
||||
}
|
||||
|
||||
$this->global_stats->remove_media_item( $media_item );
|
||||
}
|
||||
|
||||
public function adjust_on_attachment_upload( $attachment_id ) {
|
||||
$media_item = $this->media_item_cache->get( $attachment_id );
|
||||
if ( $media_item->is_valid() && $media_item->is_mime_type_supported() ) {
|
||||
// Counts
|
||||
$this->global_stats->add_image_attachment_count( 1 );
|
||||
|
||||
// Lists
|
||||
$this->global_stats->adjust_for_attachment( $attachment_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Before optimization, we need to take off the *old* stats so that we can add the new ones afterwards
|
||||
*
|
||||
* @param $attachment_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function adjust_before_optimization( $attachment_id ) {
|
||||
$media_item = $this->media_item_cache->get( $attachment_id );
|
||||
if ( ! $media_item->is_valid() || ! $media_item->is_mime_type_supported() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
if ( $optimizer->is_optimized() ) {
|
||||
// If the media item is optimized already then this is a reoptimization and we should take off the optimized count we added during the last optimization
|
||||
$this->global_stats->subtract_optimized_images_count( $optimizer->get_optimized_sizes_count() );
|
||||
}
|
||||
|
||||
// Subtract the old stats, we will add the new stats after reoptimization
|
||||
$this->global_stats->subtract_item_stats( $media_item );
|
||||
}
|
||||
|
||||
public function adjust_after_optimization( $attachment_id, $metadata, $processing_errors ) {
|
||||
$media_item = $this->media_item_cache->get( $attachment_id );
|
||||
|
||||
// We are only handling the success case here because we are relying on the fact that this method will never run when the media item has errors
|
||||
if ( ! $media_item->has_errors() && empty( $processing_errors ) ) {
|
||||
// Optimization successful
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
|
||||
// Counts
|
||||
if ( $optimizer->is_optimized() ) {
|
||||
$this->global_stats->add_optimized_images_count( $optimizer->get_optimized_sizes_count() );
|
||||
}
|
||||
|
||||
$this->global_stats->adjust_for_media_item( $media_item );
|
||||
}
|
||||
}
|
||||
|
||||
public function update_scan_started_timestamp() {
|
||||
$this->global_stats->update_stats_update_started_timestamp( time() );
|
||||
}
|
||||
|
||||
public function update_scan_finished_timestamp() {
|
||||
$this->global_stats->update_stats_updated_timestamp( time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function register_media_library_scan_processes() {
|
||||
$this->register_action( 'wp_smush_before_scan_library', array( $this, 'update_scan_started_timestamp' ),
|
||||
20 // The priority needs to be managed here because reset_counts resets the scan started timestamp as well
|
||||
);
|
||||
$this->register_action( 'wp_smush_after_scan_library', array( $this, 'update_scan_finished_timestamp' ) );
|
||||
|
||||
// Savings etc.
|
||||
$this->register_scan_process(
|
||||
array( $this, 'reset_global_stats' ),
|
||||
array( $this, 'accumulate_slice_stats' ),
|
||||
array( $this, 'save_slice_stats' )
|
||||
);
|
||||
|
||||
// Counts
|
||||
$this->register_scan_process(
|
||||
array( $this, 'reset_counts' ),
|
||||
array( $this, 'accumulate_counts' ),
|
||||
array( $this, 'save_counts' )
|
||||
);
|
||||
|
||||
// Attachment ID lists
|
||||
$this->register_scan_process(
|
||||
array( $this, 'reset_lists' ),
|
||||
array( $this, 'accumulate_attachment_ids' ),
|
||||
array( $this, 'save_optimization_lists' )
|
||||
);
|
||||
}
|
||||
|
||||
public function ajax_get_global_stats() {
|
||||
// TODO: check ajax referrer
|
||||
|
||||
if ( ! Helper::is_user_allowed() ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
wp_send_json( $this->global_stats->to_array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add tests for the scenarios where this method is called
|
||||
*
|
||||
* @param $attachment_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function adjust_global_stats_for_attachment( $attachment_id ) {
|
||||
$this->global_stats->adjust_for_attachment( $attachment_id );
|
||||
}
|
||||
|
||||
public function get_latest_modification_timestamp() {
|
||||
global $wpdb;
|
||||
|
||||
$latest_modification_date = $wpdb->get_var( "SELECT post_modified_gmt FROM $wpdb->posts WHERE post_type = 'attachment' ORDER BY post_modified_gmt DESC LIMIT 1;" );
|
||||
if ( empty( $latest_modification_date ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$date = new DateTime( $latest_modification_date, new DateTimeZone( 'GMT' ) );
|
||||
|
||||
return $date->format( 'U' );
|
||||
} catch ( \Exception $e ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function maybe_mark_as_outdated() {
|
||||
$stats_updated_timestamp = $this->global_stats->get_stats_updated_timestamp();
|
||||
if ( empty( $stats_updated_timestamp ) ) {
|
||||
// Already outdated because a scan was never run
|
||||
return;
|
||||
}
|
||||
|
||||
$latest_modification_timestamp = $this->get_latest_modification_timestamp();
|
||||
if ( empty( $latest_modification_timestamp ) ) {
|
||||
// Something went wrong
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $latest_modification_timestamp < $stats_updated_timestamp ) {
|
||||
// A scan was done after the latest change in the media library
|
||||
return;
|
||||
}
|
||||
|
||||
$this->global_stats->mark_as_outdated();
|
||||
}
|
||||
}
|
549
wp-content/plugins/wp-smushit/core/stats/class-global-stats.php
Normal file
549
wp-content/plugins/wp-smushit/core/stats/class-global-stats.php
Normal file
@ -0,0 +1,549 @@
|
||||
<?php
|
||||
|
||||
namespace Smush\Core\Stats;
|
||||
|
||||
use Smush\Core\Array_Utils;
|
||||
use Smush\Core\Attachment_Id_List;
|
||||
use Smush\Core\Media\Media_Item;
|
||||
use Smush\Core\Media\Media_Item_Cache;
|
||||
use Smush\Core\Media\Media_Item_Optimization_Global_Stats;
|
||||
use Smush\Core\Media\Media_Item_Optimizer;
|
||||
use Smush\Core\Media\Media_Item_Query;
|
||||
use Smush\Core\Media\Media_Item_Stats;
|
||||
use Smush\Core\Modules\Background\Mutex;
|
||||
|
||||
class Global_Stats {
|
||||
const GLOBAL_STATS_OPTION_ID = 'wp_smush_global_stats';
|
||||
const OPTIMIZE_LIST_OPTION_ID = 'wp-smush-optimize-list';
|
||||
const REOPTIMIZE_LIST_OPTION_ID = 'wp-smush-reoptimize-list';
|
||||
const ERROR_LIST_OPTION_ID = 'wp-smush-error-items-list';
|
||||
const IGNORE_LIST_OPTION_ID = 'wp-smush-ignored-items-list';
|
||||
const ANIMATED_LIST_OPTION_ID = 'wp-smush-animated-items-list';
|
||||
/**
|
||||
* @var Global_Stats
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var Media_Item_Optimization_Global_Stats[]
|
||||
*/
|
||||
private $optimization_stats;
|
||||
|
||||
/**
|
||||
* @var Attachment_Id_List
|
||||
*/
|
||||
private $optimize_list;
|
||||
/**
|
||||
* @var Attachment_Id_List
|
||||
*/
|
||||
private $reoptimize_list;
|
||||
/**
|
||||
* @var Attachment_Id_List
|
||||
*/
|
||||
private $error_list;
|
||||
/**
|
||||
* @var Attachment_Id_List
|
||||
*/
|
||||
private $ignore_list;
|
||||
/**
|
||||
* @var Attachment_Id_List
|
||||
*/
|
||||
private $animated_list;
|
||||
/**
|
||||
* @var Media_Item_Cache
|
||||
*/
|
||||
private $media_item_cache;
|
||||
/**
|
||||
* @var Array_Utils
|
||||
*/
|
||||
private $array_utils;
|
||||
private $media_item_query;
|
||||
|
||||
public function __construct() {
|
||||
$this->optimize_list = new Attachment_Id_List( self::OPTIMIZE_LIST_OPTION_ID );
|
||||
$this->reoptimize_list = new Attachment_Id_List( self::REOPTIMIZE_LIST_OPTION_ID );
|
||||
$this->error_list = new Attachment_Id_List( self::ERROR_LIST_OPTION_ID );
|
||||
$this->ignore_list = new Attachment_Id_List( self::IGNORE_LIST_OPTION_ID );
|
||||
$this->animated_list = new Attachment_Id_List( self::ANIMATED_LIST_OPTION_ID );
|
||||
|
||||
$this->media_item_cache = Media_Item_Cache::get_instance();
|
||||
$this->array_utils = new Array_Utils();
|
||||
$this->media_item_query = new Media_Item_Query();
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
if ( empty( self::$instance ) ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Media_Item_Optimization_Global_Stats_Persistable[]
|
||||
*/
|
||||
public function get_persistable_stats_for_optimizations() {
|
||||
if ( is_null( $this->optimization_stats ) ) {
|
||||
$this->optimization_stats = $this->initialize_stats_for_optimizations();
|
||||
}
|
||||
|
||||
return $this->optimization_stats;
|
||||
}
|
||||
|
||||
private function initialize_stats_for_optimizations() {
|
||||
return apply_filters( 'wp_smush_global_optimization_stats', array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $optimization_key
|
||||
*
|
||||
* @return Media_Item_Optimization_Global_Stats
|
||||
*/
|
||||
public function create_global_stats_object( $optimization_key ) {
|
||||
return apply_filters(
|
||||
'wp_smush_optimization_global_stats_instance',
|
||||
new Media_Item_Optimization_Global_Stats(),
|
||||
$optimization_key
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $optimization_key
|
||||
*
|
||||
* @return Media_Item_Optimization_Global_Stats_Persistable
|
||||
*/
|
||||
public function get_persistable_stats_for_optimization( $optimization_key ) {
|
||||
return $this->get_array_value(
|
||||
$this->get_persistable_stats_for_optimizations(),
|
||||
$optimization_key
|
||||
);
|
||||
}
|
||||
|
||||
private function get_array_value( $array, $key ) {
|
||||
return $array && isset( $array[ $key ] )
|
||||
? $array[ $key ]
|
||||
: null;
|
||||
}
|
||||
|
||||
public function delete_global_stats_option() {
|
||||
delete_option( self::GLOBAL_STATS_OPTION_ID );
|
||||
}
|
||||
|
||||
private function get_global_stats_option_value( $key ) {
|
||||
$option = $this->get_global_stats_option();
|
||||
|
||||
return $this->get_array_value( $option, $key );
|
||||
}
|
||||
|
||||
private function update_global_stats_option_value( $key, $value ) {
|
||||
$option = $this->get_global_stats_option();
|
||||
|
||||
update_option( self::GLOBAL_STATS_OPTION_ID, array_merge( $option, array(
|
||||
$key => $value,
|
||||
) ), false );
|
||||
}
|
||||
|
||||
public function is_outdated() {
|
||||
if ( $this->is_media_library_empty() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stats_updated_timestamp = $this->get_stats_updated_timestamp();
|
||||
if ( empty( $stats_updated_timestamp ) ) {
|
||||
// The scan has never been run
|
||||
return true;
|
||||
}
|
||||
|
||||
$rescan_required_timestamp = $this->get_rescan_required_timestamp();
|
||||
|
||||
return $rescan_required_timestamp > $stats_updated_timestamp;
|
||||
}
|
||||
|
||||
private function is_media_library_empty() {
|
||||
if ( 0 !== $this->get_image_attachment_count() ) {
|
||||
// Cached attachment count is not empty, so we definitely have some media items.
|
||||
// No need to make a DB call.
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0 === $this->media_item_query->get_image_attachment_count();
|
||||
}
|
||||
|
||||
public function mark_as_outdated() {
|
||||
$this->update_rescan_required_timestamp( time() );
|
||||
}
|
||||
|
||||
public function get_stats_update_started_timestamp() {
|
||||
return (int) $this->get_global_stats_option_value( 'stats_update_started_timestamp' );
|
||||
}
|
||||
|
||||
public function update_stats_update_started_timestamp( $timestamp ) {
|
||||
$this->update_global_stats_option_value( 'stats_update_started_timestamp', $timestamp );
|
||||
}
|
||||
|
||||
public function get_stats_updated_timestamp() {
|
||||
return (int) $this->get_global_stats_option_value( 'stats_updated_timestamp' );
|
||||
}
|
||||
|
||||
public function update_stats_updated_timestamp( $timestamp ) {
|
||||
$this->update_global_stats_option_value( 'stats_updated_timestamp', $timestamp );
|
||||
}
|
||||
|
||||
public function get_rescan_required_timestamp() {
|
||||
return (int) $this->get_global_stats_option_value( 'rescan_required_timestamp' );
|
||||
}
|
||||
|
||||
public function update_rescan_required_timestamp( $timestamp ) {
|
||||
$this->update_global_stats_option_value( 'rescan_required_timestamp', $timestamp );
|
||||
}
|
||||
|
||||
public function get_image_attachment_count() {
|
||||
return (int) $this->get_global_stats_option_value( 'image_attachment_count' );
|
||||
}
|
||||
|
||||
public function add_image_attachment_count( $image_attachment_count ) {
|
||||
$this->mutex( function () use ( $image_attachment_count ) {
|
||||
$old_image_attachment_count = $this->get_image_attachment_count();
|
||||
$this->update_global_stats_option_value( 'image_attachment_count', $old_image_attachment_count + $image_attachment_count );
|
||||
} );
|
||||
}
|
||||
|
||||
public function subtract_image_attachment_count( $image_attachment_count ) {
|
||||
$this->mutex( function () use ( $image_attachment_count ) {
|
||||
$old_image_attachment_count = $this->get_image_attachment_count();
|
||||
$this->update_global_stats_option_value( 'image_attachment_count', max( $old_image_attachment_count - $image_attachment_count, 0 ) );
|
||||
} );
|
||||
}
|
||||
|
||||
public function get_optimized_images_count() {
|
||||
return (int) $this->get_global_stats_option_value( 'optimized_images_count' );
|
||||
}
|
||||
|
||||
public function add_optimized_images_count( $optimized_images_count ) {
|
||||
$this->mutex( function () use ( $optimized_images_count ) {
|
||||
$old_count = $this->get_optimized_images_count();
|
||||
$this->update_global_stats_option_value( 'optimized_images_count', $old_count + $optimized_images_count );
|
||||
} );
|
||||
}
|
||||
|
||||
public function subtract_optimized_images_count( $optimized_images_count ) {
|
||||
$this->mutex( function () use ( $optimized_images_count ) {
|
||||
$old_count = $this->get_optimized_images_count();
|
||||
$this->update_global_stats_option_value( 'optimized_images_count', max( $old_count - $optimized_images_count, 0 ) );
|
||||
} );
|
||||
}
|
||||
|
||||
public function get_sum_of_optimization_global_stats() {
|
||||
$stats = new Media_Item_Stats();
|
||||
|
||||
foreach ( $this->get_persistable_stats_for_optimizations() as $optimization ) {
|
||||
$stats->add( $optimization->get_stats() );
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function mutex( $operation ) {
|
||||
$option_id = self::GLOBAL_STATS_OPTION_ID;
|
||||
( new Mutex( "{$option_id}_mutex" ) )->execute( $operation );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Attachment_Id_List
|
||||
*/
|
||||
public function get_optimize_list() {
|
||||
return $this->optimize_list;
|
||||
}
|
||||
|
||||
public function get_redo_ids() {
|
||||
return array_merge(
|
||||
$this->get_reoptimize_list()->get_ids(),
|
||||
$this->get_error_list()->get_ids()
|
||||
);
|
||||
}
|
||||
|
||||
public function get_redo_count() {
|
||||
return $this->get_reoptimize_list()->get_count()
|
||||
+ $this->get_error_list()->get_count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Attachment_Id_List
|
||||
*/
|
||||
public function get_reoptimize_list() {
|
||||
return $this->reoptimize_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Attachment_Id_List
|
||||
*/
|
||||
public function get_error_list() {
|
||||
return $this->error_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Attachment_Id_List
|
||||
*/
|
||||
public function get_ignore_list() {
|
||||
return $this->ignore_list;
|
||||
}
|
||||
|
||||
public function get_animated_list() {
|
||||
return $this->animated_list;
|
||||
}
|
||||
|
||||
public function to_array() {
|
||||
$array = array(
|
||||
'is_outdated' => $this->is_outdated(),
|
||||
'image_attachment_count' => $this->get_image_attachment_count(),
|
||||
'optimized_images_count' => $this->get_optimized_images_count(),
|
||||
);
|
||||
|
||||
foreach ( $this->get_persistable_stats_for_optimizations() as $optimization_key => $optimization_stats ) {
|
||||
$array[ $optimization_key ] = $optimization_stats->get_stats()->to_array();
|
||||
}
|
||||
|
||||
$array['optimize_list'] = $this->optimize_list->get_ids();
|
||||
$array['optimize_count'] = $this->optimize_list->get_count();
|
||||
$array['reoptimize_list'] = $this->reoptimize_list->get_ids();
|
||||
$array['reoptimize_count'] = $this->reoptimize_list->get_count();
|
||||
$array['error_list'] = $this->error_list->get_ids();
|
||||
$array['error_count'] = $this->error_list->get_count();
|
||||
$array['ignore_list'] = $this->ignore_list->get_ids();
|
||||
$array['ignore_count'] = $this->ignore_list->get_count();
|
||||
$array['animated_list'] = $this->animated_list->get_ids();
|
||||
$array['animated_count'] = $this->animated_list->get_count();
|
||||
|
||||
$total_stats = $this->get_sum_of_optimization_global_stats();
|
||||
$array['size_before'] = $total_stats->get_size_before();
|
||||
$array['size_after'] = $total_stats->get_size_after();
|
||||
$array['savings_percent'] = $total_stats->get_percent();
|
||||
|
||||
$array['remaining_count'] = $this->get_remaining_count();
|
||||
|
||||
$array['percent_optimized'] = $this->get_percent_optimized();
|
||||
$array['percent_metric'] = $this->get_percent_metric();
|
||||
$array['grade_class'] = $this->get_grade_class();
|
||||
|
||||
$array['total_optimizable_items_count'] = $this->get_total_optimizable_items_count();
|
||||
$array['skipped_ids'] = $this->get_skipped_ids();
|
||||
$array['skipped_count'] = $this->get_skipped_count();
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function get_remaining_count() {
|
||||
return $this->optimize_list->get_count()
|
||||
+ $this->reoptimize_list->get_count()
|
||||
+ $this->error_list->get_count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_global_stats_option() {
|
||||
// Cached values are problematic in parallel
|
||||
wp_cache_delete( self::GLOBAL_STATS_OPTION_ID, 'options' );
|
||||
$option = get_option( self::GLOBAL_STATS_OPTION_ID, array() );
|
||||
|
||||
return empty( $option ) || ! is_array( $option )
|
||||
? array()
|
||||
: $option;
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
$this->get_reoptimize_list()->delete_ids();
|
||||
$this->get_optimize_list()->delete_ids();
|
||||
$this->get_error_list()->delete_ids();
|
||||
$this->get_ignore_list()->delete_ids();
|
||||
$this->get_animated_list()->delete_ids();
|
||||
|
||||
$this->delete_global_stats_option();
|
||||
foreach ( $this->get_persistable_stats_for_optimizations() as $persistable_stats_for_optimization ) {
|
||||
$persistable_stats_for_optimization->reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Total number of items that could be optimized, this includes items that have already been optimized.
|
||||
*
|
||||
* For a count that only contains items yet to be optimized/reoptimized {@see self::get_remaining_count()}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_optimizable_items_count() {
|
||||
return $this->get_image_attachment_count() - $this->get_skipped_count();
|
||||
}
|
||||
|
||||
public function get_skipped_count() {
|
||||
return count( $this->get_skipped_ids() );
|
||||
}
|
||||
|
||||
public function get_skipped_ids() {
|
||||
$skipped_ids = array_merge(
|
||||
$this->get_ignore_list()->get_ids(),
|
||||
$this->get_animated_list()->get_ids()
|
||||
);
|
||||
|
||||
return $this->array_utils->fast_array_unique( $skipped_ids );
|
||||
}
|
||||
|
||||
public function get_percent_optimized() {
|
||||
$total_optimizable_count = $this->get_total_optimizable_items_count();
|
||||
$remaining_count = $this->get_remaining_count();
|
||||
if (
|
||||
$total_optimizable_count === 0 ||
|
||||
$total_optimizable_count <= $remaining_count
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
$percent_optimized = floor( ( $total_optimizable_count - $remaining_count ) * 100 / $total_optimizable_count );
|
||||
if ( $percent_optimized > 100 ) {
|
||||
$percent_optimized = 100;
|
||||
} elseif ( $percent_optimized < 0 ) {
|
||||
$percent_optimized = 0;
|
||||
}
|
||||
|
||||
return $percent_optimized;
|
||||
}
|
||||
|
||||
public function get_percent_metric() {
|
||||
$percent_optimized = $this->get_percent_optimized();
|
||||
|
||||
return 0.0 === (float) $percent_optimized ? 100 : $percent_optimized;
|
||||
}
|
||||
|
||||
public function get_grade_class() {
|
||||
$total_optimizable_items_count = $this->get_total_optimizable_items_count();
|
||||
if ( 0 === $total_optimizable_items_count ) {
|
||||
$grade = 'sui-grade-dismissed';
|
||||
} else {
|
||||
$percent_optimized = $this->get_percent_optimized();
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $media_item Media_Item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove_media_item( $media_item ) {
|
||||
$attachment_id = $media_item->get_id();
|
||||
|
||||
// Remove from all the lists
|
||||
$this->remove_from_all_lists( $attachment_id );
|
||||
|
||||
// Remove stats
|
||||
$this->subtract_item_stats( $media_item );
|
||||
}
|
||||
|
||||
public function adjust_for_attachment( $attachment_id ) {
|
||||
$media_item = $this->media_item_cache->get( $attachment_id );
|
||||
$this->adjust_for_media_item( $media_item );
|
||||
}
|
||||
|
||||
/**
|
||||
* When the status of a media item changes this method can make the necessary changes to the global stats
|
||||
*
|
||||
* @param $media_item Media_Item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function adjust_for_media_item( $media_item ) {
|
||||
$this->adjust_lists_for_media_item( $media_item );
|
||||
|
||||
$belongs_in_stats = ! $media_item->is_skipped() && ! $media_item->has_errors();
|
||||
if ( $belongs_in_stats ) {
|
||||
$this->add_item_stats( $media_item );
|
||||
} else {
|
||||
$this->subtract_item_stats( $media_item );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $media_item Media_Item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_item_stats( $media_item ) {
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
foreach ( $this->get_persistable_stats_for_optimizations() as $optimization_key => $optimization_global_stats ) {
|
||||
$optimization = $optimizer->get_optimization( $optimization_key );
|
||||
if ( $optimization && $optimization->is_optimized() ) {
|
||||
$optimization_global_stats->add_item_stats( $media_item->get_id(), $optimization->get_stats() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $media_item Media_Item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function subtract_item_stats( $media_item ) {
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
foreach ( $this->get_persistable_stats_for_optimizations() as $optimization_key => $optimization_global_stats ) {
|
||||
$optimization = $optimizer->get_optimization( $optimization_key );
|
||||
if ( $optimization && $optimization->is_optimized() ) {
|
||||
$optimization_global_stats->subtract_item_stats( $media_item->get_id(), $optimization->get_stats() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $attachment_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function remove_from_all_lists( $attachment_id ) {
|
||||
$this->get_optimize_list()->remove_id( $attachment_id );
|
||||
$this->get_reoptimize_list()->remove_id( $attachment_id );
|
||||
$this->get_error_list()->remove_id( $attachment_id );
|
||||
$this->get_ignore_list()->remove_id( $attachment_id );
|
||||
$this->get_animated_list()->remove_id( $attachment_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Also:
|
||||
* @see Global_Stats_Controller::accumulate_attachment_ids()
|
||||
*/
|
||||
public function adjust_lists_for_media_item( $media_item ) {
|
||||
$attachment_id = $media_item->get_id();
|
||||
$optimizer = new Media_Item_Optimizer( $media_item );
|
||||
|
||||
// First remove from all the lists.
|
||||
$this->remove_from_all_lists( $attachment_id );
|
||||
|
||||
// Now add only to the lists where it belongs.
|
||||
if ( $media_item->is_ignored() ) {
|
||||
$this->get_ignore_list()->add_id( $attachment_id );
|
||||
} elseif ( $media_item->is_animated() ) {
|
||||
$this->get_animated_list()->add_id( $attachment_id );
|
||||
} elseif ( $media_item->has_errors() ) {
|
||||
$this->get_error_list()->add_id( $attachment_id );
|
||||
} else {
|
||||
if ( $optimizer->is_optimized() ) {
|
||||
if ( $optimizer->should_reoptimize() ) {
|
||||
$this->get_reoptimize_list()->add_id( $attachment_id );
|
||||
}
|
||||
} else {
|
||||
if ( $optimizer->should_optimize() ) {
|
||||
$this->get_optimize_list()->add_id( $attachment_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Smush\Core\Stats;
|
||||
|
||||
use Smush\Core\Media\Media_Item_Optimization_Global_Stats;
|
||||
use Smush\Core\Modules\Background\Mutex;
|
||||
|
||||
class Media_Item_Optimization_Global_Stats_Persistable {
|
||||
/**
|
||||
* @var Media_Item_Optimization_Global_Stats
|
||||
*/
|
||||
private $stats;
|
||||
|
||||
private $option_id;
|
||||
|
||||
public function __construct( $option_id, $stats = null ) {
|
||||
$this->option_id = $option_id;
|
||||
$this->stats = is_a( $stats, '\Smush\Core\Media\Media_Item_Optimization_Global_Stats' )
|
||||
? $stats
|
||||
: new Media_Item_Optimization_Global_Stats();
|
||||
|
||||
$this->fetch_from_option( $option_id );
|
||||
}
|
||||
|
||||
public function save() {
|
||||
update_option( $this->option_id, $this->stats->to_array(), false );
|
||||
}
|
||||
|
||||
public function has_attachment_id( $attachment_id ) {
|
||||
return $this->stats->has_attachment_id( $attachment_id );
|
||||
}
|
||||
|
||||
public function add_item_stats( $attachment_id, $addend ) {
|
||||
$this->mutex( function () use ( $attachment_id, $addend ) {
|
||||
$this->fetch_from_option( $this->option_id );
|
||||
$this->stats->add_item_stats( $attachment_id, $addend );
|
||||
$this->save();
|
||||
} );
|
||||
}
|
||||
|
||||
public function subtract_item_stats( $attachment_id, $subtrahend ) {
|
||||
$this->mutex( function () use ( $attachment_id, $subtrahend ) {
|
||||
$this->fetch_from_option( $this->option_id );
|
||||
$this->stats->subtract_item_stats( $attachment_id, $subtrahend );
|
||||
$this->save();
|
||||
} );
|
||||
}
|
||||
|
||||
public function add( $addend ) {
|
||||
$this->mutex( function () use ( $addend ) {
|
||||
$this->fetch_from_option( $this->option_id );
|
||||
$this->stats->add( $addend );
|
||||
$this->save();
|
||||
} );
|
||||
}
|
||||
|
||||
public function subtract( $subtrahend ) {
|
||||
$this->mutex( function () use ( $subtrahend ) {
|
||||
$this->fetch_from_option( $this->option_id );
|
||||
$this->stats->subtract( $subtrahend );
|
||||
$this->save();
|
||||
} );
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
public function initialize() {
|
||||
$this->mutex( function () {
|
||||
$this->stats->from_array( array() );
|
||||
$this->save();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $option_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function fetch_from_option( $option_id ) {
|
||||
// Two threads may access this at the same time, so cached values can cause reader-writer problem
|
||||
wp_cache_delete( $this->option_id, 'options' );
|
||||
$this->stats->from_array( get_option( $option_id, array() ) );
|
||||
}
|
||||
|
||||
protected function mutex( $operation ) {
|
||||
( new Mutex( $this->mutex_key() ) )->execute( $operation );
|
||||
}
|
||||
|
||||
private function mutex_key() {
|
||||
return 'update_global_stats_mutex_' . $this->option_id;
|
||||
}
|
||||
|
||||
public function get_option_id() {
|
||||
return $this->option_id;
|
||||
}
|
||||
|
||||
public function get_stats() {
|
||||
return $this->stats;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user