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