PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / classes / Abilities / GetMediaStatus.php

GetMediaStatus.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF trunk, at classes/Abilities/GetMediaStatus.php

286 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 declare(strict_types=1);
3
4 namespace Imagify\Abilities;
5
6 use Imagify\Optimization\Data\WP;
7 use Imagify\Optimization\Process\ProcessInterface;
8
9 /**
10 * MCP ability: imagify/get-media-status
11 *
12 * Returns the optimization status and key metrics for a given WordPress
13 * media library attachment.
14 *
15 * @since 2.3.0
16 */
17 class GetMediaStatus extends AbstractAbility {
18
19 /**
20 * Returns the ability slug.
21 *
22 * @return string
23 */
24 public function get_id(): string {
25 return 'imagify/get-media-status';
26 }
27
28 /**
29 * Returns the ability label.
30 *
31 * @return string
32 */
33 public function get_name(): string {
34 return 'Get Media Status';
35 }
36
37 /**
38 * Register the ability with the WP Abilities API.
39 *
40 * No-ops silently when `wp_register_ability` is unavailable (WP < 6.9).
41 *
42 * @since 2.3.0
43 *
44 * @return void
45 */
46 public function register(): void {
47 if ( ! function_exists( 'wp_register_ability' ) ) {
48 return;
49 }
50
51 wp_register_ability(
52 'imagify/get-media-status',
53 [
54 'label' => __( 'Get Media Status', 'imagify' ),
55 'description' => __( 'Retrieve the optimization status and metrics for a WordPress media library attachment.', 'imagify' ),
56 'category' => 'imagify',
57 'input_schema' => [
58 'type' => 'object',
59 'properties' => [
60 'media_id' => [
61 'type' => 'integer',
62 'description' => __( 'The WordPress attachment ID. Provide media_filename or media_url instead when the ID is unknown.', 'imagify' ),
63 ],
64 ] + MediaResolver::get_input_schema_properties(),
65 ],
66 'output_schema' => [
67 'type' => 'object',
68 'properties' => [
69 'status' => [
70 'type' => 'string',
71 'description' => __( 'Optimization status: "success", "error", or "unoptimized".', 'imagify' ),
72 'enum' => [ 'success', 'error', 'unoptimized' ],
73 ],
74 'optimization_level' => [
75 'type' => [ 'integer', 'null' ],
76 'description' => __( '0 = lossless, 1 = aggressive, 2 = ultra. Null when not optimized.', 'imagify' ),
77 ],
78 'original_size' => [
79 'type' => 'integer',
80 'description' => __( 'File size in bytes before optimization.', 'imagify' ),
81 ],
82 'optimized_size' => [
83 'type' => 'integer',
84 'description' => __( 'File size in bytes after optimization.', 'imagify' ),
85 ],
86 'webp_available' => [
87 'type' => 'boolean',
88 'description' => __( 'True when a WebP version of the full-size image has been generated.', 'imagify' ),
89 ],
90 'avif_available' => [
91 'type' => 'boolean',
92 'description' => __( 'True when an AVIF version of the full-size image has been generated.', 'imagify' ),
93 ],
94 'error_message' => [
95 'type' => [ 'string', 'null' ],
96 'description' => __( 'Human-readable error message when status is "error". Null otherwise.', 'imagify' ),
97 ],
98 ],
99 ],
100 'execute_callback' => [ $this, 'execute' ],
101 'permission_callback' => [ $this, 'check_permissions' ],
102 'meta' => [
103 'show_in_rest' => true,
104 'mcp' => [ 'public' => true ],
105 'annotations' => [
106 'readonly' => true,
107 'destructive' => false,
108 'idempotent' => true,
109 ],
110 ],
111 ]
112 );
113 }
114
115 /**
116 * Routes through Imagify's capability abstraction so the `imagify_capacity`
117 * filter and multisite network-admin logic are honoured.
118 *
119 * @since 2.3.0
120 *
121 * @return bool True when the current user has the Imagify `manage` capability.
122 */
123 protected function has_permission(): bool {
124 return imagify_get_context( 'wp' )->current_user_can( 'manage' );
125 }
126
127 /**
128 * Execute the ability and return the media optimization status.
129 *
130 * @since 2.3.0
131 *
132 * @param array $args Input arguments. Expects `media_id` (int) — the WordPress attachment ID.
133 * @return array Optimization status response keyed by status, optimization_level, original_size,
134 * optimized_size, webp_available, avif_available, and error_message.
135 */
136 public function execute( array $args = [] ): array {
137 $start_time = microtime( true );
138 $result = $this->do_execute( $args );
139
140 $this->fire_executed( $result, $start_time, $args );
141
142 return $result;
143 }
144
145 /**
146 * Internal execution logic for the ability.
147 *
148 * @param array $args Input arguments.
149 * @return array
150 */
151 private function do_execute( array $args ): array {
152 $media_id = MediaResolver::resolve_id( $args );
153
154 if ( is_wp_error( $media_id ) ) {
155 return $this->error_response( $media_id->get_error_message() );
156 }
157
158 // Verify the attachment exists.
159 if ( ! get_post( $media_id ) ) {
160 return $this->error_response( 'Media not found.' );
161 }
162
163 $wp_data = $this->create_wp_data( $media_id );
164 $opt_data = $wp_data->get_optimization_data();
165
166 $internal_status = isset( $opt_data['status'] ) ? (string) $opt_data['status'] : '';
167 $status = $this->map_status( $internal_status );
168
169 $level = isset( $opt_data['level'] ) && false !== $opt_data['level']
170 ? (int) $opt_data['level']
171 : null;
172
173 // get_original_size(false) falls back to the filesystem when no optimization data
174 // exists yet (unoptimized media), which avoids the 0-byte read from empty meta.
175 $original_size = $wp_data->get_original_size( false );
176 $optimized_size = isset( $opt_data['stats']['optimized_size'] ) ? (int) $opt_data['stats']['optimized_size'] : 0;
177
178 $webp_available = false;
179 $avif_available = false;
180
181 if ( ! empty( $opt_data['sizes'] ) && is_array( $opt_data['sizes'] ) ) {
182 foreach ( array_keys( $opt_data['sizes'] ) as $size_key ) {
183 if ( ! $webp_available && $this->size_key_ends_with( (string) $size_key, ProcessInterface::WEBP_SUFFIX ) ) {
184 $webp_available = true;
185 }
186 if ( ! $avif_available && $this->size_key_ends_with( (string) $size_key, ProcessInterface::AVIF_SUFFIX ) ) {
187 $avif_available = true;
188 }
189 if ( $webp_available && $avif_available ) {
190 break;
191 }
192 }
193 }
194
195 $error_message = null;
196 if ( 'error' === $status ) {
197 $error_message = isset( $opt_data['message'] ) && '' !== $opt_data['message']
198 ? (string) $opt_data['message']
199 : null;
200 }
201
202 return [
203 'status' => $status,
204 'optimization_level' => $level,
205 'original_size' => $original_size,
206 'optimized_size' => $optimized_size,
207 'webp_available' => $webp_available,
208 'avif_available' => $avif_available,
209 'error_message' => $error_message,
210 ];
211 }
212
213 /**
214 * Build an error response with every output field set to a neutral value.
215 *
216 * @since 2.3.3
217 *
218 * @param string $error_message Human-readable error message.
219 * @return array{status: string, optimization_level: null, original_size: int, optimized_size: int, webp_available: bool, avif_available: bool, error_message: string}
220 */
221 private function error_response( string $error_message ): array {
222 return [
223 'status' => 'error',
224 'optimization_level' => null,
225 'original_size' => 0,
226 'optimized_size' => 0,
227 'webp_available' => false,
228 'avif_available' => false,
229 'error_message' => $error_message,
230 ];
231 }
232
233 /**
234 * Instantiate the WP optimization-data object for a given media.
235 *
236 * Extracted to a protected method so tests can override it without hitting the database.
237 *
238 * @since 2.3.0
239 *
240 * @param int $media_id WordPress attachment ID.
241 * @return WP
242 */
243 protected function create_wp_data( int $media_id ): WP {
244 return new WP( $media_id );
245 }
246
247 /**
248 * Map the internal Imagify optimization status to the public API status.
249 *
250 * @since 2.3.0
251 *
252 * @param string $internal_status The internal status string from `_imagify_status` meta.
253 * @return string 'success', 'error', or 'unoptimized'.
254 */
255 private function map_status( string $internal_status ): string {
256 if ( 'success' === $internal_status || 'already_optimized' === $internal_status ) {
257 return 'success';
258 }
259
260 if ( 'error' === $internal_status ) {
261 return 'error';
262 }
263
264 return 'unoptimized';
265 }
266
267 /**
268 * Check whether a size key ends with a given suffix.
269 *
270 * @since 2.3.0
271 *
272 * @param string $size_key The thumbnail size key to inspect.
273 * @param string $suffix The suffix to check for.
274 * @return bool
275 */
276 private function size_key_ends_with( string $size_key, string $suffix ): bool {
277 $suffix_length = strlen( $suffix );
278
279 if ( 0 === $suffix_length ) {
280 return true;
281 }
282
283 return substr( $size_key, -$suffix_length ) === $suffix;
284 }
285 }
286