PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0-beta3
Elementor Website Builder – more than just a page builder v4.3.0-beta3
4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 All 452 releases
elementor / modules / mcp / abilities / manage-variable-ability.php

manage-variable-ability.php in Elementor Website Builder – more than just a page builder 4.3.0-beta3, at modules/mcp/abilities/manage-variable-ability.php

393 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\Mcp\Abilities;
4
5 use Elementor\Modules\Mcp\Abilities\Utils\Bulk_Operations_Result;
6 use Elementor\Modules\Mcp\Abilities\Utils\Tool_Performance_Metrics;
7 use Elementor\Modules\Mcp\Events\Mcp_Event_Dispatcher;
8 use Elementor\Modules\Variables\Services\Batch_Operations\Batch_Processor;
9 use Elementor\Modules\Variables\Services\Variables_Service;
10 use Elementor\Modules\Variables\Storage\Exceptions\FatalError;
11 use Elementor\Modules\Variables\Storage\Variables_Repository;
12 use Elementor\Plugin;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 class Manage_Variable_Ability extends Abstract_Ability {
19
20 const TYPE_COLOR = 'global-color-variable';
21 const TYPE_FONT = 'global-font-variable';
22 const TYPE_SIZE = 'global-size-variable';
23 const TYPE_CUSTOM_SIZE = 'global-custom-size-variable';
24 const MAX_BATCH_SIZE = 50;
25
26 private ?Variables_Service $service;
27
28 public function __construct( ?Variables_Service $service = null ) {
29 $this->service = $service;
30 }
31
32 protected function get_ability_id(): string {
33 return 'elementor/manage-global-variable';
34 }
35
36 protected function get_definition(): Ability_Definition {
37 return new Ability_Definition(
38 __( 'Manage Global Variable', 'elementor' ),
39 __( 'Manage V4 global variables (color, font, size, custom-size). Bulk create, update, or delete on the active kit (up to 50 operations).', 'elementor' ),
40 'elementor',
41 [
42 'type' => 'object',
43 'required' => [ 'status', 'results' ],
44 'properties' => [
45 'status' => [ 'type' => 'string' ],
46 'results' => [ 'type' => 'array' ],
47 'watermark' => [ 'type' => 'integer' ],
48 ],
49 ],
50 [
51 'annotations' => [
52 'readonly' => false,
53 'idempotent' => false,
54 'destructive' => false,
55 ],
56 ],
57 fn() => current_user_can( 'manage_options' ),
58 [
59 'type' => 'object',
60 'required' => [ 'operations' ],
61 'properties' => [
62 'operations' => [
63 'type' => 'array',
64 'description' => 'Bulk operations (1–50). Each item requires action; create needs type/label/value, update needs id/label/value, delete needs id.',
65 'items' => [
66 'type' => 'object',
67 'required' => [ 'action' ],
68 'properties' => [
69 'action' => [
70 'type' => 'string',
71 'enum' => [ 'create', 'update', 'delete' ],
72 ],
73 'id' => [ 'type' => 'string' ],
74 'type' => [
75 'type' => 'string',
76 'enum' => [ self::TYPE_COLOR, self::TYPE_FONT, self::TYPE_SIZE, self::TYPE_CUSTOM_SIZE ],
77 ],
78 'label' => [ 'type' => 'string' ],
79 'value' => [ 'type' => 'string' ],
80 ],
81 ],
82 ],
83 ],
84 ]
85 );
86 }
87
88 public function execute( $input = [] ) {
89 $started_at = hrtime( true );
90 $input = is_array( $input ) ? $input : [];
91 $operations = $input['operations'] ?? null;
92
93 if ( ! is_array( $operations ) ) {
94 $error = $this->bad_request( __( 'operations array is required.', 'elementor' ) );
95 $this->emit_mcp_manage_global_variable_executed( $started_at, [], $error );
96 return $error;
97 }
98
99 if ( empty( $operations ) ) {
100 $error = $this->bad_request( __( 'operations must not be empty.', 'elementor' ) );
101 $this->emit_mcp_manage_global_variable_executed( $started_at, [], $error );
102 return $error;
103 }
104
105 if ( count( $operations ) > self::MAX_BATCH_SIZE ) {
106 $error = new \WP_Error(
107 'batch_size_exceeded',
108 sprintf(
109 /* translators: %d: maximum operations per request */
110 __( 'Maximum %d operations per request.', 'elementor' ),
111 self::MAX_BATCH_SIZE
112 ),
113 [
114 'status' => \WP_Http::BAD_REQUEST,
115 'max_allowed' => self::MAX_BATCH_SIZE,
116 ]
117 );
118 $this->emit_mcp_manage_global_variable_executed( $started_at, $operations, $error );
119 return $error;
120 }
121
122 $response = $this->handle_bulk( $operations );
123 if ( is_wp_error( $response ) ) {
124 $this->emit_mcp_manage_global_variable_executed( $started_at, $operations, $response );
125 } else {
126 $this->emit_mcp_manage_global_variable_executed( $started_at, $operations, null, $response );
127 }
128 return $response;
129 }
130
131 private function handle_bulk( array $operations ) {
132 $results = new Bulk_Operations_Result();
133 $batch_operations = [];
134 $index_map = [];
135
136 foreach ( $operations as $index => $operation ) {
137 if ( ! is_array( $operation ) ) {
138 $results->add_error( $index, '', 'invalid_input', __( 'Invalid operation.', 'elementor' ) );
139 continue;
140 }
141
142 $translated = $this->translate_operation( $operation );
143
144 if ( is_wp_error( $translated ) ) {
145 $results->add_error(
146 $index,
147 $operation['action'] ?? '',
148 $translated->get_error_code(),
149 $translated->get_error_message()
150 );
151 continue;
152 }
153
154 $index_map[ count( $batch_operations ) ] = $index;
155 $batch_operations[] = $translated;
156 }
157
158 $watermark = null;
159
160 if ( ! empty( $batch_operations ) ) {
161 try {
162 $batch_result = $this->get_service()->process_batch( $batch_operations, true );
163 $watermark = $batch_result['watermark'] ?? null;
164
165 $this->merge_batch_results( $batch_result['results'], $index_map, $results );
166 $this->emit_variable_events( $batch_result['results'] );
167 } catch ( FatalError $e ) {
168 return new \WP_Error(
169 'unexpected_server_error',
170 __( 'Unexpected server error', 'elementor' ),
171 [ 'status' => \WP_Http::INTERNAL_SERVER_ERROR ]
172 );
173 }
174
175 $this->clear_cache();
176 }
177
178 $response = $results->to_array();
179
180 if ( null !== $watermark ) {
181 $response['watermark'] = $watermark;
182 }
183
184 return $response;
185 }
186
187 private function translate_operation( array $operation ) {
188 $action = $operation['action'] ?? '';
189
190 switch ( $action ) {
191 case 'create':
192 $type = $operation['type'] ?? '';
193 $label = $operation['label'] ?? '';
194 $value = $operation['value'] ?? '';
195
196 if ( '' === $type || '' === $label || '' === $value ) {
197 return $this->bad_request( __( 'Create requires type, label, and value.', 'elementor' ) );
198 }
199
200 return [
201 'type' => 'create',
202 'variable' => [
203 'type' => $type,
204 'label' => $label,
205 'value' => $value,
206 ],
207 ];
208
209 case 'update':
210 $id = $operation['id'] ?? '';
211 $label = $operation['label'] ?? '';
212 $value = $operation['value'] ?? '';
213
214 if ( '' === $id || '' === $label || '' === $value ) {
215 return $this->bad_request( __( 'Update requires id, label, and value.', 'elementor' ) );
216 }
217
218 return [
219 'type' => 'update',
220 'id' => $id,
221 'variable' => [
222 'label' => $label,
223 'value' => $value,
224 ],
225 ];
226
227 case 'delete':
228 $id = $operation['id'] ?? '';
229
230 if ( '' === $id ) {
231 return $this->bad_request( __( 'Delete requires id.', 'elementor' ) );
232 }
233
234 return [
235 'type' => 'delete',
236 'id' => $id,
237 ];
238
239 default:
240 return $this->bad_request( sprintf(
241 /* translators: %s: action name */
242 __( 'Unknown action: %s.', 'elementor' ),
243 $action
244 ) );
245 }
246 }
247
248 private function merge_batch_results( array $batch_results, array $index_map, Bulk_Operations_Result $results ): void {
249 foreach ( $batch_results as $batch_index => $result ) {
250 $original_index = $index_map[ $batch_index ];
251
252 if ( 'ok' === ( $result['status'] ?? '' ) ) {
253 $extra = array_diff_key( $result, array_flip( [ 'index', 'status' ] ) );
254 $results->add_success( $original_index, $result['action'] ?? '', $extra );
255 } else {
256 $results->add_error(
257 $original_index,
258 $result['action'] ?? '',
259 $result['code'] ?? 'unknown_error',
260 $result['message'] ?? ''
261 );
262 }
263 }
264 }
265
266 private function bad_request( string $message ): \WP_Error {
267 return new \WP_Error( 'invalid_input', $message, [ 'status' => \WP_Http::BAD_REQUEST ] );
268 }
269
270 private function clear_cache(): void {
271 if ( ! class_exists( Plugin::class ) || ! isset( Plugin::$instance ) ) {
272 return;
273 }
274
275 Plugin::$instance->files_manager->clear_cache();
276 $this->flush_runtime_cache();
277 }
278
279 private function flush_runtime_cache(): void {
280 if ( function_exists( 'wp_cache_flush_runtime' ) ) {
281 wp_cache_flush_runtime();
282 }
283 }
284
285 private function get_service(): Variables_Service {
286 if ( $this->service ) {
287 return $this->service;
288 }
289
290 $kit = Plugin::$instance->kits_manager->get_active_kit();
291
292 return new Variables_Service(
293 new Variables_Repository( $kit ),
294 new Batch_Processor()
295 );
296 }
297
298 private function emit_variable_events( array $batch_results ): void {
299 $event_by_action = [
300 'create' => 'variable_created',
301 'update' => 'variable_updated',
302 ];
303
304 $var_type_labels = [
305 self::TYPE_COLOR => 'color',
306 self::TYPE_FONT => 'font',
307 self::TYPE_SIZE => 'size',
308 self::TYPE_CUSTOM_SIZE => 'size',
309 ];
310
311 foreach ( $batch_results as $result ) {
312 if ( 'ok' !== ( $result['status'] ?? '' ) ) {
313 continue;
314 }
315
316 $action = $result['action'] ?? '';
317
318 if ( ! isset( $event_by_action[ $action ] ) ) {
319 continue;
320 }
321
322 $raw_type = $result['type'] ?? '';
323 $var_type = $var_type_labels[ $raw_type ] ?? $raw_type;
324
325 Mcp_Event_Dispatcher::emit( $event_by_action[ $action ], [
326 'id' => $result['id'] ?? '',
327 'name' => $result['label'] ?? '',
328 'var_type' => $var_type,
329 ] );
330 }
331 }
332
333 private function emit_mcp_manage_global_variable_executed(
334 int $started_at,
335 array $operations,
336 ?\WP_Error $top_level_error = null,
337 array $response = []
338 ): void {
339 $duration_ms = Tool_Performance_Metrics::duration_ms_since( $started_at );
340
341 [ 'status' => $status, 'error_code' => $error_code ] = Tool_Performance_Metrics::resolve_status( $response, $top_level_error );
342
343 $failed_results = array_filter( $response['results'] ?? [], fn( $r ) => 'error' === ( $r['status'] ?? '' ) );
344 $failed_count = count( $failed_results );
345 $failed_codes = array_values( array_unique( array_column( $failed_results, 'code' ) ) );
346
347 $ops_count = count( $operations );
348 $by_action = [];
349 $variable_types = [];
350
351 foreach ( $operations as $op ) {
352 $action = $op['action'] ?? '';
353 if ( is_string( $action ) && '' !== $action ) {
354 $by_action[ $action ] = ( $by_action[ $action ] ?? 0 ) + 1;
355 }
356 }
357
358 $ok_results = array_filter( $response['results'] ?? [], fn( $r ) => 'ok' === ( $r['status'] ?? '' ) );
359 foreach ( $ok_results as $row ) {
360 $type = $row['type'] ?? '';
361 if ( is_string( $type ) && '' !== $type ) {
362 $variable_types[ $type ] = ( $variable_types[ $type ] ?? 0 ) + 1;
363 }
364 }
365
366 $dominant_action = '';
367 if ( ! empty( $by_action ) ) {
368 arsort( $by_action );
369 $dominant_action = (string) array_key_first( $by_action );
370 }
371
372 $payload = [
373 'tool_name' => $this->get_ability_id(),
374 'status' => $status,
375 'duration_ms' => $duration_ms,
376 'action' => $dominant_action,
377 'operations_count' => $ops_count,
378 'operations_by_type' => $by_action,
379 'variable_types' => $variable_types,
380 'failed_operations_count' => $failed_count,
381 'failed_operation_codes' => $failed_codes,
382 'warning_count' => 0,
383 'warning_types' => [],
384 ];
385
386 if ( null !== $error_code ) {
387 $payload['error_code'] = $error_code;
388 }
389
390 Mcp_Event_Dispatcher::emit( 'mcp_manage_global_variable_executed', $payload );
391 }
392 }
393