| @@ -11,9 +11,9 @@ | ||
| 11 | 11 | * `Imagify\Optimization\Process\WP` class. |
| 12 | 12 | * |
| 13 | 13 | * @since 2.3.0 |
| 14 | 14 | */ |
| 15 | -class OptimizeMedia extends AbstractAbility { | |
| 15 | +class OptimizeMedia extends AbstractAbility implements CreditConsumingAbilityInterface { | |
| 16 | 16 | |
| 17 | 17 | const ABILITY_ID = 'imagify/optimize-media'; |
| 18 | 18 | const ABILITY_NAME = 'Optimize media'; |
| 19 | 19 | |
| @@ -46,8 +46,15 @@ | ||
| 46 | 46 | if ( ! function_exists( 'wp_register_ability' ) ) { |
| 47 | 47 | return; |
| 48 | 48 | } |
| 49 | 49 | |
| 50 | + $media_properties = [ | |
| 51 | + 'media_id' => [ | |
| 52 | + 'type' => 'integer', | |
| 53 | + 'description' => __( 'The WordPress attachment ID to optimize. Provide media_filename or media_url instead when the ID is unknown.', 'imagify' ), | |
| 54 | + ], | |
| 55 | + ] + MediaResolver::get_input_schema_properties(); | |
| 56 | + | |
| 50 | 57 | wp_register_ability( |
| 51 | 58 | 'imagify/optimize-media', |
| 52 | 59 | [ |
| 53 | 60 | 'label' => __( 'Optimize media', 'imagify' ), |
| @@ -54,13 +61,9 @@ | ||
| 54 | 61 | 'description' => __( 'Optimizes a specific media on-demand using Imagify.', 'imagify' ), |
| 55 | 62 | 'category' => 'imagify', |
| 56 | 63 | 'input_schema' => [ |
| 57 | 64 | 'type' => 'object', |
| 58 | - 'properties' => [ | |
| 59 | - 'media_id' => [ | |
| 60 | - 'type' => 'integer', | |
| 61 | - 'description' => __( 'The WordPress attachment ID to optimize.', 'imagify' ), | |
| 62 | - ], | |
| 65 | + 'properties' => $media_properties + [ | |
| 63 | 66 | 'optimization_level' => [ |
| 64 | 67 | 'type' => 'integer', |
| 65 | 68 | 'description' => __( 'Optimization level: 0 (normal), 1 (aggressive), or 2 (ultra). If omitted, uses the global setting.', 'imagify' ), |
| 66 | 69 | 'minimum' => 0, |
| @@ -65,10 +68,14 @@ | ||
| 65 | 68 | 'description' => __( 'Optimization level: 0 (normal), 1 (aggressive), or 2 (ultra). If omitted, uses the global setting.', 'imagify' ), |
| 66 | 69 | 'minimum' => 0, |
| 67 | 70 | 'maximum' => 2, |
| 68 | 71 | ], |
| 72 | + 'confirm' => [ | |
| 73 | + 'type' => 'boolean', | |
| 74 | + 'description' => __( 'Set to true to execute after reviewing the credit-consumption preview returned by a prior call without this flag.', 'imagify' ), | |
| 75 | + 'default' => false, | |
| 76 | + ], | |
| 69 | 77 | ], |
| 70 | - 'required' => [ 'media_id' ], | |
| 71 | 78 | ], |
| 72 | 79 | 'output_schema' => [ |
| 73 | 80 | 'type' => 'object', |
| 74 | 81 | 'properties' => [ |
| @@ -73,10 +80,10 @@ | ||
| 73 | 80 | 'type' => 'object', |
| 74 | 81 | 'properties' => [ |
| 75 | 82 | 'status' => [ |
| 76 | 83 | 'type' => 'string', |
| 77 | - 'description' => __( 'Result status: "success" or "error".', 'imagify' ), | |
| 78 | - 'enum' => [ 'success', 'error' ], | |
| 84 | + 'description' => __( 'Result status: "success", "error", "confirmation_required", "insufficient_quota", or "invalid_api_key".', 'imagify' ), | |
| 85 | + 'enum' => [ 'success', 'error', 'confirmation_required', 'insufficient_quota', 'invalid_api_key' ], | |
| 79 | 86 | ], |
| 80 | 87 | 'original_size' => [ |
| 81 | 88 | 'type' => [ 'integer', 'null' ], |
| 82 | 89 | 'description' => __( 'Original file size in bytes before optimization, or null on error.', 'imagify' ), |
| @@ -129,19 +136,41 @@ | ||
| 129 | 136 | return imagify_get_context( 'wp' )->current_user_can( 'manage' ); |
| 130 | 137 | } |
| 131 | 138 | |
| 132 | 139 | /** |
| 140 | + * Returns the credit-consumption impact estimate for a single media optimization. | |
| 141 | + * | |
| 142 | + * @param array $args Input arguments (unused: optimizing a single media always costs 1 unit). | |
| 143 | + * @return array{unit: string, count: int, label: string} | |
| 144 | + */ | |
| 145 | + public function get_impact_estimate( array $args ): array { | |
| 146 | + return [ | |
| 147 | + 'unit' => 'image', | |
| 148 | + 'count' => 1, | |
| 149 | + 'label' => 'this media', | |
| 150 | + ]; | |
| 151 | + } | |
| 152 | + | |
| 153 | + /** | |
| 133 | 154 | * Execute the ability: optimize the media. |
| 134 | 155 | * |
| 135 | - * Fires `imagify_mcp_ability_executed` after the ability resolves so that | |
| 136 | - * tracking and other subscribers can react to both success and failure outcomes. | |
| 156 | + * Wraps the real execution behind `guard_credit_confirmation()` so the | |
| 157 | + * caller must pass `confirm: true` once quota is confirmed (and is not | |
| 158 | + * over quota, and the API key is valid). Fires `imagify_mcp_ability_executed` | |
| 159 | + * after the ability resolves so that tracking and other subscribers can | |
| 160 | + * react to every outcome (previews included). | |
| 137 | 161 | * |
| 138 | - * @param array $args Input arguments. Expects `media_id` (int) and optionally `optimization_level` (int). | |
| 139 | - * @return array{status: string, original_size: int|null, optimized_size: int|null, savings_percent: float|null, error_message: string|null} | |
| 162 | + * @param array $args Input arguments. Expects `media_id` (int) and optionally `optimization_level` (int), `confirm` (bool). | |
| 163 | + * @return array<string, mixed> Guard response (invalid_api_key/insufficient_quota/confirmation_required) or the do_execute() result shape. | |
| 140 | 164 | */ |
| 141 | 165 | public function execute( array $args = [] ): array { |
| 142 | 166 | $start_time = microtime( true ); |
| 143 | - $result = $this->do_execute( $args ); | |
| 167 | + $result = $this->guard_credit_confirmation( | |
| 168 | + $args, | |
| 169 | + function ( array $a ) { | |
| 170 | + return $this->do_execute( $a ); | |
| 171 | + } | |
| 172 | + ); | |
| 144 | 173 | |
| 145 | 174 | $this->fire_executed( $result, $start_time, $args ); |
| 146 | 175 | |
| 147 | 176 | return $result; |
| @@ -156,12 +185,12 @@ | ||
| 156 | 185 | * @param array $args Input arguments. |
| 157 | 186 | * @return array{status: string, original_size: int|null, optimized_size: int|null, savings_percent: float|null, error_message: string|null} |
| 158 | 187 | */ |
| 159 | 188 | private function do_execute( array $args ): array { |
| 160 | - $media_id = isset( $args['media_id'] ) ? (int) $args['media_id'] : 0; | |
| 189 | + $media_id = MediaResolver::resolve_id( $args ); | |
| 161 | 190 | |
| 162 | - if ( $media_id <= 0 ) { | |
| 163 | - return $this->error_response( 'Invalid or missing media_id.' ); | |
| 191 | + if ( is_wp_error( $media_id ) ) { | |
| 192 | + return $this->error_response( $media_id->get_error_message() ); | |
| 164 | 193 | } |
| 165 | 194 | |
| 166 | 195 | // Verify the attachment exists. |
| 167 | 196 | $post = get_post( $media_id ); |