PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.3
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.3
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
← All changes | admin/modules/settings/class-wpfnl-settings.php +137 -214 3.12.12 → 3.13.3 View file →
@@ -9,9 +9,8 @@
9 9
10 10 use WPFunnels\Admin\Module\Wpfnl_Admin_Module;
11 11 use WPFunnels\Rollback;
12 12 use WPFunnels\Traits\SingletonTrait;
13 -use WPFunnels\Wpfnl;
14 13 use WPFunnels\Wpfnl_functions;
15 14
16 15 class Module extends Wpfnl_Admin_Module
17 16 {
@@ -20,93 +19,10 @@
20 19 protected $validations;
21 20
22 21 protected $prefix = '_wpfunnels_';
23 22
24 - protected $general_settings;
25 -
26 - protected $permalink_settings;
27 -
28 - protected $optin_settings;
29 -
30 - protected $offer_settings;
31 -
32 - protected $user_roles;
33 -
34 - protected $gtm_events;
35 -
36 - protected $gtm_settings;
37 -
38 - protected $facebook_pixel_events;
39 -
40 - protected $facebook_pixel_settings;
41 -
42 - protected $recaptcha_settings;
43 -
44 - protected $utm_params;
45 -
46 - protected $utm_settings;
47 -
48 - protected $is_allow_sales;
49 -
50 - /**
51 - * User Roles Settings.
52 - *
53 - * @var array $user_roles_settings An associative array containing user roles settings.
54 - */
55 - protected $user_roles_settings;
56 -
57 - /**
58 - * Notification Settings.
59 - *
60 - * @var array $notification_settings An associative array containing notification settings.
61 - */
62 - protected $notification_settings;
63 -
64 -
65 - /**
66 - * Google Maps API Key.
67 - *
68 - * This property stores the API key used for accessing Google Maps services.
69 - *
70 - * @var string $google_map_api_key The Google Maps API key.
71 - */
72 - protected $google_map_api_key;
73 -
74 - /**
75 - * Page hooks
76 - *
77 - * @var array
78 - */
79 - private $page_hooks = [
80 - 'toplevel_page_wp_funnels',
81 - 'wp-funnels_page_wp_funnel_settings',
82 - 'wpfunnels_page_wpfnl_settings',
83 - 'wp-funnels_page_edit_funnel',
84 - 'wp-funnels_page_create_funnel',
85 - 'wp-funnels_page_wpfnl_settings',
86 - 'wp-funnels_page_wpf-license',
87 - 'wpfunnels_page_email-builder',
88 - ];
89 -
90 - protected $settings_meta_keys = [
91 - '_wpfunnels_funnel_type' => 'sales',
92 - '_wpfunnels_builder' => 'elementor',
93 - '_wpfunnels_paypal_reference' => '',
94 - '_wpfunnels_order_bump' => '',
95 - '_wpfunnels_ab_testing' => '',
96 - '_wpfunnels_allow_funnels' => [
97 - 'administrator' => true,
98 - ],
99 - '_wpfunnels_permalink_settings' => '',
100 - '_wpfunnels_optin_settings' => '',
101 - '_wpfunnels_permalink_step_base' => 'wpfunnels',
102 - '_wpfunnels_permalink_flow_base' => 'step',
103 - '_wpfunnels_set_permalink' => 'step',
104 - ];
105 -
106 23 public function __construct()
107 24 {
108 - add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
109 25 add_action('admin_init', [$this, 'after_permalink_settings_saved']);
110 26 add_action( 'admin_post_wpfunnels_rollback', [ $this, 'post_wpfunnels_rollback' ] );
111 27 add_action( 'wpfunnels_send_revenue_report_email', [ $this, 'send_revenue_report_email' ] );
112 28 $this->init_ajax();
@@ -111,50 +27,36 @@
111 27 add_action( 'wpfunnels_send_revenue_report_email', [ $this, 'send_revenue_report_email' ] );
112 28 $this->init_ajax();
113 29 }
114 30
115 - public function is_wc_installed()
31 + /**
32 + * Render the settings screen.
33 + *
34 + * The screen is a route of the React admin now — `?page=wpfunnels#/settings`
35 + * — which admin_init sends this page to (see
36 + * Wpfnl_Menus::maybe_redirect_to_react_admin). Printing the app's root here
37 + * keeps the page working for anyone who filters that redirect off; there is
38 + * no server-rendered settings markup any more.
39 + *
40 + * @return void
41 + * @since 1.0.0
42 + */
43 + public function get_view()
116 44 {
117 - $path = 'woocommerce/woocommerce.php';
118 - $plugins = get_plugins();
45 + /*
46 + * The app reads its route from the fragment, which never reaches the
47 + * server — so landing here with no fragment would open the dashboard.
48 + * The bundle is a footer script, so this still prints before it runs.
49 + */
50 + wp_add_inline_script(
51 + 'wpfnl-react-admin',
52 + "if ( ! window.location.hash ) { window.location.hash = '#/settings'; }",
53 + 'before'
54 + );
119 55
120 - return isset($plugins[ $path ]);
56 + require WPFNL_ADMIN_DIR . 'views/react-admin.php';
121 57 }
122 58
123 - public function is_ff_installed()
124 - {
125 - $path = 'fluentform/fluentform.php';
126 - $plugins = get_plugins();
127 -
128 - return isset($plugins[ $path ]);
129 - }
130 -
131 - public function is_elementor_installed()
132 - {
133 - $path = 'elementor/elementor.php';
134 - $plugins = get_plugins();
135 -
136 - return isset($plugins[ $path ]);
137 - }
138 -
139 -
140 - public function enqueue_scripts( $hook )
141 - {
142 - if ( ! in_array( $hook, $this->page_hooks, true ) ) {
143 - return;
144 - }
145 - wp_enqueue_script('settings', plugin_dir_url(__FILE__) . 'js/settings.js', ['jquery'], WPFNL_VERSION, true);
146 - }
147 -
148 -
149 - public function get_view()
150 - {
151 - $this->init_settings();
152 - $is_pro_activated = Wpfnl_functions::is_wpfnl_pro_activated();
153 - $global_funnel_type = Wpfnl_functions::get_global_funnel_type();
154 - require_once WPFNL_DIR . '/admin/modules/settings/views/view.php';
155 - }
156 -
157 59 /**
158 60 * Init ajax hooks for
159 61 * saving metas
160 62 *
@@ -161,8 +63,17 @@
161 63 * @since 1.0.0
162 64 */
163 65 public function init_ajax()
164 66 {
67 + /*
68 + * Only the two actions that are still posted to from outside this module
69 + * are registered: the Vue template library's sync button
70 + * (`clear-templates`) and the setup wizard (`update-general-settings`).
71 + * Everything the settings screen itself used to post goes through
72 + * wpfunnels/v1/settings/* now — see
73 + * includes/core/rest-api/Controllers/class-settings-page-controller.php,
74 + * which calls the very same handlers below.
75 + */
165 76 $this->validations = [
166 77 'logged_in' => true,
167 78 'user_can' => 'wpf_manage_funnels',
168 79 ];
@@ -173,23 +84,8 @@
173 84 wp_ajax_helper()->handle('clear-templates')
174 85 ->with_callback([ $this, 'clear_templates_data' ])
175 86 ->with_validation($this->validations);
176 87
177 - wp_ajax_helper()->handle('clear-transient')
178 - ->with_callback([ $this, 'clear_transient_cache_data' ])
179 - ->with_validation($this->validations);
180 -
181 - wp_ajax_helper()->handle('wpfnl-show-log')
182 - ->with_callback([ $this, 'wpfnl_show_log' ])
183 - ->with_validation($this->validations);
184 -
185 - wp_ajax_helper()->handle('wpfnl-delete-log')
186 - ->with_callback([ $this, 'wpfnl_delete_log' ])
187 - ->with_validation($this->validations);
188 -
189 - wp_ajax_helper()->handle('wpfnl-send-test-notification')
190 - ->with_callback([ $this, 'send_test_notification' ])
191 - ->with_validation($this->validations);
192 88 }
193 89
194 90
195 91 /**
@@ -203,10 +99,12 @@
203 99 */
204 100 public function update_general_settings($payload)
205 101 {
206 102 do_action('wpfunnels/before_settings_saved', $payload);
103 +
104 + $this->save_ai_settings($payload);
105 + $existing_settings = Wpfnl_functions::get_general_settings();
207 106 $general_settings = [
208 - 'funnel_type' => sanitize_text_field($payload['funnel_type']),
209 107 'builder' => sanitize_text_field($payload['builder']),
210 108 'uninstall_cleanup' => isset($payload['uninstall_cleanup']) ? sanitize_text_field($payload['uninstall_cleanup']) : 'off',
211 109 'disable_analytics' => isset($payload['analytics_roles']) ? $payload['analytics_roles'] : '',
212 110 'allow_funnels' => isset($payload['permission_role']) ? $payload['permission_role'] : [],
@@ -249,9 +147,8 @@
249 147
250 148
251 149 foreach ($payload as $key => $value) {
252 150 switch ($key) {
253 - case 'funnel_type':
254 151 case 'builder':
255 152 $cache_key = 'wpfunnels_remote_template_data_' . WPFNL_VERSION;
256 153 delete_transient($cache_key);
257 154 delete_option(WPFNL_TEMPLATES_OPTION_KEY);
@@ -270,8 +167,9 @@
270 167 Wpfnl_functions::update_admin_settings($this->prefix.'general_settings', $general_settings);
271 168 Wpfnl_functions::update_admin_settings($this->prefix.'permalink_settings', $permalink_settings);
272 169 Wpfnl_functions::update_admin_settings($this->prefix.'optin_settings', $optin_settings);
273 170 $this->save_recaptcha_settings($payload);
171 + $this->save_tracking_consent($payload);
274 172 $this->save_user_role_management_data($payload);
275 173 $this->save_google_map_key($payload);
276 174 $this->save_lms_settings($payload);
277 175 $this->save_notification_settings($payload);
@@ -292,8 +190,54 @@
292 190 ];
293 191 }
294 192
295 193 /**
194 + * Persist the usage-tracking opt-in from the Advanced panel.
195 + *
196 + * The toggle only sends a value when the user actually changed it (see
197 + * getTrackingConsentChange() in js/settings.js), so an unrelated save on a
198 + * site that has never answered the setup-wizard question leaves that
199 + * "undecided" state alone instead of writing an explicit "no". Consent
200 + * itself lives in the Linno telemetry option, not in general settings, and
201 + * the SDK migrates the legacy `wpfunnels_allow_tracking` key on read — so
202 + * existing users keep whatever they agreed to during onboarding.
203 + *
204 + * @param array $payload Settings payload from the save request.
205 + *
206 + * @since 3.13.0
207 + */
208 + private function save_tracking_consent( $payload ) {
209 + if ( ! isset( $payload['allow_tracking'] ) ) {
210 + return;
211 + }
212 +
213 + $submitted = sanitize_text_field( $payload['allow_tracking'] );
214 +
215 + if ( ! in_array( $submitted, [ 'yes', 'no' ], true ) ) {
216 + return;
217 + }
218 +
219 + $consent = 'yes' === $submitted;
220 +
221 + $telemetry = \WPFunnels\Tracking\Telemetry::instance();
222 +
223 + if ( ! $telemetry ) {
224 + return;
225 + }
226 +
227 + // Nothing to do when the toggle already matches what is stored — this
228 + // also stops a redundant save from turning "never decided" into "no".
229 + if ( $telemetry->get_consent_state() === ( $consent ? 'yes' : 'no' ) ) {
230 + return;
231 + }
232 +
233 + $telemetry->save_consent( $consent );
234 +
235 + do_action( 'wpfunnels_tracking_consent_updated', $consent );
236 + }
237 +
238 +
239 + /**
296 240 * Save Facebook pixel settings
297 241 *
298 242 * @param $settings
299 243 *
@@ -433,42 +377,9 @@
433 377 *
434 378 * @since 3.2.0
435 379 */
436 380 private function schedule_revenue_report_email( $settings ) {
437 - if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
438 - return;
439 - }
440 -
441 - // Calculate next run time based on send_time
442 - $send_time = isset( $settings['send_time'] ) ? $settings['send_time'] : '10:00';
443 - $frequency = isset( $settings['revenue_report_frequency'] ) ? $settings['revenue_report_frequency'] : 'weekly';
444 -
445 - // Parse time (format: HH:MM)
446 - list( $hour, $minute ) = explode( ':', $send_time );
447 -
448 - // Get current time
449 - $now = current_time( 'timestamp' );
450 - $today = strtotime( date( 'Y-m-d', $now ) );
451 -
452 - // Calculate first run time (today at specified time)
453 - $first_run = $today + ( $hour * HOUR_IN_SECONDS ) + ( $minute * MINUTE_IN_SECONDS );
454 -
455 - // If the time has already passed today, schedule for tomorrow
456 - if ( $first_run <= $now ) {
457 - $first_run += DAY_IN_SECONDS;
458 - }
459 -
460 - // Calculate interval based on frequency
461 - $interval = ( $frequency === 'monthly' ) ? MONTH_IN_SECONDS : WEEK_IN_SECONDS;
462 -
463 - // Schedule recurring action
464 - as_schedule_recurring_action(
465 - $first_run,
466 - $interval,
467 - 'wpfunnels_send_revenue_report_email',
468 - array(),
469 - 'wpfunnels'
470 - );
381 + Wpfnl_functions::schedule_revenue_report_email( $settings );
471 382 }
472 383
473 384
474 385 /**
@@ -476,44 +387,69 @@
476 387 *
477 388 * @since 3.2.0
478 389 */
479 390 private function unschedule_revenue_report_email() {
480 - if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
391 + Wpfnl_functions::unschedule_revenue_report_email();
392 + }
393 +
394 +
395 + /**
396 + * Persist the AI Assistant fields that ride along with the settings save.
397 + *
398 + * Keys are stored through AISettings::connect(), which encrypts them. A
399 + * blank field means "keep the stored key", so an unrelated settings save
400 + * never wipes a connection.
401 + *
402 + * @param array $payload Settings payload.
403 + *
404 + * @since 3.13.0
405 + * @return void
406 + */
407 + private function save_ai_settings( $payload ) {
408 + if ( ! class_exists( '\WPFunnels\AI\Settings\AISettings' ) ) {
481 409 return;
482 410 }
483 -
484 - as_unschedule_all_actions( 'wpfunnels_send_revenue_report_email', array(), 'wpfunnels' );
485 - }
486 411
412 + $settings = '\WPFunnels\AI\Settings\AISettings';
487 413
488 - /**
489 - * Initialize all the settings value
490 - *
491 - * @since 1.0.0
492 - */
493 - public function init_settings()
494 - {
495 - $this->general_settings = Wpfnl_functions::get_general_settings();
496 - $this->is_allow_sales = Wpfnl_functions::maybe_allow_sales_funnel();
497 - $this->permalink_settings = Wpfnl_functions::get_permalink_settings();
498 - $this->optin_settings = Wpfnl_functions::get_optin_settings();
499 - $this->offer_settings = Wpfnl_functions::get_offer_settings();
500 - $this->user_roles = Wpfnl_functions::get_user_roles();
501 - $this->gtm_events = Wpfnl_functions::get_gtm_events();
502 - $this->gtm_settings = Wpfnl_functions::get_gtm_settings();
503 - $this->facebook_pixel_events = Wpfnl_functions::get_facebook_events();
504 - $this->facebook_pixel_settings = Wpfnl_functions::get_facebook_pixel_settings();
505 - $this->utm_params = Wpfnl_functions::get_utm_params();
506 - $this->utm_settings = Wpfnl_functions::get_utm_settings();
507 - $this->recaptcha_settings = Wpfnl_functions::get_recaptcha_settings();
508 - $this->user_roles_settings = Wpfnl_functions::get_user_role_settings();
509 - $this->google_map_api_key = Wpfnl_functions::get_google_map_api_key();
510 - $this->notification_settings = Wpfnl_functions::get_notification_settings();
511 - }
414 + foreach ( array( 'anthropic', 'openai', 'gemini' ) as $provider ) {
415 + $field = 'ai_' . $provider . '_key';
416 + $key = isset( $payload[ $field ] ) ? trim( sanitize_text_field( $payload[ $field ] ) ) : '';
512 417
418 + // An untouched field arrives blank, and the placeholder mask must
419 + // never be stored as a credential.
420 + if ( '' === $key || false !== strpos( $key, '•' ) ) {
421 + continue;
422 + }
513 423
424 + call_user_func( array( $settings, 'connect' ), $provider, $key );
425 + }
514 426
427 + if ( isset( $payload['ai_provider'] ) && '' !== $payload['ai_provider'] ) {
428 + $provider = sanitize_text_field( $payload['ai_provider'] );
515 429
430 + // wordpress_ai needs no key; activating it is the whole connection.
431 + if ( 'wordpress_ai' === $provider && ! call_user_func( array( $settings, 'isConnected' ), $provider ) ) {
432 + call_user_func( array( $settings, 'connect' ), $provider, '' );
433 + }
434 +
435 + call_user_func( array( $settings, 'setActiveProvider' ), $provider );
436 + }
437 +
438 + if ( isset( $payload['ai_custom_instructions'] ) ) {
439 + call_user_func( array( $settings, 'saveCustomInstructions' ), sanitize_textarea_field( $payload['ai_custom_instructions'] ) );
440 + }
441 +
442 + if ( isset( $payload['ai_enabled'] ) ) {
443 + call_user_func( array( $settings, 'setEnabled' ), 'yes' === $payload['ai_enabled'] );
444 + }
445 +
446 + if ( isset( $payload['mcp_enabled'] ) ) {
447 + update_option( '_wpfnl_mcp_enabled', 'yes' === $payload['mcp_enabled'] ? 'yes' : 'no' );
448 + }
449 + }
450 +
451 +
516 452 /**
517 453 * Clear saved templates data
518 454 *
519 455 * @param $payload
@@ -582,21 +518,8 @@
582 518 }
583 519 }
584 520 }
585 521
586 -
587 - /**
588 - * Get settings by meta key
589 - *
590 - * @param $key
591 - *
592 - * @return mixed|string
593 - * @since 1.0.0
594 - */
595 - public function get_settings_by_key($key)
596 - {
597 - return isset($this->settings_meta_keys[$key]) ? $this->settings_meta_keys[$key]: '';
598 - }
599 522
600 523 public function get_name()
601 524 {
602 525 return __('settings','wpfnl');