PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.6.5
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.6.5
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 / trunk / modules / stats / classes / optimization-stats.php

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

239 lines 6.7 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 ( $pages_count <= 1 ) {
67 unset( $output['pages'] );
68
69 return $output;
70 }
71
72 if ( $pages_count > 1 && Async_Operation::OPERATION_STATUS_NOT_STARTED === self::get_stats_calculation_status() ) {
73 try {
74 Async_Operation::create(
75 Async_Operation_Hook::CALCULATE_OPTIMIZATION_STATS,
76 [
77 'page' => 2,
78 'pages_count' => $pages_count,
79 'output' => $output,
80 ],
81 Async_Operation_Queue::STATS
82 );
83 } catch ( Async_Operation_Exception $aoe ) {
84 Logger::log( Logger::LEVEL_ERROR, 'Error while creating a stats calculation task: ' . $aoe->getMessage() );
85 }
86 }
87
88 unset( $stats['updated_at'] );
89
90 return $stats;
91 }
92
93 /**
94 * @return array{pages: int, total_image_count: int, optimized_image_count: int, current_image_size: int,
95 * initial_image_size: int}
96 */
97 public static function get_image_stats_chunk( int $paged = 1, ?int $image_id = null ): array {
98 $output = [
99 'pages' => 1,
100 'total_image_count' => 0,
101 'optimized_image_count' => 0,
102 'current_image_size' => 0,
103 'initial_image_size' => 0,
104 ];
105
106 $query = ( new Image_Query_Builder() )
107 ->set_paging_size( self::PAGING_SIZE )
108 ->set_current_page( $paged );
109
110 if ( $image_id ) {
111 $query->set_image_ids( [ $image_id ] );
112 }
113
114 $query = $query->execute();
115
116 $output['pages'] = (int) $query->max_num_pages;
117
118 foreach ( $query->posts as $attachment_id ) {
119 try {
120 Validate_Image::is_valid( $attachment_id );
121 $wp_meta = new WP_Image_Meta( $attachment_id );
122 } catch ( Invalid_Image_Exception |Image_Validation_Error $ie ) {
123 continue;
124 }
125
126 $meta = new Image_Meta( $attachment_id );
127 $image_sizes = $wp_meta->get_size_keys();
128
129 $current_sizes = self::filter_only_enabled_sizes( $image_sizes );
130 $optimized_sizes = self::filter_only_enabled_sizes( $meta->get_optimized_sizes() );
131
132 $output['total_image_count'] += count( $current_sizes );
133 $output['optimized_image_count'] += count( $optimized_sizes );
134
135 foreach ( $image_sizes as $image_size ) {
136 $output['current_image_size'] += self::calculate_current_image_file_size( $attachment_id, $wp_meta, $image_size );
137 $output['initial_image_size'] += self::calculate_initial_image_file_size( $attachment_id, $meta, $wp_meta, $image_size );
138 }
139 }
140
141 return $output;
142 }
143
144 private static function calculate_current_image_file_size( int $image_id, WP_Image_Meta $wp_meta, string $image_size ): int {
145 $size_from_meta = $wp_meta->get_file_size( $image_size );
146
147 if ( $size_from_meta ) {
148 return $size_from_meta;
149 }
150
151 try {
152 return File_System::size( ( new Image( $image_id ) )->get_file_path( $image_size ) );
153 } catch ( File_System_Operation_Error $e ) {
154 return 0;
155 }
156 }
157
158 private static function calculate_initial_image_file_size( int $image_id, Image_Meta $meta, WP_Image_Meta $wp_meta, string $image_size ): int {
159 $size_from_meta = $meta->get_original_file_size( $image_size ) ?? $wp_meta->get_file_size( $image_size );
160
161 if ( $size_from_meta ) {
162 return $size_from_meta;
163 }
164
165 try {
166 return File_System::size( ( new Image( $image_id ) )->get_file_path( $image_size ) );
167 } catch ( File_System_Operation_Error $e ) {
168 return 0;
169 }
170 }
171
172 private static function filter_only_enabled_sizes( array $size_keys ): array {
173 $enabled_sizes = Settings::get( Settings::CUSTOM_SIZES_OPTION_NAME );
174
175 if ( 'all' === $enabled_sizes ) {
176 return array_filter( $size_keys, fn( string $size_key ) => ! str_starts_with( $size_key, 'elementor_' ) );
177 }
178
179 return array_filter($size_keys, function( string $size ) use ( $enabled_sizes ) {
180 if ( Image::SIZE_FULL === $size ) {
181 return true;
182 }
183
184 if ( in_array( $size, $enabled_sizes, true ) ) {
185 return true;
186 }
187
188 return false;
189 });
190 }
191
192 /**
193 * Retrieves stats data.
194 *
195 * @return array{total_image_count: ?int, optimized_image_count: ?int, current_image_size: ?int, initial_image_size:
196 * ?int, updated_at: ?int}
197 */
198 public static function get_stored_stats(): array {
199 $default = [
200 'total_image_count' => null,
201 'optimized_image_count' => null,
202 'current_image_size' => null,
203 'initial_image_size' => null,
204 'updated_at' => null,
205 ];
206
207 return json_decode( get_option( 'image_optimizer_optimization_stats', json_encode( $default ) ), ARRAY_A );
208 }
209
210 /**
211 * Updates the optimization stats with fresh values.
212 *
213 * @param array $stats
214 *
215 * @return bool
216 */
217 public static function set_stored_stats( array $stats ) {
218 $stats['updated_at'] = time();
219
220 return update_option( 'image_optimizer_optimization_stats', json_encode( $stats ) );
221 }
222
223 private static function get_stats_calculation_status(): string {
224 $active_query = ( new Operation_Query() )
225 ->set_hook( Async_Operation_Hook::CALCULATE_OPTIMIZATION_STATS )
226 ->set_status( [
227 Async_Operation::OPERATION_STATUS_PENDING,
228 Async_Operation::OPERATION_STATUS_RUNNING,
229 ] )
230 ->set_limit( 1 );
231
232 $active_operations = Async_Operation::get( $active_query );
233
234 return ! empty( $active_operations )
235 ? Async_Operation::OPERATION_STATUS_RUNNING
236 : Async_Operation::OPERATION_STATUS_NOT_STARTED;
237 }
238 }
239