PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / 1.3.6
FrontBlocks for Gutenberg/GeneratePress v1.3.6
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 1 month ago
Settings.php
2050 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 $active_blocks = apply_filters(
888 'frbl_active_blocks',
889 array(
890 array(
891 'icon' => 'animations',
892 'title' => __( 'Animations', 'frontblocks' ),
893 'desc' => __( 'Add animations to any block using Animate.css', 'frontblocks' ),
894 ),
895 array(
896 'icon' => 'carousel',
897 'title' => __( 'Carousel/Slider', 'frontblocks' ),
898 'desc' => __( 'Transform any Grid block into a carousel or slider', 'frontblocks' ),
899 ),
900 array(
901 'icon' => 'gallery',
902 'title' => __( 'Native Gallery', 'frontblocks' ),
903 'desc' => __( 'Enhanced gallery block with carousel and masonry options', 'frontblocks' ),
904 ),
905 array(
906 'icon' => 'sticky',
907 'title' => __( 'Sticky Columns', 'frontblocks' ),
908 'desc' => __( 'Make Grid blocks sticky when scrolling', 'frontblocks' ),
909 ),
910 array(
911 'icon' => 'insert_post',
912 'title' => __( 'Insert Post Block', 'frontblocks' ),
913 'desc' => __( 'Display content from other posts, pages or custom post types', 'frontblocks' ),
914 ),
915 array(
916 'icon' => 'counter',
917 'title' => __( 'Counter Block', 'frontblocks' ),
918 'desc' => __( 'Display animated counters with start and end values', 'frontblocks' ),
919 ),
920 array(
921 'icon' => 'reading_time',
922 'title' => __( 'Reading Time Block', 'frontblocks' ),
923 'desc' => __( 'Show estimated reading time for posts', 'frontblocks' ),
924 ),
925 array(
926 'icon' => 'stacked_images',
927 'title' => __( 'Stacked Images Block', 'frontblocks' ),
928 'desc' => __( 'Display images with animated stacking effect from different directions', 'frontblocks' ),
929 ),
930 array(
931 'icon' => 'product_categories',
932 'title' => __( 'Product Categories Block', 'frontblocks' ),
933 'desc' => __( 'Display WooCommerce product categories', 'frontblocks' ),
934 ),
935 array(
936 'icon' => 'headline_marquee',
937 'title' => __( 'Headline Marquee', 'frontblocks' ),
938 'desc' => __( 'Infinite scrolling marquee effect for headline/text blocks with customizable speed', 'frontblocks' ),
939 ),
940 array(
941 'icon' => 'svg_upload',
942 'title' => __( 'SVG Uploads', 'frontblocks' ),
943 'desc' => __( 'Upload SVG files to the media library. Files are automatically sanitized to prevent security risks.', 'frontblocks' ),
944 ),
945 )
946 );
947
948 $pro_blocks = apply_filters( 'frbl_pro_blocks', $this->get_default_pro_blocks() );
949 $license_valid = function_exists( 'frblp_is_license_valid' ) && frblp_is_license_valid();
950
951 ?>
952 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
953 <?php echo esc_html__( 'These blocks and features are always active and available in the block editor.', 'frontblocks' ); ?>
954 </p>
955 <div class="frbl-features-grid">
956 <?php
957 foreach ( $active_blocks as $block ) {
958 UI::show_info_card( $block['icon'], $block['title'], $block['desc'] );
959 }
960
961 if ( ! $license_valid ) {
962 foreach ( $pro_blocks as $block ) {
963 UI::show_pro_info_card( $block['icon'], $block['title'], $block['desc'] );
964 }
965 }
966 ?>
967 </div>
968 <?php
969 }
970
971 /**
972 * Features section callback.
973 *
974 * @return void
975 */
976 private function section_features_callback() {
977 $license_valid = function_exists( 'frblp_is_license_valid' ) && frblp_is_license_valid();
978 ?>
979 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
980 <?php echo esc_html__( 'Enable or disable these optional features as needed.', 'frontblocks' ); ?>
981 </p>
982 <?php
983 }
984
985 /**
986 * Render a single settings section as a card.
987 *
988 * @param array $section Section data.
989 * @return void
990 */
991 private function render_settings_section( $section ) {
992 global $wp_settings_fields;
993
994 $has_fields = isset( $wp_settings_fields[ $this->page_slug ][ $section['id'] ] );
995
996 // Si no hay campos Y no hay callback, no renderizar nada.
997 if ( ! $has_fields && ! $section['callback'] ) {
998 return;
999 }
1000
1001 // Check if this is a section with callback only (like active_blocks).
1002 $is_callback_only = ! $has_fields && $section['callback'];
1003
1004 // Check if this is the custom post types section - render it full width.
1005 $is_cpt_section = 'frontblocks_section_custom_post_types' === $section['id'];
1006
1007 // Show PRO CTA button before the Optional Features section.
1008 if ( 'frontblocks_section_features' === $section['id'] && ! $this->is_license_valid ) {
1009 ?>
1010 <div class="tw-mb-4">
1011 <a
1012 href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings-optional-cta"
1013 target="_blank"
1014 rel="noopener noreferrer"
1015 class="tw-inline-flex tw-items-center tw-border tw-border-transparent tw-text-sm tw-font-medium tw-rounded-lg tw-shadow-sm tw-text-white tw-transition-colors tw-duration-200"
1016 style="background-color: #ef4444; padding: 10px 20px;"
1017 onmouseover="this.style.backgroundColor='#dc2626'"
1018 onmouseout="this.style.backgroundColor='#ef4444'"
1019 >
1020 <?php echo esc_html__( 'Get FrontBlocks PRO', 'frontblocks' ); ?>
1021 </a>
1022 </div>
1023 <?php
1024 }
1025
1026 if ( $is_callback_only ) {
1027 // Render section with only callback (no fields).
1028 ?>
1029 <div class="frbl-section-wrapper">
1030 <div class="frbl-section-header">
1031 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
1032 <?php echo esc_html( $section['title'] ); ?>
1033 </h2>
1034 </div>
1035 <?php call_user_func( $section['callback'], $section ); ?>
1036 </div>
1037 <?php
1038 return;
1039 }
1040
1041 if ( $is_cpt_section ) {
1042 // Render CPT section as a full-width card.
1043 ?>
1044 <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">
1045 <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">
1046 <h2 class="tw-text-xl tw-font-semibold tw-text-gray-900">
1047 <?php echo esc_html( $section['title'] ); ?>
1048 </h2>
1049 <?php
1050 if ( $section['callback'] ) {
1051 echo '<div class="tw-mt-2 tw-text-sm tw-text-gray-600">';
1052 call_user_func( $section['callback'], $section );
1053 echo '</div>';
1054 }
1055 ?>
1056 </div>
1057 <div class="tw-px-6 tw-py-5">
1058 <?php
1059 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
1060 call_user_func( $field['callback'], $field['args'] );
1061 }
1062 ?>
1063 </div>
1064 </div>
1065 <?php
1066 } else {
1067 // Render regular sections with feature grid.
1068 ?>
1069 <div class="frbl-section-wrapper">
1070 <!-- Section Header -->
1071 <div class="frbl-section-header">
1072 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
1073 <?php echo esc_html( $section['title'] ); ?>
1074 </h2>
1075 <?php
1076 if ( $section['callback'] ) {
1077 echo '<div class="tw-text-sm tw-text-gray-600">';
1078 call_user_func( $section['callback'], $section );
1079 echo '</div>';
1080 }
1081 ?>
1082 </div>
1083
1084 <!-- Features Grid -->
1085 <div class="frbl-features-grid">
1086 <?php
1087 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
1088 $this->render_settings_field( $field );
1089 }
1090 ?>
1091 </div>
1092 </div>
1093 <?php
1094 }
1095 }
1096
1097 /**
1098 * Render a single settings field as a card.
1099 *
1100 * @param array $field Field data.
1101 * @return void
1102 */
1103 private function render_settings_field( $field ) {
1104 // Determine if this is a PRO feature (always, regardless of license status).
1105 $is_pro_feature = in_array(
1106 $field['id'],
1107 array(
1108 $this->option_enable_gutenberg,
1109 $this->option_enable_simple_prices_variable_products,
1110 $this->option_enable_after_add_to_cart,
1111 $this->option_deactivate_short_description,
1112 $this->option_move_content_to_short_description,
1113 $this->option_disable_zoom_images,
1114 $this->option_add_share_buttons,
1115 $this->option_deactivate_product_tabs,
1116 $this->option_horizontal_product_form,
1117 $this->option_enable_fullpage_scroll,
1118 $this->option_enable_language_banner,
1119 ),
1120 true
1121 );
1122
1123 // Apply PRO styling only if license is not valid.
1124 $needs_license = $is_pro_feature && ! $this->is_license_valid;
1125
1126 // Get icon for this feature.
1127 $icon = $this->get_feature_icon( $field['id'] );
1128
1129 // Short descriptions for each field.
1130 $descriptions = array(
1131 $this->option_enable_testimonials => __( 'Add a testimonials block to display customer reviews.', 'frontblocks' ),
1132 $this->option_enable_reading_progress => __( 'Show a progress bar at the top of the page while reading posts.', 'frontblocks' ),
1133 $this->option_enable_back_button => __( 'Add a floating back button for easy navigation.', 'frontblocks' ),
1134 $this->option_enable_events => __( 'Register and display events using a CPT or blog posts.', 'frontblocks' ),
1135 $this->option_enable_fluid_typography => __( 'Font sizes scale smoothly between mobile and desktop using CSS clamp().', 'frontblocks' ),
1136 $this->option_enable_gutenberg => __( 'Use the block editor to write WooCommerce product descriptions.', 'frontblocks' ),
1137 $this->option_enable_simple_prices_variable_products => __( 'Show a simplified price range for variable products.', 'frontblocks' ),
1138 $this->option_enable_after_add_to_cart => __( 'Insert custom block content right after the Add to Cart button.', 'frontblocks' ),
1139 $this->option_deactivate_short_description => __( 'Remove the short description field from product pages.', 'frontblocks' ),
1140 $this->option_move_content_to_short_description => __( 'Move the main product content into the short description area.', 'frontblocks' ),
1141 $this->option_disable_zoom_images => __( 'Remove the zoom effect on WooCommerce product images.', 'frontblocks' ),
1142 $this->option_add_share_buttons => __( 'Add social share buttons to WooCommerce product pages.', 'frontblocks' ),
1143 $this->option_deactivate_product_tabs => __( 'Remove the default description, reviews and attributes tabs.', 'frontblocks' ),
1144 $this->option_horizontal_product_form => __( 'Display quantity and Add to Cart button side by side.', 'frontblocks' ),
1145 $this->option_enable_fullpage_scroll => __( 'Enable full-page scroll navigation between sections.', 'frontblocks' ),
1146 $this->option_enable_language_banner => __( 'Show a banner when the visitor language differs from the site language.', 'frontblocks' ),
1147 );
1148
1149 $desc = $descriptions[ $field['id'] ] ?? '';
1150
1151 ?>
1152 <div class="frbl-feature-card <?php echo $needs_license ? 'frbl-feature-pro' : ''; ?>">
1153 <?php if ( $is_pro_feature ) : ?>
1154 <div class="frbl-pro-badge">PRO</div>
1155 <?php endif; ?>
1156
1157 <div class="frbl-feature-content">
1158 <div class="frbl-feature-icon">
1159 <?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
1160 </div>
1161 <div class="frbl-feature-info">
1162 <h3 class="frbl-feature-title">
1163 <?php echo esc_html( $field['title'] ); ?>
1164 </h3>
1165 <?php if ( $desc ) : ?>
1166 <p class="frbl-feature-description">
1167 <?php echo esc_html( $desc ); ?>
1168 </p>
1169 <?php endif; ?>
1170 </div>
1171 <div class="frbl-feature-toggle">
1172 <?php call_user_func( $field['callback'], $field['args'] ); ?>
1173 </div>
1174 </div>
1175 </div>
1176 <?php
1177 }
1178
1179 /**
1180 * Get icon SVG for a feature.
1181 *
1182 * @param string $field_id Field ID.
1183 * @return string SVG icon markup.
1184 */
1185 private function get_feature_icon( $field_id ) {
1186 // Map field IDs to icon file names.
1187 $icon_map = array(
1188 $this->option_enable_testimonials => 'testimonials',
1189 $this->option_enable_reading_progress => 'reading-progress',
1190 $this->option_enable_back_button => 'back-button',
1191 $this->option_enable_events => 'events',
1192 $this->option_enable_fluid_typography => 'fluid-typography',
1193 $this->option_enable_gutenberg => 'gutenberg',
1194 $this->option_enable_simple_prices_variable_products => 'simple-prices',
1195 $this->option_enable_after_add_to_cart => 'after-add-to-cart',
1196 $this->option_deactivate_short_description => 'deactivate-description',
1197 $this->option_move_content_to_short_description => 'move-content',
1198 $this->option_disable_zoom_images => 'disable-zoom',
1199 $this->option_add_share_buttons => 'share-buttons',
1200 $this->option_deactivate_product_tabs => 'deactivate-tabs',
1201 $this->option_horizontal_product_form => 'horizontal-form',
1202 $this->option_enable_fullpage_scroll => 'fullpage-scroll',
1203 $this->option_enable_language_banner => 'language-banner',
1204 );
1205
1206 $icon_name = $icon_map[ $field_id ] ?? 'default';
1207 $icon_path = FRBL_PLUGIN_PATH . 'assets/admin/icons/' . $icon_name . '.svg';
1208
1209 if ( file_exists( $icon_path ) ) {
1210 $svg_content = file_get_contents( $icon_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1211 return $svg_content ? $svg_content : '';
1212 }
1213
1214 return '';
1215 }
1216
1217 /**
1218 * PRO Features section description.
1219 *
1220 * @return void
1221 */
1222 public function section_woo_features_callback() {
1223 if ( ! frbl_is_pro_active() ) {
1224 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
1225 echo '<div class="tw-flex">';
1226 echo '<div class="tw-flex-shrink-0">';
1227 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>';
1228 echo '</div>';
1229 echo '<div class="tw-ml-3">';
1230 echo '<p class="tw-text-sm tw-text-blue-700">';
1231 printf(
1232 /* translators: %s: FrontBlocks PRO link */
1233 esc_html__( 'These features require %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
1234 '<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>'
1235 );
1236 echo '</p>';
1237 echo '</div>';
1238 echo '</div>';
1239 echo '</div>';
1240 } elseif ( ! $this->is_license_valid ) {
1241 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
1242 echo '<div class="tw-flex">';
1243 echo '<div class="tw-flex-shrink-0">';
1244 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>';
1245 echo '</div>';
1246 echo '<div class="tw-ml-3">';
1247 echo '<p class="tw-text-sm tw-text-yellow-700">';
1248 printf(
1249 /* translators: %s: License section link */
1250 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
1251 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1252 );
1253 echo '</p>';
1254 echo '</div>';
1255 echo '</div>';
1256 echo '</div>';
1257 } else {
1258 ?>
1259 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1260 <?php echo esc_html__( 'Advanced features for WooCommerce and more.', 'frontblocks' ); ?>
1261 </p>
1262 <?php
1263 }
1264 }
1265
1266 /**
1267 * Render toggle field for enable testimonials.
1268 *
1269 * @return void
1270 */
1271 public function field_enable_testimonials() {
1272 $options = get_option( 'frontblocks_settings', array() );
1273 $enabled = (bool) ( $options[ $this->option_enable_testimonials ] ?? false );
1274 ?>
1275 <label class="frbl-toggle">
1276 <input type="checkbox"
1277 id="<?php echo esc_attr( $this->option_enable_testimonials ); ?>"
1278 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_testimonials ); ?>]"
1279 value="1"
1280 <?php checked( true, $enabled ); ?>
1281 />
1282 <span></span>
1283 </label>
1284 <?php
1285 }
1286
1287 /**
1288 * Render toggle field for enable reading progress bar.
1289 *
1290 * @return void
1291 */
1292 public function field_enable_reading_progress() {
1293 $options = get_option( 'frontblocks_settings', array() );
1294 $enabled = (bool) ( $options[ $this->option_enable_reading_progress ] ?? false );
1295 ?>
1296 <label class="frbl-toggle">
1297 <input type="checkbox"
1298 id="<?php echo esc_attr( $this->option_enable_reading_progress ); ?>"
1299 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_reading_progress ); ?>]"
1300 value="1"
1301 <?php checked( true, $enabled ); ?>
1302 />
1303 <span></span>
1304 </label>
1305 <?php
1306 }
1307
1308 /**
1309 * Render toggle field for enable back button.
1310 *
1311 * @return void
1312 */
1313 public function field_enable_back_button() {
1314 $options = get_option( 'frontblocks_settings', array() );
1315 $enabled = (bool) ( $options[ $this->option_enable_back_button ] ?? false );
1316 ?>
1317 <label class="frbl-toggle">
1318 <input type="checkbox"
1319 id="<?php echo esc_attr( $this->option_enable_back_button ); ?>"
1320 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_back_button ); ?>]"
1321 value="1"
1322 <?php checked( true, $enabled ); ?>
1323 />
1324 <span></span>
1325 </label>
1326 <?php
1327 }
1328
1329 /**
1330 * Render toggle field for enable events.
1331 *
1332 * @return void
1333 */
1334 public function field_enable_events() {
1335 $options = get_option( 'frontblocks_settings', array() );
1336 $enabled = (bool) ( $options[ $this->option_enable_events ] ?? false );
1337 $events_type = sanitize_text_field( $options[ $this->option_events_type ] ?? 'cpt' );
1338 ?>
1339 <!-- Toggle - stays in horizontal layout with icon and text -->
1340 <label class="frbl-toggle">
1341 <input type="checkbox"
1342 id="<?php echo esc_attr( $this->option_enable_events ); ?>"
1343 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_events ); ?>]"
1344 value="1"
1345 <?php checked( true, $enabled ); ?>
1346 />
1347 <span></span>
1348 </label>
1349
1350 <!-- Select and description - will be moved below the card by JavaScript -->
1351 <div id="events-type-wrapper" class="tw-mt-4" style="<?php echo $enabled ? 'width: 100%; min-width: 100%; display: block;' : 'display: none;'; ?>">
1352 <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">
1353 <?php echo esc_html__( 'Event type', 'frontblocks' ); ?>
1354 </label>
1355 <select
1356 id="<?php echo esc_attr( $this->option_events_type ); ?>"
1357 name="frontblocks_settings[<?php echo esc_attr( $this->option_events_type ); ?>]"
1358 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"
1359 style="width: 100%; min-width: 100%; max-width: 100%; box-sizing: border-box;"
1360 >
1361 <option value="cpt" <?php selected( $events_type, 'cpt' ); ?>>
1362 <?php echo esc_html__( 'Custom Post Type (CPT)', 'frontblocks' ); ?>
1363 </option>
1364 <option value="posts" <?php selected( $events_type, 'posts' ); ?>>
1365 <?php echo esc_html__( 'Blog posts', 'frontblocks' ); ?>
1366 </option>
1367 </select>
1368 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1369 <?php echo esc_html__( 'Choose whether events will be created in a dedicated CPT or in regular blog posts.', 'frontblocks' ); ?>
1370 </p>
1371 </div>
1372 <?php
1373 }
1374
1375 /**
1376 * Render toggle field for enable fluid typography.
1377 *
1378 * @return void
1379 */
1380 public function field_enable_fluid_typography() {
1381 $options = get_option( 'frontblocks_settings', array() );
1382 $enabled = (bool) ( $options[ $this->option_enable_fluid_typography ] ?? true );
1383 ?>
1384 <label class="frbl-toggle">
1385 <input type="checkbox"
1386 id="<?php echo esc_attr( $this->option_enable_fluid_typography ); ?>"
1387 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_fluid_typography ); ?>]"
1388 value="1"
1389 <?php checked( true, $enabled ); ?>
1390 />
1391 <span></span>
1392 </label>
1393 <?php
1394 }
1395
1396 /**
1397 * Render toggle field for enable Gutenberg in products (PRO).
1398 *
1399 * @return void
1400 */
1401 public function field_enable_gutenberg() {
1402 $this->render_pro_toggle( $this->option_enable_gutenberg );
1403 }
1404
1405 /**
1406 * Render toggle field for enable Simple Prices Variable Products (PRO).
1407 *
1408 * @return void
1409 */
1410 public function field_enable_simple_prices_variable_products() {
1411 $this->render_pro_toggle( $this->option_enable_simple_prices_variable_products );
1412 }
1413
1414 /**
1415 * Render After Add to Cart Block field.
1416 *
1417 * @return void
1418 */
1419 public function field_enable_after_add_to_cart() {
1420 $this->render_pro_toggle( $this->option_enable_after_add_to_cart );
1421 }
1422
1423 /**
1424 * Render Deactivate Short Description field.
1425 *
1426 * @return void
1427 */
1428 public function field_deactivate_short_description() {
1429 $this->render_pro_toggle( $this->option_deactivate_short_description );
1430 }
1431
1432 /**
1433 * Render Move Content to Short Description field.
1434 *
1435 * @return void
1436 */
1437 public function field_move_content_to_short_description() {
1438 $this->render_pro_toggle( $this->option_move_content_to_short_description );
1439 }
1440
1441 /**
1442 * Render Disable Zoom in Product Images field.
1443 *
1444 * @return void
1445 */
1446 public function field_disable_zoom_images() {
1447 $this->render_pro_toggle( $this->option_disable_zoom_images );
1448 }
1449
1450 /**
1451 * Render Add Share Buttons in Product Page field.
1452 *
1453 * @return void
1454 */
1455 public function field_add_share_buttons() {
1456 $this->render_pro_toggle( $this->option_add_share_buttons );
1457 }
1458
1459 /**
1460 * Render Deactivate Product Tabs field.
1461 *
1462 * @return void
1463 */
1464 public function field_deactivate_product_tabs() {
1465 $this->render_pro_toggle( $this->option_deactivate_product_tabs );
1466 }
1467
1468 /**
1469 * Render Horizontal Product Form Layout field.
1470 *
1471 * @return void
1472 */
1473 public function field_horizontal_product_form() {
1474 $this->render_pro_toggle( $this->option_horizontal_product_form );
1475 }
1476
1477 /**
1478 * Render Enable Full Page Scroll field.
1479 *
1480 * @return void
1481 */
1482 public function field_enable_fullpage_scroll() {
1483 $this->render_pro_toggle( $this->option_enable_fullpage_scroll );
1484 }
1485
1486 /**
1487 * Render Enable Language Banner field.
1488 *
1489 * @return void
1490 */
1491 public function field_enable_language_banner() {
1492 $this->render_pro_toggle( $this->option_enable_language_banner );
1493 }
1494
1495 /**
1496 * Custom Post Types section callback.
1497 *
1498 * @return void
1499 */
1500 public function section_custom_post_types_callback() {
1501 if ( ! frbl_is_pro_active() ) {
1502 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
1503 echo '<div class="tw-flex">';
1504 echo '<div class="tw-flex-shrink-0">';
1505 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>';
1506 echo '</div>';
1507 echo '<div class="tw-ml-3">';
1508 echo '<p class="tw-text-sm tw-text-blue-700">';
1509 printf(
1510 /* translators: %s: FrontBlocks PRO link */
1511 esc_html__( 'This feature requires %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
1512 '<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>'
1513 );
1514 echo '</p>';
1515 echo '</div>';
1516 echo '</div>';
1517 echo '</div>';
1518 } elseif ( ! $this->is_license_valid ) {
1519 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
1520 echo '<div class="tw-flex">';
1521 echo '<div class="tw-flex-shrink-0">';
1522 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>';
1523 echo '</div>';
1524 echo '<div class="tw-ml-3">';
1525 echo '<p class="tw-text-sm tw-text-yellow-700">';
1526 printf(
1527 /* translators: %s: License section link */
1528 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
1529 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1530 );
1531 echo '</p>';
1532 echo '</div>';
1533 echo '</div>';
1534 echo '</div>';
1535 } else {
1536 ?>
1537 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1538 <?php echo esc_html__( 'Create and manage custom post types with advanced configuration options.', 'frontblocks' ); ?>
1539 </p>
1540 <?php
1541 }
1542 }
1543
1544 /**
1545 * Render toggle field for enable custom post types.
1546 *
1547 * @return void
1548 */
1549 public function field_enable_custom_post_types() {
1550 $options = get_option( 'frontblocks_settings', array() );
1551 $enabled = (bool) ( $options[ $this->option_enable_custom_post_types ] ?? false );
1552 $is_enabled = $this->is_license_valid;
1553 $disabled = ! $is_enabled ? 'disabled' : '';
1554 ?>
1555 <div class="frbl-custom-post-types-wrapper">
1556 <div class="tw-flex tw-items-center tw-justify-between tw-mb-4">
1557 <label for="<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>" class="tw-text-base tw-font-medium tw-text-gray-900">
1558 <?php echo esc_html__( 'Enable Custom Post Types Builder', 'frontblocks' ); ?>
1559 </label>
1560 <label class="frbl-toggle">
1561 <input type="checkbox"
1562 id="<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>"
1563 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>]"
1564 value="1"
1565 <?php checked( true, $enabled ); ?>
1566 <?php echo esc_attr( $disabled ); ?>
1567 />
1568 <span></span>
1569 </label>
1570 </div>
1571
1572 <?php if ( $is_enabled ) : ?>
1573 <div id="frbl-cpt-builder" class="frbl-cpt-builder" style="<?php echo $enabled ? '' : 'display: none;'; ?>">
1574 <div class="tw-mt-4 tw-p-4 tw-bg-gray-50 tw-rounded-lg tw-border tw-border-gray-200">
1575 <label for="frbl-cpt-name" class="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-2">
1576 <?php echo esc_html__( 'Post Type Name', 'frontblocks' ); ?>
1577 </label>
1578 <div class="tw-flex tw-gap-2">
1579 <input
1580 type="text"
1581 id="frbl-cpt-name"
1582 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"
1583 placeholder="<?php echo esc_attr__( 'e.g., Portfolio, Team, Services', 'frontblocks' ); ?>"
1584 />
1585 <button
1586 type="button"
1587 id="frbl-create-cpt-btn"
1588 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"
1589 >
1590 <?php echo esc_html__( 'Create', 'frontblocks' ); ?>
1591 </button>
1592 </div>
1593 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1594 <?php echo esc_html__( 'Enter a singular name for your custom post type (e.g., "Portfolio" will create "portfolio" post type).', 'frontblocks' ); ?>
1595 </p>
1596 </div>
1597
1598 <?php do_action( 'frontblocks_render_existing_cpts' ); ?>
1599 </div>
1600 <?php endif; ?>
1601 </div>
1602 <?php
1603 }
1604
1605 /**
1606 * Render PRO features section after the main form.
1607 * Shows when frbl_pro_blocks filter returns blocks and license is not active.
1608 *
1609 * @return void
1610 */
1611 /**
1612 * Default PRO blocks list — shown as promotional cards when PRO is not installed.
1613 * The frbl_pro_blocks filter lets the PRO plugin override this list.
1614 *
1615 * @return array<int, array<string, string>>
1616 */
1617 private function get_default_pro_blocks(): array {
1618 return array(
1619 array(
1620 'icon' => 'meta-fields',
1621 'title' => __( 'Dynamic Meta Fields', 'frontblocks' ),
1622 'desc' => __( 'Bind any paragraph or heading to a custom post meta field, editable from the block editor.', 'frontblocks' ),
1623 ),
1624 array(
1625 'icon' => 'user-text',
1626 'title' => __( 'User Data Block', 'frontblocks' ),
1627 'desc' => __( 'Display logged-in user data with placeholders like {nombre}, {email}, {username}.', 'frontblocks' ),
1628 ),
1629 array(
1630 'icon' => 'fullpage-scroll',
1631 'title' => __( 'Full Page Scroll', 'frontblocks' ),
1632 'desc' => __( 'Full-page scroll navigation between sections with smooth transitions.', 'frontblocks' ),
1633 ),
1634 array(
1635 'icon' => 'language-banner',
1636 'title' => __( 'Language Banner', 'frontblocks' ),
1637 'desc' => __( 'Detect visitor language and show a recommendation banner (WPML/Polylang).', 'frontblocks' ),
1638 ),
1639 array(
1640 'icon' => 'gutenberg',
1641 'title' => __( 'Gutenberg in Products', 'frontblocks' ),
1642 'desc' => __( 'Use the full block editor to build WooCommerce product descriptions.', 'frontblocks' ),
1643 ),
1644 array(
1645 'icon' => 'after-add-to-cart',
1646 'title' => __( 'After Add to Cart Block', 'frontblocks' ),
1647 'desc' => __( 'Insert custom block content right after the Add to Cart button.', 'frontblocks' ),
1648 ),
1649 array(
1650 'icon' => 'simple-prices',
1651 'title' => __( 'Simple Prices Variable Products', 'frontblocks' ),
1652 'desc' => __( 'Simplified price display for variable WooCommerce products.', 'frontblocks' ),
1653 ),
1654 array(
1655 'icon' => 'horizontal-form',
1656 'title' => __( 'Horizontal Product Form', 'frontblocks' ),
1657 'desc' => __( 'Switch the WooCommerce product form to a horizontal layout.', 'frontblocks' ),
1658 ),
1659 array(
1660 'icon' => 'deactivate-tabs',
1661 'title' => __( 'Deactivate Product Tabs', 'frontblocks' ),
1662 'desc' => __( 'Remove the default tabs from WooCommerce product pages.', 'frontblocks' ),
1663 ),
1664 array(
1665 'icon' => 'disable-zoom',
1666 'title' => __( 'Disable Product Image Zoom', 'frontblocks' ),
1667 'desc' => __( 'Remove zoom effect on WooCommerce product images.', 'frontblocks' ),
1668 ),
1669 array(
1670 'icon' => 'share-buttons',
1671 'title' => __( 'Share Buttons', 'frontblocks' ),
1672 'desc' => __( 'Add social share buttons to WooCommerce product pages.', 'frontblocks' ),
1673 ),
1674 array(
1675 'icon' => 'deactivate-description',
1676 'title' => __( 'Manage Short Description', 'frontblocks' ),
1677 'desc' => __( 'Deactivate or move the WooCommerce product short description.', 'frontblocks' ),
1678 ),
1679 array(
1680 'icon' => 'default',
1681 'title' => __( 'Custom Post Types Builder', 'frontblocks' ),
1682 'desc' => __( 'Create and manage custom post types directly from the admin panel.', 'frontblocks' ),
1683 ),
1684 );
1685 }
1686
1687 /**
1688 * Render PRO features section after the main form.
1689 *
1690 * @return void
1691 */
1692 private function render_pro_section(): void {
1693 $pro_blocks = apply_filters( 'frbl_pro_blocks', $this->get_default_pro_blocks() );
1694 $license_valid = function_exists( 'frblp_is_license_valid' ) && frblp_is_license_valid();
1695
1696 if ( empty( $pro_blocks ) || $license_valid ) {
1697 return;
1698 }
1699
1700 ?>
1701 <div class="frbl-section-wrapper tw-mt-6">
1702 <div class="frbl-section-header">
1703 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
1704 <?php echo esc_html__( 'FrontBlocks PRO Features', 'frontblocks' ); ?>
1705 </h2>
1706 <div class="tw-text-sm tw-text-gray-600">
1707 <p class="tw-mt-0 tw-mb-4">
1708 <?php
1709 $anchor = frbl_is_pro_active()
1710 ? '<a href="#frontblocks_section_license" class="tw-font-medium tw-text-red-600 hover:tw-text-red-700 tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1711 : '<a href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings-pro-showcase" target="_blank" rel="noopener noreferrer" class="tw-font-medium tw-text-red-600 hover:tw-text-red-700 tw-underline">FrontBlocks PRO</a>';
1712 printf(
1713 frbl_is_pro_active()
1714 /* translators: %s: license section link */
1715 ? esc_html__( 'Activate your license in the %s section below to unlock all features.', 'frontblocks' )
1716 /* translators: %s: FrontBlocks PRO link */
1717 : esc_html__( 'Unlock these features with %s.', 'frontblocks' ),
1718 $anchor // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1719 );
1720 ?>
1721 </p>
1722 </div>
1723 </div>
1724
1725 <div class="frbl-features-grid">
1726 <?php foreach ( $pro_blocks as $block ) : ?>
1727 <?php UI::show_pro_info_card( $block['icon'], $block['title'], $block['desc'] ); ?>
1728 <?php endforeach; ?>
1729 </div>
1730
1731 <?php if ( ! frbl_is_pro_active() ) : ?>
1732 <div class="tw-mt-6 tw-text-center">
1733 <a
1734 href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings-pro-cta"
1735 target="_blank"
1736 rel="noopener noreferrer"
1737 class="tw-inline-flex tw-items-center tw-px-6 tw-py-3 tw-border tw-border-transparent tw-text-base tw-font-medium tw-rounded-lg tw-shadow-sm tw-text-white tw-transition-colors tw-duration-200"
1738 style="background-color: #ef4444;"
1739 onmouseover="this.style.backgroundColor='#dc2626'"
1740 onmouseout="this.style.backgroundColor='#ef4444'"
1741 >
1742 <?php echo esc_html__( 'Get FrontBlocks PRO', 'frontblocks' ); ?>
1743 </a>
1744 </div>
1745 <?php endif; ?>
1746 </div>
1747 <?php
1748 }
1749
1750 /**
1751 * Render license section (separate from main form).
1752 *
1753 * @return void
1754 */
1755 private function render_license_section() {
1756 global $frblp_license;
1757
1758 ?>
1759 <div class="tw-mt-6" id="frontblocks_section_license">
1760 <?php
1761 // Check if license instance exists.
1762 if ( ! $frblp_license ) {
1763 ?>
1764 <div class="tw-p-4 tw-rounded-lg tw-bg-red-50 tw-border tw-border-red-200">
1765 <p class="tw-text-sm tw-text-red-700">
1766 <?php echo esc_html__( 'License manager not initialized.', 'frontblocks' ); ?>
1767 </p>
1768 </div>
1769 <?php
1770 return;
1771 }
1772
1773 // Check if License class exists (requires FrontBlocks PRO).
1774 if ( ! class_exists( '\Closemarketing\WPLicenseManager\License' ) ) {
1775 ?>
1776 <div class="tw-p-4 tw-rounded-lg tw-bg-yellow-50 tw-border tw-border-yellow-200">
1777 <p class="tw-text-sm tw-text-yellow-700">
1778 <?php echo esc_html__( 'License management requires FrontBlocks PRO to be installed and active.', 'frontblocks' ); ?>
1779 </p>
1780 </div>
1781 <?php
1782 return;
1783 }
1784
1785 // Render license settings inline.
1786 $this->render_inline_license_settings( $frblp_license );
1787 ?>
1788 </div>
1789 <?php
1790 }
1791
1792 /**
1793 * Render inline license settings.
1794 *
1795 * @param \Closemarketing\WPLicenseManager\License $license License instance.
1796 * @return void
1797 */
1798 private function render_inline_license_settings( $license ) {
1799 // Get license data.
1800 $license_key = $license->get_option_value( 'apikey' );
1801 $is_active = $license->is_license_active();
1802 $license_status = get_option( 'frontblocks-pro_license_activated', 'Deactivated' );
1803
1804 ?>
1805 <div class="formscrm-license-wrapper">
1806 <!-- Main Card -->
1807 <div class="formscrm-card">
1808 <!-- Header -->
1809 <div class="formscrm-card-header">
1810 <h2><?php echo esc_html__( 'FrontBlocks PRO License', 'frontblocks' ); ?></h2>
1811 <p><?php echo esc_html__( 'Manage your license to receive automatic updates and support.', 'frontblocks' ); ?></p>
1812 </div>
1813
1814 <!-- License Status -->
1815 <div class="formscrm-form-group">
1816 <?php if ( $is_active ) : ?>
1817 <div class="formscrm-status-box formscrm-status-active">
1818 <span class="formscrm-status-icon">
1819 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
1820 <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"/>
1821 </svg>
1822 </span>
1823 <span class="formscrm-status-text"><?php echo esc_html__( 'License Active', 'frontblocks' ); ?></span>
1824 </div>
1825 <?php else : ?>
1826 <div class="formscrm-status-box formscrm-status-inactive">
1827 <span class="formscrm-status-icon">
1828 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
1829 <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"/>
1830 </svg>
1831 </span>
1832 <span class="formscrm-status-text"><?php echo esc_html__( 'License Inactive', 'frontblocks' ); ?></span>
1833 </div>
1834 <?php endif; ?>
1835 </div>
1836
1837 <!-- License Form -->
1838 <form method="post" action="options.php" class="formscrm-license-form">
1839 <?php settings_fields( 'frontblocks-pro_license' ); ?>
1840 <?php wp_nonce_field( 'Update_License_Options', 'license_nonce' ); ?>
1841
1842 <!-- License Key Field -->
1843 <div class="formscrm-form-group">
1844 <label class="formscrm-label" for="frontblocks-pro_license_apikey">
1845 <?php echo esc_html__( 'License Key', 'frontblocks' ); ?>
1846 </label>
1847 <div class="formscrm-input-group">
1848 <input
1849 type="text"
1850 id="frontblocks-pro_license_apikey"
1851 name="frontblocks-pro_license_apikey"
1852 value="<?php echo esc_attr( $license_key ); ?>"
1853 class="formscrm-input"
1854 placeholder="<?php echo esc_attr__( 'CTECH-XXXXX-XXXXX-XXXXX-XXXXX', 'frontblocks' ); ?>"
1855 <?php echo $is_active ? 'readonly' : ''; ?>
1856 />
1857 <?php if ( $is_active ) : ?>
1858 <label class="formscrm-deactivate-label">
1859 <input type="checkbox" name="frontblocks-pro_license_deactivate_checkbox" value="on" />
1860 <span><?php echo esc_html__( 'Deactivate', 'frontblocks' ); ?></span>
1861 </label>
1862 <?php endif; ?>
1863 </div>
1864 <p class="formscrm-help-text">
1865 <?php
1866 printf(
1867 /* translators: %s: Purchase URL */
1868 esc_html__( 'Enter your license key. You can find it in %s.', 'frontblocks' ),
1869 '<a href="https://close.technology/my-account/" target="_blank">' . esc_html__( 'your account', 'frontblocks' ) . '</a>'
1870 );
1871 ?>
1872 </p>
1873 </div>
1874
1875 <!-- License Status -->
1876 <div class="formscrm-form-group">
1877 <label class="formscrm-label"><?php echo esc_html__( 'License Status', 'frontblocks' ); ?></label>
1878 <div class="formscrm-status-box <?php echo $is_active ? 'formscrm-status-active' : 'formscrm-status-inactive'; ?>">
1879 <span class="formscrm-status-icon">
1880 <?php if ( $is_active ) : ?>
1881 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
1882 <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"/>
1883 </svg>
1884 <?php else : ?>
1885 <svg class="formscrm-icon" fill="currentColor" viewBox="0 0 20 20">
1886 <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"/>
1887 </svg>
1888 <?php endif; ?>
1889 </span>
1890 <span class="formscrm-status-text">
1891 <?php echo $is_active ? esc_html__( 'Active', 'frontblocks' ) : esc_html__( 'Not Activated', 'frontblocks' ); ?>
1892 </span>
1893 </div>
1894 </div>
1895
1896 <!-- Submit Button -->
1897 <div class="formscrm-form-actions">
1898 <button type="submit" name="submit_license" class="formscrm-button formscrm-button-primary">
1899 <?php echo $is_active ? esc_html__( 'Update License', 'frontblocks' ) : esc_html__( 'Activate License', 'frontblocks' ); ?>
1900 </button>
1901 </div>
1902 </form>
1903 </div>
1904
1905 <!-- Sidebar Info -->
1906 <div class="formscrm-info-card">
1907 <h3><?php echo esc_html__( 'License Benefits', 'frontblocks' ); ?></h3>
1908 <p><?php echo esc_html__( 'An active license provides the following benefits:', 'frontblocks' ); ?></p>
1909
1910 <ul class="formscrm-benefits-list">
1911 <li><?php echo esc_html__( 'Automatic plugin updates', 'frontblocks' ); ?></li>
1912 <li><?php echo esc_html__( 'Access to new features', 'frontblocks' ); ?></li>
1913 <li><?php echo esc_html__( 'Priority support', 'frontblocks' ); ?></li>
1914 <li><?php echo esc_html__( 'Security patches', 'frontblocks' ); ?></li>
1915 </ul>
1916
1917 <hr style="margin: 20px 0; border: none; border-top: 1px solid #e2e8f0;">
1918
1919 <div style="font-size: 0.875rem; color: #64748b;">
1920 <p style="margin-bottom: 8px;">
1921 <strong><?php echo esc_html__( 'Need Help?', 'frontblocks' ); ?></strong>
1922 </p>
1923 <p style="margin-bottom: 8px;">
1924 <a href="https://close.technology/wordpress-plugins/frontblocks-pro/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
1925 <?php echo esc_html__( 'Purchase License', 'frontblocks' ); ?>
1926 </a>
1927 </p>
1928 <p style="margin-bottom: 8px;">
1929 <a href="https://close.technology/my-account/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
1930 <?php echo esc_html__( 'My Account', 'frontblocks' ); ?>
1931 </a>
1932 </p>
1933 <p>
1934 <a href="https://close.technology/support/" target="_blank" style="color: #8b5cf6; text-decoration: none;">
1935 <?php echo esc_html__( 'Support', 'frontblocks' ); ?>
1936 </a>
1937 </p>
1938 </div>
1939 </div>
1940 </div>
1941 <?php
1942 }
1943
1944 /**
1945 * Helper method to render PRO toggle fields.
1946 *
1947 * @param string $option_key Option key.
1948 * @return void
1949 */
1950 private function render_pro_toggle( $option_key ) {
1951 $options = get_option( 'frontblocks_settings', array() );
1952 $enabled = (bool) ( $options[ $option_key ] ?? false );
1953 $is_enabled = $this->is_license_valid;
1954 $disabled = ! $is_enabled ? 'disabled' : '';
1955 ?>
1956 <label class="frbl-toggle">
1957 <input type="checkbox"
1958 id="<?php echo esc_attr( $option_key ); ?>"
1959 name="frontblocks_settings[<?php echo esc_attr( $option_key ); ?>]"
1960 value="1"
1961 <?php checked( true, $enabled ); ?>
1962 <?php echo esc_attr( $disabled ); ?>
1963 />
1964 <span></span>
1965 </label>
1966 <?php
1967 }
1968
1969 /**
1970 * Sanitize settings array.
1971 *
1972 * @param array $value Raw value.
1973 * @return array
1974 */
1975 public function sanitize_settings( $value ) {
1976 // Nonce verification.
1977 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
1978 if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'frontblocks_settings-options' ) ) {
1979 add_settings_error( 'frontblocks_settings', 'frontblocks_settings_nonce', esc_html__( 'Security check failed. Please try again.', 'frontblocks' ), 'error' );
1980
1981 return get_option( 'frontblocks_settings', array() );
1982 }
1983
1984 if ( ! is_array( $value ) ) {
1985 return array();
1986 }
1987
1988 // Get current options to preserve unchecked checkboxes.
1989 $current_options = get_option( 'frontblocks_settings', array() );
1990
1991 // Initialize sanitized array with current values.
1992 $sanitized = $current_options;
1993
1994 // List of all boolean options (checkboxes).
1995 $boolean_options = array(
1996 $this->option_enable_testimonials,
1997 $this->option_enable_reading_progress,
1998 $this->option_enable_back_button,
1999 $this->option_enable_events,
2000 $this->option_enable_fluid_typography,
2001 $this->option_enable_gutenberg,
2002 $this->option_enable_simple_prices_variable_products,
2003 $this->option_enable_after_add_to_cart,
2004 $this->option_deactivate_short_description,
2005 $this->option_move_content_to_short_description,
2006 $this->option_disable_zoom_images,
2007 $this->option_add_share_buttons,
2008 $this->option_deactivate_product_tabs,
2009 $this->option_horizontal_product_form,
2010 $this->option_enable_custom_post_types,
2011 $this->option_enable_fullpage_scroll,
2012 $this->option_enable_language_banner,
2013 );
2014
2015 // Initialize all boolean options to false (unchecked checkboxes are not submitted).
2016 foreach ( $boolean_options as $option ) {
2017 $sanitized[ $option ] = false;
2018 }
2019
2020 // Process submitted values.
2021 foreach ( $value as $key => $val ) {
2022 if ( in_array( $key, $boolean_options, true ) ) {
2023 $sanitized[ $key ] = (bool) $val;
2024 } elseif ( $this->option_events_type === $key ) {
2025 // Sanitize events type: only allow 'cpt' or 'posts'.
2026 $sanitized[ $key ] = in_array( $val, array( 'cpt', 'posts' ), true ) ? $val : 'cpt';
2027 }
2028 }
2029
2030 // Ensure mutual exclusion: if both description options are enabled, keep only the last one changed.
2031 if ( ! empty( $sanitized[ $this->option_deactivate_short_description ] ) && ! empty( $sanitized[ $this->option_move_content_to_short_description ] ) ) {
2032 // Get current saved values to determine which one was just changed.
2033 $current_deactivate = ! empty( $current_options[ $this->option_deactivate_short_description ] );
2034 $current_move = ! empty( $current_options[ $this->option_move_content_to_short_description ] );
2035
2036 // If deactivate was already on, turn it off (move is the new one).
2037 if ( $current_deactivate ) {
2038 $sanitized[ $this->option_deactivate_short_description ] = false;
2039 } else {
2040 // Otherwise turn off move (deactivate is the new one).
2041 $sanitized[ $this->option_move_content_to_short_description ] = false;
2042 }
2043 }
2044
2045 do_action( 'frontblocks_sanitize_settings', $sanitized );
2046
2047 return $sanitized;
2048 }
2049 }
2050