PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / 1.3.3
FrontBlocks for Gutenberg/GeneratePress v1.3.3
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 4 months ago UI.php 4 months ago
Settings.php
1802 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 fluid typography feature.
59 *
60 * @var string
61 */
62 private $option_enable_fluid_typography = 'enable_fluid_typography';
63
64 /**
65 * Option key for Gutenberg in products (PRO).
66 *
67 * @var string
68 */
69 private $option_enable_gutenberg = 'enable_gutenberg';
70
71 /**
72 * Option key for Simple Prices Variable Products (PRO).
73 *
74 * @var string
75 */
76 private $option_enable_simple_prices_variable_products = 'enable_simple_prices_variable_products';
77
78 /**
79 * Option key for After Add to Cart Block (PRO).
80 *
81 * @var string
82 */
83 private $option_enable_after_add_to_cart = 'enable_after_add_to_cart';
84
85 /**
86 * Option key for deactivate short description (PRO).
87 *
88 * @var string
89 */
90 private $option_deactivate_short_description = 'deactivate_short_description';
91
92 /**
93 * Option key for move content to short description (PRO).
94 *
95 * @var string
96 */
97 private $option_move_content_to_short_description = 'move_content_to_short_description';
98
99 /**
100 * Option key for disable zoom in WooCommerce images (PRO).
101 *
102 * @var string
103 */
104 private $option_disable_zoom_images = 'disable_zoom_images';
105
106 /**
107 * Option key for add share buttons in product page (PRO).
108 *
109 * @var string
110 */
111 private $option_add_share_buttons = 'add_share_buttons';
112
113 /**
114 * Option key for deactivate product tabs (PRO).
115 *
116 * @var string
117 */
118 private $option_deactivate_product_tabs = 'deactivate_product_tabs';
119
120 /**
121 * Option key for horizontal product form layout (PRO).
122 *
123 * @var string
124 */
125 private $option_horizontal_product_form = 'horizontal_product_form';
126
127 /**
128 * Option key for custom post types builder (PRO).
129 *
130 * @var string
131 */
132 private $option_enable_custom_post_types = 'enable_custom_post_types';
133
134 /**
135 * Option key for full page scroll feature (PRO).
136 *
137 * @var string
138 */
139 private $option_enable_fullpage_scroll = 'enable_fullpage_scroll';
140
141 /**
142 * Option key for language banner feature (PRO).
143 *
144 * @var string
145 */
146 private $option_enable_language_banner = 'enable_language_banner';
147
148 /**
149 * Page slug.
150 *
151 * @var string
152 */
153 private $page_slug = 'frontblocks-settings';
154
155 /**
156 * Is license valid.
157 *
158 * @var bool
159 */
160 private $is_license_valid = false;
161
162 /**
163 * Constructor.
164 */
165 public function __construct() {
166 // Check license via FrontBlocks PRO helper function.
167 $this->is_license_valid = function_exists( 'frblp_is_license_valid' ) && frblp_is_license_valid();
168
169 add_action( 'admin_menu', array( $this, 'register_menu' ) );
170 add_action( 'admin_init', array( $this, 'register_settings' ) );
171 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
172 add_action( 'admin_head', array( $this, 'add_menu_icon_styles' ) );
173 }
174
175 /**
176 * Add menu icon styles.
177 *
178 * @return void
179 */
180 public function add_menu_icon_styles() {
181 ?>
182 <style>
183 #toplevel_page_frontblocks-settings .wp-menu-image img,
184 #toplevel_page_frontblocks-settings .wp-menu-image svg {
185 width: 20px !important;
186 height: 20px !important;
187 max-width: 20px !important;
188 max-height: 20px !important;
189 }
190 </style>
191 <?php
192 }
193
194 /**
195 * Enqueue admin styles for settings page.
196 *
197 * @param string $hook Current admin page hook.
198 * @return void
199 */
200 public function enqueue_admin_styles( $hook ) {
201 if ( 'toplevel_page_' . $this->page_slug !== $hook ) {
202 return;
203 }
204
205 wp_enqueue_style(
206 'frontblocks-admin-settings',
207 FRBL_PLUGIN_URL . 'assets/admin/settings.css',
208 array(),
209 FRBL_VERSION
210 );
211
212 wp_add_inline_script(
213 'jquery',
214 "
215 document.addEventListener('DOMContentLoaded', function() {
216 const deactivateCheckbox = document.getElementById('deactivate_short_description');
217 const moveContentCheckbox = document.getElementById('move_content_to_short_description');
218
219 if (deactivateCheckbox && moveContentCheckbox) {
220 function updateMutualExclusion() {
221 const deactivateWrapper = deactivateCheckbox.closest('.tw-flex');
222 const moveContentWrapper = moveContentCheckbox.closest('.tw-flex');
223
224 // Check if license is valid (not just PRO active).
225 const isLicenseValid = " . ( $this->is_license_valid ? 'true' : 'false' ) . ";
226
227 if (deactivateCheckbox.checked) {
228 moveContentCheckbox.disabled = true;
229 if (moveContentWrapper) {
230 moveContentWrapper.style.opacity = '0.5';
231 moveContentWrapper.style.filter = 'grayscale(100%)';
232 const toggle = moveContentWrapper.querySelector('.frbl-toggle');
233 if (toggle) {
234 toggle.style.borderColor = '#ef4444';
235 toggle.style.opacity = '0.7';
236 }
237 }
238 } else {
239 moveContentCheckbox.disabled = !isLicenseValid;
240 if (moveContentWrapper) {
241 moveContentWrapper.style.opacity = isLicenseValid ? '1' : '0.5';
242 moveContentWrapper.style.filter = '';
243 const toggle = moveContentWrapper.querySelector('.frbl-toggle');
244 if (toggle) {
245 toggle.style.borderColor = '';
246 toggle.style.opacity = '';
247 }
248 }
249 }
250
251 if (moveContentCheckbox.checked) {
252 deactivateCheckbox.disabled = true;
253 if (deactivateWrapper) {
254 deactivateWrapper.style.opacity = '0.5';
255 deactivateWrapper.style.filter = 'grayscale(100%)';
256 const toggle = deactivateWrapper.querySelector('.frbl-toggle');
257 if (toggle) {
258 toggle.style.borderColor = '#ef4444';
259 toggle.style.opacity = '0.7';
260 }
261 }
262 } else {
263 deactivateCheckbox.disabled = !isLicenseValid;
264 if (deactivateWrapper) {
265 deactivateWrapper.style.opacity = isLicenseValid ? '1' : '0.5';
266 deactivateWrapper.style.filter = '';
267 const toggle = deactivateWrapper.querySelector('.frbl-toggle');
268 if (toggle) {
269 toggle.style.borderColor = '';
270 toggle.style.opacity = '';
271 }
272 }
273 }
274 }
275
276 deactivateCheckbox.addEventListener('change', updateMutualExclusion);
277 moveContentCheckbox.addEventListener('change', updateMutualExclusion);
278
279 updateMutualExclusion();
280 }
281
282 // Show/hide events type select based on toggle state.
283 const eventsCheckbox = document.getElementById('enable_events');
284 const eventsTypeWrapper = document.getElementById('events-type-wrapper');
285
286 if (eventsCheckbox && eventsTypeWrapper) {
287 // Find the parent feature card and feature content.
288 const featureCard = eventsCheckbox.closest('.frbl-feature-card');
289 const featureContent = featureCard ? featureCard.querySelector('.frbl-feature-content') : null;
290
291 // Move the wrapper outside of frbl-feature-content but inside frbl-feature-card.
292 // This ensures it appears below the entire horizontal row (icon, text, toggle).
293 if (featureCard && featureContent) {
294 // Check if wrapper is still inside feature-content and move it.
295 if (featureContent.contains(eventsTypeWrapper)) {
296 // Move it to be a direct child of feature-card, after feature-content.
297 featureCard.appendChild(eventsTypeWrapper);
298 }
299 }
300
301 function updateEventsTypeVisibility() {
302 if (eventsCheckbox.checked) {
303 eventsTypeWrapper.style.display = 'block';
304 eventsTypeWrapper.style.width = '100%';
305 eventsTypeWrapper.style.minWidth = '100%';
306 eventsTypeWrapper.style.marginTop = '1rem';
307 eventsTypeWrapper.style.paddingTop = '1rem';
308 eventsTypeWrapper.style.paddingLeft = '1rem';
309 eventsTypeWrapper.style.paddingRight = '1rem';
310 eventsTypeWrapper.style.paddingBottom = '1rem';
311 eventsTypeWrapper.style.borderTop = '1px solid #e5e7eb';
312 eventsTypeWrapper.style.backgroundColor = '#f9fafb';
313 // Set feature card to column layout.
314 if (featureCard) {
315 featureCard.style.display = 'flex';
316 featureCard.style.flexDirection = 'column';
317 }
318 // Keep the feature content horizontal - never change it.
319 if (featureContent) {
320 featureContent.style.flexDirection = 'row';
321 featureContent.style.alignItems = 'center';
322 featureContent.style.justifyContent = 'space-between';
323 }
324 } else {
325 eventsTypeWrapper.style.display = 'none';
326 // Reset feature card layout.
327 if (featureCard) {
328 featureCard.style.display = '';
329 featureCard.style.flexDirection = '';
330 }
331 // Keep the feature content horizontal.
332 if (featureContent) {
333 featureContent.style.flexDirection = 'row';
334 featureContent.style.alignItems = 'center';
335 featureContent.style.justifyContent = 'space-between';
336 }
337 }
338 }
339
340 // Run immediately to move the element on page load.
341 if (featureCard && featureContent && featureContent.contains(eventsTypeWrapper)) {
342 featureCard.appendChild(eventsTypeWrapper);
343 }
344
345 eventsCheckbox.addEventListener('change', updateEventsTypeVisibility);
346 updateEventsTypeVisibility();
347 }
348
349 });
350 "
351 );
352
353 // Enqueue script for custom post types if PRO is active and license is valid.
354 if ( frbl_is_pro_active() && $this->is_license_valid ) {
355 wp_enqueue_script(
356 'frontblocks-cpt-admin',
357 FRBL_PLUGIN_URL . 'assets/admin/custom-post-types.js',
358 array( 'jquery' ),
359 FRBL_VERSION,
360 true
361 );
362
363 wp_localize_script(
364 'frontblocks-cpt-admin',
365 'frontblocksCpt',
366 array(
367 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
368 'nonce' => wp_create_nonce( 'frontblocks_create_cpt' ),
369 'i18n' => array(
370 'creating' => __( 'Creating...', 'frontblocks' ),
371 'error' => __( 'Error creating post type. Please try again.', 'frontblocks' ),
372 'success' => __( 'Post type created successfully!', 'frontblocks' ),
373 ),
374 )
375 );
376 }
377 }
378
379 /**
380 * Register options page as dedicated menu.
381 *
382 * @return void
383 */
384 public function register_menu() {
385 // Use SVG icon if available, otherwise fallback to dashicon.
386 $icon_url = FRBL_PLUGIN_URL . 'assets/admin/icons/icon-menu.svg';
387 $icon_path = FRBL_PLUGIN_PATH . 'assets/admin/icons/icon-menu.svg';
388 $menu_icon = file_exists( $icon_path ) ? $icon_url : 'dashicons-block-default';
389
390 add_menu_page(
391 __( 'FrontBlocks Settings', 'frontblocks' ),
392 __( 'FrontBlocks', 'frontblocks' ),
393 'edit_theme_options',
394 $this->page_slug,
395 array( $this, 'render_page' ),
396 $menu_icon,
397 81
398 );
399 }
400
401 /**
402 * Register settings, sections and fields.
403 *
404 * @return void
405 */
406 public function register_settings() {
407 register_setting(
408 'frontblocks_settings',
409 'frontblocks_settings',
410 array(
411 'type' => 'array',
412 'sanitize_callback' => array( $this, 'sanitize_settings' ),
413 'default' => array(),
414 'show_in_rest' => false,
415 )
416 );
417
418 // Register license setting group for FrontBlocks PRO.
419 global $frblp_license;
420 if ( $frblp_license && class_exists( '\Closemarketing\WPLicenseManager\License' ) ) {
421 // Register each individual license field.
422 register_setting(
423 'frontblocks-pro_license',
424 'frontblocks-pro_license_apikey',
425 array(
426 'type' => 'string',
427 'sanitize_callback' => 'sanitize_text_field',
428 )
429 );
430
431 register_setting(
432 'frontblocks-pro_license',
433 'frontblocks-pro_license_deactivate_checkbox',
434 array(
435 'type' => 'string',
436 'sanitize_callback' => 'sanitize_text_field',
437 )
438 );
439
440 // Hook into admin_init to process license activation/deactivation.
441 add_action(
442 'admin_init',
443 function () use ( $frblp_license ) {
444 // Check if license form was submitted and verify nonce.
445 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' ) ) {
446 if ( isset( $_POST['submit_license'] ) ) {
447 // Build input array for validate_license.
448 $input = array(
449 'frontblocks-pro_license_apikey' => isset( $_POST['frontblocks-pro_license_apikey'] ) ? sanitize_text_field( wp_unslash( $_POST['frontblocks-pro_license_apikey'] ) ) : '',
450 'frontblocks-pro_license_deactivate_checkbox' => isset( $_POST['frontblocks-pro_license_deactivate_checkbox'] ) ? sanitize_text_field( wp_unslash( $_POST['frontblocks-pro_license_deactivate_checkbox'] ) ) : '',
451 );
452
453 // Call the license validation.
454 $frblp_license->validate_license( $input );
455 }
456 }
457 },
458 15
459 );
460 }
461
462 // Always Active Blocks section.
463 add_settings_section(
464 'frontblocks_section_active_blocks',
465 __( 'Active Blocks & Features', 'frontblocks' ),
466 array( $this, 'section_active_blocks_callback' ),
467 $this->page_slug
468 );
469
470 add_settings_section(
471 'frontblocks_section_features',
472 __( 'Optional Features', 'frontblocks' ),
473 array( $this, 'section_features_callback' ),
474 $this->page_slug
475 );
476
477 add_settings_field(
478 $this->option_enable_testimonials,
479 __( 'Enable testimonials', 'frontblocks' ),
480 array( $this, 'field_enable_testimonials' ),
481 $this->page_slug,
482 'frontblocks_section_features'
483 );
484
485 add_settings_field(
486 $this->option_enable_reading_progress,
487 __( 'Enable reading progress bar', 'frontblocks' ),
488 array( $this, 'field_enable_reading_progress' ),
489 $this->page_slug,
490 'frontblocks_section_features'
491 );
492
493 add_settings_field(
494 $this->option_enable_back_button,
495 __( 'Enable Back Button', 'frontblocks' ),
496 array( $this, 'field_enable_back_button' ),
497 $this->page_slug,
498 'frontblocks_section_features'
499 );
500
501 add_settings_field(
502 $this->option_enable_events,
503 __( 'Enable Events', 'frontblocks' ),
504 array( $this, 'field_enable_events' ),
505 $this->page_slug,
506 'frontblocks_section_features'
507 );
508
509 add_settings_field(
510 $this->option_enable_fluid_typography,
511 __( 'Enable Fluid Typography', 'frontblocks' ),
512 array( $this, 'field_enable_fluid_typography' ),
513 $this->page_slug,
514 'frontblocks_section_features'
515 );
516
517 // PRO Features section.
518 add_settings_section(
519 'frontblocks_section_woocommerce_features',
520 __( 'WooCommerce Features', 'frontblocks' ),
521 array( $this, 'section_woo_features_callback' ),
522 $this->page_slug
523 );
524
525 add_settings_field(
526 $this->option_enable_gutenberg,
527 __( 'Enable Gutenberg in Products', 'frontblocks' ),
528 array( $this, 'field_enable_gutenberg' ),
529 $this->page_slug,
530 'frontblocks_section_woocommerce_features'
531 );
532
533 add_settings_field(
534 $this->option_enable_simple_prices_variable_products,
535 __( 'Enable Simple Prices Variable Products', 'frontblocks' ),
536 array( $this, 'field_enable_simple_prices_variable_products' ),
537 $this->page_slug,
538 'frontblocks_section_woocommerce_features'
539 );
540
541 add_settings_field(
542 $this->option_enable_after_add_to_cart,
543 __( 'Enable After Add to Cart Block', 'frontblocks' ),
544 array( $this, 'field_enable_after_add_to_cart' ),
545 $this->page_slug,
546 'frontblocks_section_woocommerce_features'
547 );
548
549 add_settings_field(
550 $this->option_deactivate_short_description,
551 __( 'Deactivate Short Description', 'frontblocks' ),
552 array( $this, 'field_deactivate_short_description' ),
553 $this->page_slug,
554 'frontblocks_section_woocommerce_features'
555 );
556
557 add_settings_field(
558 $this->option_move_content_to_short_description,
559 __( 'Move Content to Short Description', 'frontblocks' ),
560 array( $this, 'field_move_content_to_short_description' ),
561 $this->page_slug,
562 'frontblocks_section_woocommerce_features'
563 );
564
565 add_settings_field(
566 $this->option_disable_zoom_images,
567 __( 'Disable Zoom in Product Images', 'frontblocks' ),
568 array( $this, 'field_disable_zoom_images' ),
569 $this->page_slug,
570 'frontblocks_section_woocommerce_features'
571 );
572
573 add_settings_field(
574 $this->option_add_share_buttons,
575 __( 'Add Share Buttons in Product Page', 'frontblocks' ),
576 array( $this, 'field_add_share_buttons' ),
577 $this->page_slug,
578 'frontblocks_section_woocommerce_features'
579 );
580
581 add_settings_field(
582 $this->option_deactivate_product_tabs,
583 __( 'Deactivate Product Tabs', 'frontblocks' ),
584 array( $this, 'field_deactivate_product_tabs' ),
585 $this->page_slug,
586 'frontblocks_section_woocommerce_features'
587 );
588
589 add_settings_field(
590 $this->option_horizontal_product_form,
591 __( 'Horizontal Product Form Layout', 'frontblocks' ),
592 array( $this, 'field_horizontal_product_form' ),
593 $this->page_slug,
594 'frontblocks_section_woocommerce_features'
595 );
596
597 add_settings_field(
598 $this->option_enable_fullpage_scroll,
599 __( 'Enable Full Page Scroll', 'frontblocks' ),
600 array( $this, 'field_enable_fullpage_scroll' ),
601 $this->page_slug,
602 'frontblocks_section_woocommerce_features'
603 );
604
605 add_settings_field(
606 $this->option_enable_language_banner,
607 __( 'Enable Language Banner', 'frontblocks' ),
608 array( $this, 'field_enable_language_banner' ),
609 $this->page_slug,
610 'frontblocks_section_woocommerce_features'
611 );
612
613 // Custom Post Types section (PRO).
614 if ( frbl_is_pro_active() ) {
615 add_settings_section(
616 'frontblocks_section_custom_post_types',
617 __( 'Custom Post Types', 'frontblocks' ),
618 array( $this, 'section_custom_post_types_callback' ),
619 $this->page_slug
620 );
621
622 add_settings_field(
623 $this->option_enable_custom_post_types,
624 __( 'Enable Custom Post Types Builder', 'frontblocks' ),
625 array( $this, 'field_enable_custom_post_types' ),
626 $this->page_slug,
627 'frontblocks_section_custom_post_types'
628 );
629 }
630
631 // Note: License section is rendered separately outside the main form.
632 // See render_license_section() method called from render_page().
633
634 do_action( 'frontblocks_register_settings' );
635 }
636
637 /**
638 * Render settings page.
639 *
640 * @return void
641 */
642 public function render_page() {
643 if ( ! current_user_can( 'edit_theme_options' ) ) {
644 return;
645 }
646 ?>
647 <div class="frbl-settings-wrapper tw-min-h-screen tw-bg-gray-50 tw-py-8">
648 <div class="tw-max-w-5xl tw-mx-auto tw-px-4 sm:tw-px-6 lg:tw-px-8">
649 <!-- Header Section -->
650 <div class="tw-mb-8 frbl-animate-slide-in">
651 <div class="tw-flex tw-items-center tw-justify-between">
652 <div>
653 <h1 class="tw-text-3xl tw-font-bold tw-text-gray-900 tw-mb-2">
654 <?php echo esc_html__( 'FrontBlocks Settings', 'frontblocks' ); ?>
655 </h1>
656 <p class="tw-text-gray-600">
657 <?php echo esc_html__( 'Add visual enhancements to your website with FrontBlocks.', 'frontblocks' ); ?>
658 </p>
659 </div>
660 <div class="tw-flex tw-items-center tw-space-x-2">
661 <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">
662 <?php echo esc_html__( 'Version', 'frontblocks' ) . ' ' . esc_html( FRBL_VERSION ); ?>
663 </span>
664 </div>
665 </div>
666 </div>
667
668 <?php
669 // Show success message after settings are saved.
670 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
671 if ( isset( $_GET['settings-updated'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) ) :
672 ?>
673 <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);">
674 <div class="tw-flex">
675 <div class="tw-flex-shrink-0">
676 <svg class="tw-h-5 tw-w-5" style="color: #4ade80;" viewBox="0 0 20 20" fill="currentColor">
677 <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"/>
678 </svg>
679 </div>
680 <div class="tw-ml-3">
681 <p class="tw-text-sm tw-font-medium" style="color: #15803d; margin: 0;">
682 <?php esc_html_e( 'Changes saved successfully', 'frontblocks' ); ?>
683 </p>
684 </div>
685 </div>
686 </div>
687 <?php
688 endif;
689
690 // Show error message if saving failed.
691 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
692 if ( isset( $_GET['settings-error'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-error'] ) ) ) :
693 ?>
694 <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);">
695 <div class="tw-flex">
696 <div class="tw-flex-shrink-0">
697 <svg class="tw-h-5 tw-w-5" style="color: #f87171;" viewBox="0 0 20 20" fill="currentColor">
698 <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"/>
699 </svg>
700 </div>
701 <div class="tw-ml-3">
702 <p class="tw-text-sm tw-font-medium" style="color: #991b1b; margin: 0;">
703 <?php esc_html_e( 'Failed to save changes. Please try again.', 'frontblocks' ); ?>
704 </p>
705 </div>
706 </div>
707 </div>
708 <?php
709 endif;
710
711 // Show license activated message.
712 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
713 if ( isset( $_GET['license_activated'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['license_activated'] ) ) ) :
714 ?>
715 <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);">
716 <div class="tw-flex">
717 <div class="tw-flex-shrink-0">
718 <svg class="tw-h-5 tw-w-5" style="color: #4ade80;" viewBox="0 0 20 20" fill="currentColor">
719 <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"/>
720 </svg>
721 </div>
722 <div class="tw-ml-3">
723 <p class="tw-text-sm tw-font-medium" style="color: #15803d; margin: 0;">
724 <?php esc_html_e( 'License activated successfully!', 'frontblocks' ); ?>
725 </p>
726 </div>
727 </div>
728 </div>
729 <?php
730 endif;
731
732 // Show license deactivated message.
733 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
734 if ( isset( $_GET['license_deactivated'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['license_deactivated'] ) ) ) :
735 ?>
736 <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);">
737 <div class="tw-flex">
738 <div class="tw-flex-shrink-0">
739 <svg class="tw-h-5 tw-w-5" style="color: #fbbf24;" viewBox="0 0 20 20" fill="currentColor">
740 <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"/>
741 </svg>
742 </div>
743 <div class="tw-ml-3">
744 <p class="tw-text-sm tw-font-medium" style="color: #92400e; margin: 0;">
745 <?php esc_html_e( 'License deactivated successfully.', 'frontblocks' ); ?>
746 </p>
747 </div>
748 </div>
749 </div>
750 <?php
751 endif;
752
753 // Show license error message.
754 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
755 if ( isset( $_GET['license_error'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['license_error'] ) ) ) :
756 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
757 $error_msg = isset( $_GET['error_msg'] ) ? sanitize_text_field( wp_unslash( $_GET['error_msg'] ) ) : '';
758 ?>
759 <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);">
760 <div class="tw-flex">
761 <div class="tw-flex-shrink-0">
762 <svg class="tw-h-5 tw-w-5" style="color: #f87171;" viewBox="0 0 20 20" fill="currentColor">
763 <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"/>
764 </svg>
765 </div>
766 <div class="tw-ml-3">
767 <p class="tw-text-sm tw-font-medium" style="color: #991b1b; margin: 0;">
768 <?php
769 if ( ! empty( $error_msg ) ) {
770 echo esc_html__( 'Failed to activate license: ', 'frontblocks' ) . '<br><strong>' . esc_html( $error_msg ) . '</strong>';
771 } else {
772 esc_html_e( 'Failed to activate license. Please check your license key and try again.', 'frontblocks' );
773 }
774 ?>
775 </p>
776 </div>
777 </div>
778 </div>
779 <?php
780 endif;
781 ?>
782
783 <!-- Settings Form -->
784 <form method="post" action="options.php" class="tw-space-y-6">
785 <?php settings_fields( 'frontblocks_settings' ); ?>
786
787 <?php
788 // Get all sections for this page.
789 global $wp_settings_sections, $wp_settings_fields;
790
791 if ( ! isset( $wp_settings_sections[ $this->page_slug ] ) ) {
792 return;
793 }
794
795 foreach ( (array) $wp_settings_sections[ $this->page_slug ] as $section ) {
796 $this->render_settings_section( $section );
797 }
798 ?>
799
800 <!-- Submit Button -->
801 <div class="tw-flex tw-items-center tw-justify-between tw-pt-6 tw-border-t tw-border-gray-200">
802 <div class="tw-text-sm tw-text-gray-500">
803 <?php echo esc_html__( 'Changes will be applied immediately after saving.', 'frontblocks' ); ?>
804 </div>
805 <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">
806 <svg class="tw-w-5 tw-h-5 tw-mr-2 tw--ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
807 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
808 </svg>
809 <?php echo esc_html__( 'Save Settings', 'frontblocks' ); ?>
810 </button>
811 </div>
812 </form>
813
814 <?php
815 // Render license section separately (outside main form) if PRO is active.
816 if ( frbl_is_pro_active() ) {
817 $this->render_license_section();
818 }
819 ?>
820
821 <!-- Footer Info -->
822 <div class="tw-mt-8 tw-text-center tw-text-sm tw-text-gray-500">
823 <?php
824 printf(
825 /* translators: %s: Close·marketing link */
826 esc_html__( 'Made with ❤️ by %s', 'frontblocks' ),
827 '<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>'
828 );
829 ?>
830 </div>
831
832 <?php $this->render_debug_section(); ?>
833 </div>
834 </div>
835 <?php
836 }
837
838 /**
839 * Render debug section for Fluid Typography.
840 *
841 * @return void
842 */
843 private function render_debug_section() {
844 // Only show if Fluid Typography is enabled and user requested debug.
845 $options = get_option( 'frontblocks_settings', array() );
846 $enabled = ! empty( $options['enable_fluid_typography'] );
847
848 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
849 if ( ! $enabled || ! isset( $_GET['frbl_debug_typography'] ) ) {
850 return;
851 }
852
853 // Get GeneratePress settings.
854 $gp_settings = get_option( 'generate_settings', array() );
855
856 // Filter only font-related settings.
857 $font_settings = array();
858 foreach ( $gp_settings as $key => $value ) {
859 if ( strpos( $key, 'font' ) !== false || strpos( $key, 'heading' ) !== false ) {
860 $font_settings[ $key ] = $value;
861 }
862 }
863
864 ?>
865 <div class="tw-mt-8 tw-p-6 tw-bg-yellow-50 tw-border tw-border-yellow-200 tw-rounded-lg">
866 <h3 class="tw-text-lg tw-font-semibold tw-text-gray-900 tw-mb-4">
867 🐛 Debug: Fluid Typography Settings
868 </h3>
869 <p class="tw-text-sm tw-text-gray-600 tw-mb-4">
870 <?php echo esc_html__( 'This shows the GeneratePress font settings being used by the Fluid Typography module.', 'frontblocks' ); ?>
871 </p>
872 <div class="tw-bg-white tw-p-4 tw-rounded tw-border tw-border-gray-300 tw-overflow-auto" style="max-height: 400px;">
873 <pre style="margin: 0; font-size: 12px;"><?php print_r( $font_settings ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r ?></pre>
874 </div>
875 <p class="tw-text-xs tw-text-gray-500 tw-mt-4">
876 <?php
877 printf(
878 /* translators: %s: URL parameter */
879 esc_html__( 'To hide this debug info, remove %s from the URL.', 'frontblocks' ),
880 '<code>?frbl_debug_typography=1</code>'
881 );
882 ?>
883 </p>
884 </div>
885 <?php
886 }
887
888 /**
889 * Active Blocks section callback.
890 *
891 * @return void
892 */
893 private function section_active_blocks_callback() {
894 ?>
895 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
896 <?php echo esc_html__( 'These blocks and features are always active and available in the block editor.', 'frontblocks' ); ?>
897 </p>
898 <div class="frbl-features-grid">
899 <?php
900 UI::show_info_card( 'animations', __( 'Animations', 'frontblocks' ), __( 'Add animations to any block using Animate.css', 'frontblocks' ) );
901 UI::show_info_card( 'carousel', __( 'Carousel/Slider', 'frontblocks' ), __( 'Transform any Grid block into a carousel or slider', 'frontblocks' ) );
902 UI::show_info_card( 'gallery', __( 'Native Gallery', 'frontblocks' ), __( 'Enhanced gallery block with carousel and masonry options', 'frontblocks' ) );
903 UI::show_info_card( 'sticky', __( 'Sticky Columns', 'frontblocks' ), __( 'Make Grid blocks sticky when scrolling', 'frontblocks' ) );
904 UI::show_info_card( 'insert_post', __( 'Insert Post Block', 'frontblocks' ), __( 'Display content from other posts, pages or custom post types', 'frontblocks' ) );
905 UI::show_info_card( 'counter', __( 'Counter Block', 'frontblocks' ), __( 'Display animated counters with start and end values', 'frontblocks' ) );
906 UI::show_info_card( 'reading_time', __( 'Reading Time Block', 'frontblocks' ), __( 'Show estimated reading time for posts', 'frontblocks' ) );
907 UI::show_info_card( 'stacked_images', __( 'Stacked Images Block', 'frontblocks' ), __( 'Display images with animated stacking effect from different directions', 'frontblocks' ) );
908 UI::show_info_card( 'product_categories', __( 'Product Categories Block', 'frontblocks' ), __( 'Display WooCommerce product categories', 'frontblocks' ) );
909 UI::show_info_card( 'headline_marquee', __( 'Headline Marquee', 'frontblocks' ), __( 'Infinite scrolling marquee effect for headline/text blocks with customizable speed', 'frontblocks' ) );
910 ?>
911 </div>
912 <?php
913 }
914
915 /**
916 * Features section callback.
917 *
918 * @return void
919 */
920 private function section_features_callback() {
921 ?>
922 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
923 <?php echo esc_html__( 'Enable or disable these optional features as needed.', 'frontblocks' ); ?>
924 </p>
925 <?php
926 }
927
928 /**
929 * Render a single settings section as a card.
930 *
931 * @param array $section Section data.
932 * @return void
933 */
934 private function render_settings_section( $section ) {
935 global $wp_settings_fields;
936
937 $has_fields = isset( $wp_settings_fields[ $this->page_slug ][ $section['id'] ] );
938
939 // Si no hay campos Y no hay callback, no renderizar nada.
940 if ( ! $has_fields && ! $section['callback'] ) {
941 return;
942 }
943
944 // Check if this is a section with callback only (like active_blocks).
945 $is_callback_only = ! $has_fields && $section['callback'];
946
947 // Check if this is the custom post types section - render it full width.
948 $is_cpt_section = 'frontblocks_section_custom_post_types' === $section['id'];
949
950 if ( $is_callback_only ) {
951 // Render section with only callback (no fields).
952 ?>
953 <div class="frbl-section-wrapper">
954 <div class="frbl-section-header">
955 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
956 <?php echo esc_html( $section['title'] ); ?>
957 </h2>
958 </div>
959 <?php call_user_func( $section['callback'], $section ); ?>
960 </div>
961 <?php
962 return;
963 }
964
965 if ( $is_cpt_section ) {
966 // Render CPT section as a full-width card.
967 ?>
968 <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">
969 <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">
970 <h2 class="tw-text-xl tw-font-semibold tw-text-gray-900">
971 <?php echo esc_html( $section['title'] ); ?>
972 </h2>
973 <?php
974 if ( $section['callback'] ) {
975 echo '<div class="tw-mt-2 tw-text-sm tw-text-gray-600">';
976 call_user_func( $section['callback'], $section );
977 echo '</div>';
978 }
979 ?>
980 </div>
981 <div class="tw-px-6 tw-py-5">
982 <?php
983 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
984 call_user_func( $field['callback'], $field['args'] );
985 }
986 ?>
987 </div>
988 </div>
989 <?php
990 } else {
991 // Render regular sections with feature grid.
992 ?>
993 <div class="frbl-section-wrapper">
994 <!-- Section Header -->
995 <div class="frbl-section-header">
996 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
997 <?php echo esc_html( $section['title'] ); ?>
998 </h2>
999 <?php
1000 if ( $section['callback'] ) {
1001 echo '<div class="tw-text-sm tw-text-gray-600">';
1002 call_user_func( $section['callback'], $section );
1003 echo '</div>';
1004 }
1005 ?>
1006 </div>
1007
1008 <!-- Features Grid -->
1009 <div class="frbl-features-grid">
1010 <?php
1011 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
1012 $this->render_settings_field( $field );
1013 }
1014 ?>
1015 </div>
1016 </div>
1017 <?php
1018 }
1019 }
1020
1021 /**
1022 * Render a single settings field as a card.
1023 *
1024 * @param array $field Field data.
1025 * @return void
1026 */
1027 private function render_settings_field( $field ) {
1028 // Determine if this is a PRO feature (always, regardless of license status).
1029 $is_pro_feature = in_array(
1030 $field['id'],
1031 array(
1032 $this->option_enable_gutenberg,
1033 $this->option_enable_simple_prices_variable_products,
1034 $this->option_enable_after_add_to_cart,
1035 $this->option_deactivate_short_description,
1036 $this->option_move_content_to_short_description,
1037 $this->option_disable_zoom_images,
1038 $this->option_add_share_buttons,
1039 $this->option_deactivate_product_tabs,
1040 $this->option_horizontal_product_form,
1041 $this->option_enable_fullpage_scroll,
1042 $this->option_enable_language_banner,
1043 ),
1044 true
1045 );
1046
1047 // Apply PRO styling only if license is not valid.
1048 $needs_license = $is_pro_feature && ! $this->is_license_valid;
1049
1050 // Get icon for this feature.
1051 $icon = $this->get_feature_icon( $field['id'] );
1052
1053 ?>
1054 <div class="frbl-feature-card <?php echo $needs_license ? 'frbl-feature-pro' : ''; ?>">
1055 <?php if ( $is_pro_feature ) : ?>
1056 <div class="frbl-pro-badge">PRO</div>
1057 <?php endif; ?>
1058
1059 <div class="frbl-feature-content">
1060 <div class="frbl-feature-icon">
1061 <?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
1062 </div>
1063 <div class="frbl-feature-info">
1064 <h3 class="frbl-feature-title">
1065 <?php echo esc_html( $field['title'] ); ?>
1066 </h3>
1067 </div>
1068 <div class="frbl-feature-toggle">
1069 <?php call_user_func( $field['callback'], $field['args'] ); ?>
1070 </div>
1071 </div>
1072 </div>
1073 <?php
1074 }
1075
1076 /**
1077 * Get icon SVG for a feature.
1078 *
1079 * @param string $field_id Field ID.
1080 * @return string SVG icon markup.
1081 */
1082 private function get_feature_icon( $field_id ) {
1083 // Map field IDs to icon file names.
1084 $icon_map = array(
1085 $this->option_enable_testimonials => 'testimonials',
1086 $this->option_enable_reading_progress => 'reading-progress',
1087 $this->option_enable_back_button => 'back-button',
1088 $this->option_enable_events => 'events',
1089 $this->option_enable_fluid_typography => 'fluid-typography',
1090 $this->option_enable_gutenberg => 'gutenberg',
1091 $this->option_enable_simple_prices_variable_products => 'simple-prices',
1092 $this->option_enable_after_add_to_cart => 'after-add-to-cart',
1093 $this->option_deactivate_short_description => 'deactivate-description',
1094 $this->option_move_content_to_short_description => 'move-content',
1095 $this->option_disable_zoom_images => 'disable-zoom',
1096 $this->option_add_share_buttons => 'share-buttons',
1097 $this->option_deactivate_product_tabs => 'deactivate-tabs',
1098 $this->option_horizontal_product_form => 'horizontal-form',
1099 $this->option_enable_fullpage_scroll => 'fullpage-scroll',
1100 $this->option_enable_language_banner => 'language-banner',
1101 );
1102
1103 $icon_name = $icon_map[ $field_id ] ?? 'default';
1104 $icon_path = FRBL_PLUGIN_PATH . 'assets/admin/icons/' . $icon_name . '.svg';
1105
1106 if ( file_exists( $icon_path ) ) {
1107 $svg_content = file_get_contents( $icon_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1108 return $svg_content ? $svg_content : '';
1109 }
1110
1111 return '';
1112 }
1113
1114 /**
1115 * PRO Features section description.
1116 *
1117 * @return void
1118 */
1119 public function section_woo_features_callback() {
1120 if ( ! frbl_is_pro_active() ) {
1121 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
1122 echo '<div class="tw-flex">';
1123 echo '<div class="tw-flex-shrink-0">';
1124 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>';
1125 echo '</div>';
1126 echo '<div class="tw-ml-3">';
1127 echo '<p class="tw-text-sm tw-text-blue-700">';
1128 printf(
1129 /* translators: %s: FrontBlocks PRO link */
1130 esc_html__( 'These features require %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
1131 '<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>'
1132 );
1133 echo '</p>';
1134 echo '</div>';
1135 echo '</div>';
1136 echo '</div>';
1137 } elseif ( ! $this->is_license_valid ) {
1138 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
1139 echo '<div class="tw-flex">';
1140 echo '<div class="tw-flex-shrink-0">';
1141 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>';
1142 echo '</div>';
1143 echo '<div class="tw-ml-3">';
1144 echo '<p class="tw-text-sm tw-text-yellow-700">';
1145 printf(
1146 /* translators: %s: License section link */
1147 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
1148 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1149 );
1150 echo '</p>';
1151 echo '</div>';
1152 echo '</div>';
1153 echo '</div>';
1154 } else {
1155 ?>
1156 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1157 <?php echo esc_html__( 'Advanced features for WooCommerce and more.', 'frontblocks' ); ?>
1158 </p>
1159 <?php
1160 }
1161 }
1162
1163 /**
1164 * Render toggle field for enable testimonials.
1165 *
1166 * @return void
1167 */
1168 public function field_enable_testimonials() {
1169 $options = get_option( 'frontblocks_settings', array() );
1170 $enabled = (bool) ( $options[ $this->option_enable_testimonials ] ?? false );
1171 ?>
1172 <label class="frbl-toggle">
1173 <input type="checkbox"
1174 id="<?php echo esc_attr( $this->option_enable_testimonials ); ?>"
1175 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_testimonials ); ?>]"
1176 value="1"
1177 <?php checked( true, $enabled ); ?>
1178 />
1179 <span></span>
1180 </label>
1181 <?php
1182 }
1183
1184 /**
1185 * Render toggle field for enable reading progress bar.
1186 *
1187 * @return void
1188 */
1189 public function field_enable_reading_progress() {
1190 $options = get_option( 'frontblocks_settings', array() );
1191 $enabled = (bool) ( $options[ $this->option_enable_reading_progress ] ?? false );
1192 ?>
1193 <label class="frbl-toggle">
1194 <input type="checkbox"
1195 id="<?php echo esc_attr( $this->option_enable_reading_progress ); ?>"
1196 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_reading_progress ); ?>]"
1197 value="1"
1198 <?php checked( true, $enabled ); ?>
1199 />
1200 <span></span>
1201 </label>
1202 <?php
1203 }
1204
1205 /**
1206 * Render toggle field for enable back button.
1207 *
1208 * @return void
1209 */
1210 public function field_enable_back_button() {
1211 $options = get_option( 'frontblocks_settings', array() );
1212 $enabled = (bool) ( $options[ $this->option_enable_back_button ] ?? false );
1213 ?>
1214 <label class="frbl-toggle">
1215 <input type="checkbox"
1216 id="<?php echo esc_attr( $this->option_enable_back_button ); ?>"
1217 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_back_button ); ?>]"
1218 value="1"
1219 <?php checked( true, $enabled ); ?>
1220 />
1221 <span></span>
1222 </label>
1223 <?php
1224 }
1225
1226 /**
1227 * Render toggle field for enable events.
1228 *
1229 * @return void
1230 */
1231 public function field_enable_events() {
1232 $options = get_option( 'frontblocks_settings', array() );
1233 $enabled = (bool) ( $options[ $this->option_enable_events ] ?? false );
1234 $events_type = sanitize_text_field( $options[ $this->option_events_type ] ?? 'cpt' );
1235 ?>
1236 <!-- Toggle - stays in horizontal layout with icon and text -->
1237 <label class="frbl-toggle">
1238 <input type="checkbox"
1239 id="<?php echo esc_attr( $this->option_enable_events ); ?>"
1240 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_events ); ?>]"
1241 value="1"
1242 <?php checked( true, $enabled ); ?>
1243 />
1244 <span></span>
1245 </label>
1246
1247 <!-- Select and description - will be moved below the card by JavaScript -->
1248 <div id="events-type-wrapper" class="tw-mt-4" style="<?php echo $enabled ? 'width: 100%; min-width: 100%; display: block;' : 'display: none;'; ?>">
1249 <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">
1250 <?php echo esc_html__( 'Tipo de eventos', 'frontblocks' ); ?>
1251 </label>
1252 <select
1253 id="<?php echo esc_attr( $this->option_events_type ); ?>"
1254 name="frontblocks_settings[<?php echo esc_attr( $this->option_events_type ); ?>]"
1255 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"
1256 style="width: 100%; min-width: 100%; max-width: 100%; box-sizing: border-box;"
1257 >
1258 <option value="cpt" <?php selected( $events_type, 'cpt' ); ?>>
1259 <?php echo esc_html__( 'Custom Post Type (CPT)', 'frontblocks' ); ?>
1260 </option>
1261 <option value="posts" <?php selected( $events_type, 'posts' ); ?>>
1262 <?php echo esc_html__( 'Entradas de blog', 'frontblocks' ); ?>
1263 </option>
1264 </select>
1265 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1266 <?php echo esc_html__( 'Elige si los eventos se crearán en un CPT dedicado o en las entradas de blog normales.', 'frontblocks' ); ?>
1267 </p>
1268 </div>
1269 <?php
1270 }
1271
1272 /**
1273 * Render toggle field for enable fluid typography.
1274 *
1275 * @return void
1276 */
1277 public function field_enable_fluid_typography() {
1278 $options = get_option( 'frontblocks_settings', array() );
1279 $enabled = (bool) ( $options[ $this->option_enable_fluid_typography ] ?? false );
1280 ?>
1281 <label class="frbl-toggle">
1282 <input type="checkbox"
1283 id="<?php echo esc_attr( $this->option_enable_fluid_typography ); ?>"
1284 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_fluid_typography ); ?>]"
1285 value="1"
1286 <?php checked( true, $enabled ); ?>
1287 />
1288 <span></span>
1289 </label>
1290 <?php
1291 }
1292
1293 /**
1294 * Render toggle field for enable Gutenberg in products (PRO).
1295 *
1296 * @return void
1297 */
1298 public function field_enable_gutenberg() {
1299 $this->render_pro_toggle( $this->option_enable_gutenberg );
1300 }
1301
1302 /**
1303 * Render toggle field for enable Simple Prices Variable Products (PRO).
1304 *
1305 * @return void
1306 */
1307 public function field_enable_simple_prices_variable_products() {
1308 $this->render_pro_toggle( $this->option_enable_simple_prices_variable_products );
1309 }
1310
1311 /**
1312 * Render After Add to Cart Block field.
1313 *
1314 * @return void
1315 */
1316 public function field_enable_after_add_to_cart() {
1317 $this->render_pro_toggle( $this->option_enable_after_add_to_cart );
1318 }
1319
1320 /**
1321 * Render Deactivate Short Description field.
1322 *
1323 * @return void
1324 */
1325 public function field_deactivate_short_description() {
1326 $this->render_pro_toggle( $this->option_deactivate_short_description );
1327 }
1328
1329 /**
1330 * Render Move Content to Short Description field.
1331 *
1332 * @return void
1333 */
1334 public function field_move_content_to_short_description() {
1335 $this->render_pro_toggle( $this->option_move_content_to_short_description );
1336 }
1337
1338 /**
1339 * Render Disable Zoom in Product Images field.
1340 *
1341 * @return void
1342 */
1343 public function field_disable_zoom_images() {
1344 $this->render_pro_toggle( $this->option_disable_zoom_images );
1345 }
1346
1347 /**
1348 * Render Add Share Buttons in Product Page field.
1349 *
1350 * @return void
1351 */
1352 public function field_add_share_buttons() {
1353 $this->render_pro_toggle( $this->option_add_share_buttons );
1354 }
1355
1356 /**
1357 * Render Deactivate Product Tabs field.
1358 *
1359 * @return void
1360 */
1361 public function field_deactivate_product_tabs() {
1362 $this->render_pro_toggle( $this->option_deactivate_product_tabs );
1363 }
1364
1365 /**
1366 * Render Horizontal Product Form Layout field.
1367 *
1368 * @return void
1369 */
1370 public function field_horizontal_product_form() {
1371 $this->render_pro_toggle( $this->option_horizontal_product_form );
1372 }
1373
1374 /**
1375 * Render Enable Full Page Scroll field.
1376 *
1377 * @return void
1378 */
1379 public function field_enable_fullpage_scroll() {
1380 $this->render_pro_toggle( $this->option_enable_fullpage_scroll );
1381 }
1382
1383 /**
1384 * Render Enable Language Banner field.
1385 *
1386 * @return void
1387 */
1388 public function field_enable_language_banner() {
1389 $this->render_pro_toggle( $this->option_enable_language_banner );
1390 }
1391
1392 /**
1393 * Custom Post Types section callback.
1394 *
1395 * @return void
1396 */
1397 public function section_custom_post_types_callback() {
1398 if ( ! frbl_is_pro_active() ) {
1399 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
1400 echo '<div class="tw-flex">';
1401 echo '<div class="tw-flex-shrink-0">';
1402 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>';
1403 echo '</div>';
1404 echo '<div class="tw-ml-3">';
1405 echo '<p class="tw-text-sm tw-text-blue-700">';
1406 printf(
1407 /* translators: %s: FrontBlocks PRO link */
1408 esc_html__( 'This feature requires %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
1409 '<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>'
1410 );
1411 echo '</p>';
1412 echo '</div>';
1413 echo '</div>';
1414 echo '</div>';
1415 } elseif ( ! $this->is_license_valid ) {
1416 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
1417 echo '<div class="tw-flex">';
1418 echo '<div class="tw-flex-shrink-0">';
1419 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>';
1420 echo '</div>';
1421 echo '<div class="tw-ml-3">';
1422 echo '<p class="tw-text-sm tw-text-yellow-700">';
1423 printf(
1424 /* translators: %s: License section link */
1425 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
1426 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1427 );
1428 echo '</p>';
1429 echo '</div>';
1430 echo '</div>';
1431 echo '</div>';
1432 } else {
1433 ?>
1434 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1435 <?php echo esc_html__( 'Create and manage custom post types with advanced configuration options.', 'frontblocks' ); ?>
1436 </p>
1437 <?php
1438 }
1439 }
1440
1441 /**
1442 * Render toggle field for enable custom post types.
1443 *
1444 * @return void
1445 */
1446 public function field_enable_custom_post_types() {
1447 $options = get_option( 'frontblocks_settings', array() );
1448 $enabled = (bool) ( $options[ $this->option_enable_custom_post_types ] ?? false );
1449 $is_enabled = $this->is_license_valid;
1450 $disabled = ! $is_enabled ? 'disabled' : '';
1451 ?>
1452 <div class="frbl-custom-post-types-wrapper">
1453 <div class="tw-flex tw-items-center tw-justify-between tw-mb-4">
1454 <label for="<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>" class="tw-text-base tw-font-medium tw-text-gray-900">
1455 <?php echo esc_html__( 'Enable Custom Post Types Builder', 'frontblocks' ); ?>
1456 </label>
1457 <label class="frbl-toggle">
1458 <input type="checkbox"
1459 id="<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>"
1460 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>]"
1461 value="1"
1462 <?php checked( true, $enabled ); ?>
1463 <?php echo esc_attr( $disabled ); ?>
1464 />
1465 <span></span>
1466 </label>
1467 </div>
1468
1469 <?php if ( $is_enabled ) : ?>
1470 <div id="frbl-cpt-builder" class="frbl-cpt-builder" style="<?php echo $enabled ? '' : 'display: none;'; ?>">
1471 <div class="tw-mt-4 tw-p-4 tw-bg-gray-50 tw-rounded-lg tw-border tw-border-gray-200">
1472 <label for="frbl-cpt-name" class="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-2">
1473 <?php echo esc_html__( 'Post Type Name', 'frontblocks' ); ?>
1474 </label>
1475 <div class="tw-flex tw-gap-2">
1476 <input
1477 type="text"
1478 id="frbl-cpt-name"
1479 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"
1480 placeholder="<?php echo esc_attr__( 'e.g., Portfolio, Team, Services', 'frontblocks' ); ?>"
1481 />
1482 <button
1483 type="button"
1484 id="frbl-create-cpt-btn"
1485 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"
1486 >
1487 <?php echo esc_html__( 'Crear', 'frontblocks' ); ?>
1488 </button>
1489 </div>
1490 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1491 <?php echo esc_html__( 'Enter a singular name for your custom post type (e.g., "Portfolio" will create "portfolio" post type).', 'frontblocks' ); ?>
1492 </p>
1493 </div>
1494
1495 <?php do_action( 'frontblocks_render_existing_cpts' ); ?>
1496 </div>
1497 <?php endif; ?>
1498 </div>
1499 <?php
1500 }
1501
1502 /**
1503 * Render license section (separate from main form).
1504 *
1505 * @return void
1506 */
1507 private function render_license_section() {
1508 global $frblp_license;
1509
1510 ?>
1511 <div class="tw-mt-6" id="frontblocks_section_license">
1512 <?php
1513 // Check if license instance exists.
1514 if ( ! $frblp_license ) {
1515 ?>
1516 <div class="tw-p-4 tw-rounded-lg tw-bg-red-50 tw-border tw-border-red-200">
1517 <p class="tw-text-sm tw-text-red-700">
1518 <?php echo esc_html__( 'License manager not initialized.', 'frontblocks' ); ?>
1519 </p>
1520 </div>
1521 <?php
1522 return;
1523 }
1524
1525 // Check if License class exists (requires FrontBlocks PRO).
1526 if ( ! class_exists( '\Closemarketing\WPLicenseManager\License' ) ) {
1527 ?>
1528 <div class="tw-p-4 tw-rounded-lg tw-bg-yellow-50 tw-border tw-border-yellow-200">
1529 <p class="tw-text-sm tw-text-yellow-700">
1530 <?php echo esc_html__( 'License management requires FrontBlocks PRO to be installed and active.', 'frontblocks' ); ?>
1531 </p>
1532 </div>
1533 <?php
1534 return;
1535 }
1536
1537 // Render license settings inline.
1538 $this->render_inline_license_settings( $frblp_license );
1539 ?>
1540 </div>
1541 <?php
1542 }
1543
1544 /**
1545 * Render inline license settings.
1546 *
1547 * @param \Closemarketing\WPLicenseManager\License $license License instance.
1548 * @return void
1549 */
1550 private function render_inline_license_settings( $license ) {
1551 // Get license data.
1552 $license_key = $license->get_option_value( 'apikey' );
1553 $is_active = $license->is_license_active();
1554 $license_status = get_option( 'frontblocks-pro_license_activated', 'Deactivated' );
1555
1556 ?>
1557 <div class="formscrm-license-wrapper">
1558 <!-- Main Card -->
1559 <div class="formscrm-card">
1560 <!-- Header -->
1561 <div class="formscrm-card-header">
1562 <h2><?php echo esc_html__( 'FrontBlocks PRO License', 'frontblocks' ); ?></h2>
1563 <p><?php echo esc_html__( 'Manage your license to receive automatic updates and support.', 'frontblocks' ); ?></p>
1564 </div>
1565
1566 <!-- License Status -->
1567 <div class="formscrm-form-group">
1568 <?php if ( $is_active ) : ?>
1569 <div class="formscrm-status-box formscrm-status-active">
1570 <span class="formscrm-status-icon">
1571 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
1572 <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"/>
1573 </svg>
1574 </span>
1575 <span class="formscrm-status-text"><?php echo esc_html__( 'License Active', 'frontblocks' ); ?></span>
1576 </div>
1577 <?php else : ?>
1578 <div class="formscrm-status-box formscrm-status-inactive">
1579 <span class="formscrm-status-icon">
1580 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
1581 <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"/>
1582 </svg>
1583 </span>
1584 <span class="formscrm-status-text"><?php echo esc_html__( 'License Inactive', 'frontblocks' ); ?></span>
1585 </div>
1586 <?php endif; ?>
1587 </div>
1588
1589 <!-- License Form -->
1590 <form method="post" action="options.php" class="formscrm-license-form">
1591 <?php settings_fields( 'frontblocks-pro_license' ); ?>
1592 <?php wp_nonce_field( 'Update_License_Options', 'license_nonce' ); ?>
1593
1594 <!-- License Key Field -->
1595 <div class="formscrm-form-group">
1596 <label class="formscrm-label" for="frontblocks-pro_license_apikey">
1597 <?php echo esc_html__( 'License Key', 'frontblocks' ); ?>
1598 </label>
1599 <div class="formscrm-input-group">
1600 <input
1601 type="text"
1602 id="frontblocks-pro_license_apikey"
1603 name="frontblocks-pro_license_apikey"
1604 value="<?php echo esc_attr( $license_key ); ?>"
1605 class="formscrm-input"
1606 placeholder="<?php echo esc_attr__( 'CTECH-XXXXX-XXXXX-XXXXX-XXXXX', 'frontblocks' ); ?>"
1607 <?php echo $is_active ? 'readonly' : ''; ?>
1608 />
1609 <?php if ( $is_active ) : ?>
1610 <label class="formscrm-deactivate-label">
1611 <input type="checkbox" name="frontblocks-pro_license_deactivate_checkbox" value="on" />
1612 <span><?php echo esc_html__( 'Deactivate', 'frontblocks' ); ?></span>
1613 </label>
1614 <?php endif; ?>
1615 </div>
1616 <p class="formscrm-help-text">
1617 <?php
1618 printf(
1619 /* translators: %s: Purchase URL */
1620 esc_html__( 'Enter your license key. You can find it in %s.', 'frontblocks' ),
1621 '<a href="https://close.technology/my-account/" target="_blank">' . esc_html__( 'your account', 'frontblocks' ) . '</a>'
1622 );
1623 ?>
1624 </p>
1625 </div>
1626
1627 <!-- License Status -->
1628 <div class="formscrm-form-group">
1629 <label class="formscrm-label"><?php echo esc_html__( 'License Status', 'frontblocks' ); ?></label>
1630 <div class="formscrm-status-box <?php echo $is_active ? 'formscrm-status-active' : 'formscrm-status-inactive'; ?>">
1631 <span class="formscrm-status-icon">
1632 <?php if ( $is_active ) : ?>
1633 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
1634 <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"/>
1635 </svg>
1636 <?php else : ?>
1637 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
1638 <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"/>
1639 </svg>
1640 <?php endif; ?>
1641 </span>
1642 <span class="formscrm-status-text">
1643 <?php echo $is_active ? esc_html__( 'Active', 'frontblocks' ) : esc_html__( 'Not Activated', 'frontblocks' ); ?>
1644 </span>
1645 </div>
1646 </div>
1647
1648 <!-- Submit Button -->
1649 <div class="formscrm-form-actions">
1650 <button type="submit" name="submit_license" class="formscrm-button formscrm-button-primary">
1651 <?php echo $is_active ? esc_html__( 'Update License', 'frontblocks' ) : esc_html__( 'Activate License', 'frontblocks' ); ?>
1652 </button>
1653 </div>
1654 </form>
1655 </div>
1656
1657 <!-- Sidebar Info -->
1658 <div class="formscrm-info-card">
1659 <h3><?php echo esc_html__( 'License Benefits', 'frontblocks' ); ?></h3>
1660 <p><?php echo esc_html__( 'An active license provides the following benefits:', 'frontblocks' ); ?></p>
1661
1662 <ul class="formscrm-benefits-list">
1663 <li><?php echo esc_html__( 'Automatic plugin updates', 'frontblocks' ); ?></li>
1664 <li><?php echo esc_html__( 'Access to new features', 'frontblocks' ); ?></li>
1665 <li><?php echo esc_html__( 'Priority support', 'frontblocks' ); ?></li>
1666 <li><?php echo esc_html__( 'Security patches', 'frontblocks' ); ?></li>
1667 </ul>
1668
1669 <hr style="margin: 20px 0; border: none; border-top: 1px solid #e2e8f0;">
1670
1671 <div style="font-size: 0.875rem; color: #64748b;">
1672 <p style="margin-bottom: 8px;">
1673 <strong><?php echo esc_html__( 'Need Help?', 'frontblocks' ); ?></strong>
1674 </p>
1675 <p style="margin-bottom: 8px;">
1676 <a href="https://close.technology/wordpress-plugins/frontblocks-pro/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
1677 <?php echo esc_html__( 'Purchase License', 'frontblocks' ); ?>
1678 </a>
1679 </p>
1680 <p style="margin-bottom: 8px;">
1681 <a href="https://close.technology/my-account/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
1682 <?php echo esc_html__( 'My Account', 'frontblocks' ); ?>
1683 </a>
1684 </p>
1685 <p>
1686 <a href="https://close.technology/support/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
1687 <?php echo esc_html__( 'Support', 'frontblocks' ); ?>
1688 </a>
1689 </p>
1690 </div>
1691 </div>
1692 </div>
1693 <?php
1694 }
1695
1696 /**
1697 * Helper method to render PRO toggle fields.
1698 *
1699 * @param string $option_key Option key.
1700 * @return void
1701 */
1702 private function render_pro_toggle( $option_key ) {
1703 $options = get_option( 'frontblocks_settings', array() );
1704 $enabled = (bool) ( $options[ $option_key ] ?? false );
1705 $is_enabled = $this->is_license_valid;
1706 $disabled = ! $is_enabled ? 'disabled' : '';
1707 ?>
1708 <label class="frbl-toggle">
1709 <input type="checkbox"
1710 id="<?php echo esc_attr( $option_key ); ?>"
1711 name="frontblocks_settings[<?php echo esc_attr( $option_key ); ?>]"
1712 value="1"
1713 <?php checked( true, $enabled ); ?>
1714 <?php echo esc_attr( $disabled ); ?>
1715 />
1716 <span></span>
1717 </label>
1718 <?php
1719 }
1720
1721 /**
1722 * Sanitize settings array.
1723 *
1724 * @param array $value Raw value.
1725 * @return array
1726 */
1727 public function sanitize_settings( $value ) {
1728 // Nonce verification.
1729 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
1730 if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'frontblocks_settings-options' ) ) {
1731 add_settings_error( 'frontblocks_settings', 'frontblocks_settings_nonce', esc_html__( 'Security check failed. Please try again.', 'frontblocks' ), 'error' );
1732
1733 return get_option( 'frontblocks_settings', array() );
1734 }
1735
1736 if ( ! is_array( $value ) ) {
1737 return array();
1738 }
1739
1740 // Get current options to preserve unchecked checkboxes.
1741 $current_options = get_option( 'frontblocks_settings', array() );
1742
1743 // Initialize sanitized array with current values.
1744 $sanitized = $current_options;
1745
1746 // List of all boolean options (checkboxes).
1747 $boolean_options = array(
1748 $this->option_enable_testimonials,
1749 $this->option_enable_reading_progress,
1750 $this->option_enable_back_button,
1751 $this->option_enable_events,
1752 $this->option_enable_fluid_typography,
1753 $this->option_enable_gutenberg,
1754 $this->option_enable_simple_prices_variable_products,
1755 $this->option_enable_after_add_to_cart,
1756 $this->option_deactivate_short_description,
1757 $this->option_move_content_to_short_description,
1758 $this->option_disable_zoom_images,
1759 $this->option_add_share_buttons,
1760 $this->option_deactivate_product_tabs,
1761 $this->option_horizontal_product_form,
1762 $this->option_enable_custom_post_types,
1763 $this->option_enable_fullpage_scroll,
1764 $this->option_enable_language_banner,
1765 );
1766
1767 // Initialize all boolean options to false (unchecked checkboxes are not submitted).
1768 foreach ( $boolean_options as $option ) {
1769 $sanitized[ $option ] = false;
1770 }
1771
1772 // Process submitted values.
1773 foreach ( $value as $key => $val ) {
1774 if ( in_array( $key, $boolean_options, true ) ) {
1775 $sanitized[ $key ] = (bool) $val;
1776 } elseif ( $this->option_events_type === $key ) {
1777 // Sanitize events type: only allow 'cpt' or 'posts'.
1778 $sanitized[ $key ] = in_array( $val, array( 'cpt', 'posts' ), true ) ? $val : 'cpt';
1779 }
1780 }
1781
1782 // Ensure mutual exclusion: if both description options are enabled, keep only the last one changed.
1783 if ( ! empty( $sanitized[ $this->option_deactivate_short_description ] ) && ! empty( $sanitized[ $this->option_move_content_to_short_description ] ) ) {
1784 // Get current saved values to determine which one was just changed.
1785 $current_deactivate = ! empty( $current_options[ $this->option_deactivate_short_description ] );
1786 $current_move = ! empty( $current_options[ $this->option_move_content_to_short_description ] );
1787
1788 // If deactivate was already on, turn it off (move is the new one).
1789 if ( $current_deactivate ) {
1790 $sanitized[ $this->option_deactivate_short_description ] = false;
1791 } else {
1792 // Otherwise turn off move (deactivate is the new one).
1793 $sanitized[ $this->option_move_content_to_short_description ] = false;
1794 }
1795 }
1796
1797 do_action( 'frontblocks_sanitize_settings', $sanitized );
1798
1799 return $sanitized;
1800 }
1801 }
1802