PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / 1.3.1
FrontBlocks for Gutenberg/GeneratePress v1.3.1
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 7 months ago UI.php 7 months ago
Settings.php
1456 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 Gutenberg in products (PRO).
59 *
60 * @var string
61 */
62 private $option_enable_gutenberg = 'enable_gutenberg';
63
64 /**
65 * Option key for Simple Prices Variable Products (PRO).
66 *
67 * @var string
68 */
69 private $option_enable_simple_prices_variable_products = 'enable_simple_prices_variable_products';
70
71 /**
72 * Option key for After Add to Cart Block (PRO).
73 *
74 * @var string
75 */
76 private $option_enable_after_add_to_cart = 'enable_after_add_to_cart';
77
78 /**
79 * Option key for deactivate short description (PRO).
80 *
81 * @var string
82 */
83 private $option_deactivate_short_description = 'deactivate_short_description';
84
85 /**
86 * Option key for move content to short description (PRO).
87 *
88 * @var string
89 */
90 private $option_move_content_to_short_description = 'move_content_to_short_description';
91
92 /**
93 * Option key for disable zoom in WooCommerce images (PRO).
94 *
95 * @var string
96 */
97 private $option_disable_zoom_images = 'disable_zoom_images';
98
99 /**
100 * Option key for add share buttons in product page (PRO).
101 *
102 * @var string
103 */
104 private $option_add_share_buttons = 'add_share_buttons';
105
106 /**
107 * Option key for deactivate product tabs (PRO).
108 *
109 * @var string
110 */
111 private $option_deactivate_product_tabs = 'deactivate_product_tabs';
112
113 /**
114 * Option key for horizontal product form layout (PRO).
115 *
116 * @var string
117 */
118 private $option_horizontal_product_form = 'horizontal_product_form';
119
120 /**
121 * Option key for custom post types builder (PRO).
122 *
123 * @var string
124 */
125 private $option_enable_custom_post_types = 'enable_custom_post_types';
126
127 /**
128 * Page slug.
129 *
130 * @var string
131 */
132 private $page_slug = 'frontblocks-settings';
133
134 /**
135 * Is license valid.
136 *
137 * @var bool
138 */
139 private $is_license_valid = false;
140
141 /**
142 * Constructor.
143 */
144 public function __construct() {
145 // Check license via FrontBlocks PRO helper function.
146 $this->is_license_valid = function_exists( 'frblp_is_license_valid' ) && frblp_is_license_valid();
147
148 add_action( 'admin_menu', array( $this, 'register_menu' ) );
149 add_action( 'admin_init', array( $this, 'register_settings' ) );
150 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
151 add_action( 'admin_head', array( $this, 'add_menu_icon_styles' ) );
152 }
153
154 /**
155 * Add menu icon styles.
156 *
157 * @return void
158 */
159 public function add_menu_icon_styles() {
160 ?>
161 <style>
162 #toplevel_page_frontblocks-settings .wp-menu-image img,
163 #toplevel_page_frontblocks-settings .wp-menu-image svg {
164 width: 20px !important;
165 height: 20px !important;
166 max-width: 20px !important;
167 max-height: 20px !important;
168 }
169 </style>
170 <?php
171 }
172
173 /**
174 * Enqueue admin styles for settings page.
175 *
176 * @param string $hook Current admin page hook.
177 * @return void
178 */
179 public function enqueue_admin_styles( $hook ) {
180 if ( 'toplevel_page_' . $this->page_slug !== $hook ) {
181 return;
182 }
183
184 wp_enqueue_style(
185 'frontblocks-admin-settings',
186 FRBL_PLUGIN_URL . 'assets/admin/settings.css',
187 array(),
188 FRBL_VERSION
189 );
190
191 wp_add_inline_script(
192 'jquery',
193 "
194 document.addEventListener('DOMContentLoaded', function() {
195 const deactivateCheckbox = document.getElementById('deactivate_short_description');
196 const moveContentCheckbox = document.getElementById('move_content_to_short_description');
197
198 if (deactivateCheckbox && moveContentCheckbox) {
199 function updateMutualExclusion() {
200 const deactivateWrapper = deactivateCheckbox.closest('.tw-flex');
201 const moveContentWrapper = moveContentCheckbox.closest('.tw-flex');
202
203 // Check if license is valid (not just PRO active).
204 const isLicenseValid = " . ( $this->is_license_valid ? 'true' : 'false' ) . ";
205
206 if (deactivateCheckbox.checked) {
207 moveContentCheckbox.disabled = true;
208 if (moveContentWrapper) {
209 moveContentWrapper.style.opacity = '0.5';
210 moveContentWrapper.style.filter = 'grayscale(100%)';
211 const toggle = moveContentWrapper.querySelector('.frbl-toggle');
212 if (toggle) {
213 toggle.style.borderColor = '#ef4444';
214 toggle.style.opacity = '0.7';
215 }
216 }
217 } else {
218 moveContentCheckbox.disabled = !isLicenseValid;
219 if (moveContentWrapper) {
220 moveContentWrapper.style.opacity = isLicenseValid ? '1' : '0.5';
221 moveContentWrapper.style.filter = '';
222 const toggle = moveContentWrapper.querySelector('.frbl-toggle');
223 if (toggle) {
224 toggle.style.borderColor = '';
225 toggle.style.opacity = '';
226 }
227 }
228 }
229
230 if (moveContentCheckbox.checked) {
231 deactivateCheckbox.disabled = true;
232 if (deactivateWrapper) {
233 deactivateWrapper.style.opacity = '0.5';
234 deactivateWrapper.style.filter = 'grayscale(100%)';
235 const toggle = deactivateWrapper.querySelector('.frbl-toggle');
236 if (toggle) {
237 toggle.style.borderColor = '#ef4444';
238 toggle.style.opacity = '0.7';
239 }
240 }
241 } else {
242 deactivateCheckbox.disabled = !isLicenseValid;
243 if (deactivateWrapper) {
244 deactivateWrapper.style.opacity = isLicenseValid ? '1' : '0.5';
245 deactivateWrapper.style.filter = '';
246 const toggle = deactivateWrapper.querySelector('.frbl-toggle');
247 if (toggle) {
248 toggle.style.borderColor = '';
249 toggle.style.opacity = '';
250 }
251 }
252 }
253 }
254
255 deactivateCheckbox.addEventListener('change', updateMutualExclusion);
256 moveContentCheckbox.addEventListener('change', updateMutualExclusion);
257
258 updateMutualExclusion();
259 }
260
261 // Show/hide events type select based on toggle state.
262 const eventsCheckbox = document.getElementById('enable_events');
263 const eventsTypeWrapper = document.getElementById('events-type-wrapper');
264
265 if (eventsCheckbox && eventsTypeWrapper) {
266 // Find the parent feature card and feature content.
267 const featureCard = eventsCheckbox.closest('.frbl-feature-card');
268 const featureContent = featureCard ? featureCard.querySelector('.frbl-feature-content') : null;
269
270 // Move the wrapper outside of frbl-feature-content but inside frbl-feature-card.
271 // This ensures it appears below the entire horizontal row (icon, text, toggle).
272 if (featureCard && featureContent) {
273 // Check if wrapper is still inside feature-content and move it.
274 if (featureContent.contains(eventsTypeWrapper)) {
275 // Move it to be a direct child of feature-card, after feature-content.
276 featureCard.appendChild(eventsTypeWrapper);
277 }
278 }
279
280 function updateEventsTypeVisibility() {
281 if (eventsCheckbox.checked) {
282 eventsTypeWrapper.style.display = 'block';
283 eventsTypeWrapper.style.width = '100%';
284 eventsTypeWrapper.style.minWidth = '100%';
285 eventsTypeWrapper.style.marginTop = '1rem';
286 eventsTypeWrapper.style.paddingTop = '1rem';
287 eventsTypeWrapper.style.paddingLeft = '1rem';
288 eventsTypeWrapper.style.paddingRight = '1rem';
289 eventsTypeWrapper.style.paddingBottom = '1rem';
290 eventsTypeWrapper.style.borderTop = '1px solid #e5e7eb';
291 eventsTypeWrapper.style.backgroundColor = '#f9fafb';
292 // Set feature card to column layout.
293 if (featureCard) {
294 featureCard.style.display = 'flex';
295 featureCard.style.flexDirection = 'column';
296 }
297 // Keep the feature content horizontal - never change it.
298 if (featureContent) {
299 featureContent.style.flexDirection = 'row';
300 featureContent.style.alignItems = 'center';
301 featureContent.style.justifyContent = 'space-between';
302 }
303 } else {
304 eventsTypeWrapper.style.display = 'none';
305 // Reset feature card layout.
306 if (featureCard) {
307 featureCard.style.display = '';
308 featureCard.style.flexDirection = '';
309 }
310 // Keep the feature content horizontal.
311 if (featureContent) {
312 featureContent.style.flexDirection = 'row';
313 featureContent.style.alignItems = 'center';
314 featureContent.style.justifyContent = 'space-between';
315 }
316 }
317 }
318
319 // Run immediately to move the element on page load.
320 if (featureCard && featureContent && featureContent.contains(eventsTypeWrapper)) {
321 featureCard.appendChild(eventsTypeWrapper);
322 }
323
324 eventsCheckbox.addEventListener('change', updateEventsTypeVisibility);
325 updateEventsTypeVisibility();
326 }
327
328 });
329 "
330 );
331
332 // Enqueue script for custom post types if PRO is active and license is valid.
333 if ( frbl_is_pro_active() && $this->is_license_valid ) {
334 wp_enqueue_script(
335 'frontblocks-cpt-admin',
336 FRBL_PLUGIN_URL . 'assets/admin/custom-post-types.js',
337 array( 'jquery' ),
338 FRBL_VERSION,
339 true
340 );
341
342 wp_localize_script(
343 'frontblocks-cpt-admin',
344 'frontblocksCpt',
345 array(
346 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
347 'nonce' => wp_create_nonce( 'frontblocks_create_cpt' ),
348 'i18n' => array(
349 'creating' => __( 'Creating...', 'frontblocks' ),
350 'error' => __( 'Error creating post type. Please try again.', 'frontblocks' ),
351 'success' => __( 'Post type created successfully!', 'frontblocks' ),
352 ),
353 )
354 );
355 }
356 }
357
358 /**
359 * Register options page as dedicated menu.
360 *
361 * @return void
362 */
363 public function register_menu() {
364 // Use SVG icon if available, otherwise fallback to dashicon.
365 $icon_url = FRBL_PLUGIN_URL . 'assets/admin/icons/icon-menu.svg';
366 $icon_path = FRBL_PLUGIN_PATH . 'assets/admin/icons/icon-menu.svg';
367 $menu_icon = file_exists( $icon_path ) ? $icon_url : 'dashicons-block-default';
368
369 add_menu_page(
370 __( 'FrontBlocks Settings', 'frontblocks' ),
371 __( 'FrontBlocks', 'frontblocks' ),
372 'edit_theme_options',
373 $this->page_slug,
374 array( $this, 'render_page' ),
375 $menu_icon,
376 81
377 );
378 }
379
380 /**
381 * Register settings, sections and fields.
382 *
383 * @return void
384 */
385 public function register_settings() {
386 register_setting(
387 'frontblocks_settings',
388 'frontblocks_settings',
389 array(
390 'type' => 'array',
391 'sanitize_callback' => array( $this, 'sanitize_settings' ),
392 'default' => array(),
393 'show_in_rest' => false,
394 )
395 );
396
397 // Always Active Blocks section.
398 add_settings_section(
399 'frontblocks_section_active_blocks',
400 __( 'Active Blocks & Features', 'frontblocks' ),
401 array( $this, 'section_active_blocks_callback' ),
402 $this->page_slug
403 );
404
405 add_settings_section(
406 'frontblocks_section_features',
407 __( 'Optional Features', 'frontblocks' ),
408 array( $this, 'section_features_callback' ),
409 $this->page_slug
410 );
411
412 add_settings_field(
413 $this->option_enable_testimonials,
414 __( 'Enable testimonials', 'frontblocks' ),
415 array( $this, 'field_enable_testimonials' ),
416 $this->page_slug,
417 'frontblocks_section_features'
418 );
419
420 add_settings_field(
421 $this->option_enable_reading_progress,
422 __( 'Enable reading progress bar', 'frontblocks' ),
423 array( $this, 'field_enable_reading_progress' ),
424 $this->page_slug,
425 'frontblocks_section_features'
426 );
427
428 add_settings_field(
429 $this->option_enable_back_button,
430 __( 'Enable Back Button', 'frontblocks' ),
431 array( $this, 'field_enable_back_button' ),
432 $this->page_slug,
433 'frontblocks_section_features'
434 );
435
436 add_settings_field(
437 $this->option_enable_events,
438 __( 'Enable Events', 'frontblocks' ),
439 array( $this, 'field_enable_events' ),
440 $this->page_slug,
441 'frontblocks_section_features'
442 );
443
444 // PRO Features section.
445 add_settings_section(
446 'frontblocks_section_woocommerce_features',
447 __( 'WooCommerce Features', 'frontblocks' ),
448 array( $this, 'section_woo_features_callback' ),
449 $this->page_slug
450 );
451
452 add_settings_field(
453 $this->option_enable_gutenberg,
454 __( 'Enable Gutenberg in Products', 'frontblocks' ),
455 array( $this, 'field_enable_gutenberg' ),
456 $this->page_slug,
457 'frontblocks_section_woocommerce_features'
458 );
459
460 add_settings_field(
461 $this->option_enable_simple_prices_variable_products,
462 __( 'Enable Simple Prices Variable Products', 'frontblocks' ),
463 array( $this, 'field_enable_simple_prices_variable_products' ),
464 $this->page_slug,
465 'frontblocks_section_woocommerce_features'
466 );
467
468 add_settings_field(
469 $this->option_enable_after_add_to_cart,
470 __( 'Enable After Add to Cart Block', 'frontblocks' ),
471 array( $this, 'field_enable_after_add_to_cart' ),
472 $this->page_slug,
473 'frontblocks_section_woocommerce_features'
474 );
475
476 add_settings_field(
477 $this->option_deactivate_short_description,
478 __( 'Deactivate Short Description', 'frontblocks' ),
479 array( $this, 'field_deactivate_short_description' ),
480 $this->page_slug,
481 'frontblocks_section_woocommerce_features'
482 );
483
484 add_settings_field(
485 $this->option_move_content_to_short_description,
486 __( 'Move Content to Short Description', 'frontblocks' ),
487 array( $this, 'field_move_content_to_short_description' ),
488 $this->page_slug,
489 'frontblocks_section_woocommerce_features'
490 );
491
492 add_settings_field(
493 $this->option_disable_zoom_images,
494 __( 'Disable Zoom in Product Images', 'frontblocks' ),
495 array( $this, 'field_disable_zoom_images' ),
496 $this->page_slug,
497 'frontblocks_section_woocommerce_features'
498 );
499
500 add_settings_field(
501 $this->option_add_share_buttons,
502 __( 'Add Share Buttons in Product Page', 'frontblocks' ),
503 array( $this, 'field_add_share_buttons' ),
504 $this->page_slug,
505 'frontblocks_section_woocommerce_features'
506 );
507
508 add_settings_field(
509 $this->option_deactivate_product_tabs,
510 __( 'Deactivate Product Tabs', 'frontblocks' ),
511 array( $this, 'field_deactivate_product_tabs' ),
512 $this->page_slug,
513 'frontblocks_section_woocommerce_features'
514 );
515
516 add_settings_field(
517 $this->option_horizontal_product_form,
518 __( 'Horizontal Product Form Layout', 'frontblocks' ),
519 array( $this, 'field_horizontal_product_form' ),
520 $this->page_slug,
521 'frontblocks_section_woocommerce_features'
522 );
523
524 // Custom Post Types section (PRO).
525 if ( frbl_is_pro_active() ) {
526 add_settings_section(
527 'frontblocks_section_custom_post_types',
528 __( 'Custom Post Types', 'frontblocks' ),
529 array( $this, 'section_custom_post_types_callback' ),
530 $this->page_slug
531 );
532
533 add_settings_field(
534 $this->option_enable_custom_post_types,
535 __( 'Enable Custom Post Types Builder', 'frontblocks' ),
536 array( $this, 'field_enable_custom_post_types' ),
537 $this->page_slug,
538 'frontblocks_section_custom_post_types'
539 );
540 }
541
542 // License section (only if PRO is active).
543 if ( frbl_is_pro_active() ) {
544 add_settings_section(
545 'frontblocks_section_license',
546 __( 'License', 'frontblocks' ),
547 array( $this, 'section_license_callback' ),
548 $this->page_slug
549 );
550
551 add_settings_field(
552 'frblp_license_info',
553 __( 'License Information', 'frontblocks' ),
554 array( $this, 'field_license_key' ),
555 $this->page_slug,
556 'frontblocks_section_license'
557 );
558 }
559
560 do_action( 'frontblocks_register_settings' );
561 }
562
563 /**
564 * Render settings page.
565 *
566 * @return void
567 */
568 public function render_page() {
569 if ( ! current_user_can( 'edit_theme_options' ) ) {
570 return;
571 }
572 ?>
573 <div class="frbl-settings-wrapper tw-min-h-screen tw-bg-gray-50 tw-py-8">
574 <div class="tw-max-w-5xl tw-mx-auto tw-px-4 sm:tw-px-6 lg:tw-px-8">
575 <!-- Header Section -->
576 <div class="tw-mb-8 frbl-animate-slide-in">
577 <div class="tw-flex tw-items-center tw-justify-between">
578 <div>
579 <h1 class="tw-text-3xl tw-font-bold tw-text-gray-900 tw-mb-2">
580 <?php echo esc_html__( 'FrontBlocks Settings', 'frontblocks' ); ?>
581 </h1>
582 <p class="tw-text-gray-600">
583 <?php echo esc_html__( 'Add visual enhancements to your website with FrontBlocks.', 'frontblocks' ); ?>
584 </p>
585 </div>
586 <div class="tw-flex tw-items-center tw-space-x-2">
587 <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">
588 <?php echo esc_html__( 'Version', 'frontblocks' ) . ' ' . esc_html( FRBL_VERSION ); ?>
589 </span>
590 </div>
591 </div>
592 </div>
593
594 <?php
595 // Show success message after settings are saved.
596 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
597 if ( isset( $_GET['settings-updated'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) ) :
598 ?>
599 <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);">
600 <div class="tw-flex">
601 <div class="tw-flex-shrink-0">
602 <svg class="tw-h-5 tw-w-5" style="color: #4ade80;" viewBox="0 0 20 20" fill="currentColor">
603 <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"/>
604 </svg>
605 </div>
606 <div class="tw-ml-3">
607 <p class="tw-text-sm tw-font-medium" style="color: #15803d; margin: 0;">
608 <?php esc_html_e( 'Changes saved successfully', 'frontblocks' ); ?>
609 </p>
610 </div>
611 </div>
612 </div>
613 <?php
614 endif;
615
616 // Show error message if saving failed.
617 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
618 if ( isset( $_GET['settings-error'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-error'] ) ) ) :
619 ?>
620 <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);">
621 <div class="tw-flex">
622 <div class="tw-flex-shrink-0">
623 <svg class="tw-h-5 tw-w-5" style="color: #f87171;" viewBox="0 0 20 20" fill="currentColor">
624 <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"/>
625 </svg>
626 </div>
627 <div class="tw-ml-3">
628 <p class="tw-text-sm tw-font-medium" style="color: #991b1b; margin: 0;">
629 <?php esc_html_e( 'Failed to save changes. Please try again.', 'frontblocks' ); ?>
630 </p>
631 </div>
632 </div>
633 </div>
634 <?php
635 endif;
636 ?>
637
638 <!-- Settings Form -->
639 <form method="post" action="options.php" class="tw-space-y-6">
640 <?php settings_fields( 'frontblocks_settings' ); ?>
641
642 <?php
643 // Get all sections for this page.
644 global $wp_settings_sections, $wp_settings_fields;
645
646 if ( ! isset( $wp_settings_sections[ $this->page_slug ] ) ) {
647 return;
648 }
649
650 foreach ( (array) $wp_settings_sections[ $this->page_slug ] as $section ) {
651 $this->render_settings_section( $section );
652 }
653 ?>
654
655 <!-- Submit Button -->
656 <div class="tw-flex tw-items-center tw-justify-between tw-pt-6 tw-border-t tw-border-gray-200">
657 <div class="tw-text-sm tw-text-gray-500">
658 <?php echo esc_html__( 'Changes will be applied immediately after saving.', 'frontblocks' ); ?>
659 </div>
660 <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">
661 <svg class="tw-w-5 tw-h-5 tw-mr-2 tw--ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
662 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
663 </svg>
664 <?php echo esc_html__( 'Save Settings', 'frontblocks' ); ?>
665 </button>
666 </div>
667 </form>
668
669 <!-- Footer Info -->
670 <div class="tw-mt-8 tw-text-center tw-text-sm tw-text-gray-500">
671 <?php
672 printf(
673 /* translators: %s: Close·marketing link */
674 esc_html__( 'Made with ❤️ by %s', 'frontblocks' ),
675 '<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>'
676 );
677 ?>
678 </div>
679 </div>
680 </div>
681 <?php
682 }
683
684 /**
685 * Active Blocks section callback.
686 *
687 * @return void
688 */
689 private function section_active_blocks_callback() {
690 ?>
691 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
692 <?php echo esc_html__( 'These blocks and features are always active and available in the block editor.', 'frontblocks' ); ?>
693 </p>
694 <div class="frbl-features-grid">
695 <?php
696 UI::show_info_card( 'animations', __( 'Animations', 'frontblocks' ), __( 'Add animations to any block using Animate.css', 'frontblocks' ) );
697 UI::show_info_card( 'carousel', __( 'Carousel/Slider', 'frontblocks' ), __( 'Transform any Grid block into a carousel or slider', 'frontblocks' ) );
698 UI::show_info_card( 'gallery', __( 'Native Gallery', 'frontblocks' ), __( 'Enhanced gallery block with carousel and masonry options', 'frontblocks' ) );
699 UI::show_info_card( 'sticky', __( 'Sticky Columns', 'frontblocks' ), __( 'Make Grid blocks sticky when scrolling', 'frontblocks' ) );
700 UI::show_info_card( 'insert_post', __( 'Insert Post Block', 'frontblocks' ), __( 'Display content from other posts, pages or custom post types', 'frontblocks' ) );
701 UI::show_info_card( 'counter', __( 'Counter Block', 'frontblocks' ), __( 'Display animated counters with start and end values', 'frontblocks' ) );
702 UI::show_info_card( 'reading_time', __( 'Reading Time Block', 'frontblocks' ), __( 'Show estimated reading time for posts', 'frontblocks' ) );
703 UI::show_info_card( 'product_categories', __( 'Product Categories Block', 'frontblocks' ), __( 'Display WooCommerce product categories', 'frontblocks' ) );
704 ?>
705 </div>
706 <?php
707 }
708
709 /**
710 * Features section callback.
711 *
712 * @return void
713 */
714 private function section_features_callback() {
715 ?>
716 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
717 <?php echo esc_html__( 'Enable or disable these optional features as needed.', 'frontblocks' ); ?>
718 </p>
719 <?php
720 }
721
722 /**
723 * Render a single settings section as a card.
724 *
725 * @param array $section Section data.
726 * @return void
727 */
728 private function render_settings_section( $section ) {
729 global $wp_settings_fields;
730
731 $has_fields = isset( $wp_settings_fields[ $this->page_slug ][ $section['id'] ] );
732
733 // Si no hay campos Y no hay callback, no renderizar nada.
734 if ( ! $has_fields && ! $section['callback'] ) {
735 return;
736 }
737
738 // Check if this is a section with callback only (like active_blocks).
739 $is_callback_only = ! $has_fields && $section['callback'];
740
741 // Check if this is the license section - render it full width.
742 $is_license_section = 'frontblocks_section_license' === $section['id'];
743
744 // Check if this is the custom post types section - render it full width.
745 $is_cpt_section = 'frontblocks_section_custom_post_types' === $section['id'];
746
747 if ( $is_callback_only ) {
748 // Render section with only callback (no fields).
749 ?>
750 <div class="frbl-section-wrapper">
751 <div class="frbl-section-header">
752 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
753 <?php echo esc_html( $section['title'] ); ?>
754 </h2>
755 </div>
756 <?php call_user_func( $section['callback'], $section ); ?>
757 </div>
758 <?php
759 return;
760 }
761
762 if ( $is_license_section || $is_cpt_section ) {
763 // Render license or CPT section as a full-width card.
764 ?>
765 <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">
766 <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">
767 <h2 class="tw-text-xl tw-font-semibold tw-text-gray-900">
768 <?php echo esc_html( $section['title'] ); ?>
769 </h2>
770 <?php
771 if ( $section['callback'] ) {
772 echo '<div class="tw-mt-2 tw-text-sm tw-text-gray-600">';
773 call_user_func( $section['callback'], $section );
774 echo '</div>';
775 }
776 ?>
777 </div>
778 <div class="tw-px-6 tw-py-5">
779 <?php
780 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
781 call_user_func( $field['callback'], $field['args'] );
782 }
783 ?>
784 </div>
785 </div>
786 <?php
787 } else {
788 // Render regular sections with feature grid.
789 ?>
790 <div class="frbl-section-wrapper">
791 <!-- Section Header -->
792 <div class="frbl-section-header">
793 <h2 class="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-0">
794 <?php echo esc_html( $section['title'] ); ?>
795 </h2>
796 <?php
797 if ( $section['callback'] ) {
798 echo '<div class="tw-text-sm tw-text-gray-600">';
799 call_user_func( $section['callback'], $section );
800 echo '</div>';
801 }
802 ?>
803 </div>
804
805 <!-- Features Grid -->
806 <div class="frbl-features-grid">
807 <?php
808 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
809 $this->render_settings_field( $field );
810 }
811 ?>
812 </div>
813 </div>
814 <?php
815 }
816 }
817
818 /**
819 * Render a single settings field as a card.
820 *
821 * @param array $field Field data.
822 * @return void
823 */
824 private function render_settings_field( $field ) {
825 // Determine if this is a PRO feature (always, regardless of license status).
826 $is_pro_feature = in_array(
827 $field['id'],
828 array(
829 $this->option_enable_gutenberg,
830 $this->option_enable_simple_prices_variable_products,
831 $this->option_enable_after_add_to_cart,
832 $this->option_deactivate_short_description,
833 $this->option_move_content_to_short_description,
834 $this->option_disable_zoom_images,
835 $this->option_add_share_buttons,
836 $this->option_deactivate_product_tabs,
837 $this->option_horizontal_product_form,
838 ),
839 true
840 );
841
842 // Apply PRO styling only if license is not valid.
843 $needs_license = $is_pro_feature && ! $this->is_license_valid;
844
845 // Get icon for this feature.
846 $icon = $this->get_feature_icon( $field['id'] );
847
848 ?>
849 <div class="frbl-feature-card <?php echo $needs_license ? 'frbl-feature-pro' : ''; ?>">
850 <?php if ( $is_pro_feature ) : ?>
851 <div class="frbl-pro-badge">PRO</div>
852 <?php endif; ?>
853
854 <div class="frbl-feature-content">
855 <div class="frbl-feature-icon">
856 <?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
857 </div>
858 <div class="frbl-feature-info">
859 <h3 class="frbl-feature-title">
860 <?php echo esc_html( $field['title'] ); ?>
861 </h3>
862 </div>
863 <div class="frbl-feature-toggle">
864 <?php call_user_func( $field['callback'], $field['args'] ); ?>
865 </div>
866 </div>
867 </div>
868 <?php
869 }
870
871 /**
872 * Get icon SVG for a feature.
873 *
874 * @param string $field_id Field ID.
875 * @return string SVG icon markup.
876 */
877 private function get_feature_icon( $field_id ) {
878 // Map field IDs to icon file names.
879 $icon_map = array(
880 $this->option_enable_testimonials => 'testimonials',
881 $this->option_enable_reading_progress => 'reading-progress',
882 $this->option_enable_back_button => 'back-button',
883 $this->option_enable_gutenberg => 'gutenberg',
884 $this->option_enable_simple_prices_variable_products => 'simple-prices',
885 $this->option_enable_after_add_to_cart => 'after-add-to-cart',
886 $this->option_deactivate_short_description => 'deactivate-description',
887 $this->option_move_content_to_short_description => 'move-content',
888 $this->option_disable_zoom_images => 'disable-zoom',
889 $this->option_add_share_buttons => 'share-buttons',
890 $this->option_deactivate_product_tabs => 'deactivate-tabs',
891 $this->option_horizontal_product_form => 'horizontal-form',
892 );
893
894 $icon_name = $icon_map[ $field_id ] ?? 'default';
895 $icon_path = FRBL_PLUGIN_PATH . 'assets/admin/icons/' . $icon_name . '.svg';
896
897 if ( file_exists( $icon_path ) ) {
898 $svg_content = file_get_contents( $icon_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
899 return $svg_content ? $svg_content : '';
900 }
901
902 return '';
903 }
904
905 /**
906 * PRO Features section description.
907 *
908 * @return void
909 */
910 public function section_woo_features_callback() {
911 if ( ! frbl_is_pro_active() ) {
912 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
913 echo '<div class="tw-flex">';
914 echo '<div class="tw-flex-shrink-0">';
915 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>';
916 echo '</div>';
917 echo '<div class="tw-ml-3">';
918 echo '<p class="tw-text-sm tw-text-blue-700">';
919 printf(
920 /* translators: %s: FrontBlocks PRO link */
921 esc_html__( 'These features require %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
922 '<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>'
923 );
924 echo '</p>';
925 echo '</div>';
926 echo '</div>';
927 echo '</div>';
928 } elseif ( ! $this->is_license_valid ) {
929 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
930 echo '<div class="tw-flex">';
931 echo '<div class="tw-flex-shrink-0">';
932 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>';
933 echo '</div>';
934 echo '<div class="tw-ml-3">';
935 echo '<p class="tw-text-sm tw-text-yellow-700">';
936 printf(
937 /* translators: %s: License section link */
938 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
939 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
940 );
941 echo '</p>';
942 echo '</div>';
943 echo '</div>';
944 echo '</div>';
945 } else {
946 ?>
947 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
948 <?php echo esc_html__( 'Advanced features for WooCommerce and more.', 'frontblocks' ); ?>
949 </p>
950 <?php
951 }
952 }
953
954 /**
955 * Render toggle field for enable testimonials.
956 *
957 * @return void
958 */
959 public function field_enable_testimonials() {
960 $options = get_option( 'frontblocks_settings', array() );
961 $enabled = (bool) ( $options[ $this->option_enable_testimonials ] ?? false );
962 ?>
963 <label class="frbl-toggle">
964 <input type="checkbox"
965 id="<?php echo esc_attr( $this->option_enable_testimonials ); ?>"
966 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_testimonials ); ?>]"
967 value="1"
968 <?php checked( true, $enabled ); ?>
969 />
970 <span></span>
971 </label>
972 <?php
973 }
974
975 /**
976 * Render toggle field for enable reading progress bar.
977 *
978 * @return void
979 */
980 public function field_enable_reading_progress() {
981 $options = get_option( 'frontblocks_settings', array() );
982 $enabled = (bool) ( $options[ $this->option_enable_reading_progress ] ?? false );
983 ?>
984 <label class="frbl-toggle">
985 <input type="checkbox"
986 id="<?php echo esc_attr( $this->option_enable_reading_progress ); ?>"
987 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_reading_progress ); ?>]"
988 value="1"
989 <?php checked( true, $enabled ); ?>
990 />
991 <span></span>
992 </label>
993 <?php
994 }
995
996 /**
997 * Render toggle field for enable back button.
998 *
999 * @return void
1000 */
1001 public function field_enable_back_button() {
1002 $options = get_option( 'frontblocks_settings', array() );
1003 $enabled = (bool) ( $options[ $this->option_enable_back_button ] ?? false );
1004 ?>
1005 <label class="frbl-toggle">
1006 <input type="checkbox"
1007 id="<?php echo esc_attr( $this->option_enable_back_button ); ?>"
1008 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_back_button ); ?>]"
1009 value="1"
1010 <?php checked( true, $enabled ); ?>
1011 />
1012 <span></span>
1013 </label>
1014 <?php
1015 }
1016
1017 /**
1018 * Render toggle field for enable events.
1019 *
1020 * @return void
1021 */
1022 public function field_enable_events() {
1023 $options = get_option( 'frontblocks_settings', array() );
1024 $enabled = (bool) ( $options[ $this->option_enable_events ] ?? false );
1025 $events_type = sanitize_text_field( $options[ $this->option_events_type ] ?? 'cpt' );
1026 ?>
1027 <!-- Toggle - stays in horizontal layout with icon and text -->
1028 <label class="frbl-toggle">
1029 <input type="checkbox"
1030 id="<?php echo esc_attr( $this->option_enable_events ); ?>"
1031 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_events ); ?>]"
1032 value="1"
1033 <?php checked( true, $enabled ); ?>
1034 />
1035 <span></span>
1036 </label>
1037
1038 <!-- Select and description - will be moved below the card by JavaScript -->
1039 <div id="events-type-wrapper" class="tw-mt-4" style="<?php echo $enabled ? 'width: 100%; min-width: 100%; display: block;' : 'display: none;'; ?>">
1040 <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">
1041 <?php echo esc_html__( 'Tipo de eventos', 'frontblocks' ); ?>
1042 </label>
1043 <select
1044 id="<?php echo esc_attr( $this->option_events_type ); ?>"
1045 name="frontblocks_settings[<?php echo esc_attr( $this->option_events_type ); ?>]"
1046 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"
1047 style="width: 100%; min-width: 100%; max-width: 100%; box-sizing: border-box;"
1048 >
1049 <option value="cpt" <?php selected( $events_type, 'cpt' ); ?>>
1050 <?php echo esc_html__( 'Custom Post Type (CPT)', 'frontblocks' ); ?>
1051 </option>
1052 <option value="posts" <?php selected( $events_type, 'posts' ); ?>>
1053 <?php echo esc_html__( 'Entradas de blog', 'frontblocks' ); ?>
1054 </option>
1055 </select>
1056 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1057 <?php echo esc_html__( 'Elige si los eventos se crearán en un CPT dedicado o en las entradas de blog normales.', 'frontblocks' ); ?>
1058 </p>
1059 </div>
1060 <?php
1061 }
1062
1063 /**
1064 * Render toggle field for enable Gutenberg in products (PRO).
1065 *
1066 * @return void
1067 */
1068 public function field_enable_gutenberg() {
1069 $this->render_pro_toggle( $this->option_enable_gutenberg );
1070 }
1071
1072 /**
1073 * Render toggle field for enable Simple Prices Variable Products (PRO).
1074 *
1075 * @return void
1076 */
1077 public function field_enable_simple_prices_variable_products() {
1078 $this->render_pro_toggle( $this->option_enable_simple_prices_variable_products );
1079 }
1080
1081 /**
1082 * Render After Add to Cart Block field.
1083 *
1084 * @return void
1085 */
1086 public function field_enable_after_add_to_cart() {
1087 $this->render_pro_toggle( $this->option_enable_after_add_to_cart );
1088 }
1089
1090 /**
1091 * Render Deactivate Short Description field.
1092 *
1093 * @return void
1094 */
1095 public function field_deactivate_short_description() {
1096 $this->render_pro_toggle( $this->option_deactivate_short_description );
1097 }
1098
1099 /**
1100 * Render Move Content to Short Description field.
1101 *
1102 * @return void
1103 */
1104 public function field_move_content_to_short_description() {
1105 $this->render_pro_toggle( $this->option_move_content_to_short_description );
1106 }
1107
1108 /**
1109 * Render Disable Zoom in Product Images field.
1110 *
1111 * @return void
1112 */
1113 public function field_disable_zoom_images() {
1114 $this->render_pro_toggle( $this->option_disable_zoom_images );
1115 }
1116
1117 /**
1118 * Render Add Share Buttons in Product Page field.
1119 *
1120 * @return void
1121 */
1122 public function field_add_share_buttons() {
1123 $this->render_pro_toggle( $this->option_add_share_buttons );
1124 }
1125
1126 /**
1127 * Render Deactivate Product Tabs field.
1128 *
1129 * @return void
1130 */
1131 public function field_deactivate_product_tabs() {
1132 $this->render_pro_toggle( $this->option_deactivate_product_tabs );
1133 }
1134
1135 /**
1136 * Render Horizontal Product Form Layout field.
1137 *
1138 * @return void
1139 */
1140 public function field_horizontal_product_form() {
1141 $this->render_pro_toggle( $this->option_horizontal_product_form );
1142 }
1143
1144 /**
1145 * Custom Post Types section callback.
1146 *
1147 * @return void
1148 */
1149 public function section_custom_post_types_callback() {
1150 if ( ! frbl_is_pro_active() ) {
1151 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
1152 echo '<div class="tw-flex">';
1153 echo '<div class="tw-flex-shrink-0">';
1154 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>';
1155 echo '</div>';
1156 echo '<div class="tw-ml-3">';
1157 echo '<p class="tw-text-sm tw-text-blue-700">';
1158 printf(
1159 /* translators: %s: FrontBlocks PRO link */
1160 esc_html__( 'This feature requires %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
1161 '<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>'
1162 );
1163 echo '</p>';
1164 echo '</div>';
1165 echo '</div>';
1166 echo '</div>';
1167 } elseif ( ! $this->is_license_valid ) {
1168 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
1169 echo '<div class="tw-flex">';
1170 echo '<div class="tw-flex-shrink-0">';
1171 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>';
1172 echo '</div>';
1173 echo '<div class="tw-ml-3">';
1174 echo '<p class="tw-text-sm tw-text-yellow-700">';
1175 printf(
1176 /* translators: %s: License section link */
1177 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
1178 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
1179 );
1180 echo '</p>';
1181 echo '</div>';
1182 echo '</div>';
1183 echo '</div>';
1184 } else {
1185 ?>
1186 <p class="tw-text-sm tw-text-gray-600 tw-mt-0 tw-mb-4">
1187 <?php echo esc_html__( 'Create and manage custom post types with advanced configuration options.', 'frontblocks' ); ?>
1188 </p>
1189 <?php
1190 }
1191 }
1192
1193 /**
1194 * Render toggle field for enable custom post types.
1195 *
1196 * @return void
1197 */
1198 public function field_enable_custom_post_types() {
1199 $options = get_option( 'frontblocks_settings', array() );
1200 $enabled = (bool) ( $options[ $this->option_enable_custom_post_types ] ?? false );
1201 $is_enabled = $this->is_license_valid;
1202 $disabled = ! $is_enabled ? 'disabled' : '';
1203 ?>
1204 <div class="frbl-custom-post-types-wrapper">
1205 <div class="tw-flex tw-items-center tw-justify-between tw-mb-4">
1206 <label for="<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>" class="tw-text-base tw-font-medium tw-text-gray-900">
1207 <?php echo esc_html__( 'Enable Custom Post Types Builder', 'frontblocks' ); ?>
1208 </label>
1209 <label class="frbl-toggle">
1210 <input type="checkbox"
1211 id="<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>"
1212 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_custom_post_types ); ?>]"
1213 value="1"
1214 <?php checked( true, $enabled ); ?>
1215 <?php echo esc_attr( $disabled ); ?>
1216 />
1217 <span></span>
1218 </label>
1219 </div>
1220
1221 <?php if ( $is_enabled ) : ?>
1222 <div id="frbl-cpt-builder" class="frbl-cpt-builder" style="<?php echo $enabled ? '' : 'display: none;'; ?>">
1223 <div class="tw-mt-4 tw-p-4 tw-bg-gray-50 tw-rounded-lg tw-border tw-border-gray-200">
1224 <label for="frbl-cpt-name" class="tw-block tw-text-sm tw-font-medium tw-text-gray-700 tw-mb-2">
1225 <?php echo esc_html__( 'Post Type Name', 'frontblocks' ); ?>
1226 </label>
1227 <div class="tw-flex tw-gap-2">
1228 <input
1229 type="text"
1230 id="frbl-cpt-name"
1231 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"
1232 placeholder="<?php echo esc_attr__( 'e.g., Portfolio, Team, Services', 'frontblocks' ); ?>"
1233 />
1234 <button
1235 type="button"
1236 id="frbl-create-cpt-btn"
1237 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"
1238 >
1239 <?php echo esc_html__( 'Crear', 'frontblocks' ); ?>
1240 </button>
1241 </div>
1242 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1243 <?php echo esc_html__( 'Enter a singular name for your custom post type (e.g., "Portfolio" will create "portfolio" post type).', 'frontblocks' ); ?>
1244 </p>
1245 </div>
1246
1247 <?php do_action( 'frontblocks_render_existing_cpts' ); ?>
1248 </div>
1249 <?php endif; ?>
1250 </div>
1251 <?php
1252 }
1253
1254 /**
1255 * License section description.
1256 *
1257 * @return void
1258 */
1259 public function section_license_callback() {
1260 echo '<p>' . esc_html__( 'Manage your FrontBlocks PRO license.', 'frontblocks' ) . '</p>';
1261 }
1262
1263 /**
1264 * Render license key field.
1265 *
1266 * Uses wp-plugin-license-manager library.
1267 *
1268 * @return void
1269 */
1270 public function field_license_key() {
1271 // Get license data from FrontBlocks PRO.
1272 $license_status = function_exists( 'frblp_get_license_status' ) ? frblp_get_license_status() : 'inactive';
1273 $license_key = function_exists( 'frblp_get_stored_license_key' ) ? frblp_get_stored_license_key() : '';
1274
1275 $status_text = '';
1276 $status_class = '';
1277 $status_icon = '';
1278
1279 switch ( $license_status ) {
1280 case 'active':
1281 $status_text = __( 'Active', 'frontblocks' );
1282 $status_class = 'tw-bg-green-100 tw-text-green-800 tw-border-green-300';
1283 $status_icon = '<svg class="tw-w-5 tw-h-5" fill="currentColor" viewBox="0 0 20 20"><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"/></svg>';
1284 break;
1285 case 'expired':
1286 $status_text = __( 'Expired', 'frontblocks' );
1287 $status_class = 'tw-bg-red-100 tw-text-red-800 tw-border-red-300';
1288 $status_icon = '<svg class="tw-w-5 tw-h-5" fill="currentColor" viewBox="0 0 20 20"><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"/></svg>';
1289 break;
1290 default: // inactive.
1291 $status_text = __( 'Not Activated', 'frontblocks' );
1292 $status_class = 'tw-bg-yellow-100 tw-text-yellow-800 tw-border-yellow-300';
1293 $status_icon = '<svg class="tw-w-5 tw-h-5" 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>';
1294 break;
1295 }
1296 ?>
1297 </form>
1298 <form method="post" action="options.php" class="tw-mt-0">
1299 <?php settings_fields( 'frontblocks-pro_license' ); ?>
1300 <div class="tw-space-y-4" id="frblp-license-section">
1301 <!-- License Key Input -->
1302 <div>
1303 <label for="frontblocks-pro_license_apikey" class="tw-block tw-text-sm tw-font-medium tw-text-gray-900 tw-mb-2">
1304 <?php echo esc_html__( 'License Key', 'frontblocks' ); ?>
1305 </label>
1306 <div class="tw-flex tw-gap-2">
1307 <input type="text"
1308 id="frontblocks-pro_license_apikey"
1309 name="frontblocks-pro_license_apikey"
1310 value="<?php echo esc_attr( $license_key ); ?>"
1311 placeholder="<?php echo esc_attr__( 'Enter your license key', 'frontblocks' ); ?>"
1312 class="tw-flex-1 tw-px-4 tw-py-3 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"
1313 <?php echo 'active' === $license_status ? 'readonly' : ''; ?>
1314 />
1315 <?php if ( 'active' === $license_status ) : ?>
1316 <label class="tw-flex tw-items-center tw-gap-2 tw-px-4 tw-py-2 tw-bg-red-50 tw-border tw-border-red-200 tw-rounded-lg tw-cursor-pointer hover:tw-bg-red-100 tw-transition-colors">
1317 <input type="checkbox" name="frontblocks-pro_license_deactivate_checkbox" value="on" class="tw-rounded tw-border-red-300 tw-text-red-600 focus:tw-ring-red-500" />
1318 <span class="tw-text-sm tw-font-medium tw-text-red-700"><?php echo esc_html__( 'Deactivate', 'frontblocks' ); ?></span>
1319 </label>
1320 <?php endif; ?>
1321 </div>
1322 <p class="tw-text-xs tw-text-gray-500 tw-mt-2">
1323 <?php echo esc_html__( 'Enter your license key from your purchase confirmation email.', 'frontblocks' ); ?>
1324 </p>
1325 </div>
1326
1327 <!-- License Status -->
1328 <div>
1329 <label class="tw-block tw-text-sm tw-font-medium tw-text-gray-900 tw-mb-2">
1330 <?php echo esc_html__( 'License Status', 'frontblocks' ); ?>
1331 </label>
1332 <div id="frblp_license_status" class="tw-flex tw-items-center tw-gap-3 tw-px-4 tw-py-3 tw-border tw-rounded-lg <?php echo esc_attr( $status_class ); ?>">
1333 <span class="tw-flex-shrink-0">
1334 <?php echo $status_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
1335 </span>
1336 <span class="tw-font-semibold tw-text-base">
1337 <?php echo esc_html( $status_text ); ?>
1338 </span>
1339 </div>
1340 </div>
1341
1342 <!-- Help Text -->
1343 <?php if ( empty( $license_key ) ) : ?>
1344 <div class="tw-p-4 tw-rounded-lg tw-bg-gray-50 tw-border tw-border-gray-200">
1345 <p class="tw-text-sm tw-text-gray-600">
1346 <?php
1347 printf(
1348 /* translators: %s: purchase link */
1349 esc_html__( 'Don\'t have a license? %s to get started.', 'frontblocks' ),
1350 '<a href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings-license" target="_blank" rel="noopener noreferrer" class="tw-text-primary-500 hover:tw-text-primary-600 tw-font-medium">' . esc_html__( 'Purchase FrontBlocks PRO', 'frontblocks' ) . '</a>'
1351 );
1352 ?>
1353 </p>
1354 </div>
1355 <?php endif; ?>
1356
1357 <?php if ( 'expired' === $license_status ) : ?>
1358 <div class="tw-p-4 tw-rounded-lg tw-bg-red-50 tw-border tw-border-red-200">
1359 <p class="tw-text-sm tw-text-red-700">
1360 <?php
1361 printf(
1362 /* translators: %s: renewal link */
1363 esc_html__( 'Your license has expired. %s to continue receiving updates and support.', 'frontblocks' ),
1364 '<a href="https://close.technology/my-account/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=renew-license" target="_blank" rel="noopener noreferrer" class="tw-font-medium tw-underline hover:tw-no-underline">' . esc_html__( 'Renew your license', 'frontblocks' ) . '</a>'
1365 );
1366 ?>
1367 </p>
1368 </div>
1369 <?php endif; ?>
1370
1371 <!-- Submit Button for License -->
1372 <div class="tw-pt-4">
1373 <button type="submit" name="submit_license" class="tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-border tw-border-transparent tw-text-sm 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">
1374 <?php echo 'active' === $license_status ? esc_html__( 'Update License', 'frontblocks' ) : esc_html__( 'Activate License', 'frontblocks' ); ?>
1375 </button>
1376 </div>
1377 </div>
1378 <?php
1379 }
1380
1381 /**
1382 * Helper method to render PRO toggle fields.
1383 *
1384 * @param string $option_key Option key.
1385 * @return void
1386 */
1387 private function render_pro_toggle( $option_key ) {
1388 $options = get_option( 'frontblocks_settings', array() );
1389 $enabled = (bool) ( $options[ $option_key ] ?? false );
1390 $is_enabled = $this->is_license_valid;
1391 $disabled = ! $is_enabled ? 'disabled' : '';
1392 ?>
1393 <label class="frbl-toggle">
1394 <input type="checkbox"
1395 id="<?php echo esc_attr( $option_key ); ?>"
1396 name="frontblocks_settings[<?php echo esc_attr( $option_key ); ?>]"
1397 value="1"
1398 <?php checked( true, $enabled ); ?>
1399 <?php echo esc_attr( $disabled ); ?>
1400 />
1401 <span></span>
1402 </label>
1403 <?php
1404 }
1405
1406 /**
1407 * Sanitize settings array.
1408 *
1409 * @param array $value Raw value.
1410 * @return array
1411 */
1412 public function sanitize_settings( $value ) {
1413 // Nonce verification.
1414 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
1415 if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'frontblocks_settings-options' ) ) {
1416 add_settings_error( 'frontblocks_settings', 'frontblocks_settings_nonce', esc_html__( 'Security check failed. Please try again.', 'frontblocks' ), 'error' );
1417
1418 return get_option( 'frontblocks_settings', array() );
1419 }
1420
1421 if ( ! is_array( $value ) ) {
1422 return array();
1423 }
1424
1425 $sanitized = array();
1426 foreach ( $value as $key => $val ) {
1427 if ( $this->option_enable_testimonials === $key || $this->option_enable_reading_progress === $key || $this->option_enable_back_button === $key || $this->option_enable_events === $key || $this->option_enable_gutenberg === $key || $this->option_enable_simple_prices_variable_products === $key || $this->option_enable_after_add_to_cart === $key || $this->option_deactivate_short_description === $key || $this->option_move_content_to_short_description === $key || $this->option_disable_zoom_images === $key || $this->option_add_share_buttons === $key || $this->option_deactivate_product_tabs === $key || $this->option_horizontal_product_form === $key || $this->option_enable_custom_post_types === $key ) {
1428 $sanitized[ $key ] = (bool) $val;
1429 } elseif ( $this->option_events_type === $key ) {
1430 // Sanitize events type: only allow 'cpt' or 'posts'.
1431 $sanitized[ $key ] = in_array( $val, array( 'cpt', 'posts' ), true ) ? $val : 'cpt';
1432 }
1433 }
1434
1435 // Ensure mutual exclusion: if both description options are enabled, keep only the last one changed.
1436 if ( ! empty( $sanitized[ $this->option_deactivate_short_description ] ) && ! empty( $sanitized[ $this->option_move_content_to_short_description ] ) ) {
1437 // Get current saved values to determine which one was just changed.
1438 $current_options = get_option( 'frontblocks_settings', array() );
1439 $current_deactivate = ! empty( $current_options[ $this->option_deactivate_short_description ] );
1440 $current_move = ! empty( $current_options[ $this->option_move_content_to_short_description ] );
1441
1442 // If deactivate was already on, turn it off (move is the new one).
1443 if ( $current_deactivate ) {
1444 $sanitized[ $this->option_deactivate_short_description ] = false;
1445 } else {
1446 // Otherwise turn off move (deactivate is the new one).
1447 $sanitized[ $this->option_move_content_to_short_description ] = false;
1448 }
1449 }
1450
1451 do_action( 'frontblocks_sanitize_settings', $sanitized );
1452
1453 return $sanitized;
1454 }
1455 }
1456