PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.5.0
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.5.0
1.7.7 1.7.6 1.7.5 1.7.4 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 All 33 releases
image-optimization / modules / stats / classes / optimization-stats.php

optimization-stats.php in Image Optimizer – Compress Images and Convert to WebP or AVIF 1.5.0, at modules/stats/classes/optimization-stats.php

243 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImageOptimization\Modules\Stats\Classes;
4
5 use ImageOptimization\Classes\Image\{
6 Image,
7 Image_Meta,
8 Image_Query_Builder,
9 WP_Image_Meta,
10 Exceptions\Invalid_Image_Exception
11 };
12 use ImageOptimization\Classes\Async_Operation\{
13 Async_Operation,
14 Async_Operation_Hook,
15 Async_Operation_Queue,
16 Exceptions\Async_Operation_Exception,
17 Queries\Operation_Query,
18 };
19 use ImageOptimization\Classes\File_System\{
20 Exceptions\File_System_Operation_Error,
21 File_System,
22 };
23 use ImageOptimization\Classes\Logger;
24 use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_Validation_Error;
25 use ImageOptimization\Modules\Optimization\Classes\Validate_Image;
26 use ImageOptimization\Modules\Settings\Classes\Settings;
27
28 if ( ! defined( 'ABSPATH' ) ) {
29 exit; // Exit if accessed directly.
30 }
31
32 class Optimization_Stats {
33 const PAGING_SIZE = 25000;
34 const STATS_CALCULATION_DELAY = 60 * 15;
35
36 /**
37 * Returns image stats.
38 * If the library is too big, it queries images in chunks.
39 *
40 * @return array{total_image_count: int, optimized_image_count: int, current_image_size: int, initial_image_size:
41 * int}
42 */
43 public static function get_image_stats( ?int $image_id = null, ?bool $no_cache = false ): array {
44 // No caching needed if we check a specific image.
45 if ( $image_id ) {
46 $output = self::get_image_stats_chunk( 1, $image_id );
47
48 unset( $output['pages'] );
49
50 return $output;
51 }
52
53 // Look at the stored data first. If it's still valid -- use it.
54 $stats = self::get_stored_stats();
55
56 if ( ! $no_cache && ( time() - $stats['updated_at'] ) <= self::STATS_CALCULATION_DELAY ) {
57 unset( $stats['updated_at'] );
58
59 return $stats;
60 }
61
62 // Otherwise, recalculate the stats.
63 $output = self::get_image_stats_chunk( 1, $image_id );
64 $pages_count = $output['pages'];
65
66 if ( 1 === $pages_count ) {
67 unset( $output['pages'] );
68
69 self::set_stored_stats( $output );
70
71 return $output;
72 }
73
74 if ( $pages_count > 1 && Async_Operation::OPERATION_STATUS_NOT_STARTED === self::get_stats_calculation_status() ) {
75 try {
76 Async_Operation::create(
77 Async_Operation_Hook::CALCULATE_OPTIMIZATION_STATS,
78 [
79 'page' => 2,
80 'pages_count' => $pages_count,
81 'output' => $output,
82 ],
83 Async_Operation_Queue::STATS
84 );
85 } catch ( Async_Operation_Exception $aoe ) {
86 Logger::log( Logger::LEVEL_ERROR, 'Error while creating a stats calculation task: ' . $aoe->getMessage() );
87 }
88 }
89
90 unset( $stats['updated_at'] );
91
92 return $stats;
93 }
94
95 /**
96 * @return array{pages: int, total_image_count: int, optimized_image_count: int, current_image_size: int,
97 * initial_image_size: int}
98 */
99 public static function get_image_stats_chunk( int $paged = 1, ?int $image_id = null ): array {
100 $output = [
101 'pages' => 1,
102 'total_image_count' => 0,
103 'optimized_image_count' => 0,
104 'current_image_size' => 0,
105 'initial_image_size' => 0,
106 ];
107
108 $query = ( new Image_Query_Builder() )
109 ->set_paging_size( self::PAGING_SIZE )
110 ->set_current_page( $paged );
111
112 if ( $image_id ) {
113 $query->set_image_ids( [ $image_id ] );
114 }
115
116 $query = $query->execute();
117
118 $output['pages'] = (int) $query->max_num_pages;
119
120 foreach ( $query->posts as $attachment_id ) {
121 try {
122 Validate_Image::is_valid( $attachment_id );
123 $wp_meta = new WP_Image_Meta( $attachment_id );
124 } catch ( Invalid_Image_Exception |Image_Validation_Error $ie ) {
125 Logger::log( Logger::LEVEL_ERROR, $ie->getMessage() );
126
127 continue;
128 }
129
130 $meta = new Image_Meta( $attachment_id );
131 $image_sizes = $wp_meta->get_size_keys();
132
133 $current_sizes = self::filter_only_enabled_sizes( $image_sizes );
134 $optimized_sizes = self::filter_only_enabled_sizes( $meta->get_optimized_sizes() );
135
136 $output['total_image_count'] += count( $current_sizes );
137 $output['optimized_image_count'] += count( $optimized_sizes );
138
139 foreach ( $image_sizes as $image_size ) {
140 $output['current_image_size'] += self::calculate_current_image_file_size( $attachment_id, $wp_meta, $image_size );
141 $output['initial_image_size'] += self::calculate_initial_image_file_size( $attachment_id, $meta, $wp_meta, $image_size );
142 }
143 }
144
145 return $output;
146 }
147
148 private static function calculate_current_image_file_size( int $image_id, WP_Image_Meta $wp_meta, string $image_size ): int {
149 $size_from_meta = $wp_meta->get_file_size( $image_size );
150
151 if ( $size_from_meta ) {
152 return $size_from_meta;
153 }
154
155 try {
156 return File_System::size( ( new Image( $image_id ) )->get_file_path( $image_size ) );
157 } catch ( File_System_Operation_Error $e ) {
158 return 0;
159 }
160 }
161
162 private static function calculate_initial_image_file_size( int $image_id, Image_Meta $meta, WP_Image_Meta $wp_meta, string $image_size ): int {
163 $size_from_meta = $meta->get_original_file_size( $image_size ) ?? $wp_meta->get_file_size( $image_size );
164
165 if ( $size_from_meta ) {
166 return $size_from_meta;
167 }
168
169 try {
170 return File_System::size( ( new Image( $image_id ) )->get_file_path( $image_size ) );
171 } catch ( File_System_Operation_Error $e ) {
172 return 0;
173 }
174 }
175
176 private static function filter_only_enabled_sizes( array $size_keys ): array {
177 $enabled_sizes = Settings::get( Settings::CUSTOM_SIZES_OPTION_NAME );
178
179 if ( 'all' === $enabled_sizes ) {
180 return array_filter( $size_keys, fn( string $size_key ) => ! str_starts_with( $size_key, 'elementor_' ) );
181 }
182
183 return array_filter($size_keys, function( string $size ) use ( $enabled_sizes ) {
184 if ( Image::SIZE_FULL === $size ) {
185 return true;
186 }
187
188 if ( in_array( $size, $enabled_sizes, true ) ) {
189 return true;
190 }
191
192 return false;
193 });
194 }
195
196 /**
197 * Retrieves stats data.
198 *
199 * @return array{total_image_count: ?int, optimized_image_count: ?int, current_image_size: ?int, initial_image_size:
200 * ?int, updated_at: ?int}
201 */
202 public static function get_stored_stats(): array {
203 $default = [
204 'total_image_count' => null,
205 'optimized_image_count' => null,
206 'current_image_size' => null,
207 'initial_image_size' => null,
208 'updated_at' => null,
209 ];
210
211 return json_decode( get_option( 'image_optimizer_optimization_stats', json_encode( $default ) ), ARRAY_A );
212 }
213
214 /**
215 * Updates the optimization stats with fresh values.
216 *
217 * @param array $stats
218 *
219 * @return bool
220 */
221 public static function set_stored_stats( array $stats ) {
222 $stats['updated_at'] = time();
223
224 return update_option( 'image_optimizer_optimization_stats', json_encode( $stats ) );
225 }
226
227 private static function get_stats_calculation_status(): string {
228 $active_query = ( new Operation_Query() )
229 ->set_hook( Async_Operation_Hook::CALCULATE_OPTIMIZATION_STATS )
230 ->set_status( [
231 Async_Operation::OPERATION_STATUS_PENDING,
232 Async_Operation::OPERATION_STATUS_RUNNING,
233 ] )
234 ->set_limit( 1 );
235
236 $active_operations = Async_Operation::get( $active_query );
237
238 return ! empty( $active_operations )
239 ? Async_Operation::OPERATION_STATUS_RUNNING
240 : Async_Operation::OPERATION_STATUS_NOT_STARTED;
241 }
242 }
243