PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / 1.2.0
FrontBlocks for Gutenberg/GeneratePress v1.2.0
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 8 months ago
Settings.php
949 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 /**
16 * Settings class
17 */
18 class Settings {
19
20 /**
21 * Option key for testimonials feature.
22 *
23 * @var string
24 */
25 private $option_enable_testimonials = 'enable_testimonials';
26
27 /**
28 * Option key for Gutenberg in products (PRO).
29 *
30 * @var string
31 */
32 private $option_enable_gutenberg = 'enable_gutenberg';
33
34 /**
35 * Option key for Simple Prices Variable Products (PRO).
36 *
37 * @var string
38 */
39 private $option_enable_simple_prices_variable_products = 'enable_simple_prices_variable_products';
40
41 /**
42 * Option key for After Add to Cart Block (PRO).
43 *
44 * @var string
45 */
46 private $option_enable_after_add_to_cart = 'enable_after_add_to_cart';
47
48 /**
49 * Option key for deactivate short description (PRO).
50 *
51 * @var string
52 */
53 private $option_deactivate_short_description = 'deactivate_short_description';
54
55 /**
56 * Option key for move content to short description (PRO).
57 *
58 * @var string
59 */
60 private $option_move_content_to_short_description = 'move_content_to_short_description';
61
62 /**
63 * Option key for disable zoom in WooCommerce images (PRO).
64 *
65 * @var string
66 */
67 private $option_disable_zoom_images = 'disable_zoom_images';
68
69 /**
70 * Option key for add share buttons in product page (PRO).
71 *
72 * @var string
73 */
74 private $option_add_share_buttons = 'add_share_buttons';
75
76 /**
77 * Option key for deactivate product tabs (PRO).
78 *
79 * @var string
80 */
81 private $option_deactivate_product_tabs = 'deactivate_product_tabs';
82
83 /**
84 * Page slug.
85 *
86 * @var string
87 */
88 private $page_slug = 'frontblocks-settings';
89
90 /**
91 * Is license valid.
92 *
93 * @var bool
94 */
95 private $is_license_valid = false;
96
97 /**
98 * Option key for license key.
99 *
100 * @var string
101 */
102 private $option_license_key;
103
104 /**
105 * Option key for product ID.
106 *
107 * @var string
108 */
109 private $option_product_id;
110
111 /**
112 * Constructor.
113 */
114 public function __construct() {
115 global $frontblocks_pro_license;
116 $this->is_license_valid = ! empty( $frontblocks_pro_license ) && $frontblocks_pro_license->get_api_key_status( true );
117
118 $this->option_license_key = ! empty( $frontblocks_pro_license ) ? $frontblocks_pro_license->get_option_key( 'apikey' ) : '';
119 $this->option_product_id = ! empty( $frontblocks_pro_license ) ? $frontblocks_pro_license->get_option_key( 'product_id' ) : '';
120
121 add_action( 'admin_menu', array( $this, 'register_menu' ) );
122 add_action( 'admin_init', array( $this, 'register_settings' ) );
123 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
124 }
125
126 /**
127 * Enqueue admin styles for settings page.
128 *
129 * @param string $hook Current admin page hook.
130 * @return void
131 */
132 public function enqueue_admin_styles( $hook ) {
133 if ( 'appearance_page_' . $this->page_slug !== $hook ) {
134 return;
135 }
136
137 wp_enqueue_style(
138 'frontblocks-admin-settings',
139 FRBL_PLUGIN_URL . 'assets/admin/settings.css',
140 array(),
141 FRBL_VERSION
142 );
143
144 wp_add_inline_script(
145 'jquery',
146 "
147 document.addEventListener('DOMContentLoaded', function() {
148 const deactivateCheckbox = document.getElementById('deactivate_short_description');
149 const moveContentCheckbox = document.getElementById('move_content_to_short_description');
150
151 if (!deactivateCheckbox || !moveContentCheckbox) return;
152
153 function updateMutualExclusion() {
154 const deactivateWrapper = deactivateCheckbox.closest('.tw-flex');
155 const moveContentWrapper = moveContentCheckbox.closest('.tw-flex');
156
157 // Check if license is valid (not just PRO active).
158 const isLicenseValid = " . ( $this->is_license_valid ? 'true' : 'false' ) . ";
159
160 if (deactivateCheckbox.checked) {
161 moveContentCheckbox.disabled = true;
162 if (moveContentWrapper) {
163 moveContentWrapper.style.opacity = '0.5';
164 moveContentWrapper.style.filter = 'grayscale(100%)';
165 const toggle = moveContentWrapper.querySelector('.frbl-toggle');
166 if (toggle) {
167 toggle.style.borderColor = '#ef4444';
168 toggle.style.opacity = '0.7';
169 }
170 }
171 } else {
172 moveContentCheckbox.disabled = !isLicenseValid;
173 if (moveContentWrapper) {
174 moveContentWrapper.style.opacity = isLicenseValid ? '1' : '0.5';
175 moveContentWrapper.style.filter = '';
176 const toggle = moveContentWrapper.querySelector('.frbl-toggle');
177 if (toggle) {
178 toggle.style.borderColor = '';
179 toggle.style.opacity = '';
180 }
181 }
182 }
183
184 if (moveContentCheckbox.checked) {
185 deactivateCheckbox.disabled = true;
186 if (deactivateWrapper) {
187 deactivateWrapper.style.opacity = '0.5';
188 deactivateWrapper.style.filter = 'grayscale(100%)';
189 const toggle = deactivateWrapper.querySelector('.frbl-toggle');
190 if (toggle) {
191 toggle.style.borderColor = '#ef4444';
192 toggle.style.opacity = '0.7';
193 }
194 }
195 } else {
196 deactivateCheckbox.disabled = !isLicenseValid;
197 if (deactivateWrapper) {
198 deactivateWrapper.style.opacity = isLicenseValid ? '1' : '0.5';
199 deactivateWrapper.style.filter = '';
200 const toggle = deactivateWrapper.querySelector('.frbl-toggle');
201 if (toggle) {
202 toggle.style.borderColor = '';
203 toggle.style.opacity = '';
204 }
205 }
206 }
207 }
208
209 deactivateCheckbox.addEventListener('change', updateMutualExclusion);
210 moveContentCheckbox.addEventListener('change', updateMutualExclusion);
211
212 updateMutualExclusion();
213 });
214 "
215 );
216 }
217
218 /**
219 * Register options page under Appearance.
220 *
221 * @return void
222 */
223 public function register_menu() {
224 add_theme_page(
225 __( 'FrontBlocks Settings', 'frontblocks' ),
226 __( 'FrontBlocks', 'frontblocks' ),
227 'edit_theme_options',
228 $this->page_slug,
229 array( $this, 'render_page' )
230 );
231 }
232
233 /**
234 * Register settings, sections and fields.
235 *
236 * @return void
237 */
238 public function register_settings() {
239 global $frontblocks_pro_license;
240 register_setting(
241 'frontblocks_settings',
242 'frontblocks_settings',
243 array(
244 'type' => 'array',
245 'sanitize_callback' => array( $this, 'sanitize_settings' ),
246 'default' => array(),
247 'show_in_rest' => false,
248 )
249 );
250
251 add_settings_section(
252 'frontblocks_section_features',
253 __( 'Features', 'frontblocks' ),
254 function () {
255 echo '<p>' . esc_html__( 'Visual enhancements for your website.', 'frontblocks' ) . '</p>';
256 },
257 $this->page_slug
258 );
259
260 add_settings_field(
261 $this->option_enable_testimonials,
262 __( 'Enable testimonials', 'frontblocks' ),
263 array( $this, 'field_enable_testimonials' ),
264 $this->page_slug,
265 'frontblocks_section_features'
266 );
267
268 // PRO Features section.
269 add_settings_section(
270 'frontblocks_section_woocommerce_features',
271 __( 'PRO Features', 'frontblocks' ),
272 array( $this, 'section_pro_features_callback' ),
273 $this->page_slug
274 );
275
276 add_settings_field(
277 $this->option_enable_gutenberg,
278 __( 'Enable Gutenberg in Products', 'frontblocks' ),
279 array( $this, 'field_enable_gutenberg' ),
280 $this->page_slug,
281 'frontblocks_section_woocommerce_features'
282 );
283
284 add_settings_field(
285 $this->option_enable_simple_prices_variable_products,
286 __( 'Enable Simple Prices Variable Products', 'frontblocks' ),
287 array( $this, 'field_enable_simple_prices_variable_products' ),
288 $this->page_slug,
289 'frontblocks_section_woocommerce_features'
290 );
291
292 add_settings_field(
293 $this->option_enable_after_add_to_cart,
294 __( 'Enable After Add to Cart Block', 'frontblocks' ),
295 array( $this, 'field_enable_after_add_to_cart' ),
296 $this->page_slug,
297 'frontblocks_section_woocommerce_features'
298 );
299
300 add_settings_field(
301 $this->option_deactivate_short_description,
302 __( 'Deactivate Short Description', 'frontblocks' ),
303 array( $this, 'field_deactivate_short_description' ),
304 $this->page_slug,
305 'frontblocks_section_woocommerce_features'
306 );
307
308 add_settings_field(
309 $this->option_move_content_to_short_description,
310 __( 'Move Content to Short Description', 'frontblocks' ),
311 array( $this, 'field_move_content_to_short_description' ),
312 $this->page_slug,
313 'frontblocks_section_woocommerce_features'
314 );
315
316 add_settings_field(
317 $this->option_disable_zoom_images,
318 __( 'Disable Zoom in Product Images', 'frontblocks' ),
319 array( $this, 'field_disable_zoom_images' ),
320 $this->page_slug,
321 'frontblocks_section_woocommerce_features'
322 );
323
324 add_settings_field(
325 $this->option_add_share_buttons,
326 __( 'Add Share Buttons in Product Page', 'frontblocks' ),
327 array( $this, 'field_add_share_buttons' ),
328 $this->page_slug,
329 'frontblocks_section_woocommerce_features'
330 );
331
332 add_settings_field(
333 $this->option_deactivate_product_tabs,
334 __( 'Deactivate Product Tabs', 'frontblocks' ),
335 array( $this, 'field_deactivate_product_tabs' ),
336 $this->page_slug,
337 'frontblocks_section_woocommerce_features'
338 );
339
340 // License section (only if PRO is active).
341 if ( frbl_is_pro_active() && ! empty( $frontblocks_pro_license ) ) {
342 add_settings_section(
343 'frontblocks_section_license',
344 __( 'License', 'frontblocks' ),
345 array( $this, 'section_license_callback' ),
346 $this->page_slug
347 );
348
349 add_settings_field(
350 $frontblocks_pro_license->get_option_key( 'apikey' ),
351 __( 'License Information', 'frontblocks' ),
352 array( $this, 'field_license_key' ),
353 $this->page_slug,
354 'frontblocks_section_license'
355 );
356 }
357
358 do_action( 'frontblocks_register_settings' );
359 }
360
361 /**
362 * Render settings page.
363 *
364 * @return void
365 */
366 public function render_page() {
367 if ( ! current_user_can( 'edit_theme_options' ) ) {
368 return;
369 }
370 ?>
371 <div class="frbl-settings-wrapper tw-min-h-screen tw-bg-gray-50 tw-py-8">
372 <div class="tw-max-w-5xl tw-mx-auto tw-px-4 sm:tw-px-6 lg:tw-px-8">
373 <!-- Header Section -->
374 <div class="tw-mb-8 frbl-animate-slide-in">
375 <div class="tw-flex tw-items-center tw-justify-between">
376 <div>
377 <h1 class="tw-text-3xl tw-font-bold tw-text-gray-900 tw-mb-2">
378 <?php echo esc_html__( 'FrontBlocks Settings', 'frontblocks' ); ?>
379 </h1>
380 <p class="tw-text-gray-600">
381 <?php echo esc_html__( 'Add visual enhancements to your website with FrontBlocks.', 'frontblocks' ); ?>
382 </p>
383 </div>
384 <div class="tw-flex tw-items-center tw-space-x-2">
385 <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">
386 <?php echo esc_html__( 'Version', 'frontblocks' ) . ' ' . esc_html( FRBL_VERSION ); ?>
387 </span>
388 </div>
389 </div>
390 </div>
391
392 <?php
393 // Show success message after settings are saved.
394 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
395 if ( isset( $_GET['settings-updated'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) ) :
396 ?>
397 <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);">
398 <div class="tw-flex">
399 <div class="tw-flex-shrink-0">
400 <svg class="tw-h-5 tw-w-5" style="color: #4ade80;" viewBox="0 0 20 20" fill="currentColor">
401 <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"/>
402 </svg>
403 </div>
404 <div class="tw-ml-3">
405 <p class="tw-text-sm tw-font-medium" style="color: #15803d; margin: 0;">
406 <?php esc_html_e( 'Changes saved successfully', 'frontblocks' ); ?>
407 </p>
408 </div>
409 </div>
410 </div>
411 <?php
412 endif;
413
414 // Show error message if saving failed.
415 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
416 if ( isset( $_GET['settings-error'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-error'] ) ) ) :
417 ?>
418 <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);">
419 <div class="tw-flex">
420 <div class="tw-flex-shrink-0">
421 <svg class="tw-h-5 tw-w-5" style="color: #f87171;" viewBox="0 0 20 20" fill="currentColor">
422 <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"/>
423 </svg>
424 </div>
425 <div class="tw-ml-3">
426 <p class="tw-text-sm tw-font-medium" style="color: #991b1b; margin: 0;">
427 <?php esc_html_e( 'Failed to save changes. Please try again.', 'frontblocks' ); ?>
428 </p>
429 </div>
430 </div>
431 </div>
432 <?php
433 endif;
434 ?>
435
436 <!-- Settings Form -->
437 <form method="post" action="options.php" class="tw-space-y-6">
438 <?php settings_fields( 'frontblocks_settings' ); ?>
439
440 <?php
441 // Get all sections for this page.
442 global $wp_settings_sections, $wp_settings_fields;
443
444 if ( ! isset( $wp_settings_sections[ $this->page_slug ] ) ) {
445 return;
446 }
447
448 foreach ( (array) $wp_settings_sections[ $this->page_slug ] as $section ) {
449 $this->render_settings_section( $section );
450 }
451 ?>
452
453 <!-- Submit Button -->
454 <div class="tw-flex tw-items-center tw-justify-between tw-pt-6 tw-border-t tw-border-gray-200">
455 <div class="tw-text-sm tw-text-gray-500">
456 <?php echo esc_html__( 'Changes will be applied immediately after saving.', 'frontblocks' ); ?>
457 </div>
458 <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">
459 <svg class="tw-w-5 tw-h-5 tw-mr-2 tw--ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
460 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
461 </svg>
462 <?php echo esc_html__( 'Save Settings', 'frontblocks' ); ?>
463 </button>
464 </div>
465 </form>
466
467 <!-- Footer Info -->
468 <div class="tw-mt-8 tw-text-center tw-text-sm tw-text-gray-500">
469 <?php
470 printf(
471 /* translators: %s: Close·marketing link */
472 esc_html__( 'Made with ❤️ by %s', 'frontblocks' ),
473 '<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>'
474 );
475 ?>
476 </div>
477 </div>
478 </div>
479 <?php
480 }
481
482 /**
483 * Render a single settings section as a card.
484 *
485 * @param array $section Section data.
486 * @return void
487 */
488 private function render_settings_section( $section ) {
489 global $wp_settings_fields;
490
491 if ( ! isset( $wp_settings_fields[ $this->page_slug ][ $section['id'] ] ) ) {
492 return;
493 }
494 ?>
495 <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">
496 <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">
497 <h2 class="tw-text-xl tw-font-semibold tw-text-gray-900">
498 <?php echo esc_html( $section['title'] ); ?>
499 </h2>
500 <?php
501 if ( $section['callback'] ) {
502 echo '<div class="tw-mt-2 tw-text-sm tw-text-gray-600">';
503 call_user_func( $section['callback'], $section );
504 echo '</div>';
505 }
506 ?>
507 </div>
508 <div class="tw-px-6 tw-py-5">
509 <div class="tw-space-y-6">
510 <?php
511 foreach ( (array) $wp_settings_fields[ $this->page_slug ][ $section['id'] ] as $field ) {
512 $this->render_settings_field( $field );
513 }
514 ?>
515 </div>
516 </div>
517 </div>
518 <?php
519 }
520
521 /**
522 * Render a single settings field.
523 *
524 * @param array $field Field data.
525 * @return void
526 */
527 private function render_settings_field( $field ) {
528 ?>
529 <div class="tw-flex tw-items-start">
530 <div class="tw-flex-grow">
531 <label class="tw-block tw-text-sm tw-font-medium tw-text-gray-900 tw-mb-2">
532 <?php echo esc_html( $field['title'] ); ?>
533 </label>
534 <div class="tw-mt-1">
535 <?php call_user_func( $field['callback'], $field['args'] ); ?>
536 </div>
537 </div>
538 </div>
539 <?php
540 }
541
542 /**
543 * PRO Features section description.
544 *
545 * @return void
546 */
547 public function section_pro_features_callback() {
548 global $frontblocks_pro_license;
549 if ( ! frbl_is_pro_active() ) {
550 echo '<div class="tw-bg-blue-50 tw-border-l-4 tw-border-blue-400 tw-p-4 tw-mb-4">';
551 echo '<div class="tw-flex">';
552 echo '<div class="tw-flex-shrink-0">';
553 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>';
554 echo '</div>';
555 echo '<div class="tw-ml-3">';
556 echo '<p class="tw-text-sm tw-text-blue-700">';
557 printf(
558 /* translators: %s: FrontBlocks PRO link */
559 esc_html__( 'These features require %s. Upgrade to unlock advanced functionality.', 'frontblocks' ),
560 '<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>'
561 );
562 echo '</p>';
563 echo '</div>';
564 echo '</div>';
565 echo '</div>';
566 } elseif ( ! $this->is_license_valid ) {
567 echo '<div class="tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400 tw-p-4 tw-mb-4">';
568 echo '<div class="tw-flex">';
569 echo '<div class="tw-flex-shrink-0">';
570 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>';
571 echo '</div>';
572 echo '<div class="tw-ml-3">';
573 echo '<p class="tw-text-sm tw-text-yellow-700">';
574 printf(
575 /* translators: %s: License section link */
576 esc_html__( 'License is not activated. Please activate your license in the %s section below to enable these features.', 'frontblocks' ),
577 '<a href="#frontblocks_section_license" class="tw-font-medium tw-underline">' . esc_html__( 'License', 'frontblocks' ) . '</a>'
578 );
579 echo '</p>';
580 echo '</div>';
581 echo '</div>';
582 echo '</div>';
583 } else {
584 echo '<p>' . esc_html__( 'Advanced features for WooCommerce and more.', 'frontblocks' ) . '</p>';
585 }
586 }
587
588 /**
589 * Render toggle field for enable testimonials.
590 *
591 * @return void
592 */
593 public function field_enable_testimonials() {
594 $options = get_option( 'frontblocks_settings', array() );
595 $enabled = (bool) ( $options[ $this->option_enable_testimonials ] ?? false );
596 ?>
597 <div class="tw-flex tw-items-center tw-justify-between">
598 <div class="tw-flex-grow">
599 <p class="tw-mt-1 tw-text-sm tw-text-gray-500">
600 <?php echo esc_html__( 'Create and manage testimonials in the WordPress admin.', 'frontblocks' ); ?>
601 </p>
602 </div>
603 <label class="frbl-toggle">
604 <input type="checkbox"
605 id="<?php echo esc_attr( $this->option_enable_testimonials ); ?>"
606 name="frontblocks_settings[<?php echo esc_attr( $this->option_enable_testimonials ); ?>]"
607 value="1"
608 <?php checked( true, $enabled ); ?>
609 />
610 <span></span>
611 </label>
612 </div>
613 <?php
614 }
615
616 /**
617 * Render toggle field for enable Gutenberg in products (PRO).
618 *
619 * @return void
620 */
621 public function field_enable_gutenberg() {
622 $this->render_pro_toggle(
623 $this->option_enable_gutenberg,
624 __( 'Use the Gutenberg block editor for WooCommerce products.', 'frontblocks' )
625 );
626 }
627
628 /**
629 * Render toggle field for enable Simple Prices Variable Products (PRO).
630 *
631 * @return void
632 */
633 public function field_enable_simple_prices_variable_products() {
634 $this->render_pro_toggle(
635 $this->option_enable_simple_prices_variable_products,
636 __( 'Replaces the price range with "From" + minimum price on variable products.', 'frontblocks' )
637 );
638 }
639
640 /**
641 * Render After Add to Cart Block field.
642 *
643 * @return void
644 */
645 public function field_enable_after_add_to_cart() {
646 $this->render_pro_toggle(
647 $this->option_enable_after_add_to_cart,
648 __( 'Display custom content after the Add to Cart button on WooCommerce product pages.', 'frontblocks' )
649 );
650 }
651
652 /**
653 * Render Deactivate Short Description field.
654 *
655 * @return void
656 */
657 public function field_deactivate_short_description() {
658 $this->render_pro_toggle(
659 $this->option_deactivate_short_description,
660 __( 'Remove the short description from product pages.', 'frontblocks' )
661 );
662 }
663
664 /**
665 * Render Move Content to Short Description field.
666 *
667 * @return void
668 */
669 public function field_move_content_to_short_description() {
670 $this->render_pro_toggle(
671 $this->option_move_content_to_short_description,
672 __( 'Display the main product content in place of the short description and remove the description tab.', 'frontblocks' )
673 );
674 }
675
676 /**
677 * Render Disable Zoom in Product Images field.
678 *
679 * @return void
680 */
681 public function field_disable_zoom_images() {
682 $this->render_pro_toggle(
683 $this->option_disable_zoom_images,
684 __( 'Disable the zoom effect on WooCommerce product images.', 'frontblocks' )
685 );
686 }
687
688 /**
689 * Render Add Share Buttons in Product Page field.
690 *
691 * @return void
692 */
693 public function field_add_share_buttons() {
694 $this->render_pro_toggle(
695 $this->option_add_share_buttons,
696 __( 'Add social share buttons (Facebook, Twitter, WhatsApp, Email) at the end of product meta section.', 'frontblocks' )
697 );
698 }
699
700 /**
701 * Render Deactivate Product Tabs field.
702 *
703 * @return void
704 */
705 public function field_deactivate_product_tabs() {
706 $this->render_pro_toggle(
707 $this->option_deactivate_product_tabs,
708 __( 'Remove all product tabs (Description, Additional Information, Reviews) from single product pages.', 'frontblocks' )
709 );
710 }
711
712 /**
713 * License section description.
714 *
715 * @return void
716 */
717 public function section_license_callback() {
718 echo '<p>' . esc_html__( 'Manage your FrontBlocks PRO license.', 'frontblocks' ) . '</p>';
719 }
720
721 /**
722 * Render license key field.
723 *
724 * @return void
725 */
726 public function field_license_key() {
727 global $frontblocks_pro_license;
728 $license_key = get_option( $this->option_license_key );
729 $product_id = get_option( $this->option_product_id );
730 ?>
731 <div class="tw-space-y-4">
732 <!-- License Key and Product ID Fields in a row -->
733 <div class="tw-flex tw-w-full">
734 <!-- License Key Field - 66.6% (2/3) -->
735 <div style="flex: 4 1 0%;">
736 <input type="text"
737 id="<?php echo esc_attr( $this->option_license_key ); ?>"
738 name="<?php echo esc_attr( $this->option_license_key ); ?>"
739 value="<?php echo esc_attr( $license_key ); ?>"
740 placeholder="<?php echo esc_attr__( 'Enter your license key', 'frontblocks' ); ?>"
741 class="tw-block tw-w-full 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"
742 />
743 </div>
744
745 <!-- Product ID Field - 33.3% (1/3) -->
746 <div style="flex: 1 1 0%;">
747 <input type="text"
748 id="<?php echo esc_attr( $this->option_product_id ); ?>"
749 name="<?php echo esc_attr( $this->option_product_id ); ?>"
750 value="<?php echo esc_attr( $product_id ); ?>"
751 placeholder="<?php echo esc_attr__( 'Product ID', 'frontblocks' ); ?>"
752 title="<?php echo esc_attr__( 'Product ID - You can find this in your purchase confirmation email.', 'frontblocks' ); ?>"
753 class="tw-block tw-w-full 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"
754 />
755 </div>
756 </div>
757
758 <!-- Help Text for Product ID -->
759 <p class="tw-text-xs tw-text-gray-500 tw-mt-1">
760 <?php echo esc_html__( 'Enter your license key and product ID. You can find both in your purchase confirmation email.', 'frontblocks' ); ?>
761 </p>
762
763 <!-- License Status Field (Read-only) -->
764 <div>
765 <label class="tw-block tw-text-sm tw-font-medium tw-text-gray-900 tw-mb-2">
766 <?php echo esc_html__( 'License Status', 'frontblocks' ); ?>
767 </label>
768 <?php
769 $status_text = '';
770 $status_class = '';
771 $status_icon = '';
772 global $frontblocks_pro_license;
773 $license_data = $frontblocks_pro_license->license_key_status( true );
774 $license_status = empty( $license_data ) ? 'not_activated' : $license_data['status_check'];
775
776 switch ( $license_status ) {
777 case 'active':
778 $status_text = __( 'Active', 'frontblocks' );
779 $status_class = 'tw-bg-green-100 tw-text-green-800 tw-border-green-300';
780 $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>';
781 break;
782 case 'expired':
783 $status_text = __( 'Expired', 'frontblocks' );
784 $status_class = 'tw-bg-red-100 tw-text-red-800 tw-border-red-300';
785 $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>';
786 break;
787 default:
788 $status_text = __( 'Not Activated', 'frontblocks' );
789 $status_class = 'tw-bg-yellow-100 tw-text-yellow-800 tw-border-yellow-300';
790 $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>';
791 break;
792 }
793 ?>
794 <div 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 ); ?>">
795 <span class="tw-flex-shrink-0">
796 <?php echo $status_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
797 </span>
798 <span class="tw-font-semibold tw-text-base">
799 <?php echo esc_html( $status_text ); ?>
800 </span>
801 <?php if ( ! empty( $license_data['expires'] ) && 'valid' === $license_data['status'] ) : ?>
802 <span class="tw-ml-auto tw-text-sm">
803 <?php
804 printf(
805 /* translators: %s: expiration date */
806 esc_html__( 'Expires: %s', 'frontblocks' ),
807 esc_html( $license_data['expires'] )
808 );
809 ?>
810 </span>
811 <?php endif; ?>
812 </div>
813 </div>
814
815 <!-- Help Text -->
816 <?php if ( empty( $license_key ) && empty( $product_id ) ) : ?>
817 <div class="tw-p-4 tw-rounded-lg tw-bg-gray-50 tw-border tw-border-gray-200">
818 <p class="tw-text-sm tw-text-gray-600">
819 <?php
820 printf(
821 /* translators: %s: purchase link */
822 esc_html__( 'Don\'t have a license? %s to get started.', 'frontblocks' ),
823 '<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>'
824 );
825 ?>
826 </p>
827 </div>
828 <?php endif; ?>
829
830 <?php if ( 'expired' === $license_status ) : ?>
831 <div class="tw-p-3 tw-rounded-lg tw-bg-red-50 tw-border tw-border-red-200">
832 <p class="tw-text-sm tw-text-red-700">
833 <?php
834 printf(
835 /* translators: %s: renewal link */
836 esc_html__( 'Your license has expired. %s to continue receiving updates and support.', 'frontblocks' ),
837 '<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>'
838 );
839 ?>
840 </p>
841 </div>
842 <?php endif; ?>
843 </div>
844 <?php
845 }
846
847 /**
848 * Helper method to render PRO toggle fields.
849 *
850 * @param string $option_key Option key.
851 * @param string $description Field description.
852 * @return void
853 */
854 private function render_pro_toggle( $option_key, $description ) {
855 $options = get_option( 'frontblocks_settings', array() );
856 $enabled = (bool) ( $options[ $option_key ] ?? false );
857 $is_enabled = $this->is_license_valid;
858 $disabled = ! $is_enabled ? 'disabled' : '';
859 $opacity_cls = ! $is_enabled ? 'tw-opacity-50' : '';
860 ?>
861 <div class="tw-flex tw-items-center tw-justify-between <?php echo esc_attr( $opacity_cls ); ?>">
862 <div class="tw-flex-grow tw-pr-4">
863 <div class="tw-flex tw-items-center tw-gap-2">
864 <p class="tw-text-sm tw-text-gray-500">
865 <?php echo esc_html( $description ); ?>
866 </p>
867 <?php if ( ! $is_enabled ) : ?>
868 <a href="https://close.technology/wordpress-plugins/frontblocks-pro/?utm_source=frontblocks&utm_medium=plugin&utm_campaign=settings-badge"
869 target="_blank"
870 rel="noopener noreferrer"
871 class="tw-inline-flex tw-items-center tw-px-3 tw-py-1 tw-ml-2 tw-rounded tw-text-xs tw-font-semibold tw-bg-primary-100 tw-text-primary-700 hover:tw-bg-primary-200 tw-transition-colors tw-no-underline"
872 title="<?php echo esc_attr__( 'Upgrade to FrontBlocks PRO', 'frontblocks' ); ?>">
873 PRO
874 </a>
875 <?php endif; ?>
876 </div>
877 </div>
878 <label class="frbl-toggle">
879 <input type="checkbox"
880 id="<?php echo esc_attr( $option_key ); ?>"
881 name="frontblocks_settings[<?php echo esc_attr( $option_key ); ?>]"
882 value="1"
883 <?php checked( true, $enabled ); ?>
884 <?php echo esc_attr( $disabled ); ?>
885 />
886 <span></span>
887 </label>
888 </div>
889 <?php
890 }
891
892 /**
893 * Sanitize settings array.
894 *
895 * @param array $value Raw value.
896 * @return array
897 */
898 public function sanitize_settings( $value ) {
899 // Nonce verification.
900 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
901 if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'frontblocks_settings-options' ) ) {
902 add_settings_error( 'frontblocks_settings', 'frontblocks_settings_nonce', esc_html__( 'Security check failed. Please try again.', 'frontblocks' ), 'error' );
903
904 return get_option( 'frontblocks_settings', array() );
905 }
906
907 if ( ! is_array( $value ) ) {
908 return array();
909 }
910
911 $sanitized = array();
912 foreach ( $value as $key => $val ) {
913 if ( $this->option_enable_testimonials === $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 ) {
914 $sanitized[ $key ] = (bool) $val;
915 }
916 }
917
918 // Ensure mutual exclusion: if both description options are enabled, keep only the last one changed.
919 if ( ! empty( $sanitized[ $this->option_deactivate_short_description ] ) && ! empty( $sanitized[ $this->option_move_content_to_short_description ] ) ) {
920 // Get current saved values to determine which one was just changed.
921 $current_options = get_option( 'frontblocks_settings', array() );
922 $current_deactivate = ! empty( $current_options[ $this->option_deactivate_short_description ] );
923 $current_move = ! empty( $current_options[ $this->option_move_content_to_short_description ] );
924
925 // If deactivate was already on, turn it off (move is the new one).
926 if ( $current_deactivate ) {
927 $sanitized[ $this->option_deactivate_short_description ] = false;
928 } else {
929 // Otherwise turn off move (deactivate is the new one).
930 $sanitized[ $this->option_move_content_to_short_description ] = false;
931 }
932 }
933
934 // Save license key and product id.
935 global $frontblocks_pro_license;
936 if ( ! empty( $frontblocks_pro_license ) ) {
937 $result = $frontblocks_pro_license->sanitize_fields_license( $_POST );
938
939 if ( 'ok' === $result['status'] ) {
940 add_settings_error( 'frontblocks_settings', 'frontblocks_settings_license', $result['message'], 'updated' );
941 } else {
942 add_settings_error( 'frontblocks_settings', 'frontblocks_settings_license', $result['message'], 'error' );
943 }
944 }
945
946 return $sanitized;
947 }
948 }
949