PluginProbe
Copy Anything to Clipboard for WordPress – Copy Button, Copy Text & Copy Code / 5.5.4
Copy Anything to Clipboard for WordPress – Copy Button, Copy Text & Copy Code v5.5.4
5.5.4 5.5.3 3.1.0 3.2.0 3.2.1 3.3.0 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.6.0 3.7.0 3.8.0 3.8.1 3.8.2 3.8.3 4.0.0 4.0.2 4.0.3 4.0.4 4.0.5 4.1.0 All 79 releases
copy-the-code / includes / global-injector / class-rest.php

class-rest.php in Copy Anything to Clipboard for WordPress – Copy Button, Copy Text & Copy Code 5.5.4, at includes/global-injector/class-rest.php

785 lines 23.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Rest API
4 *
5 * @package CTC
6 * @since 5.0.0
7 */
8
9 namespace CTC\Global_Injector;
10
11 use CTC\Helper;
12
13 /**
14 * Rest API
15 *
16 * @since 5.0.0
17 */
18 class Rest {
19
20 /**
21 * Instance
22 *
23 * @since 5.0.0
24 *
25 * @access private
26 * @var object Class object.
27 */
28 private static $instance;
29
30 /**
31 * REST namespace
32 *
33 * @var string
34 */
35 private $namespace = 'ctc/v1';
36
37 /**
38 * Initiator
39 *
40 * @since 5.0.0
41 *
42 * @return object initialized object of class.
43 */
44 public static function get() {
45 if ( null === self::$instance ) {
46 self::$instance = new self();
47 }
48 return self::$instance;
49 }
50
51 /**
52 * Constructor
53 *
54 * @since 5.0.0
55 */
56 public function __construct() {
57 add_action( 'rest_api_init', [ $this, 'register_routes' ] );
58 }
59
60 /**
61 * Register REST routes
62 *
63 * @since 5.0.0
64 * @return void
65 */
66 public function register_routes() {
67 // Rules endpoints.
68 register_rest_route(
69 $this->namespace,
70 '/rules/(?P<id>\d+)',
71 [
72 'methods' => 'POST',
73 'callback' => [ $this, 'save_rule' ],
74 'permission_callback' => [ $this, 'check_permissions' ],
75 'args' => [
76 'id' => [
77 'required' => true,
78 'validate_callback' => function ( $param ) {
79 return is_numeric( $param );
80 },
81 ],
82 ],
83 ]
84 );
85
86 register_rest_route(
87 $this->namespace,
88 '/rules',
89 [
90 'methods' => 'POST',
91 'callback' => [ $this, 'create_rule' ],
92 'permission_callback' => [ $this, 'check_permissions' ],
93 ]
94 );
95
96 register_rest_route(
97 $this->namespace,
98 '/rules/(?P<id>\d+)',
99 [
100 'methods' => 'DELETE',
101 'callback' => [ $this, 'delete_rule' ],
102 'permission_callback' => [ $this, 'check_permissions' ],
103 'args' => [
104 'id' => [
105 'required' => true,
106 'validate_callback' => function ( $param ) {
107 return is_numeric( $param );
108 },
109 ],
110 ],
111 ]
112 );
113
114 // Style Presets endpoints (matching v5.0.0 convention).
115 register_rest_route(
116 $this->namespace,
117 '/style-presets',
118 [
119 'methods' => 'GET',
120 'callback' => [ $this, 'get_presets' ],
121 'permission_callback' => [ $this, 'check_permissions' ],
122 ]
123 );
124
125 register_rest_route(
126 $this->namespace,
127 '/style-presets',
128 [
129 'methods' => 'POST',
130 'callback' => [ $this, 'create_preset' ],
131 'permission_callback' => [ $this, 'check_permissions' ],
132 ]
133 );
134
135 register_rest_route(
136 $this->namespace,
137 '/style-presets/(?P<id>\d+)',
138 [
139 'methods' => [ 'PUT', 'POST' ], // Support both PUT and POST for updates.
140 'callback' => [ $this, 'update_preset' ],
141 'permission_callback' => [ $this, 'check_permissions' ],
142 'args' => [
143 'id' => [
144 'required' => true,
145 'validate_callback' => function ( $param ) {
146 return is_numeric( $param );
147 },
148 ],
149 ],
150 ]
151 );
152
153 register_rest_route(
154 $this->namespace,
155 '/style-presets/(?P<id>\d+)',
156 [
157 'methods' => 'DELETE',
158 'callback' => [ $this, 'delete_preset' ],
159 'permission_callback' => [ $this, 'check_permissions' ],
160 'args' => [
161 'id' => [
162 'required' => true,
163 'validate_callback' => function ( $param ) {
164 return is_numeric( $param );
165 },
166 ],
167 ],
168 ]
169 );
170 }
171
172 /**
173 * Check permissions
174 *
175 * @since 5.0.0
176 * @return bool
177 */
178 public function check_permissions() {
179 return current_user_can( 'manage_options' );
180 }
181
182 /**
183 * Save rule
184 *
185 * @since 5.0.0
186 * @param \WP_REST_Request $request Request object.
187 * @return \WP_REST_Response|\WP_Error
188 */
189 public function save_rule( $request ) {
190 $id = $request->get_param( 'id' );
191 $data = $request->get_json_params();
192
193 $post = get_post( $id );
194 if ( ! $post || 'copy-to-clipboard' !== $post->post_type ) {
195 return new \WP_Error(
196 'rule_not_found',
197 __( 'Rule not found.', 'ctc' ),
198 [ 'status' => 404 ]
199 );
200 }
201
202 // Update post title.
203 if ( isset( $data['title'] ) ) {
204 wp_update_post(
205 [
206 'ID' => $id,
207 'post_title' => sanitize_text_field( $data['title'] ),
208 ]
209 );
210 }
211
212 // Update meta fields.
213 $meta_mapping = [
214 'selector' => 'selector',
215 'exclude_selector' => 'exclude_selector',
216 'style_type' => 'style',
217 'visual_style' => 'visual_style',
218 'button_text' => 'button-text',
219 'success_text' => 'button-copy-text',
220 'tooltip_text' => 'tooltip-text',
221 'reveal_text' => 'reveal-text',
222 'button_position' => 'button-position',
223 'button_title' => 'button-title',
224 'copy_format' => 'copy-format',
225 'copy_as' => 'copy-as',
226 'is_active' => 'is_active',
227 'display_conditions' => 'display_conditions',
228 // Icon settings.
229 'icon_enabled' => 'icon_enabled',
230 'icon_position' => 'icon_position',
231 'icon_key' => 'icon_key',
232 // Style fields (temporary storage on rule until proper preset management).
233 // Button style fields.
234 'text_color' => 'text_color',
235 'bg_color' => 'bg_color',
236 'border_radius' => 'border_radius',
237 'font_size' => 'font_size',
238 'padding_x' => 'padding_x',
239 'padding_y' => 'padding_y',
240 // Icon style fields.
241 'icon_color' => 'icon_color',
242 'icon_hover_color' => 'icon_hover_color',
243 'border_color' => 'border_color',
244 'icon_size' => 'icon_size',
245 'padding' => 'padding',
246 'border_width' => 'border_width',
247 // Cover style fields.
248 'overlay_color' => 'overlay_color',
249 'hover_overlay_color' => 'hover_overlay_color',
250 'overlay_opacity' => 'overlay_opacity',
251 'blur' => 'blur',
252 'cover_text_color' => 'cover_text_color',
253 'cover_bg_color' => 'cover_bg_color',
254 'cover_hover_bg_color' => 'cover_hover_bg_color',
255 'cover_border_radius' => 'cover_border_radius',
256 'cover_font_size' => 'cover_font_size',
257 'cover_padding_x' => 'cover_padding_x',
258 'cover_padding_y' => 'cover_padding_y',
259 'analytics_enabled' => 'analytics_enabled',
260 ];
261
262 /**
263 * Filter the meta mapping for saving rule fields.
264 *
265 * Pro add-on can use this to add additional fields like custom_css_class.
266 *
267 * @since 5.0.0
268 * @param array $meta_mapping Array of data_key => meta_key mappings.
269 */
270 $meta_mapping = apply_filters( 'ctc/global_injector/rule_meta_mapping', $meta_mapping );
271
272 // Fields that need numeric sanitization.
273 $numeric_fields = [ 'border_radius', 'font_size', 'padding_x', 'padding_y', 'icon_size', 'padding', 'border_width', 'overlay_opacity', 'blur', 'cover_border_radius', 'cover_font_size', 'cover_padding_x', 'cover_padding_y' ];
274 // Fields that need boolean sanitization.
275 $boolean_fields = [ 'icon_enabled', 'analytics_enabled' ];
276 // Fields that need hex color sanitization.
277 $color_fields = [ 'text_color', 'bg_color', 'icon_color', 'icon_hover_color', 'border_color', 'overlay_color', 'hover_overlay_color', 'cover_text_color', 'cover_bg_color', 'cover_hover_bg_color' ];
278
279 /**
280 * Filter enum fields for sanitization (data_key => allowed values).
281 * Pro can add fields like image_format => ['png','jpeg','webp'].
282 *
283 * @since 5.1.0
284 * @param array $enum_fields Array of data_key => allowed values.
285 */
286 $enum_fields = apply_filters( 'ctc/global_injector/rule_enum_fields', [] );
287
288 /**
289 * Filter the text fields for sanitization.
290 *
291 * Pro can add custom text fields here if needed.
292 *
293 * @since 5.0.0
294 * @param array $text_fields Array of field keys that need text sanitization.
295 */
296 $text_fields = apply_filters( 'ctc/global_injector/rule_text_fields', [] );
297
298 foreach ( $meta_mapping as $data_key => $meta_key ) {
299 if ( isset( $data[ $data_key ] ) ) {
300 $value = $data[ $data_key ];
301 if ( 'display_conditions' === $data_key ) {
302 $value = Display_Conditions::sanitize( $value );
303 } elseif ( 'is_active' === $data_key ) {
304 // Save as string '1' or '0' to avoid WordPress empty string issue.
305 $value = $value ? '1' : '0';
306 } elseif ( isset( $enum_fields[ $data_key ] ) && is_array( $enum_fields[ $data_key ] ) ) {
307 $allowed = $enum_fields[ $data_key ];
308 $value = in_array( $value, $allowed, true ) ? $value : ( $allowed[0] ?? '' );
309 } elseif ( in_array( $data_key, $boolean_fields, true ) ) {
310 // Save as string '1' or '0' for boolean fields.
311 $value = $value ? '1' : '0';
312 } elseif ( in_array( $data_key, $numeric_fields, true ) ) {
313 $value = absint( $value );
314 } elseif ( in_array( $data_key, $color_fields, true ) ) {
315 $value = sanitize_hex_color( $value );
316 } else {
317 $value = sanitize_text_field( $value );
318 }
319 update_post_meta( $id, $meta_key, $value );
320 }
321 }
322
323 return rest_ensure_response(
324 [
325 'success' => true,
326 'message' => __( 'Rule saved successfully.', 'ctc' ),
327 'rule' => $this->get_rule_data( $id ),
328 ]
329 );
330 }
331
332 /**
333 * Create rule
334 *
335 * @since 5.0.0
336 * @param \WP_REST_Request $request Request object.
337 * @return \WP_REST_Response|\WP_Error
338 */
339 public function create_rule( $request ) {
340 $data = $request->get_json_params();
341
342 $post_id = wp_insert_post(
343 [
344 'post_type' => 'copy-to-clipboard',
345 'post_title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : __( 'New Rule', 'ctc' ),
346 'post_status' => 'publish',
347 ]
348 );
349
350 if ( is_wp_error( $post_id ) ) {
351 return new \WP_Error(
352 'rule_creation_failed',
353 $post_id->get_error_message(),
354 [ 'status' => 500 ]
355 );
356 }
357
358 // Set meta values (use provided data or defaults).
359 $selector = isset( $data['selector'] ) ? sanitize_text_field( $data['selector'] ) : 'pre';
360 $is_active = isset( $data['is_active'] ) ? ( $data['is_active'] ? '1' : '0' ) : '1';
361
362 update_post_meta( $post_id, 'selector', $selector );
363 update_post_meta( $post_id, 'style', 'button' );
364 update_post_meta( $post_id, 'button-text', __( 'Copy', 'ctc' ) );
365 update_post_meta( $post_id, 'button-copy-text', __( 'Copied!', 'ctc' ) );
366 update_post_meta( $post_id, 'button-position', 'inside' );
367 update_post_meta( $post_id, 'is_active', $is_active );
368 update_post_meta( $post_id, 'analytics_enabled', '1' );
369
370 return rest_ensure_response(
371 [
372 'success' => true,
373 'message' => __( 'Rule created successfully.', 'ctc' ),
374 'rule' => $this->get_rule_data( $post_id ),
375 ]
376 );
377 }
378
379 /**
380 * Delete rule
381 *
382 * @since 5.0.0
383 * @param \WP_REST_Request $request Request object.
384 * @return \WP_REST_Response|\WP_Error
385 */
386 public function delete_rule( $request ) {
387 $id = $request->get_param( 'id' );
388
389 $post = get_post( $id );
390 if ( ! $post || 'copy-to-clipboard' !== $post->post_type ) {
391 return new \WP_Error(
392 'rule_not_found',
393 __( 'Rule not found.', 'ctc' ),
394 [ 'status' => 404 ]
395 );
396 }
397
398 wp_delete_post( $id, true );
399
400 return rest_ensure_response(
401 [
402 'success' => true,
403 'message' => __( 'Rule deleted successfully.', 'ctc' ),
404 ]
405 );
406 }
407
408 /**
409 * Get rule data
410 *
411 * @since 5.0.0
412 * @param int $id Rule ID.
413 * @return array
414 */
415 private function get_rule_data( $id ) {
416 $post = get_post( $id );
417 if ( ! $post ) {
418 return [];
419 }
420
421 $is_active = get_post_meta( $id, 'is_active', true );
422 if ( '' === $is_active ) {
423 // Fallback for rules without is_active meta.
424 $is_active = 'publish' === $post->post_status;
425 } else {
426 // Handle both string '1'/'0' and boolean values.
427 $is_active = '0' !== $is_active && false !== $is_active;
428 }
429
430 // Get style fields with defaults from default preset.
431 $default_presets = Style_Presets::get_default_presets();
432 $visual_style = get_post_meta( $id, 'visual_style', true );
433 $visual_style = ! empty( $visual_style ) ? $visual_style : 'button';
434 $style_defaults = isset( $default_presets[ $visual_style ] ) ? $default_presets[ $visual_style ] : $default_presets['button'];
435
436 // Helper function for getting meta with default value.
437 $get_meta = function ( $meta_key, $default_value ) use ( $id ) {
438 $value = get_post_meta( $id, $meta_key, true );
439 return ( '' === $value || null === $value ) ? $default_value : $value;
440 };
441
442 // Helper function for getting style defaults.
443 $style_default = function ( $key, $fallback ) use ( $style_defaults ) {
444 return isset( $style_defaults[ $key ] ) ? $style_defaults[ $key ] : $fallback;
445 };
446
447 // Get icon enabled as boolean (stored as '1' or '0').
448 $icon_enabled_raw = get_post_meta( $id, 'icon_enabled', true );
449 $icon_enabled = '' === $icon_enabled_raw ? true : '1' === $icon_enabled_raw;
450
451 // Analytics enabled: default true when meta missing (existing rules).
452 $analytics_enabled_raw = get_post_meta( $id, 'analytics_enabled', true );
453 $analytics_enabled = '' === $analytics_enabled_raw ? true : '1' === $analytics_enabled_raw;
454
455 $rule = [
456 'id' => $post->ID,
457 'title' => isset( $post->post_title ) ? $post->post_title : __( 'Untitled Rule', 'ctc' ),
458 'selector' => $get_meta( 'selector', 'pre' ),
459 'exclude_selector' => $get_meta( 'exclude_selector', '' ),
460 'style_type' => $get_meta( 'style', 'button' ),
461 'visual_style' => $visual_style,
462 'button_text' => $get_meta( 'button-text', __( 'Copy', 'ctc' ) ),
463 'success_text' => $get_meta( 'button-copy-text', __( 'Copied!', 'ctc' ) ),
464 'tooltip_text' => $get_meta( 'tooltip-text', __( 'Copy to clipboard', 'ctc' ) ),
465 'reveal_text' => $get_meta( 'reveal-text', __( 'Click to Reveal', 'ctc' ) ),
466 'button_position' => $this->migrate_position( $get_meta( 'button-position', 'inside_top_right' ) ),
467 'is_active' => $is_active,
468 'display_conditions' => $get_meta( 'display_conditions', [] ),
469 'button_title' => $get_meta( 'button-title', '' ),
470 'copy_format' => $get_meta( 'copy-format', '' ),
471 'copy_as' => $get_meta( 'copy-as', '' ),
472 // Icon settings.
473 'icon_enabled' => $icon_enabled,
474 'icon_position' => $get_meta( 'icon_position', 'left' ),
475 'icon_key' => $get_meta( 'icon_key', 'clipboard' ),
476 'analytics_enabled' => $analytics_enabled,
477 // Style fields (stored on rule for now, uses default preset values as fallback).
478 // Button style fields.
479 'text_color' => $get_meta( 'text_color', $style_default( 'text_color', '#ffffff' ) ),
480 'bg_color' => $get_meta( 'bg_color', $style_default( 'bg_color', '#4f46e5' ) ),
481 'border_radius' => (int) $get_meta( 'border_radius', $style_default( 'border_radius', 6 ) ),
482 'font_size' => (int) $get_meta( 'font_size', $style_default( 'font_size', 13 ) ),
483 'padding_x' => (int) $get_meta( 'padding_x', $style_default( 'padding_x', 16 ) ),
484 'padding_y' => (int) $get_meta( 'padding_y', $style_default( 'padding_y', 8 ) ),
485 // Icon style fields.
486 'icon_color' => $get_meta( 'icon_color', $style_default( 'icon_color', '#6b7280' ) ),
487 'icon_hover_color' => $get_meta( 'icon_hover_color', $style_default( 'icon_hover_color', '#4f46e5' ) ),
488 'border_color' => $get_meta( 'border_color', $style_default( 'border_color', '#e5e7eb' ) ),
489 'icon_size' => (int) $get_meta( 'icon_size', $style_default( 'icon_size', 16 ) ),
490 'padding' => (int) $get_meta( 'padding', $style_default( 'padding', 8 ) ),
491 'border_width' => (int) $get_meta( 'border_width', $style_default( 'border_width', 1 ) ),
492 // Cover style fields.
493 'overlay_color' => $get_meta( 'overlay_color', $style_default( 'overlay_color', '#0f172a' ) ),
494 'hover_overlay_color' => $get_meta( 'hover_overlay_color', $style_default( 'hover_overlay_color', '#4f46e5' ) ),
495 'overlay_opacity' => (int) $get_meta( 'overlay_opacity', $style_default( 'overlay_opacity', 20 ) ),
496 'blur' => (int) $get_meta( 'blur', $style_default( 'blur', 2 ) ),
497 'cover_text_color' => $get_meta( 'cover_text_color', $style_default( 'text_color', '#0f172a' ) ),
498 'cover_bg_color' => $get_meta( 'cover_bg_color', $style_default( 'badge_bg_color', '#ffffff' ) ),
499 'cover_hover_bg_color' => $get_meta( 'cover_hover_bg_color', '#f1f5f9' ),
500 'cover_border_radius' => (int) $get_meta( 'cover_border_radius', $style_default( 'badge_radius', 9999 ) ),
501 'cover_font_size' => (int) $get_meta( 'cover_font_size', $style_default( 'font_size', 10 ) ),
502 'cover_padding_x' => (int) $get_meta( 'cover_padding_x', 12 ),
503 'cover_padding_y' => (int) $get_meta( 'cover_padding_y', 6 ),
504 ];
505
506 /**
507 * Filter REST rule data before returning.
508 * Pro can add fields like image_format.
509 *
510 * @since 5.1.0
511 * @param array $rule Rule data array.
512 * @param int $id Rule ID.
513 */
514 return apply_filters( 'ctc/global_injector/rest_rule_data', $rule, $id );
515 }
516
517 // =========================================================================
518 // PRESET ENDPOINTS
519 // =========================================================================
520
521 /**
522 * Get all presets
523 *
524 * @since 5.0.0
525 * @param \WP_REST_Request $request Request object.
526 * @return \WP_REST_Response
527 */
528 public function get_presets( $request ) {
529 $presets = Style_Presets::load_all_presets();
530 $defaults = Style_Presets::get_default_presets();
531
532 return rest_ensure_response(
533 [
534 'success' => true,
535 'presets' => $presets,
536 'defaults' => $defaults,
537 'limits' => [
538 'button' => [
539 'count' => Style_Presets::get_preset_count_by_style( 'button' ),
540 'max' => Style_Presets::MAX_FREE_PRESETS_PER_STYLE,
541 ],
542 'icon' => [
543 'count' => Style_Presets::get_preset_count_by_style( 'icon' ),
544 'max' => Style_Presets::MAX_FREE_PRESETS_PER_STYLE,
545 ],
546 'cover' => [
547 'count' => Style_Presets::get_preset_count_by_style( 'cover' ),
548 'max' => Style_Presets::MAX_FREE_PRESETS_PER_STYLE,
549 ],
550 ],
551 ]
552 );
553 }
554
555 /**
556 * Create preset
557 *
558 * @since 5.0.0
559 * @param \WP_REST_Request $request Request object.
560 * @return \WP_REST_Response|\WP_Error
561 */
562 public function create_preset( $request ) {
563 $data = $request->get_json_params();
564 $visual_style = isset( $data['visual_style'] ) ? sanitize_text_field( $data['visual_style'] ) : 'button';
565
566 // Check preset limit for free users.
567 $is_pro = apply_filters( 'ctc/is_pro', false );
568 if ( ! Style_Presets::can_create_preset( $visual_style, $is_pro ) ) {
569 return new \WP_Error(
570 'preset_limit_reached',
571 sprintf(
572 /* translators: %d: Maximum presets allowed */
573 __( 'You have reached the maximum limit of %d presets for this style. Upgrade to Pro for unlimited presets.', 'ctc' ),
574 Style_Presets::MAX_FREE_PRESETS_PER_STYLE
575 ),
576 [ 'status' => 403 ]
577 );
578 }
579
580 $post_id = wp_insert_post(
581 [
582 'post_type' => Style_Presets::POST_TYPE,
583 'post_title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : __( 'New Preset', 'ctc' ),
584 'post_status' => 'publish',
585 ]
586 );
587
588 if ( is_wp_error( $post_id ) ) {
589 return new \WP_Error(
590 'preset_creation_failed',
591 $post_id->get_error_message(),
592 [ 'status' => 500 ]
593 );
594 }
595
596 // Save visual style.
597 update_post_meta( $post_id, 'visual_style', $visual_style );
598
599 // Save style-specific meta fields.
600 $this->save_preset_meta( $post_id, $visual_style, $data );
601
602 $preset = Style_Presets::format_preset( get_post( $post_id ) );
603
604 return rest_ensure_response(
605 [
606 'success' => true,
607 'message' => __( 'Preset created successfully.', 'ctc' ),
608 'preset' => $preset,
609 ]
610 );
611 }
612
613 /**
614 * Update preset
615 *
616 * @since 5.0.0
617 * @param \WP_REST_Request $request Request object.
618 * @return \WP_REST_Response|\WP_Error
619 */
620 public function update_preset( $request ) {
621 $id = $request->get_param( 'id' );
622 $data = $request->get_json_params();
623
624 $post = get_post( $id );
625 if ( ! $post || Style_Presets::POST_TYPE !== $post->post_type ) {
626 return new \WP_Error(
627 'preset_not_found',
628 __( 'Preset not found.', 'ctc' ),
629 [ 'status' => 404 ]
630 );
631 }
632
633 // Update title if provided.
634 if ( isset( $data['title'] ) ) {
635 wp_update_post(
636 [
637 'ID' => $id,
638 'post_title' => sanitize_text_field( $data['title'] ),
639 ]
640 );
641 }
642
643 // Get visual style.
644 $visual_style_value = get_post_meta( $id, 'visual_style', true );
645 $visual_style = ! empty( $visual_style_value ) ? $visual_style_value : 'button';
646
647 // Save style-specific meta fields.
648 $this->save_preset_meta( $id, $visual_style, $data );
649
650 $preset = Style_Presets::format_preset( get_post( $id ) );
651
652 return rest_ensure_response(
653 [
654 'success' => true,
655 'message' => __( 'Preset updated successfully.', 'ctc' ),
656 'preset' => $preset,
657 ]
658 );
659 }
660
661 /**
662 * Delete preset
663 *
664 * @since 5.0.0
665 * @param \WP_REST_Request $request Request object.
666 * @return \WP_REST_Response|\WP_Error
667 */
668 public function delete_preset( $request ) {
669 $id = $request->get_param( 'id' );
670
671 $post = get_post( $id );
672 if ( ! $post || Style_Presets::POST_TYPE !== $post->post_type ) {
673 return new \WP_Error(
674 'preset_not_found',
675 __( 'Preset not found.', 'ctc' ),
676 [ 'status' => 404 ]
677 );
678 }
679
680 wp_delete_post( $id, true );
681
682 return rest_ensure_response(
683 [
684 'success' => true,
685 'message' => __( 'Preset deleted successfully.', 'ctc' ),
686 ]
687 );
688 }
689
690 /**
691 * Migrate legacy position values to new format
692 *
693 * Old wp.org plugin used 'inside' and 'outside' values.
694 * New format uses specific positions like 'inside_top_right'.
695 *
696 * @since 5.0.0
697 * @param string $position Position value (may be legacy or new).
698 * @return string Migrated position value.
699 */
700 private function migrate_position( $position ) {
701 // Legacy to new position mapping.
702 $legacy_map = [
703 'inside' => 'inside_top_right',
704 'outside' => 'outside_after_right',
705 ];
706
707 // All valid new position values.
708 $valid_positions = [
709 'inside_top_left',
710 'inside_top_right',
711 'inside_bottom_left',
712 'inside_bottom_right',
713 'outside_before_left',
714 'outside_before_right',
715 'outside_after_left',
716 'outside_after_right',
717 ];
718
719 // If already a valid new position, return as-is.
720 if ( in_array( $position, $valid_positions, true ) ) {
721 return $position;
722 }
723
724 // If legacy value, migrate it.
725 if ( isset( $legacy_map[ $position ] ) ) {
726 return $legacy_map[ $position ];
727 }
728
729 // Default fallback.
730 return 'inside_top_right';
731 }
732
733 /**
734 * Save preset meta fields based on visual style
735 *
736 * @since 5.0.0
737 * @param int $post_id Post ID.
738 * @param string $visual_style Visual style type.
739 * @param array $data Data to save.
740 * @return void
741 */
742 private function save_preset_meta( $post_id, $visual_style, $data ) {
743 // Define meta fields per style.
744 $meta_fields = [
745 'button' => [
746 'text_color' => 'sanitize_hex_color',
747 'bg_color' => 'sanitize_hex_color',
748 'border_radius' => 'absint',
749 'font_size' => 'absint',
750 'padding_x' => 'absint',
751 'padding_y' => 'absint',
752 ],
753 'icon' => [
754 'icon_color' => 'sanitize_hex_color',
755 'icon_hover_color' => 'sanitize_hex_color',
756 'bg_color' => 'sanitize_hex_color',
757 'border_color' => 'sanitize_hex_color',
758 'icon_size' => 'absint',
759 'padding' => 'absint',
760 'border_radius' => 'absint',
761 'border_width' => 'absint',
762 ],
763 'cover' => [
764 'overlay_color' => 'sanitize_hex_color',
765 'text_color' => 'sanitize_hex_color',
766 'badge_bg_color' => 'sanitize_hex_color',
767 'hover_overlay_color' => 'sanitize_hex_color',
768 'overlay_opacity' => 'absint',
769 'blur' => 'absint',
770 'font_size' => 'absint',
771 'badge_radius' => 'absint',
772 ],
773 ];
774
775 $fields = $meta_fields[ $visual_style ] ?? $meta_fields['button'];
776
777 foreach ( $fields as $key => $sanitize_callback ) {
778 if ( isset( $data[ $key ] ) ) {
779 $value = call_user_func( $sanitize_callback, $data[ $key ] );
780 update_post_meta( $post_id, $key, $value );
781 }
782 }
783 }
784 }
785