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 / BulkOptimize.php

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

244 lines 7.3 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\Bulk\BulkOptimizerInterface;
7
8 /**
9 * MCP ability: schedule a bulk image optimization run.
10 *
11 * Registers itself with the WP Abilities API under the slug
12 * `imagify/bulk-optimize` and delegates to `Imagify\Bulk\Bulk::run_optimize()`.
13 *
14 * @since 2.3.0
15 */
16 class BulkOptimize extends AbstractAbility implements CreditConsumingAbilityInterface {
17
18 const ABILITY_ID = 'imagify/bulk-optimize';
19 const ABILITY_NAME = 'Bulk optimize';
20
21 /**
22 * Bulk optimization instance.
23 *
24 * @var BulkOptimizerInterface
25 */
26 private $bulk;
27
28 /**
29 * Constructor.
30 *
31 * @param BulkOptimizerInterface $bulk The bulk optimization instance.
32 */
33 public function __construct( BulkOptimizerInterface $bulk ) {
34 $this->bulk = $bulk;
35 }
36
37 /**
38 * Returns the ability slug.
39 *
40 * @return string
41 */
42 public function get_id(): string {
43 return self::ABILITY_ID;
44 }
45
46 /**
47 * Returns the human-readable ability label.
48 *
49 * @return string
50 */
51 public function get_name(): string {
52 return self::ABILITY_NAME;
53 }
54
55 /**
56 * Register the ability with the WP Abilities API.
57 *
58 * No-ops gracefully when the API is not available (WP < 6.9).
59 *
60 * @return void
61 */
62 public function register(): void {
63 if ( ! function_exists( 'wp_register_ability' ) ) {
64 return;
65 }
66
67 wp_register_ability(
68 'imagify/bulk-optimize',
69 [
70 'label' => __( 'Bulk optimize', 'imagify' ),
71 'description' => __( 'Schedule a bulk image optimization run for the WordPress media library or custom folders.', 'imagify' ),
72 'category' => 'imagify',
73 'input_schema' => [
74 'type' => 'object',
75 'properties' => [
76 'context' => [
77 'type' => 'string',
78 'description' => __( 'Optimization context: "wp" for the WordPress media library or "custom-folders" for custom folder sources.', 'imagify' ),
79 'enum' => [ 'wp', 'custom-folders' ],
80 ],
81 'optimization_level' => [
82 'type' => 'integer',
83 'description' => __( 'Optimization level: 0 (normal), 1 (aggressive), or 2 (ultra). Defaults to the global Imagify setting.', 'imagify' ),
84 'minimum' => 0,
85 'maximum' => 2,
86 ],
87 'confirm' => [
88 'type' => 'boolean',
89 'description' => __( 'Set to true to execute after reviewing the credit-consumption preview returned by a prior call without this flag.', 'imagify' ),
90 'default' => false,
91 ],
92 ],
93 'required' => [ 'context' ],
94 ],
95 'output_schema' => [
96 'type' => 'object',
97 'properties' => [
98 'status' => [
99 'type' => 'string',
100 'description' => __( 'Result status: "scheduled", "error", "confirmation_required", "insufficient_quota", or "invalid_api_key".', 'imagify' ),
101 'enum' => [ 'scheduled', 'error', 'confirmation_required', 'insufficient_quota', 'invalid_api_key' ],
102 ],
103 'context' => [
104 'type' => 'string',
105 'description' => __( 'The requested optimization context, echoed back.', 'imagify' ),
106 ],
107 'error_message' => [
108 'type' => [ 'string', 'null' ],
109 'description' => __( 'Human-readable error message on failure, or null on success.', 'imagify' ),
110 ],
111 ],
112 ],
113 'execute_callback' => [ $this, 'execute' ],
114 'permission_callback' => [ $this, 'check_permissions' ],
115 'meta' => [
116 'show_in_rest' => true,
117 'mcp' => [
118 'public' => true,
119 ],
120 'annotations' => [
121 'readonly' => false,
122 'destructive' => false,
123 'idempotent' => false,
124 ],
125 ],
126 ]
127 );
128 }
129
130 /**
131 * Check if the current user has permission to execute this ability.
132 *
133 * @return bool True when the current user has the `manage_options` capability.
134 */
135 protected function has_permission(): bool {
136 return (bool) current_user_can( 'manage_options' );
137 }
138
139 /**
140 * Returns the capability required to execute this ability.
141 *
142 * @return string
143 */
144 protected function get_required_capability(): string {
145 return 'manage_options';
146 }
147
148 /**
149 * Returns the credit-consumption impact estimate for a bulk optimization run.
150 *
151 * Uses cheap COUNT-only helpers per context — never the heavy ID-fetching
152 * `get_unoptimized_media_ids()`, which would run a full JOIN query just to
153 * build a preview count.
154 *
155 * @param array $args Input arguments. Expects `context` (string).
156 * @return array{unit: string, count: int, total: int, label: string}
157 */
158 public function get_impact_estimate( array $args ): array {
159 $context = isset( $args['context'] ) ? (string) $args['context'] : '';
160 $context = ( 'custom-folders' === $context ) ? 'custom-folders' : 'wp';
161
162 if ( 'custom-folders' === $context ) {
163 $remaining = \Imagify_Files_Stats::count_unoptimized_files();
164 $total = \Imagify_Files_Stats::count_files();
165 $label = __( 'unoptimized images in custom folders', 'imagify' );
166 } else {
167 $remaining = imagify_count_unoptimized_attachments();
168 $total = imagify_count_attachments();
169 $label = __( 'unoptimized images in the WordPress media library', 'imagify' );
170 }
171
172 return [
173 'unit' => 'image',
174 'count' => (int) $remaining,
175 'total' => (int) $total,
176 'label' => $label,
177 ];
178 }
179
180 /**
181 * Execute the ability: schedule a bulk optimization run.
182 *
183 * Wraps the real execution behind `guard_credit_confirmation()`.
184 *
185 * @param array $args Input arguments. Expects `context` (string) and optionally `optimization_level` (int), `confirm` (bool).
186 * @return array<string, mixed> Guard response (invalid_api_key/insufficient_quota/confirmation_required) or the do_execute() result shape.
187 */
188 public function execute( array $args = [] ): array {
189 $start_time = microtime( true );
190 $result = $this->guard_credit_confirmation(
191 $args,
192 function ( array $a ) {
193 return $this->do_execute( $a );
194 }
195 );
196
197 $this->fire_executed( $result, $start_time, $args );
198
199 return $result;
200 }
201
202 /**
203 * Internal execution logic for the ability.
204 *
205 * Separated from execute() so guard_credit_confirmation() can invoke it
206 * via a closure without a `[$this, 'method']` callable-array visibility
207 * problem (this method is private, and the guard lives on AbstractAbility).
208 *
209 * @param array $args Input arguments. Expects `context` (string) and optionally `optimization_level` (int).
210 * @return array{status: string, context: string, error_message: string|null}
211 */
212 private function do_execute( array $args ): array {
213 $context = isset( $args['context'] ) ? (string) $args['context'] : '';
214
215 if ( ! in_array( $context, [ 'wp', 'custom-folders' ], true ) ) {
216 return [
217 'status' => 'error',
218 'context' => $context,
219 'error_message' => 'Invalid context. Allowed values: wp, custom-folders.',
220 ];
221 }
222
223 $optimization_level = isset( $args['optimization_level'] )
224 ? max( 0, min( 2, (int) $args['optimization_level'] ) )
225 : (int) get_imagify_option( 'optimization_level' );
226
227 $result = $this->bulk->run_optimize( $context, $optimization_level );
228
229 if ( $result['success'] ) {
230 return [
231 'status' => 'scheduled',
232 'context' => $context,
233 'error_message' => null,
234 ];
235 }
236
237 return [
238 'status' => 'error',
239 'context' => $context,
240 'error_message' => $result['message'],
241 ];
242 }
243 }
244