PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / trunk
FrontBlocks for Gutenberg/GeneratePress vtrunk
trunk 0.2.0 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 ci-artifacts
frontblocks / includes / Admin / Settings.php
frontblocks / includes / Admin Last commit date
Settings.php 1 week ago UI.php 1 month ago
Settings.php
2293 lines
1 <?php
2 /**
3 * Settings page
4 *
5 * @package FrontBlocks
6 * @author Closemarketing
7 * @copyright 2025 Closemarketing
8 * @version 1.0.0
9 */
10
11 namespace FrontBlocks\Admin;
12
13 defined( 'ABSPATH' ) || exit;
14
15 use FrontBlocks\Admin\UI;
16
17 /**
18 * Settings class
19 */
20 class Settings {
21
22 /**
23 * Option key for testimonials feature.
24 *
25 * @var string
26 */
27 private $option_enable_testimonials = 'enable_testimonials';
28
29 /**
30 * Option key for reading progress bar feature.
31 *
32 * @var string
33 */
34 private $option_enable_reading_progress = 'enable_reading_progress';
35
36 /**
37 * Option key for back button feature.
38 *
39 * @var string
40 */
41 private $option_enable_back_button = 'enable_back_button';
42
43 /**
44 * Option key for events CPT feature.
45 *
46 * @var string
47 */
48 private $option_enable_events = 'enable_events';
49
50 /**
51 * Option key for events type (cpt or posts).
52 *
53 * @var string
54 */
55 private $option_events_type = 'events_type';
56
57 /**
58 * Option key for popups feature.
59 *
60 * @var string
61 */
62 private $option_enable_popups = 'enable_popups';
63
64 /**
65 * Option key for fluid typography feature.
66 *
67 * @var string
68 */
69 private $option_enable_fluid_typography = 'enable_fluid_typography';
70
71 /**
72 * Option key for Gutenberg in products (PRO).
73 *
74 * @var string
75 */
76 private $option_enable_gutenberg = 'enable_gutenberg';
77
78 /**
79 * Option key for Simple Prices Variable Products (PRO).
80 *
81 * @var string
82 */
83 private $option_enable_simple_prices_variable_products = 'enable_simple_prices_variable_products';
84
85 /**
86 * Option key for After Add to Cart Block (PRO).
87 *
88 * @var string
89 */
90 private $option_enable_after_add_to_cart = 'enable_after_add_to_cart';
91
92 /**
93 * Option key for deactivate short description (PRO).
94 *
95 * @var string
96 */
97 private $option_deactivate_short_description = 'deactivate_short_description';
98
99 /**
100 * Option key for move content to short description (PRO).
101 *
102 * @var string
103 */
104 private $option_move_content_to_short_description = 'move_content_to_short_description';
105
106 /**
107 * Option key for disable zoom in WooCommerce images (PRO).
108 *
109 * @var string
110 */
111 private $option_disable_zoom_images = 'disable_zoom_images';
112
113 /**
114 * Option key for add share buttons in product page (PRO).
115 *
116 * @var string
117 */
118 private $option_add_share_buttons = 'add_share_buttons';
119
120 /**
121 * Option key for deactivate product tabs (PRO).
122 *
123 * @var string
124 */
125 private $option_deactivate_product_tabs = 'deactivate_product_tabs';
126
127 /**
128 * Option key for horizontal product form layout (PRO).
129 *
130 * @var string
131 */
132 private $option_horizontal_product_form = 'horizontal_product_form';
133
134 /**
135 * Option key for enabling variant display mode per-attribute selector (PRO).
136 *
137 * @var string
138 */
139 private $option_enable_variant_display_mode = 'enable_variant_display_mode';
140
141 /**
142 * Option key for disabling coupon field in cart (PRO).
143 *
144 * @var string
145 */
146 private $option_disable_cart_coupon = 'disable_cart_coupon';
147
148 /**
149 * Option key for disabling cross-sells in cart (PRO).
150 *
151 * @var string
152 */
153 private $option_disable_cart_cross_sells = 'disable_cart_cross_sells';
154
155 /**
156 * Option key for disabling coupon form in checkout (PRO).
157 *
158 * @var string
159 */
160 private $option_disable_checkout_coupon = 'disable_checkout_coupon';
161
162 /**
163 * Option key for disabling order notes in checkout (PRO).
164 *
165 * @var string
166 */
167 private $option_disable_checkout_order_notes = 'disable_checkout_order_notes';
168
169 /**
170 * Option key for disabling login prompt in checkout (PRO).
171 *
172 * @var string
173 */
174 private $option_disable_checkout_login_prompt = 'disable_checkout_login_prompt';
175
176 /**
177 * Option key for custom post types builder (PRO).
178 *
179 * @var string
180 */
181 private $option_enable_custom_post_types = 'enable_custom_post_types';
182
183 /**
184 * Option key for full page scroll feature (PRO).
185 *
186 * @var string
187 */
188 private $option_enable_fullpage_scroll = 'enable_fullpage_scroll';
189
190 /**
191 * Option key for language banner feature (PRO).
192 *
193 * @var string
194 */
195 private $option_enable_language_banner = 'enable_language_banner';
196
197 /**
198 * Option key for checkout inline fields (PRO).
199 *
200 * @var string
201 */
202 private $option_checkout_inline = 'checkout_inline';
203
204 /**
205 * Page slug.
206 *
207 * @var string
208 */
209 private $page_slug = 'frontblocks-settings';
210
211 /**
212 * Is license valid.
213 *
214 * @var bool
215 */
216 private $is_license_valid = false;
217
218 /**
219 * Constructor.
220 */
221 public function __construct() {
222 // Check license via FrontBlocks PRO helper function.
223 $this->is_license_valid = function_exists( 'frblp_is_license_valid' ) && frblp_is_license_valid();
224
225 add_action( 'admin_menu', array( $this, 'register_menu' ) );
226 add_action( 'admin_init', array( $this, 'register_settings' ) );
227 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
228 add_action( 'admin_head', array( $this, 'add_menu_icon_styles' ) );
229 }
230
231 /**
232 * Add menu icon styles.
233 *
234 * @return void
235 */
236 public function add_menu_icon_styles() {
237 ?>
238 <style>
239 #toplevel_page_frontblocks-settings .wp-menu-image img,
240 #toplevel_page_frontblocks-settings .wp-menu-image svg {
241 width: 20px !important;
242 height: 20px !important;
243 max-width: 20px !important;
244 max-height: 20px !important;
245 }
246 </style>
247 <?php
248 }
249
250 /**
251 * Enqueue admin styles for settings page.
252 *
253 * @param string $hook Current admin page hook.
254 * @return void
255 */
256 public function enqueue_admin_styles( $hook ) {
257 if ( 'appearance_page_' . $this->page_slug !== $hook ) {
258 return;
259 }
260
261 wp_enqueue_style(
262 'frontblocks-admin-settings',
263 FRBL_PLUGIN_URL . 'assets/admin/settings.css',
264 array(),
265 FRBL_VERSION
266 );
267
268 wp_add_inline_script(
269 'jquery',
270 "
271 document.addEventListener('DOMContentLoaded', function() {
272 const deactivateCheckbox = document.getElementById('deactivate_short_description');
273 const moveContentCheckbox = document.getElementById('move_content_to_short_description');
274
275 if (deactivateCheckbox && moveContentCheckbox) {
276 function updateMutualExclusion() {
277 const deactivateWrapper = deactivateCheckbox.closest('.tw-flex');
278 const moveContentWrapper = moveContentCheckbox.closest('.tw-flex');
279
280 // Check if license is valid (not just PRO active).
281 const isLicenseValid = " . ( $this->is_license_valid ? 'true' : 'false' ) . ";
282
283 if (deactivateCheckbox.checked) {
284 moveContentCheckbox.disabled = true;
285 if (moveContentWrapper) {
286 moveContentWrapper.style.opacity = '0.5';
287 moveContentWrapper.style.filter = 'grayscale(100%)';
288 const toggle = moveContentWrapper.querySelector('.frbl-toggle');
289 if (toggle) {
290 toggle.style.borderColor = '#ef4444';
291 toggle.style.opacity = '0.7';
292 }
293 }
294 } else {
295 moveContentCheckbox.disabled = !isLicenseValid;
296 if (moveContentWrapper) {
297 moveContentWrapper.style.opacity = isLicenseValid ? '1' : '0.5';
298 moveContentWrapper.style.filter = '';
299 const toggle = moveContentWrapper.querySelector('.frbl-toggle');
300 if (toggle) {
301 toggle.style.borderColor = '';
302 toggle.style.opacity = '';
303 }
304 }
305 }
306
307 if (moveContentCheckbox.checked) {
308 deactivateCheckbox.disabled = true;
309 if (deactivateWrapper) {
310 deactivateWrapper.style.opacity = '0.5';
311 deactivateWrapper.style.filter = 'grayscale(100%)';
312 const toggle = deactivateWrapper.querySelector('.frbl-toggle');
313 if (toggle) {
314 toggle.style.borderColor = '#ef4444';
315 toggle.style.opacity = '0.7';
316 }
317 }
318 } else {
319 deactivateCheckbox.disabled = !isLicenseValid;
320 if (deactivateWrapper) {
321 deactivateWrapper.style.opacity = isLicenseValid ? '1' : '0.5';
322 deactivateWrapper.style.filter = '';
323 const toggle = deactivateWrapper.querySelector('.frbl-toggle');
324 if (toggle) {
325 toggle.style.borderColor = '';
326 toggle.style.opacity = '';
327 }
328 }
329 }
330 }
331
332 deactivateCheckbox.addEventListener('change', updateMutualExclusion);
333 moveContentCheckbox.addEventListener('change', updateMutualExclusion);
334
335 updateMutualExclusion();
336 }
337
338 // Show/hide events type select based on toggle state.
339 const eventsCheckbox = document.getElementById('enable_events');
340 const eventsTypeWrapper = document.getElementById('events-type-wrapper');
341
342 if (eventsCheckbox && eventsTypeWrapper) {
343 // Find the parent feature card and feature content.
344 const featureCard = eventsCheckbox.closest('.frbl-feature-card');
345 const featureContent = featureCard ? featureCard.querySelector('.frbl-feature-content') : null;
346
347 // Move the wrapper outside of frbl-feature-content but inside frbl-feature-card.
348 // This ensures it appears below the entire horizontal row (icon, text, toggle).
349 if (featureCard && featureContent) {
350 // Check if wrapper is still inside feature-content and move it.
351 if (featureContent.contains(eventsTypeWrapper)) {
352 // Move it to be a direct child of feature-card, after feature-content.
353 featureCard.appendChild(eventsTypeWrapper);
354 }
355 }
356
357 function updateEventsTypeVisibility() {
358 if (eventsCheckbox.checked) {
359 eventsTypeWrapper.style.display = 'block';
360 eventsTypeWrapper.style.width = '100%';
361 eventsTypeWrapper.style.minWidth = '100%';
362 eventsTypeWrapper.style.marginTop = '1rem';
363 eventsTypeWrapper.style.paddingTop = '1rem';
364 eventsTypeWrapper.style.paddingLeft = '1rem';
365 eventsTypeWrapper.style.paddingRight = '1rem';
366 eventsTypeWrapper.style.paddingBottom = '1rem';
367 eventsTypeWrapper.style.borderTop = '1px solid #e5e7eb';
368 eventsTypeWrapper.style.backgroundColor = '#f9fafb';
369 // Set feature card to column layout.
370 if (featureCard) {
371 featureCard.style.display = 'flex';
372 featureCard.style.flexDirection = 'column';
373 }
374 // Keep the feature content horizontal - never change it.
375 if (featureContent) {
376 featureContent.style.flexDirection = 'row';
377 featureContent.style.alignItems = 'center';
378 featureContent.style.justifyContent = 'space-between';
379 }
380 } else {
381 eventsTypeWrapper.style.display = 'none';
382 // Reset feature card layout.
383 if (featureCard) {
384 featureCard.style.display = '';
385 featureCard.style.flexDirection = '';
386 }
387 // Keep the feature content horizontal.
388 if (featureContent) {
389 featureContent.style.flexDirection = 'row';
390 featureContent.style.alignItems = 'center';
391 featureContent.style.justifyContent = 'space-between';
392 }
393 }
394 }
395
396 // Run immediately to move the element on page load.
397 if (featureCard && featureContent && featureContent.contains(eventsTypeWrapper)) {
398 featureCard.appendChild(eventsTypeWrapper);
399 }
400
401 eventsCheckbox.addEventListener('change', updateEventsTypeVisibility);
402 updateEventsTypeVisibility();
403 }
404
405 });
406 "
407 );
408
409 // Enqueue script for custom post types if PRO is active and license is valid.
410 if ( frbl_is_pro_active() && $this->is_license_valid ) {
411 wp_enqueue_script(
412 'frontblocks-cpt-admin',
413 FRBL_PLUGIN_URL . 'assets/admin/custom-post-types.js',
414 array( 'jquery' ),
415 FRBL_VERSION,
416 true
417 );
418
419 wp_localize_script(
420 'frontblocks-cpt-admin',
421 'frontblocksCpt',
422 array(
423 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
424 'nonce' => wp_create_nonce( 'frontblocks_create_cpt' ),
425 'i18n' => array(
426 'creating' => __( 'Creating...', 'frontblocks' ),
427 'error' => __( 'Error creating post type. Please try again.', 'frontblocks' ),
428 'success' => __( 'Post type created successfully!', 'frontblocks' ),
429 ),
430 )
431 );
432 }
433 }
434
435 /**
436 * Register options page as dedicated menu.
437 *
438 * @return void
439 */
440 public function register_menu() {
441 add_theme_page(
442 __( 'FrontBlocks Settings', 'frontblocks' ),
443 __( 'FrontBlocks', 'frontblocks' ),
444 'edit_theme_options',
445 $this->page_slug,
446 array( $this, 'render_page' )
447 );
448 }
449
450 /**
451 * Register settings, sections and fields.
452 *
453 * @return void
454 */
455 public function register_settings() {
456 register_setting(
457 'frontblocks_settings',
458 'frontblocks_settings',
459 array(
460 'type' => 'array',
461 'sanitize_callback' => array( $this, 'sanitize_settings' ),
462 'default' => array(),
463 'show_in_rest' => false,
464 )
465 );
466
467 // Register license setting group for FrontBlocks PRO.
468 global $frblp_license;
469 if ( $frblp_license && class_exists( '\Closemarketing\WPLicenseManager\License' ) ) {
470 // Register each individual license field.
471 register_setting(
472 'frontblocks-pro_license',
473 'frontblocks-pro_license_apikey',
474 array(
475 'type' => 'string',
476 'sanitize_callback' => 'sanitize_text_field',
477 )
478 );
479
480 register_setting(
481 'frontblocks-pro_license',
482 'frontblocks-pro_license_deactivate_checkbox',
483 array(
484 'type' => 'string',
485 'sanitize_callback' => 'sanitize_text_field',
486 )
487 );
488
489 // Hook into admin_init to process license activation/deactivation.
490 add_action(
491 'admin_init',
492 function () use ( $frblp_license ) {
493 // Check if license form was submitted and verify nonce.
494 if ( isset( $_POST['option_page'], $_POST['_wpnonce'] ) && 'frontblocks-pro_license' === $_POST['option_page'] && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'frontblocks-pro_license-options' ) ) {
495 if ( isset( $_POST['submit_license'] ) ) {
496 // Build input array for validate_license.
497 $input = array(
498 'frontblocks-pro_license_apikey' => isset( $_POST['frontblocks-pro_license_apikey'] ) ? sanitize_text_field( wp_unslash( $_POST['frontblocks-pro_license_apikey'] ) ) : '',
499 'frontblocks-pro_license_deactivate_checkbox' => isset( $_POST['frontblocks-pro_license_deactivate_checkbox'] ) ? sanitize_text_field( wp_unslash( $_POST['frontblocks-pro_license_deactivate_checkbox'] ) ) : '',
500 );
501
502 // Call the license validation.
503 $frblp_license->validate_license( $input );
504 }
505 }
506 },
507 15
508 );
509 }
510
511 // Always Active Blocks section.
512 add_settings_section(
513 'frontblocks_section_active_blocks',
514 __( 'Active Blocks & Features', 'frontblocks' ),
515 array( $this, 'section_active_blocks_callback' ),
516 $this->page_slug
517 );
518
519 add_settings_section(
520 'frontblocks_section_features',
521 __( 'Optional Features', 'frontblocks' ),
522 array( $this, 'section_features_callback' ),
523 $this->page_slug
524 );
525
526 add_settings_field(
527 $this->option_enable_testimonials,
528 __( 'Enable testimonials', 'frontblocks' ),
529 array( $this, 'field_enable_testimonials' ),
530 $this->page_slug,
531 'frontblocks_section_features'
532 );
533
534 add_settings_field(
535 $this->option_enable_reading_progress,
536 __( 'Enable reading progress bar', 'frontblocks' ),
537 array( $this, 'field_enable_reading_progress' ),
538 $this->page_slug,
539 'frontblocks_section_features'
540 );
541
542 add_settings_field(
543 $this->option_enable_back_button,
544 __( 'Enable Back Button', 'frontblocks' ),
545 array( $this, 'field_enable_back_button' ),
546 $this->page_slug,
547 'frontblocks_section_features'
548 );
549
550 add_settings_field(
551 $this->option_enable_events,
552 __( 'Enable Events', 'frontblocks' ),
553 array( $this, 'field_enable_events' ),
554 $this->page_slug,
555 'frontblocks_section_features'
556 );
557
558 add_settings_field(
559 $this->option_enable_fluid_typography,
560 __( 'Enable Fluid Typography', 'frontblocks' ),
561 array( $this, 'field_enable_fluid_typography' ),
562 $this->page_slug,
563 'frontblocks_section_features'
564 );
565
566 // PRO Features section.
567 add_settings_section(
568 'frontblocks_section_woocommerce_features',
569 __( 'WooCommerce Features', 'frontblocks' ),
570 array( $this, 'section_woo_features_callback' ),
571 $this->page_slug
572 );
573
574 add_settings_field(
575 $this->option_enable_gutenberg,
576 __( 'Enable Gutenberg in Products', 'frontblocks' ),
577 array( $this, 'field_enable_gutenberg' ),
578 $this->page_slug,
579 'frontblocks_section_woocommerce_features'
580 );
581
582 add_settings_field(
583 $this->option_enable_simple_prices_variable_products,
584 __( 'Enable Simple Prices Variable Products', 'frontblocks' ),
585 array( $this, 'field_enable_simple_prices_variable_products' ),
586 $this->page_slug,
587 'frontblocks_section_woocommerce_features'
588 );
589
590 add_settings_field(
591 $this->option_enable_after_add_to_cart,
592 __( 'Enable After Add to Cart Block', 'frontblocks' ),
593 array( $this, 'field_enable_after_add_to_cart' ),
594 $this->page_slug,
595 'frontblocks_section_woocommerce_features'
596 );
597
598 add_settings_field(
599 $this->option_deactivate_short_description,
600 __( 'Deactivate Short Description', 'frontblocks' ),
601 array( $this, 'field_deactivate_short_description' ),
602 $this->page_slug,
603 'frontblocks_section_woocommerce_features'
604 );
605
606 add_settings_field(
607 $this->option_move_content_to_short_description,
608 __( 'Move Content to Short Description', 'frontblocks' ),
609 array( $this, 'field_move_content_to_short_description' ),
610 $this->page_slug,
611 'frontblocks_section_woocommerce_features'
612 );
613
614 add_settings_field(
615 $this->option_disable_zoom_images,
616 __( 'Disable Zoom in Product Images', 'frontblocks' ),
617 array( $this, 'field_disable_zoom_images' ),
618 $this->page_slug,
619 'frontblocks_section_woocommerce_features'
620 );
621
622 add_settings_field(
623 $this->option_add_share_buttons,
624 __( 'Add Share Buttons in Product Page', 'frontblocks' ),
625 array( $this, 'field_add_share_buttons' ),
626 $this->page_slug,
627 'frontblocks_section_woocommerce_features'
628 );
629
630 add_settings_field(
631 $this->option_deactivate_product_tabs,
632 __( 'Deactivate Product Tabs', 'frontblocks' ),
633 array( $this, 'field_deactivate_product_tabs' ),
634 $this->page_slug,
635 'frontblocks_section_woocommerce_features'
636 );
637
638 add_settings_field(
639 $this->option_horizontal_product_form,
640 __( 'Horizontal Product Form Layout', 'frontblocks' ),
641 array( $this, 'field_horizontal_product_form' ),
642 $this->page_slug,
643 'frontblocks_section_woocommerce_features'
644 );
645
646 add_settings_field(
647 $this->option_enable_variant_display_mode,
648 __( 'Variant Display Mode', 'frontblocks' ),
649 array( $this, 'field_enable_variant_display_mode' ),
650 $this->page_slug,
651 'frontblocks_section_woocommerce_features'
652 );
653
654 add_settings_field(
655 $this->option_disable_cart_coupon,
656 __( 'Disable Coupon Field in Cart', 'frontblocks' ),
657 array( $this, 'field_disable_cart_coupon' ),
658 $this->page_slug,
659 'frontblocks_section_woocommerce_features'
660 );
661
662 add_settings_field(
663 $this->option_disable_cart_cross_sells,
664 __( 'Disable Cross-Sells in Cart', 'frontblocks' ),
665 array( $this, 'field_disable_cart_cross_sells' ),
666 $this->page_slug,
667 'frontblocks_section_woocommerce_features'
668 );
669
670 add_settings_field(
671 $this->option_disable_checkout_coupon,
672 __( 'Disable Coupon Field in Checkout', 'frontblocks' ),
673 array( $this, 'field_disable_checkout_coupon' ),
674 $this->page_slug,
675 'frontblocks_section_woocommerce_features'
676 );
677
678 add_settings_field(
679 $this->option_disable_checkout_order_notes,
680 __( 'Disable Order Notes in Checkout', 'frontblocks' ),
681 array( $this, 'field_disable_checkout_order_notes' ),
682 $this->page_slug,
683 'frontblocks_section_woocommerce_features'
684 );
685
686 add_settings_field(
687 $this->option_disable_checkout_login_prompt,
688 __( 'Disable Login Prompt in Checkout', 'frontblocks' ),
689 array( $this, 'field_disable_checkout_login_prompt' ),
690 $this->page_slug,
691 'frontblocks_section_woocommerce_features'
692 );
693
694 add_settings_field(
695 $this->option_enable_fullpage_scroll,
696 __( 'Enable Full Page Scroll', 'frontblocks' ),
697 array( $this, 'field_enable_fullpage_scroll' ),
698 $this->page_slug,
699 'frontblocks_section_woocommerce_features'
700 );
701
702 add_settings_field(
703 $this->option_enable_language_banner,
704 __( 'Enable Language Banner', 'frontblocks' ),
705 array( $this, 'field_enable_language_banner' ),
706 $this->page_slug,
707 'frontblocks_section_woocommerce_features'
708 );
709
710 add_settings_field(
711 $this->option_enable_popups,
712 __( 'Enable Popups', 'frontblocks' ),
713 array( $this, 'field_enable_popups' ),
714 $this->page_slug,
715 'frontblocks_section_woocommerce_features'
716 );
717
718 add_settings_field(
719 $this->option_checkout_inline,
720 __( 'Checkout Inline Fields', 'frontblocks' ),
721 array( $this, 'field_checkout_inline' ),
722 $this->page_slug,
723 'frontblocks_section_woocommerce_features'
724 );
725
726 // Custom Post Types section (PRO).
727 if ( frbl_is_pro_active() ) {
728 add_settings_section(
729 'frontblocks_section_custom_post_types',
730 __( 'Custom Post Types', 'frontblocks' ),
731 array( $this, 'section_custom_post_types_callback' ),
732 $this->page_slug
733 );
734
735 add_settings_field(
736 $this->option_enable_custom_post_types,
737 __( 'Enable Custom Post Types Builder', 'frontblocks' ),
738 array( $this, 'field_enable_custom_post_types' ),
739 $this->page_slug,
740 'frontblocks_section_custom_post_types'
741 );
742 }
743
744 // Note: License section is rendered separately outside the main form.
745 // See render_license_section() method called from render_page().
746
747 do_action( 'frontblocks_register_settings' );
748 }
749
750 /**
751 * Render settings page.
752 *
753 * @return void
754 */
755 public function render_page() {
756 if ( ! current_user_can( 'edit_theme_options' ) ) {
757 return;
758 }
759 ?>
760 <div class="frbl-settings-wrapper tw-min-h-screen tw-bg-gray-50 tw-py-8">
761 <div class="tw-max-w-5xl tw-mx-auto tw-px-4 sm:tw-px-6 lg:tw-px-8">
762 <!-- Header Section -->
763 <div class="tw-mb-8 frbl-animate-slide-in">
764 <div class="tw-flex tw-items-center tw-justify-between">
765 <div>
766 <h1 class="tw-text-3xl tw-font-bold tw-text-gray-900 tw-mb-2">
767 <?php echo esc_html__( 'FrontBlocks Settings', 'frontblocks' ); ?>
768 </h1>
769 <p class="tw-text-gray-600">
770 <?php echo esc_html__( 'Add visual enhancements to your website with FrontBlocks.', 'frontblocks' ); ?>
771 </p>
772 </div>
773 <div class="tw-flex tw-items-center tw-space-x-2">
774 <span class="tw-inline-flex tw-items-center tw-px-3 tw-py-1 tw-rounded-full tw-text-sm tw-font-medium tw-bg-primary-100 tw-text-primary-700">
775 <?php echo esc_html__( 'Version', 'frontblocks' ) . ' ' . esc_html( FRBL_VERSION ); ?>
776 </span>
777 </div>
778 </div>
779 </div>
780
781 <?php
782 // Show success message after settings are saved.
783 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
784 if ( isset( $_GET['settings-updated'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) ) :
785 ?>
786 <div style="background-color: #f0fdf4; border-left: 4px solid #4ade80; border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);">
787 <div class="tw-flex">
788 <div class="tw-flex-shrink-0">
789 <svg class="tw-h-5 tw-w-5" style="color: #4ade80;" viewBox="0 0 20 20" fill="currentColor">
790 <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
791 </svg>
792 </div>
793 <div class="tw-ml-3">
794 <p class="tw-text-sm tw-font-medium" style="color: #15803d; margin: 0;">
795 <?php esc_html_e( 'Changes saved successfully', 'frontblocks' ); ?>
796 </p>
797 </div>
798 </div>
799 </div>
800 <?php
801 endif;
802
803 // Show error message if saving failed.
804 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
805 if ( isset( $_GET['settings-error'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-error'] ) ) ) :
806 ?>
807 <div style="background-color: #fef2f2; border-left: 4px solid #f87171; border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);">
808 <div class="tw-flex">
809 <div class="tw-flex-shrink-0">
810 <svg class="tw-h-5 tw-w-5" style="color: #f87171;" viewBox="0 0 20 20" fill="currentColor">
811 <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
812 </svg>
813 </div>
814 <div class="tw-ml-3">
815 <p class="tw-text-sm tw-font-medium" style="color: #991b1b; margin: 0;">
816 <?php esc_html_e( 'Failed to save changes. Please try again.', 'frontblocks' ); ?>
817 </p>
818 </div>
819 </div>
820 </div>
821 <?php
822 endif;
823
824 // Show license activated message.
825 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
826 if ( isset( $_GET['license_activated'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['license_activated'] ) ) ) :
827 ?>
828 <div style="background-color: #f0fdf4; border-left: 4px solid #4ade80; border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);">
829 <div class="tw-flex">
830 <div class="tw-flex-shrink-0">
831 <svg class="tw-h-5 tw-w-5" style="color: #4ade80;" viewBox="0 0 20 20" fill="currentColor">
832 <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
833 </svg>
834 </div>
835 <div class="tw-ml-3">
836 <p class="tw-text-sm tw-font-medium" style="color: #15803d; margin: 0;">
837 <?php esc_html_e( 'License activated successfully!', 'frontblocks' ); ?>
838 </p>
839 </div>
840 </div>
841 </div>
842 <?php
843 endif;
844
845 // Show license deactivated message.
846 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
847 if ( isset( $_GET['license_deactivated'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['license_deactivated'] ) ) ) :
848 ?>
849 <div style="background-color: #fffbeb; border-left: 4px solid #fbbf24; border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);">
850 <div class="tw-flex">
851 <div class="tw-flex-shrink-0">
852 <svg class="tw-h-5 tw-w-5" style="color: #fbbf24;" viewBox="0 0 20 20" fill="currentColor">
853 <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
854 </svg>
855 </div>
856 <div class="tw-ml-3">
857 <p class="tw-text-sm tw-font-medium" style="color: #92400e; margin: 0;">
858 <?php esc_html_e( 'License deactivated successfully.', 'frontblocks' ); ?>
859 </p>
860 </div>
861 </div>
862 </div>
863 <?php
864 endif;
865
866 // Show license error message.
867 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
868 if ( isset( $_GET['license_error'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['license_error'] ) ) ) :
869 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
870 $error_msg = isset( $_GET['error_msg'] ) ? sanitize_text_field( wp_unslash( $_GET['error_msg'] ) ) : '';
871 ?>
872 <div style="background-color: #fef2f2; border-left: 4px solid #f87171; border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);">
873 <div class="tw-flex">
874 <div class="tw-flex-shrink-0">
875 <svg class="tw-h-5 tw-w-5" style="color: #f87171;" viewBox="0 0 20 20" fill="currentColor">
876 <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
877 </svg>
878 </div>
879 <div class="tw-ml-3">
880 <p class="tw-text-sm tw-font-medium" style="color: #991b1b; margin: 0;">
881 <?php
882 if ( ! empty( $error_msg ) ) {
883 echo esc_html__( 'Failed to activate license: ', 'frontblocks' ) . '<br><strong>' . esc_html( $error_msg ) . '</strong>';
884 } else {
885 esc_html_e( 'Failed to activate license. Please check your license key and try again.', 'frontblocks' );
886 }
887 ?>
888 </p>
889 </div>
890 </div>
891 </div>
892 <?php
893 endif;
894 ?>
895
896 <!-- Settings Form -->
897 <form method="post" action="options.php" class="tw-space-y-6">
898 <?php settings_fields( 'frontblocks_settings' ); ?>
899
900 <?php
901 // Get all sections for this page.
902 global $wp_settings_sections, $wp_settings_fields;
903
904 if ( ! isset( $wp_settings_sections[ $this->page_slug ] ) ) {
905 return;
906 }
907
908 foreach ( (array) $wp_settings_sections[ $this->page_slug ] as $section ) {
909 $this->render_settings_section( $section );
910 }
911 ?>
912
913 <!-- Submit Button -->
914 <div class="tw-flex tw-items-center tw-justify-between tw-pt-6 tw-border-t tw-border-gray-200">
915 <div class="tw-text-sm tw-text-gray-500">
916 <?php echo esc_html__( 'Changes will be applied immediately after saving.', 'frontblocks' ); ?>
917 </div>
918 <button type="submit" class="tw-inline-flex tw-items-center tw-px-4 tw-py-3 tw-border tw-border-transparent tw-text-base tw-font-medium tw-rounded-lg tw-shadow-sm tw-text-white tw-bg-primary-500 hover:tw-bg-primary-600 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-offset-2 focus:tw-ring-primary-500 tw-transition-colors tw-duration-200">
919 <svg class="tw-w-5 tw-h-5 tw-mr-2 tw--ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
920 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
921 </svg>
922 <?php echo esc_html__( 'Save Settings', 'frontblocks' ); ?>
923 </button>
924 </div>
925 </form>
926
927 <?php
928 // Render license section separately (outside main form) if PRO is active.
929 if ( frbl_is_pro_active() ) {
930 $this->render_license_section();
931 }
932 ?>
933
934 <!-- Footer Info -->
935 <div class="tw-mt-8 tw-text-center tw-text-sm tw-text-gray-500">
936 <?php
937 printf(
938 /* translators: %s: Close·marketing link */
939 esc_html__( 'Made with ❤️ by %s', 'frontblocks' ),
940 '<a href="https://close.technology/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings" target="_blank" rel="noopener noreferrer" class="tw-text-primary-500 hover:tw-text-primary-600 tw-font-medium">Close·Technology</a>'
941 );
942 ?>
943 </div>
944
945 <?php $this->render_debug_section(); ?>
946 </div>
947 </div>
948 <?php
949 }
950
951 /**
952 * Render debug section for Fluid Typography.
953 *
954 * @return void
955 */
956 private function render_debug_section() {
957 // Only show if Fluid Typography is enabled and user requested debug.
958 $options = get_option( 'frontblocks_settings', array() );
959 $enabled = ! empty( $options['enable_fluid_typography'] );
960
961 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
962 if ( ! $enabled || ! isset( $_GET['frbl_debug_typography'] ) ) {
963 return;
964 }
965
966 // Get GeneratePress settings.
967 $gp_settings = get_option( 'generate_settings', array() );
968
969 // Filter only font-related settings.
970 $font_settings = array();
971 foreach ( $gp_settings as $key => $value ) {
972 if ( strpos( $key, 'font' ) !== false || strpos( $key, 'heading' ) !== false ) {
973 $font_settings[ $key ] = $value;
974 }
975 }
976
977 ?>
978 <div class="tw-mt-8 tw-p-6 tw-bg-yellow-50 tw-border tw-border-yellow-200 tw-rounded-lg">
979 <h3 class="tw-text-lg tw-font-semibold tw-text-gray-900 tw-mb-4">
980 🐛 Debug: Fluid Typography Settings
981 </h3>
982 <p class="tw-text-sm tw-text-gray-600 tw-mb-4">
983 <?php echo esc_html__( 'This shows the GeneratePress font settings being used by the Fluid Typography module.', 'frontblocks' ); ?>
984 </p>
985 <div class="tw-bg-white tw-p-4 tw-rounded tw-border tw-border-gray-300 tw-overflow-auto" style="max-height: 400px;">
986 <pre style="margin: 0; font-size: 12px;"><?php print_r( $font_settings ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r ?></pre>
987 </div>
988 <p class="tw-text-xs tw-text-gray-500 tw-mt-4">
989 <?php
990 printf(
991 /* translators: %s: URL parameter */
992 esc_html__( 'To hide this debug info, remove %s from the URL.', 'frontblocks' ),
993 '<code>?frbl_debug_typography=1</code>'
994 );
995 ?>
996 </p>
997 </div>
998 <?php
999 }
1000
1001 /**
1002 * Active Blocks section callback.
1003 *
1004 * @return void
1005 */
1006 private function section_active_blocks_callback() {
1007 $active_blocks = apply_filters(
1008 'frbl_active_blocks',
1009 array(
1010 array(
1011 'icon' => 'animations',
1012 'title' => __( 'Animations', 'frontblocks' ),
1013 'desc' => __( 'Add animations to any block using Animate.css', 'frontblocks' ),
1014 ),
1015 array(
1016 'icon' => 'carousel',
1017 'title' => __( 'Carousel/Slider', 'frontblocks' ),
1018 'desc' => __( 'Transform any Grid block into a carousel or slider', 'frontblocks' ),
1019 ),
1020 array(
1021 'icon' => 'gallery',
1022 'title' => __( 'Native Gallery', 'frontblocks' ),
1023 'desc' => __( 'Enhanced gallery block with carousel and masonry options', 'frontblocks' ),
1024 ),
1025 array(
1026 'icon' => 'sticky',
1027 'title' => __( 'Sticky Columns', 'frontblocks' ),
1028 'desc' => __( 'Make Grid blocks sticky when scrolling', 'frontblocks' ),
1029 ),
1030 array(
1031 'icon' => 'insert_post',
1032 'title' => __( 'Insert Post Block', 'frontblocks' ),
1033 'desc' => __( 'Display content from other posts, pages or custom post types', 'frontblocks' ),
1034 ),
1035 array(
1036 'icon' => 'counter',
1037 'title' => __( 'Counter Block', 'frontblocks' ),
1038 'desc' => __( 'Display animated counters with start and end values', 'frontblocks' ),
1039 ),
1040 array(
1041 'icon' => 'reading_time',
1042 'title' => __( 'Reading Time Block', 'frontblocks' ),
1043 'desc' => __( 'Show estimated reading time for posts', 'frontblocks' ),
1044 ),
1045 array(
1046 'icon' => 'stacked_images',
1047 'title' => __( 'Stacked Images Block', 'frontblocks' ),
1048 'desc' => __( 'Display images with animated stacking effect from different directions', 'frontblocks' ),
1049 ),
1050 array(
1051 'icon' => 'product_categories',
1052 'title' => __( 'Product Categories Block', 'frontblocks' ),
1053 'desc' => __( 'Display WooCommerce product categories', 'frontblocks' ),
1054 ),
1055 array(
1056 'icon' => 'headline_marquee',
1057 'title' => __( 'Headline Marquee', 'frontblocks' ),
1058 'desc' => __( 'Infinite scrolling marquee effect for headline/text blocks with customizable speed', 'frontblocks' ),
1059 ),
1060 array(
1061 'icon' => 'svg_upload',
1062 'title' => __( 'SVG Uploads', 'frontblocks' ),
1063 'desc' => __( 'Upload SVG files to the media library. Files are automatically sanitized to prevent security risks.', 'frontblocks' ),
1064 ),
1065 )
1066 );
1067
1068 $pro_blocks = apply_filters( 'frbl_pro_blocks', $this->get_default_pro_blocks() );
1069
1070 ?>
1071 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1072 <?php echo esc_html__( 'These blocks and features are always active and available in the block editor.', 'frontblocks' ); ?>
1073 </p>
1074 <div class="frbl-features-grid">
1075 <?php
1076 foreach ( $active_blocks as $block ) {
1077 UI::show_info_card( $block['icon'], $block['title'], $block['desc'] );
1078 }
1079
1080 foreach ( $pro_blocks as $block ) {
1081 UI::show_pro_info_card( $block['icon'], $block['title'], $block['desc'] );
1082 }
1083 ?>
1084 </div>
1085 <?php
1086 }
1087
1088 /**
1089 * Features section callback.
1090 *
1091 * @return void
1092 */
1093 private function section_features_callback() {
1094 $license_valid = function_exists( 'frblp_is_license_valid' ) && frblp_is_license_valid();
1095 ?>
1096 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1097 <?php echo esc_html__( 'Enable or disable these optional features as needed.', 'frontblocks' ); ?>
1098 </p>
1099 <?php
1100 }
1101
1102 /**
1103 * Render a single settings section as a card.
1104 *
1105 * @param array $section Section data.
1106 * @return void
1107 */
1108 private function render_settings_section( $section ) {
1109 global $wp_settings_fields;
1110
1111 $has_fields = isset( $wp_settings_fields[ $this->page_slug ][ $section['id'] ] );
1112
1113 // Si no hay campos Y no hay callback, no renderizar nada.
1114 if ( ! $has_fields && ! $section['callback'] ) {
1115 return;
1116 }
1117
1118 // Check if this is a section with callback only (like active_blocks).
1119 $is_callback_only = ! $has_fields && $section['callback'];
1120
1121 // Check if this is the custom post types section - render it full width.
1122 $is_cpt_section = 'frontblocks_section_custom_post_types' === $section['id'];
1123
1124 // Show PRO CTA button before the Optional Features section.
1125 if ( 'frontblocks_section_features' === $section['id'] && ! $this->is_license_valid ) {
1126 ?>
1127 <div class="tw-mb-4">
1128 <a
1129 href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings-optional-cta"
1130 target="_blank"
1131 rel="noopener noreferrer"
1132 class="tw-inline-flex tw-items-center tw-border tw-border-transparent tw-text-sm tw-font-medium tw-rounded-lg tw-shadow-sm tw-text-white tw-transition-colors tw-duration-200"
1133 style="background-color: #ef4444; padding: 10px 20px;"
1134 onmouseover="this.style.backgroundColor='#dc2626'"
1135 onmouseout="this.style.backgroundColor='#ef4444'"
1136 >
1137 <?php echo esc_html__( 'Get FrontBlocks PRO', 'frontblocks' ); ?>
1138 </a>
1139 </div>
1140 <?php
1141 }
1142
1143 if ( $is_callback_only ) {
1144 // Render section with only callback (no fields).
1145 ?>
1146 <div class="frbl-section-wrapper">
1147 <div class="frbl-section-header">
1148 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
1149 <?php echo esc_html( $section['title'] ); ?>
1150 </h2>
1151 </div>
1152 <?php call_user_func( $section['callback'], $section ); ?>
1153 </div>
1154 <?php
1155 return;
1156 }
1157
1158 if ( $is_cpt_section ) {
1159 // Render CPT section as a full-width card.
1160 ?>
1161 <div class="frbl-card tw-bg-white tw-rounded-lg tw-shadow-sm tw-border tw-border-gray-200 tw-overflow-hidden frbl-animate-slide-in tw-mb-8">
1162 <div class="tw-px-6 tw-py-5 tw-border-b tw-border-gray-200 tw-bg-gradient-to-r tw-from-gray-50 tw-to-white">
1163 <h2 class="tw-text-xl tw-font-semibold tw-text-gray-900">
1164 <?php echo esc_html( $section['title'] ); ?>
1165 </h2>
1166 <?php
1167 if ( $section['callback'] ) {
1168 echo '<div class="tw-mt-2 tw-text-sm tw-text-gray-600">';
1169 call_user_func( $section['callback'], $section );
1170 echo '</div>';
1171 }
1172 ?>
1173 </div>
1174 <div class="tw-px-6 tw-py-5">
1175 <?php
1176 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
1177 call_user_func( $field['callback'], $field['args'] );
1178 }
1179 ?>
1180 </div>
1181 </div>
1182 <?php
1183 } else {
1184 // Render regular sections with feature grid.
1185 ?>
1186 <div class="frbl-section-wrapper">
1187 <!-- Section Header -->
1188 <div class="frbl-section-header">
1189 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
1190 <?php echo esc_html( $section['title'] ); ?>
1191 </h2>
1192 <?php
1193 if ( $section['callback'] ) {
1194 echo '<div class="tw-text-sm tw-text-gray-600">';
1195 call_user_func( $section['callback'], $section );
1196 echo '</div>';
1197 }
1198 ?>
1199 </div>
1200
1201 <!-- Features Grid -->
1202 <div class="frbl-features-grid">
1203 <?php
1204 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
1205 $this->render_settings_field( $field );
1206 }
1207 ?>
1208 </div>
1209 </div>
1210 <?php
1211 }
1212 }
1213
1214 /**
1215 * Render a single settings field as a card.
1216 *
1217 * @param array $field Field data.
1218 * @return void
1219 */
1220 private function render_settings_field( $field ) {
1221 // Determine if this is a PRO feature (always, regardless of license status).
1222 $is_pro_feature = in_array(
1223 $field['id'],
1224 array(
1225 $this->option_enable_gutenberg,
1226 $this->option_enable_simple_prices_variable_products,
1227 $this->option_enable_after_add_to_cart,
1228 $this->option_deactivate_short_description,
1229 $this->option_move_content_to_short_description,
1230 $this->option_disable_zoom_images,
1231 $this->option_add_share_buttons,
1232 $this->option_deactivate_product_tabs,
1233 $this->option_horizontal_product_form,
1234 $this->option_enable_variant_display_mode,
1235 $this->option_enable_fullpage_scroll,
1236 $this->option_enable_language_banner,
1237 $this->option_enable_popups,
1238 $this->option_checkout_inline,
1239 $this->option_disable_cart_coupon,
1240 $this->option_disable_cart_cross_sells,
1241 $this->option_disable_checkout_coupon,
1242 $this->option_disable_checkout_order_notes,
1243 $this->option_disable_checkout_login_prompt,
1244 ),
1245 true
1246 );
1247
1248 // Apply PRO styling only if license is not valid.
1249 $needs_license = $is_pro_feature && ! $this->is_license_valid;
1250
1251 // Get icon for this feature.
1252 $icon = $this->get_feature_icon( $field['id'] );
1253
1254 // Short descriptions for each field.
1255 $descriptions = array(
1256 $this->option_enable_testimonials => __( 'Add a testimonials block to display customer reviews.', 'frontblocks' ),
1257 $this->option_enable_reading_progress => __( 'Show a progress bar at the top of the page while reading posts.', 'frontblocks' ),
1258 $this->option_enable_back_button => __( 'Add a floating back button for easy navigation.', 'frontblocks' ),
1259 $this->option_enable_events => __( 'Register and display events using a CPT or blog posts.', 'frontblocks' ),
1260 $this->option_enable_fluid_typography => __( 'Font sizes scale smoothly between mobile and desktop using CSS clamp().', 'frontblocks' ),
1261 $this->option_enable_popups => __( 'Create popups with the block editor and configure when and where they appear.', 'frontblocks' ),
1262 $this->option_enable_gutenberg => __( 'Use the block editor to write WooCommerce product descriptions.', 'frontblocks' ),
1263 $this->option_enable_simple_prices_variable_products => __( 'Show a simplified price range for variable products.', 'frontblocks' ),
1264 $this->option_enable_after_add_to_cart => __( 'Insert custom block content right after the Add to Cart button.', 'frontblocks' ),
1265 $this->option_deactivate_short_description => __( 'Remove the short description field from product pages.', 'frontblocks' ),
1266 $this->option_move_content_to_short_description => __( 'Move the main product content into the short description area.', 'frontblocks' ),
1267 $this->option_disable_zoom_images => __( 'Remove the zoom effect on WooCommerce product images.', 'frontblocks' ),
1268 $this->option_add_share_buttons => __( 'Add social share buttons to WooCommerce product pages.', 'frontblocks' ),
1269 $this->option_deactivate_product_tabs => __( 'Remove the default description, reviews and attributes tabs.', 'frontblocks' ),
1270 $this->option_horizontal_product_form => __( 'Display quantity and Add to Cart button side by side.', 'frontblocks' ),
1271 $this->option_enable_variant_display_mode => __( 'Choose per attribute whether to display product variations as a dropdown or radio buttons.', 'frontblocks' ),
1272 $this->option_enable_fullpage_scroll => __( 'Enable full-page scroll navigation between sections.', 'frontblocks' ),
1273 $this->option_enable_language_banner => __( 'Show a banner when the visitor language differs from the site language.', 'frontblocks' ),
1274 $this->option_checkout_inline => __( 'Display address, email and phone fields side by side in the WooCommerce checkout.', 'frontblocks' ),
1275 $this->option_disable_zoom_images => __( 'Remove the zoom effect on WooCommerce product images.', 'frontblocks' ),
1276 $this->option_add_share_buttons => __( 'Add social share buttons to WooCommerce product pages.', 'frontblocks' ),
1277 $this->option_deactivate_product_tabs => __( 'Remove the default description, reviews and attributes tabs.', 'frontblocks' ),
1278 $this->option_horizontal_product_form => __( 'Display quantity and Add to Cart button side by side.', 'frontblocks' ),
1279 $this->option_enable_fullpage_scroll => __( 'Enable full-page scroll navigation between sections.', 'frontblocks' ),
1280 $this->option_enable_language_banner => __( 'Show a banner when the visitor language differs from the site language.', 'frontblocks' ),
1281 $this->option_disable_cart_coupon => __( 'Hide the coupon code input field from the Cart page.', 'frontblocks' ),
1282 $this->option_disable_cart_cross_sells => __( 'Remove the cross-sell product suggestions from the Cart page.', 'frontblocks' ),
1283 $this->option_disable_checkout_coupon => __( 'Hide the coupon code form shown above the Checkout form.', 'frontblocks' ),
1284 $this->option_disable_checkout_order_notes => __( 'Remove the order notes / additional information field from Checkout.', 'frontblocks' ),
1285 $this->option_disable_checkout_login_prompt => __( 'Hide the login and registration prompt shown before the Checkout form.', 'frontblocks' ),
1286 );
1287
1288 $desc = $descriptions[ $field['id'] ] ?? '';
1289
1290 ?>
1291 <div class="frbl-feature-card <?php echo $needs_license ? 'frbl-feature-pro' : ''; ?>">
1292 <?php if ( $is_pro_feature ) : ?>
1293 <div class="frbl-pro-badge">PRO</div>
1294 <?php endif; ?>
1295
1296 <div class="frbl-feature-content">
1297 <div class="frbl-feature-icon">
1298 <?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
1299 </div>
1300 <div class="frbl-feature-info">
1301 <h3 class="frbl-feature-title">
1302 <?php echo esc_html( $field['title'] ); ?>
1303 </h3>
1304 <?php if ( $desc ) : ?>
1305 <p class="frbl-feature-description">
1306 <?php echo esc_html( $desc ); ?>
1307 </p>
1308 <?php endif; ?>
1309 </div>
1310 <div class="frbl-feature-toggle">
1311 <?php call_user_func( $field['callback'], $field['args'] ); ?>
1312 </div>
1313 </div>
1314 </div>
1315 <?php
1316 }
1317
1318 /**
1319 * Get icon SVG for a feature.
1320 *
1321 * @param string $field_id Field ID.
1322 * @return string SVG icon markup.
1323 */
1324 private function get_feature_icon( $field_id ) {
1325 // Map field IDs to icon file names.
1326 $icon_map = array(
1327 $this->option_enable_testimonials => 'testimonials',
1328 $this->option_enable_reading_progress => 'reading-progress',
1329 $this->option_enable_back_button => 'back-button',
1330 $this->option_enable_events => 'events',
1331 $this->option_enable_fluid_typography => 'fluid-typography',
1332 $this->option_enable_popups => 'popups',
1333 $this->option_enable_gutenberg => 'gutenberg',
1334 $this->option_enable_simple_prices_variable_products => 'simple-prices',
1335 $this->option_enable_after_add_to_cart => 'after-add-to-cart',
1336 $this->option_deactivate_short_description => 'deactivate-description',
1337 $this->option_move_content_to_short_description => 'move-content',
1338 $this->option_disable_zoom_images => 'disable-zoom',
1339 $this->option_add_share_buttons => 'share-buttons',
1340 $this->option_deactivate_product_tabs => 'deactivate-tabs',
1341 $this->option_horizontal_product_form => 'horizontal-form',
1342 $this->option_enable_variant_display_mode => 'default',
1343 $this->option_enable_fullpage_scroll => 'fullpage-scroll',
1344 $this->option_enable_language_banner => 'language-banner',
1345 $this->option_checkout_inline => 'horizontal-form',
1346 $this->option_disable_zoom_images => 'disable-zoom',
1347 $this->option_add_share_buttons => 'share-buttons',
1348 $this->option_deactivate_product_tabs => 'deactivate-tabs',
1349 $this->option_horizontal_product_form => 'horizontal-form',
1350 $this->option_enable_fullpage_scroll => 'fullpage-scroll',
1351 $this->option_enable_language_banner => 'language-banner',
1352 $this->option_disable_cart_coupon => 'default',
1353 $this->option_disable_cart_cross_sells => 'default',
1354 $this->option_disable_checkout_coupon => 'default',
1355 $this->option_disable_checkout_order_notes => 'default',
1356 $this->option_disable_checkout_login_prompt => 'default',
1357 );
1358
1359 $icon_name = $icon_map[ $field_id ] ?? 'default';
1360 $icon_path = FRBL_PLUGIN_PATH . 'assets/admin/icons/' . $icon_name . '.svg';
1361
1362 if ( file_exists( $icon_path ) ) {
1363 $svg_content = file_get_contents( $icon_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1364 return $svg_content ? $svg_content : '';
1365 }
1366
1367 return '';
1368 }
1369
1370 /**
1371 * PRO Features section description.
1372 *
1373 * @return void
1374 */
1375 public function section_woo_features_callback() {
1376 if ( ! frbl_is_pro_active() ) {
1377 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
1378 echo '<div class="tw-flex">';
1379 echo '<div class="tw-flex-shrink-0">';
1380 echo '<svg class="tw-h-5 tw-w-5 tw-text-blue-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/></svg>';
1381 echo '</div>';
1382 echo '<div class="tw-ml-3">';
1383 echo '<p class="tw-text-sm tw-text-blue-700">';
1384 printf(
1385 /* translators: %s: FrontBlocks PRO link */
1386 esc_html__( 'These features require %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
1387 '<a href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings" target="_blank" class="tw-font-medium tw-underline">FrontBlocks PRO</a>'
1388 );
1389 echo '</p>';
1390 echo '</div>';
1391 echo '</div>';
1392 echo '</div>';
1393 } elseif ( ! $this->is_license_valid ) {
1394 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
1395 echo '<div class="tw-flex">';
1396 echo '<div class="tw-flex-shrink-0">';
1397 echo '<svg class="tw-h-5 tw-w-5 tw-text-yellow-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>';
1398 echo '</div>';
1399 echo '<div class="tw-ml-3">';
1400 echo '<p class="tw-text-sm tw-text-yellow-700">';
1401 printf(
1402 /* translators: %s: License section link */
1403 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
1404 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1405 );
1406 echo '</p>';
1407 echo '</div>';
1408 echo '</div>';
1409 echo '</div>';
1410 } else {
1411 ?>
1412 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1413 <?php echo esc_html__( 'Advanced features for WooCommerce and more.', 'frontblocks' ); ?>
1414 </p>
1415 <?php
1416 }
1417 }
1418
1419 /**
1420 * Render toggle field for enable testimonials.
1421 *
1422 * @return void
1423 */
1424 public function field_enable_testimonials() {
1425 $options = get_option( 'frontblocks_settings', array() );
1426 $enabled = (bool) ( $options[ $this->option_enable_testimonials ] ?? false );
1427 ?>
1428 <label class="frbl-toggle">
1429 <input type="checkbox"
1430 id="<?php echo esc_attr( $this->option_enable_testimonials ); ?>"
1431 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_testimonials ); ?>]"
1432 value="1"
1433 <?php checked( true, $enabled ); ?>
1434 />
1435 <span></span>
1436 </label>
1437 <?php
1438 }
1439
1440 /**
1441 * Render toggle field for enable reading progress bar.
1442 *
1443 * @return void
1444 */
1445 public function field_enable_reading_progress() {
1446 $options = get_option( 'frontblocks_settings', array() );
1447 $enabled = (bool) ( $options[ $this->option_enable_reading_progress ] ?? false );
1448 ?>
1449 <label class="frbl-toggle">
1450 <input type="checkbox"
1451 id="<?php echo esc_attr( $this->option_enable_reading_progress ); ?>"
1452 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_reading_progress ); ?>]"
1453 value="1"
1454 <?php checked( true, $enabled ); ?>
1455 />
1456 <span></span>
1457 </label>
1458 <?php
1459 }
1460
1461 /**
1462 * Render toggle field for enable back button.
1463 *
1464 * @return void
1465 */
1466 public function field_enable_back_button() {
1467 $options = get_option( 'frontblocks_settings', array() );
1468 $enabled = (bool) ( $options[ $this->option_enable_back_button ] ?? false );
1469 ?>
1470 <label class="frbl-toggle">
1471 <input type="checkbox"
1472 id="<?php echo esc_attr( $this->option_enable_back_button ); ?>"
1473 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_back_button ); ?>]"
1474 value="1"
1475 <?php checked( true, $enabled ); ?>
1476 />
1477 <span></span>
1478 </label>
1479 <?php
1480 }
1481
1482 /**
1483 * Render toggle field for enable events.
1484 *
1485 * @return void
1486 */
1487 public function field_enable_events() {
1488 $options = get_option( 'frontblocks_settings', array() );
1489 $enabled = (bool) ( $options[ $this->option_enable_events ] ?? false );
1490 $events_type = sanitize_text_field( $options[ $this->option_events_type ] ?? 'cpt' );
1491 ?>
1492 <!-- Toggle - stays in horizontal layout with icon and text -->
1493 <label class="frbl-toggle">
1494 <input type="checkbox"
1495 id="<?php echo esc_attr( $this->option_enable_events ); ?>"
1496 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_events ); ?>]"
1497 value="1"
1498 <?php checked( true, $enabled ); ?>
1499 />
1500 <span></span>
1501 </label>
1502
1503 <!-- Select and description - will be moved below the card by JavaScript -->
1504 <div id="events-type-wrapper" class="tw-mt-4" style="<?php echo $enabled ? 'width: 100%; min-width: 100%; display: block;' : 'display: none;'; ?>">
1505 <label for="<?php echo esc_attr( $this->option_events_type ); ?>" class="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-2">
1506 <?php echo esc_html__( 'Event type', 'frontblocks' ); ?>
1507 </label>
1508 <select
1509 id="<?php echo esc_attr( $this->option_events_type ); ?>"
1510 name="frontblocks_settings[<?php echo esc_attr( $this->option_events_type ); ?>]"
1511 class="tw-block tw-w-full tw-px-3 tw-py-2 tw-border tw-border-gray-300 tw-rounded-lg tw-text-base focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-primary-500 focus:tw-border-transparent"
1512 style="width: 100%; min-width: 100%; max-width: 100%; box-sizing: border-box;"
1513 >
1514 <option value="cpt" <?php selected( $events_type, 'cpt' ); ?>>
1515 <?php echo esc_html__( 'Custom Post Type (CPT)', 'frontblocks' ); ?>
1516 </option>
1517 <option value="posts" <?php selected( $events_type, 'posts' ); ?>>
1518 <?php echo esc_html__( 'Blog posts', 'frontblocks' ); ?>
1519 </option>
1520 </select>
1521 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1522 <?php echo esc_html__( 'Choose whether events will be created in a dedicated CPT or in regular blog posts.', 'frontblocks' ); ?>
1523 </p>
1524 </div>
1525 <?php
1526 }
1527
1528 /**
1529 * Render toggle field for enable fluid typography.
1530 *
1531 * @return void
1532 */
1533 public function field_enable_fluid_typography() {
1534 $options = get_option( 'frontblocks_settings', array() );
1535 $enabled = (bool) ( $options[ $this->option_enable_fluid_typography ] ?? true );
1536 ?>
1537 <label class="frbl-toggle">
1538 <input type="checkbox"
1539 id="<?php echo esc_attr( $this->option_enable_fluid_typography ); ?>"
1540 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_fluid_typography ); ?>]"
1541 value="1"
1542 <?php checked( true, $enabled ); ?>
1543 />
1544 <span></span>
1545 </label>
1546 <?php
1547 }
1548
1549 /**
1550 * Render toggle field for enable popups (PRO feature).
1551 *
1552 * @return void
1553 */
1554 public function field_enable_popups() {
1555 $this->render_pro_toggle( $this->option_enable_popups );
1556 }
1557
1558 /**
1559 * Render toggle field for enable Gutenberg in products (PRO).
1560 *
1561 * @return void
1562 */
1563 public function field_enable_gutenberg() {
1564 $this->render_pro_toggle( $this->option_enable_gutenberg );
1565 }
1566
1567 /**
1568 * Render toggle field for enable Simple Prices Variable Products (PRO).
1569 *
1570 * @return void
1571 */
1572 public function field_enable_simple_prices_variable_products() {
1573 $this->render_pro_toggle( $this->option_enable_simple_prices_variable_products );
1574 }
1575
1576 /**
1577 * Render After Add to Cart Block field.
1578 *
1579 * @return void
1580 */
1581 public function field_enable_after_add_to_cart() {
1582 $this->render_pro_toggle( $this->option_enable_after_add_to_cart );
1583 }
1584
1585 /**
1586 * Render Deactivate Short Description field.
1587 *
1588 * @return void
1589 */
1590 public function field_deactivate_short_description() {
1591 $this->render_pro_toggle( $this->option_deactivate_short_description );
1592 }
1593
1594 /**
1595 * Render Move Content to Short Description field.
1596 *
1597 * @return void
1598 */
1599 public function field_move_content_to_short_description() {
1600 $this->render_pro_toggle( $this->option_move_content_to_short_description );
1601 }
1602
1603 /**
1604 * Render Disable Zoom in Product Images field.
1605 *
1606 * @return void
1607 */
1608 public function field_disable_zoom_images() {
1609 $this->render_pro_toggle( $this->option_disable_zoom_images );
1610 }
1611
1612 /**
1613 * Render Add Share Buttons in Product Page field.
1614 *
1615 * @return void
1616 */
1617 public function field_add_share_buttons() {
1618 $this->render_pro_toggle( $this->option_add_share_buttons );
1619 }
1620
1621 /**
1622 * Render Deactivate Product Tabs field.
1623 *
1624 * @return void
1625 */
1626 public function field_deactivate_product_tabs() {
1627 $this->render_pro_toggle( $this->option_deactivate_product_tabs );
1628 }
1629
1630 /**
1631 * Render Horizontal Product Form Layout field.
1632 *
1633 * @return void
1634 */
1635 public function field_horizontal_product_form() {
1636 $this->render_pro_toggle( $this->option_horizontal_product_form );
1637 }
1638
1639 /**
1640 * Render Variant Display Mode toggle field.
1641 *
1642 * @return void
1643 */
1644 public function field_enable_variant_display_mode() {
1645 $this->render_pro_toggle( $this->option_enable_variant_display_mode );
1646 }
1647
1648 /**
1649 * Render Disable Coupon Field in Cart field.
1650 *
1651 * @return void
1652 */
1653 public function field_disable_cart_coupon() {
1654 $this->render_pro_toggle( $this->option_disable_cart_coupon );
1655 }
1656
1657 /**
1658 * Render Disable Cross-Sells in Cart field.
1659 *
1660 * @return void
1661 */
1662 public function field_disable_cart_cross_sells() {
1663 $this->render_pro_toggle( $this->option_disable_cart_cross_sells );
1664 }
1665
1666 /**
1667 * Render Disable Coupon Field in Checkout field.
1668 *
1669 * @return void
1670 */
1671 public function field_disable_checkout_coupon() {
1672 $this->render_pro_toggle( $this->option_disable_checkout_coupon );
1673 }
1674
1675 /**
1676 * Render Disable Order Notes in Checkout field.
1677 *
1678 * @return void
1679 */
1680 public function field_disable_checkout_order_notes() {
1681 $this->render_pro_toggle( $this->option_disable_checkout_order_notes );
1682 }
1683
1684 /**
1685 * Render Disable Login Prompt in Checkout field.
1686 *
1687 * @return void
1688 */
1689 public function field_disable_checkout_login_prompt() {
1690 $this->render_pro_toggle( $this->option_disable_checkout_login_prompt );
1691 }
1692
1693 /**
1694 * Render Enable Full Page Scroll field.
1695 *
1696 * @return void
1697 */
1698 public function field_enable_fullpage_scroll() {
1699 $this->render_pro_toggle( $this->option_enable_fullpage_scroll );
1700 }
1701
1702 /**
1703 * Render Enable Language Banner field.
1704 *
1705 * @return void
1706 */
1707 public function field_enable_language_banner() {
1708 $this->render_pro_toggle( $this->option_enable_language_banner );
1709 }
1710
1711 /**
1712 * Render toggle field for checkout inline fields (PRO).
1713 *
1714 * @return void
1715 */
1716 public function field_checkout_inline() {
1717 $this->render_pro_toggle( $this->option_checkout_inline );
1718 }
1719
1720 /**
1721 * Custom Post Types section callback.
1722 *
1723 * @return void
1724 */
1725 public function section_custom_post_types_callback() {
1726 if ( ! frbl_is_pro_active() ) {
1727 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
1728 echo '<div class="tw-flex">';
1729 echo '<div class="tw-flex-shrink-0">';
1730 echo '<svg class="tw-h-5 tw-w-5 tw-text-blue-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/></svg>';
1731 echo '</div>';
1732 echo '<div class="tw-ml-3">';
1733 echo '<p class="tw-text-sm tw-text-blue-700">';
1734 printf(
1735 /* translators: %s: FrontBlocks PRO link */
1736 esc_html__( 'This feature requires %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
1737 '<a href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings" target="_blank" class="tw-font-medium tw-underline">FrontBlocks PRO</a>'
1738 );
1739 echo '</p>';
1740 echo '</div>';
1741 echo '</div>';
1742 echo '</div>';
1743 } elseif ( ! $this->is_license_valid ) {
1744 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
1745 echo '<div class="tw-flex">';
1746 echo '<div class="tw-flex-shrink-0">';
1747 echo '<svg class="tw-h-5 tw-w-5 tw-text-yellow-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>';
1748 echo '</div>';
1749 echo '<div class="tw-ml-3">';
1750 echo '<p class="tw-text-sm tw-text-yellow-700">';
1751 printf(
1752 /* translators: %s: License section link */
1753 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
1754 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1755 );
1756 echo '</p>';
1757 echo '</div>';
1758 echo '</div>';
1759 echo '</div>';
1760 } else {
1761 ?>
1762 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1763 <?php echo esc_html__( 'Create and manage custom post types with advanced configuration options.', 'frontblocks' ); ?>
1764 </p>
1765 <?php
1766 }
1767 }
1768
1769 /**
1770 * Render toggle field for enable custom post types.
1771 *
1772 * @return void
1773 */
1774 public function field_enable_custom_post_types() {
1775 $options = get_option( 'frontblocks_settings', array() );
1776 $enabled = (bool) ( $options[ $this->option_enable_custom_post_types ] ?? false );
1777 $is_enabled = $this->is_license_valid;
1778 $disabled = ! $is_enabled ? 'disabled' : '';
1779 ?>
1780 <div class="frbl-custom-post-types-wrapper">
1781 <div class="tw-flex tw-items-center tw-justify-between tw-mb-4">
1782 <label for="<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>" class="tw-text-base tw-font-medium tw-text-gray-900">
1783 <?php echo esc_html__( 'Enable Custom Post Types Builder', 'frontblocks' ); ?>
1784 </label>
1785 <label class="frbl-toggle">
1786 <input type="checkbox"
1787 id="<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>"
1788 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>]"
1789 value="1"
1790 <?php checked( true, $enabled ); ?>
1791 <?php echo esc_attr( $disabled ); ?>
1792 />
1793 <span></span>
1794 </label>
1795 </div>
1796
1797 <?php if ( $is_enabled ) : ?>
1798 <div id="frbl-cpt-builder" class="frbl-cpt-builder" style="<?php echo $enabled ? '' : 'display: none;'; ?>">
1799 <div class="tw-mt-4 tw-p-4 tw-bg-gray-50 tw-rounded-lg tw-border tw-border-gray-200">
1800 <label for="frbl-cpt-name" class="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-2">
1801 <?php echo esc_html__( 'Post Type Name', 'frontblocks' ); ?>
1802 </label>
1803 <div class="tw-flex tw-gap-2">
1804 <input
1805 type="text"
1806 id="frbl-cpt-name"
1807 class="tw-flex-1 tw-px-3 tw-py-2 tw-border tw-border-gray-300 tw-rounded-lg focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-primary-500 focus:tw-border-transparent"
1808 placeholder="<?php echo esc_attr__( 'e.g., Portfolio, Team, Services', 'frontblocks' ); ?>"
1809 />
1810 <button
1811 type="button"
1812 id="frbl-create-cpt-btn"
1813 class="tw-px-4 tw-py-2 tw-bg-primary-500 tw-text-white tw-rounded-lg hover:tw-bg-primary-600 focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-primary-500 tw-transition-colors"
1814 >
1815 <?php echo esc_html__( 'Create', 'frontblocks' ); ?>
1816 </button>
1817 </div>
1818 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1819 <?php echo esc_html__( 'Enter a singular name for your custom post type (e.g., "Portfolio" will create "portfolio" post type).', 'frontblocks' ); ?>
1820 </p>
1821 </div>
1822
1823 <?php do_action( 'frontblocks_render_existing_cpts' ); ?>
1824 </div>
1825 <?php endif; ?>
1826 </div>
1827 <?php
1828 }
1829
1830 /**
1831 * Render PRO features section after the main form.
1832 * Shows when frbl_pro_blocks filter returns blocks and license is not active.
1833 *
1834 * @return void
1835 */
1836 /**
1837 * Default PRO blocks list — shown as promotional cards when PRO is not installed.
1838 * The frbl_pro_blocks filter lets the PRO plugin override this list.
1839 *
1840 * @return array<int, array<string, string>>
1841 */
1842 private function get_default_pro_blocks(): array {
1843 return array(
1844 array(
1845 'icon' => 'meta-fields',
1846 'title' => __( 'Dynamic Meta Fields', 'frontblocks' ),
1847 'desc' => __( 'Bind any paragraph or heading to a custom post meta field, editable from the block editor.', 'frontblocks' ),
1848 ),
1849 array(
1850 'icon' => 'user-text',
1851 'title' => __( 'User Data Block', 'frontblocks' ),
1852 'desc' => __( 'Display logged-in user data with placeholders like {nombre}, {email}, {username}.', 'frontblocks' ),
1853 ),
1854 array(
1855 'icon' => 'fullpage-scroll',
1856 'title' => __( 'Full Page Scroll', 'frontblocks' ),
1857 'desc' => __( 'Full-page scroll navigation between sections with smooth transitions.', 'frontblocks' ),
1858 ),
1859 array(
1860 'icon' => 'language-banner',
1861 'title' => __( 'Language Banner', 'frontblocks' ),
1862 'desc' => __( 'Detect visitor language and show a recommendation banner (WPML/Polylang).', 'frontblocks' ),
1863 ),
1864 array(
1865 'icon' => 'gutenberg',
1866 'title' => __( 'Gutenberg in Products', 'frontblocks' ),
1867 'desc' => __( 'Use the full block editor to build WooCommerce product descriptions.', 'frontblocks' ),
1868 ),
1869 array(
1870 'icon' => 'after-add-to-cart',
1871 'title' => __( 'After Add to Cart Block', 'frontblocks' ),
1872 'desc' => __( 'Insert custom block content right after the Add to Cart button.', 'frontblocks' ),
1873 ),
1874 array(
1875 'icon' => 'simple-prices',
1876 'title' => __( 'Simple Prices Variable Products', 'frontblocks' ),
1877 'desc' => __( 'Simplified price display for variable WooCommerce products.', 'frontblocks' ),
1878 ),
1879 array(
1880 'icon' => 'horizontal-form',
1881 'title' => __( 'Horizontal Product Form', 'frontblocks' ),
1882 'desc' => __( 'Switch the WooCommerce product form to a horizontal layout.', 'frontblocks' ),
1883 ),
1884 array(
1885 'icon' => 'deactivate-tabs',
1886 'title' => __( 'Deactivate Product Tabs', 'frontblocks' ),
1887 'desc' => __( 'Remove the default tabs from WooCommerce product pages.', 'frontblocks' ),
1888 ),
1889 array(
1890 'icon' => 'disable-zoom',
1891 'title' => __( 'Disable Product Image Zoom', 'frontblocks' ),
1892 'desc' => __( 'Remove zoom effect on WooCommerce product images.', 'frontblocks' ),
1893 ),
1894 array(
1895 'icon' => 'share-buttons',
1896 'title' => __( 'Share Buttons', 'frontblocks' ),
1897 'desc' => __( 'Add social share buttons to WooCommerce product pages.', 'frontblocks' ),
1898 ),
1899 array(
1900 'icon' => 'deactivate-description',
1901 'title' => __( 'Manage Short Description', 'frontblocks' ),
1902 'desc' => __( 'Deactivate or move the WooCommerce product short description.', 'frontblocks' ),
1903 ),
1904 array(
1905 'icon' => 'default',
1906 'title' => __( 'Custom Post Types Builder', 'frontblocks' ),
1907 'desc' => __( 'Create and manage custom post types directly from the admin panel.', 'frontblocks' ),
1908 ),
1909 array(
1910 'icon' => 'horizontal-form',
1911 'title' => __( 'Checkout Inline Fields', 'frontblocks' ),
1912 'desc' => __( 'Display address, email and phone fields side by side in the WooCommerce checkout.', 'frontblocks' ),
1913 ),
1914 array(
1915 'icon' => 'popups',
1916 'title' => __( 'Popups', 'frontblocks' ),
1917 'desc' => __( 'Create popups with the block editor and configure when and where they appear.', 'frontblocks' ),
1918 ),
1919 );
1920 }
1921
1922 /**
1923 * Render PRO features section after the main form.
1924 *
1925 * @return void
1926 */
1927 private function render_pro_section(): void {
1928 $pro_blocks = apply_filters( 'frbl_pro_blocks', $this->get_default_pro_blocks() );
1929 $license_valid = function_exists( 'frblp_is_license_valid' ) && frblp_is_license_valid();
1930
1931 if ( empty( $pro_blocks ) || $license_valid ) {
1932 return;
1933 }
1934
1935 ?>
1936 <div class="frbl-section-wrapper tw-mt-6">
1937 <div class="frbl-section-header">
1938 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
1939 <?php echo esc_html__( 'FrontBlocks PRO Features', 'frontblocks' ); ?>
1940 </h2>
1941 <div class="tw-text-sm tw-text-gray-600">
1942 <p class="tw-mt-0 tw-mb-4">
1943 <?php
1944 $anchor = frbl_is_pro_active()
1945 ? '<a href="#frontblocks_section_license" class="tw-font-medium tw-text-red-600 hover:tw-text-red-700 tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1946 : '<a href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings-pro-showcase" target="_blank" rel="noopener noreferrer" class="tw-font-medium tw-text-red-600 hover:tw-text-red-700 tw-underline">FrontBlocks PRO</a>';
1947 printf(
1948 frbl_is_pro_active()
1949 /* translators: %s: license section link */
1950 ? esc_html__( 'Activate your license in the %s section below to unlock all features.', 'frontblocks' )
1951 /* translators: %s: FrontBlocks PRO link */
1952 : esc_html__( 'Unlock these features with %s.', 'frontblocks' ),
1953 $anchor // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1954 );
1955 ?>
1956 </p>
1957 </div>
1958 </div>
1959
1960 <div class="frbl-features-grid">
1961 <?php foreach ( $pro_blocks as $block ) : ?>
1962 <?php UI::show_pro_info_card( $block['icon'], $block['title'], $block['desc'] ); ?>
1963 <?php endforeach; ?>
1964 </div>
1965
1966 <?php if ( ! frbl_is_pro_active() ) : ?>
1967 <div class="tw-mt-6 tw-text-center">
1968 <a
1969 href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings-pro-cta"
1970 target="_blank"
1971 rel="noopener noreferrer"
1972 class="tw-inline-flex tw-items-center tw-px-6 tw-py-3 tw-border tw-border-transparent tw-text-base tw-font-medium tw-rounded-lg tw-shadow-sm tw-text-white tw-transition-colors tw-duration-200"
1973 style="background-color: #ef4444;"
1974 onmouseover="this.style.backgroundColor='#dc2626'"
1975 onmouseout="this.style.backgroundColor='#ef4444'"
1976 >
1977 <?php echo esc_html__( 'Get FrontBlocks PRO', 'frontblocks' ); ?>
1978 </a>
1979 </div>
1980 <?php endif; ?>
1981 </div>
1982 <?php
1983 }
1984
1985 /**
1986 * Render license section (separate from main form).
1987 *
1988 * @return void
1989 */
1990 private function render_license_section() {
1991 global $frblp_license;
1992
1993 ?>
1994 <div class="tw-mt-6" id="frontblocks_section_license">
1995 <?php
1996 // Check if license instance exists.
1997 if ( ! $frblp_license ) {
1998 ?>
1999 <div class="tw-p-4 tw-rounded-lg tw-bg-red-50 tw-border tw-border-red-200">
2000 <p class="tw-text-sm tw-text-red-700">
2001 <?php echo esc_html__( 'License manager not initialized.', 'frontblocks' ); ?>
2002 </p>
2003 </div>
2004 <?php
2005 return;
2006 }
2007
2008 // Check if License class exists (requires FrontBlocks PRO).
2009 if ( ! class_exists( '\Closemarketing\WPLicenseManager\License' ) ) {
2010 ?>
2011 <div class="tw-p-4 tw-rounded-lg tw-bg-yellow-50 tw-border tw-border-yellow-200">
2012 <p class="tw-text-sm tw-text-yellow-700">
2013 <?php echo esc_html__( 'License management requires FrontBlocks PRO to be installed and active.', 'frontblocks' ); ?>
2014 </p>
2015 </div>
2016 <?php
2017 return;
2018 }
2019
2020 // Render license settings inline.
2021 $this->render_inline_license_settings( $frblp_license );
2022 ?>
2023 </div>
2024 <?php
2025 }
2026
2027 /**
2028 * Render inline license settings.
2029 *
2030 * @param \Closemarketing\WPLicenseManager\License $license License instance.
2031 * @return void
2032 */
2033 private function render_inline_license_settings( $license ) {
2034 // Get license data.
2035 $license_key = $license->get_option_value( 'apikey' );
2036 $is_active = $license->is_license_active();
2037 $license_status = get_option( 'frontblocks-pro_license_activated', 'Deactivated' );
2038
2039 ?>
2040 <div class="formscrm-license-wrapper">
2041 <!-- Main Card -->
2042 <div class="formscrm-card">
2043 <!-- Header -->
2044 <div class="formscrm-card-header">
2045 <h2><?php echo esc_html__( 'FrontBlocks PRO License', 'frontblocks' ); ?></h2>
2046 <p><?php echo esc_html__( 'Manage your license to receive automatic updates and support.', 'frontblocks' ); ?></p>
2047 </div>
2048
2049 <!-- License Status -->
2050 <div class="formscrm-form-group">
2051 <?php if ( $is_active ) : ?>
2052 <div class="formscrm-status-box formscrm-status-active">
2053 <span class="formscrm-status-icon">
2054 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
2055 <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
2056 </svg>
2057 </span>
2058 <span class="formscrm-status-text"><?php echo esc_html__( 'License Active', 'frontblocks' ); ?></span>
2059 </div>
2060 <?php else : ?>
2061 <div class="formscrm-status-box formscrm-status-inactive">
2062 <span class="formscrm-status-icon">
2063 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
2064 <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
2065 </svg>
2066 </span>
2067 <span class="formscrm-status-text"><?php echo esc_html__( 'License Inactive', 'frontblocks' ); ?></span>
2068 </div>
2069 <?php endif; ?>
2070 </div>
2071
2072 <!-- License Form -->
2073 <form method="post" action="options.php" class="formscrm-license-form">
2074 <?php settings_fields( 'frontblocks-pro_license' ); ?>
2075 <?php wp_nonce_field( 'Update_License_Options', 'license_nonce' ); ?>
2076
2077 <!-- License Key Field -->
2078 <div class="formscrm-form-group">
2079 <label class="formscrm-label" for="frontblocks-pro_license_apikey">
2080 <?php echo esc_html__( 'License Key', 'frontblocks' ); ?>
2081 </label>
2082 <div class="formscrm-input-group">
2083 <input
2084 type="text"
2085 id="frontblocks-pro_license_apikey"
2086 name="frontblocks-pro_license_apikey"
2087 value="<?php echo esc_attr( $license_key ); ?>"
2088 class="formscrm-input"
2089 placeholder="<?php echo esc_attr__( 'CTECH-XXXXX-XXXXX-XXXXX-XXXXX', 'frontblocks' ); ?>"
2090 <?php echo $is_active ? 'readonly' : ''; ?>
2091 />
2092 <?php if ( $is_active ) : ?>
2093 <label class="formscrm-deactivate-label">
2094 <input type="checkbox" name="frontblocks-pro_license_deactivate_checkbox" value="on" />
2095 <span><?php echo esc_html__( 'Deactivate', 'frontblocks' ); ?></span>
2096 </label>
2097 <?php endif; ?>
2098 </div>
2099 <p class="formscrm-help-text">
2100 <?php
2101 printf(
2102 /* translators: %s: Purchase URL */
2103 esc_html__( 'Enter your license key. You can find it in %s.', 'frontblocks' ),
2104 '<a href="https://close.technology/my-account/" target="_blank">' . esc_html__( 'your account', 'frontblocks' ) . '</a>'
2105 );
2106 ?>
2107 </p>
2108 </div>
2109
2110 <!-- License Status -->
2111 <div class="formscrm-form-group">
2112 <label class="formscrm-label"><?php echo esc_html__( 'License Status', 'frontblocks' ); ?></label>
2113 <div class="formscrm-status-box <?php echo $is_active ? 'formscrm-status-active' : 'formscrm-status-inactive'; ?>">
2114 <span class="formscrm-status-icon">
2115 <?php if ( $is_active ) : ?>
2116 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
2117 <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
2118 </svg>
2119 <?php else : ?>
2120 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
2121 <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
2122 </svg>
2123 <?php endif; ?>
2124 </span>
2125 <span class="formscrm-status-text">
2126 <?php echo $is_active ? esc_html__( 'Active', 'frontblocks' ) : esc_html__( 'Not Activated', 'frontblocks' ); ?>
2127 </span>
2128 </div>
2129 </div>
2130
2131 <!-- Submit Button -->
2132 <div class="formscrm-form-actions">
2133 <button type="submit" name="submit_license" class="formscrm-button formscrm-button-primary">
2134 <?php echo $is_active ? esc_html__( 'Update License', 'frontblocks' ) : esc_html__( 'Activate License', 'frontblocks' ); ?>
2135 </button>
2136 </div>
2137 </form>
2138 </div>
2139
2140 <!-- Sidebar Info -->
2141 <div class="formscrm-info-card">
2142 <h3><?php echo esc_html__( 'License Benefits', 'frontblocks' ); ?></h3>
2143 <p><?php echo esc_html__( 'An active license provides the following benefits:', 'frontblocks' ); ?></p>
2144
2145 <ul class="formscrm-benefits-list">
2146 <li><?php echo esc_html__( 'Automatic plugin updates', 'frontblocks' ); ?></li>
2147 <li><?php echo esc_html__( 'Access to new features', 'frontblocks' ); ?></li>
2148 <li><?php echo esc_html__( 'Priority support', 'frontblocks' ); ?></li>
2149 <li><?php echo esc_html__( 'Security patches', 'frontblocks' ); ?></li>
2150 </ul>
2151
2152 <hr style="margin: 20px 0; border: none; border-top: 1px solid #e2e8f0;">
2153
2154 <div style="font-size: 0.875rem; color: #64748b;">
2155 <p style="margin-bottom: 8px;">
2156 <strong><?php echo esc_html__( 'Need Help?', 'frontblocks' ); ?></strong>
2157 </p>
2158 <p style="margin-bottom: 8px;">
2159 <a href="https://close.technology/wordpress-plugins/frontblocks-pro/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
2160 <?php echo esc_html__( 'Purchase License', 'frontblocks' ); ?>
2161 </a>
2162 </p>
2163 <p style="margin-bottom: 8px;">
2164 <a href="https://close.technology/my-account/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
2165 <?php echo esc_html__( 'My Account', 'frontblocks' ); ?>
2166 </a>
2167 </p>
2168 <p>
2169 <a href="https://close.technology/support/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
2170 <?php echo esc_html__( 'Support', 'frontblocks' ); ?>
2171 </a>
2172 </p>
2173 </div>
2174 </div>
2175 </div>
2176 <?php
2177 }
2178
2179 /**
2180 * Helper method to render PRO toggle fields.
2181 *
2182 * @param string $option_key Option key.
2183 * @return void
2184 */
2185 private function render_pro_toggle( $option_key ) {
2186 $options = get_option( 'frontblocks_settings', array() );
2187 $enabled = (bool) ( $options[ $option_key ] ?? false );
2188 $is_enabled = $this->is_license_valid;
2189 $disabled = ! $is_enabled ? 'disabled' : '';
2190 ?>
2191 <label class="frbl-toggle">
2192 <input type="checkbox"
2193 id="<?php echo esc_attr( $option_key ); ?>"
2194 name="frontblocks_settings[<?php echo esc_attr( $option_key ); ?>]"
2195 value="1"
2196 <?php checked( true, $enabled ); ?>
2197 <?php echo esc_attr( $disabled ); ?>
2198 />
2199 <span></span>
2200 </label>
2201 <?php
2202 }
2203
2204 /**
2205 * Sanitize settings array.
2206 *
2207 * @param array $value Raw value.
2208 * @return array
2209 */
2210 public function sanitize_settings( $value ) {
2211 // Nonce verification.
2212 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
2213 if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'frontblocks_settings-options' ) ) {
2214 add_settings_error( 'frontblocks_settings', 'frontblocks_settings_nonce', esc_html__( 'Security check failed. Please try again.', 'frontblocks' ), 'error' );
2215
2216 return get_option( 'frontblocks_settings', array() );
2217 }
2218
2219 if ( ! is_array( $value ) ) {
2220 return array();
2221 }
2222
2223 // Get current options to preserve unchecked checkboxes.
2224 $current_options = get_option( 'frontblocks_settings', array() );
2225
2226 // Initialize sanitized array with current values.
2227 $sanitized = $current_options;
2228
2229 // List of all boolean options (checkboxes).
2230 $boolean_options = array(
2231 $this->option_enable_testimonials,
2232 $this->option_enable_reading_progress,
2233 $this->option_enable_back_button,
2234 $this->option_enable_events,
2235 $this->option_enable_fluid_typography,
2236 $this->option_enable_gutenberg,
2237 $this->option_enable_simple_prices_variable_products,
2238 $this->option_enable_after_add_to_cart,
2239 $this->option_deactivate_short_description,
2240 $this->option_move_content_to_short_description,
2241 $this->option_disable_zoom_images,
2242 $this->option_add_share_buttons,
2243 $this->option_deactivate_product_tabs,
2244 $this->option_horizontal_product_form,
2245 $this->option_enable_variant_display_mode,
2246 $this->option_enable_custom_post_types,
2247 $this->option_enable_fullpage_scroll,
2248 $this->option_enable_language_banner,
2249 $this->option_enable_popups,
2250 $this->option_checkout_inline,
2251 $this->option_disable_cart_coupon,
2252 $this->option_disable_cart_cross_sells,
2253 $this->option_disable_checkout_coupon,
2254 $this->option_disable_checkout_order_notes,
2255 $this->option_disable_checkout_login_prompt,
2256 );
2257
2258 // Initialize all boolean options to false (unchecked checkboxes are not submitted).
2259 foreach ( $boolean_options as $option ) {
2260 $sanitized[ $option ] = false;
2261 }
2262
2263 // Process submitted values.
2264 foreach ( $value as $key => $val ) {
2265 if ( in_array( $key, $boolean_options, true ) ) {
2266 $sanitized[ $key ] = (bool) $val;
2267 } elseif ( $this->option_events_type === $key ) {
2268 // Sanitize events type: only allow 'cpt' or 'posts'.
2269 $sanitized[ $key ] = in_array( $val, array( 'cpt', 'posts' ), true ) ? $val : 'cpt';
2270 }
2271 }
2272
2273 // Ensure mutual exclusion: if both description options are enabled, keep only the last one changed.
2274 if ( ! empty( $sanitized[ $this->option_deactivate_short_description ] ) && ! empty( $sanitized[ $this->option_move_content_to_short_description ] ) ) {
2275 // Get current saved values to determine which one was just changed.
2276 $current_deactivate = ! empty( $current_options[ $this->option_deactivate_short_description ] );
2277 $current_move = ! empty( $current_options[ $this->option_move_content_to_short_description ] );
2278
2279 // If deactivate was already on, turn it off (move is the new one).
2280 if ( $current_deactivate ) {
2281 $sanitized[ $this->option_deactivate_short_description ] = false;
2282 } else {
2283 // Otherwise turn off move (deactivate is the new one).
2284 $sanitized[ $this->option_move_content_to_short_description ] = false;
2285 }
2286 }
2287
2288 do_action( 'frontblocks_sanitize_settings', $sanitized );
2289
2290 return $sanitized;
2291 }
2292 }
2293