PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / trunk
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell vtrunk
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 2.8.17 2.8.18 All 259 releases
wpfunnels / admin / modules / settings / class-wpfnl-settings.php

class-wpfnl-settings.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell trunk, at admin/modules/settings/class-wpfnl-settings.php

1,222 lines 45.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Settings module
4 *
5 * @package
6 */
7
8 namespace WPFunnels\Modules\Admin\Settings;
9
10 use WPFunnels\Admin\Module\Wpfnl_Admin_Module;
11 use WPFunnels\Rollback;
12 use WPFunnels\Traits\SingletonTrait;
13 use WPFunnels\Wpfnl;
14 use WPFunnels\Wpfnl_functions;
15
16 class Module extends Wpfnl_Admin_Module
17 {
18 use SingletonTrait;
19
20 protected $validations;
21
22 protected $prefix = '_wpfunnels_';
23
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_builder' => 'elementor',
92 '_wpfunnels_paypal_reference' => '',
93 '_wpfunnels_order_bump' => '',
94 '_wpfunnels_ab_testing' => '',
95 '_wpfunnels_allow_funnels' => [
96 'administrator' => true,
97 ],
98 '_wpfunnels_permalink_settings' => '',
99 '_wpfunnels_optin_settings' => '',
100 '_wpfunnels_permalink_step_base' => 'wpfunnels',
101 '_wpfunnels_permalink_flow_base' => 'step',
102 '_wpfunnels_set_permalink' => 'step',
103 ];
104
105 public function __construct()
106 {
107 add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
108 add_action('admin_init', [$this, 'after_permalink_settings_saved']);
109 add_action( 'admin_post_wpfunnels_rollback', [ $this, 'post_wpfunnels_rollback' ] );
110 add_action( 'wpfunnels_send_revenue_report_email', [ $this, 'send_revenue_report_email' ] );
111 $this->init_ajax();
112 }
113
114 public function is_wc_installed()
115 {
116 $path = 'woocommerce/woocommerce.php';
117 $plugins = get_plugins();
118
119 return isset($plugins[ $path ]);
120 }
121
122 public function is_ff_installed()
123 {
124 $path = 'fluentform/fluentform.php';
125 $plugins = get_plugins();
126
127 return isset($plugins[ $path ]);
128 }
129
130 public function is_elementor_installed()
131 {
132 $path = 'elementor/elementor.php';
133 $plugins = get_plugins();
134
135 return isset($plugins[ $path ]);
136 }
137
138
139 public function enqueue_scripts( $hook )
140 {
141 if ( ! in_array( $hook, $this->page_hooks, true ) ) {
142 return;
143 }
144 wp_enqueue_script('settings', plugin_dir_url(__FILE__) . 'js/settings.js', ['jquery'], WPFNL_VERSION, true);
145 }
146
147
148 public function get_view()
149 {
150 $this->init_settings();
151 $is_pro_activated = Wpfnl_functions::is_wpfnl_pro_activated();
152 require_once WPFNL_DIR . '/admin/modules/settings/views/view.php';
153 }
154
155 /**
156 * Init ajax hooks for
157 * saving metas
158 *
159 * @since 1.0.0
160 */
161 public function init_ajax()
162 {
163 $this->validations = [
164 'logged_in' => true,
165 'user_can' => 'wpf_manage_funnels',
166 ];
167 wp_ajax_helper()->handle('update-general-settings')
168 ->with_callback([ $this, 'update_general_settings' ])
169 ->with_validation($this->validations);
170
171 wp_ajax_helper()->handle('clear-templates')
172 ->with_callback([ $this, 'clear_templates_data' ])
173 ->with_validation($this->validations);
174
175 wp_ajax_helper()->handle('clear-transient')
176 ->with_callback([ $this, 'clear_transient_cache_data' ])
177 ->with_validation($this->validations);
178
179 wp_ajax_helper()->handle('wpfnl-show-log')
180 ->with_callback([ $this, 'wpfnl_show_log' ])
181 ->with_validation($this->validations);
182
183 wp_ajax_helper()->handle('wpfnl-delete-log')
184 ->with_callback([ $this, 'wpfnl_delete_log' ])
185 ->with_validation($this->validations);
186
187 wp_ajax_helper()->handle('wpfnl-send-test-notification')
188 ->with_callback([ $this, 'send_test_notification' ])
189 ->with_validation($this->validations);
190 }
191
192
193 /**
194 * Update handler for settings
195 * page
196 *
197 * @param $payload
198 *
199 * @return array
200 * @since 1.0.0
201 */
202 public function update_general_settings($payload)
203 {
204 do_action('wpfunnels/before_settings_saved', $payload);
205
206 $this->save_ai_settings($payload);
207 $existing_settings = Wpfnl_functions::get_general_settings();
208 $general_settings = [
209 'builder' => sanitize_text_field($payload['builder']),
210 'uninstall_cleanup' => isset($payload['uninstall_cleanup']) ? sanitize_text_field($payload['uninstall_cleanup']) : 'off',
211 'disable_analytics' => isset($payload['analytics_roles']) ? $payload['analytics_roles'] : '',
212 'allow_funnels' => isset($payload['permission_role']) ? $payload['permission_role'] : [],
213 'paypal_reference' => isset($payload['paypal_reference']) ? sanitize_text_field($payload['paypal_reference']) : 'off',
214 'order_bump' => isset($payload['order_bump']) ? sanitize_text_field($payload['order_bump']) : 'off',
215 'ab_testing' => isset($payload['ab_testing']) ? sanitize_text_field($payload['ab_testing']) : 'off',
216 'disable_theme_style' => isset($payload['disable_theme_style']) ? sanitize_text_field($payload['disable_theme_style']) : 'off',
217 'enable_log_status' => isset($payload['enable_log_status']) ? sanitize_text_field($payload['enable_log_status']) : 'off',
218 'enable_skip_cart' => isset($payload['enable_skip_cart']) ? $payload['enable_skip_cart'] : 'no',
219 'skip_cart_for' => isset($payload['skip_cart_for']) ? $payload['skip_cart_for'] : 'whole',
220 'funnel_builder_mode' => isset($payload['funnel_builder_mode']) ? sanitize_text_field($payload['funnel_builder_mode']) : Wpfnl_functions::get_default_funnel_builder_mode(),
221 ];
222
223 if ( isset( $payload['funnel_builder_mode'] ) ) {
224 // Choosing the site default here is an explicit statement of intent, so
225 // drop this user's own canvas override — otherwise a view they toggled
226 // earlier would keep winning over the mode they just picked.
227 delete_user_meta( get_current_user_id(), Wpfnl_functions::CANVAS_VIEW_MODE_META_KEY );
228
229 if ( 'vertical' === $payload['funnel_builder_mode'] ) {
230 do_action( 'wpfunnels_vertical_mode_used' );
231 }
232 }
233
234 $permalink_settings = [
235 'structure' => isset($payload['permalink_settings']) ? sanitize_text_field($payload['permalink_settings']) : 'default',
236 'step_base' => isset($payload['permalink_step_base']) ? sanitize_text_field($payload['permalink_step_base']) : WPFNL_STEPS_POST_TYPE,
237 'funnel_base' => isset($payload['permalink_funnel_base']) ? sanitize_text_field($payload['permalink_funnel_base']) : WPFNL_FUNNELS_POST_TYPE,
238 ];
239
240 if( isset($payload['sender_name']) && isset($payload['sender_email']) ){
241 $optin_settings = [
242 'sender_name' => sanitize_text_field($payload['sender_name']),
243 'sender_email' => sanitize_text_field($payload['sender_email']),
244 'email_subject' => isset($payload['email_subject']) ? sanitize_text_field($payload['email_subject']) : '',
245 ];
246 }else{
247 $optin_settings = Wpfnl_functions::get_optin_settings();
248 }
249
250
251 foreach ($payload as $key => $value) {
252 switch ($key) {
253 case 'builder':
254 $cache_key = 'wpfunnels_remote_template_data_' . WPFNL_VERSION;
255 delete_transient($cache_key);
256 delete_option(WPFNL_TEMPLATES_OPTION_KEY);
257 break;
258 case 'permalink_settings':
259 Wpfnl_functions::update_admin_settings($this->prefix.'permalink_saved', true);
260 break;
261 case 'advanced_settings':
262 Wpfnl_functions::update_admin_settings($this->prefix.'advanced_settings', $value);
263 break;
264 default:
265 break;
266 }
267 }
268
269 Wpfnl_functions::update_admin_settings($this->prefix.'general_settings', $general_settings);
270 Wpfnl_functions::update_admin_settings($this->prefix.'permalink_settings', $permalink_settings);
271 Wpfnl_functions::update_admin_settings($this->prefix.'optin_settings', $optin_settings);
272 $this->save_recaptcha_settings($payload);
273 $this->save_tracking_consent($payload);
274 $this->save_user_role_management_data($payload);
275 $this->save_google_map_key($payload);
276 $this->save_lms_settings($payload);
277 $this->save_notification_settings($payload);
278
279 delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_74');
280 delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_73');
281 delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_52');
282 delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_53');
283
284 delete_transient('wpfunnels_remote_template_data_74_' . WPFNL_VERSION);
285 delete_transient('wpfunnels_remote_template_data_73_' . WPFNL_VERSION);
286 delete_transient('wpfunnels_remote_template_data_52_' . WPFNL_VERSION);
287 delete_transient('wpfunnels_remote_template_data_53_' . WPFNL_VERSION);
288
289 do_action('wpfunnels/after_settings_saved', $payload );
290 return [
291 'success' => true
292 ];
293 }
294
295 /**
296 * Persist the usage-tracking opt-in from the Advanced panel.
297 *
298 * The toggle only sends a value when the user actually changed it (see
299 * getTrackingConsentChange() in js/settings.js), so an unrelated save on a
300 * site that has never answered the setup-wizard question leaves that
301 * "undecided" state alone instead of writing an explicit "no". Consent
302 * itself lives in the Linno telemetry option, not in general settings, and
303 * the SDK migrates the legacy `wpfunnels_allow_tracking` key on read — so
304 * existing users keep whatever they agreed to during onboarding.
305 *
306 * @param array $payload Settings payload from the save request.
307 *
308 * @since 3.13.0
309 */
310 private function save_tracking_consent( $payload ) {
311 if ( ! isset( $payload['allow_tracking'] ) ) {
312 return;
313 }
314
315 $submitted = sanitize_text_field( $payload['allow_tracking'] );
316
317 if ( ! in_array( $submitted, [ 'yes', 'no' ], true ) ) {
318 return;
319 }
320
321 $consent = 'yes' === $submitted;
322
323 $telemetry = \WPFunnels\Tracking\Telemetry::instance();
324
325 if ( ! $telemetry ) {
326 return;
327 }
328
329 // Nothing to do when the toggle already matches what is stored — this
330 // also stops a redundant save from turning "never decided" into "no".
331 if ( $telemetry->get_consent_state() === ( $consent ? 'yes' : 'no' ) ) {
332 return;
333 }
334
335 $telemetry->save_consent( $consent );
336
337 do_action( 'wpfunnels_tracking_consent_updated', $consent );
338 }
339
340
341 /**
342 * Save Facebook pixel settings
343 *
344 * @param $settings
345 *
346 * @since 1.0.0
347 */
348 public function save_recaptcha_settings( $settings ) {
349 $enable_recapcha = isset($settings['enable_recaptcha']) ? $settings['enable_recaptcha'] : 'no';
350 $recapcha_site_key = isset($settings['recaptcha_site_key']) ? $settings['recaptcha_site_key'] : '';
351 $recapcha_site_secret = isset($settings['recaptcha_site_secret']) ? $settings['recaptcha_site_secret'] : '';
352 $recaptcha_settings = array(
353 'enable_recaptcha' => $enable_recapcha,
354 'recaptcha_site_key' => $recapcha_site_key,
355 'recaptcha_site_secret' => $recapcha_site_secret
356 );
357 Wpfnl_functions::update_admin_settings('_wpfunnels_recaptcha_setting', $recaptcha_settings );
358 }
359
360
361 /**
362 * Save user role management data
363 *
364 * @param $payload
365 *
366 * @since 3.1.4
367 */
368 public function save_user_role_management_data($payload) {
369 if ( !isset($payload['user_role_management']) ) {
370 return;
371 }
372
373 $roles = wp_unslash( $payload['user_role_management']['roles'] );
374 $sanitized_settings = array();
375 foreach ( $roles as $key => $value ) {
376 $sanitized_settings[ $key ] = ( isset( $roles[ $key ] ) ) ? sanitize_text_field( $value ) : '';
377 }
378 Wpfnl_functions::update_admin_settings('_wpfunnels_user_roles', $sanitized_settings );
379
380 $this->update_user_role_management( $sanitized_settings );
381 }
382
383
384 /**
385 * Update user role management data
386 *
387 * @param $sanitized_settings
388 *
389 * @since 3.1.4
390 */
391 private function update_user_role_management( $sanitized_settings ) {
392
393 foreach ( $sanitized_settings as $user_role => $value ) {
394 $user_role_obj = get_role( $user_role );
395 if ( $user_role_obj ) {
396 if ( 'yes' === $value ) {
397 $user_role_obj->add_cap('wpf_manage_funnels' );
398 } elseif ( 'no' === $value ) {
399 $user_role_obj->remove_cap('wpf_manage_funnels' );
400 }
401 }
402 }
403 }
404
405
406
407 /**
408 * Save google map api key settings key
409 *
410 * @param $payload
411 *
412 * @since 3.1.3
413 */
414 public function save_google_map_key( $payload ) {
415 if ( isset($payload['google_map_api_key']) ) {
416 Wpfnl_functions::update_admin_settings('_wpfunnels_google_map_api_key', $payload['google_map_api_key'] );
417 }
418 }
419
420
421 /**
422 * Save LMS provider settings
423 *
424 * @param $payload
425 *
426 * @since 2.0.0
427 */
428 public function save_lms_settings( $payload ) {
429 if ( isset($payload['lms_provider']) ) {
430 $lms_settings = array(
431 'lms_provider' => sanitize_text_field( $payload['lms_provider'] )
432 );
433 update_option( '_wpfunnels_lms_settings', $lms_settings );
434 }
435 }
436
437 /**
438 * Save notification settings
439 *
440 * @param $payload
441 *
442 * @since 3.2.0
443 */
444 public function save_notification_settings( $payload ) {
445 $old_settings = Wpfnl_functions::get_notification_settings();
446 $old_enabled = isset($old_settings['enable_revenue_report']) ? $old_settings['enable_revenue_report'] : 'no';
447
448 $notification_settings = array(
449 'enable_revenue_report' => isset($payload['enable_revenue_report']) ? sanitize_text_field($payload['enable_revenue_report']) : 'no',
450 'revenue_report_frequency' => isset($payload['revenue_report_frequency']) ? sanitize_text_field($payload['revenue_report_frequency']) : 'weekly',
451 'revenue_report_recipient' => isset($payload['revenue_report_recipient']) ? sanitize_text_field($payload['revenue_report_recipient']) : get_option('admin_email'),
452 'revenue_report_subject' => isset($payload['revenue_report_subject']) ? sanitize_text_field($payload['revenue_report_subject']) : 'Store Revenue Report - {period}',
453 'send_time' => isset($payload['send_time']) ? sanitize_text_field($payload['send_time']) : '10:00',
454 );
455
456 Wpfnl_functions::update_admin_settings('_wpfunnels_notification_settings', $notification_settings );
457
458 $new_enabled = $notification_settings['enable_revenue_report'];
459
460 // Handle action scheduler based on enable/disable
461 if ( $new_enabled === 'yes' && $old_enabled !== 'yes' ) {
462 // Enabled: Schedule the action
463 $this->schedule_revenue_report_email( $notification_settings );
464 } elseif ( $new_enabled !== 'yes' && $old_enabled === 'yes' ) {
465 // Disabled: Remove the scheduled action
466 $this->unschedule_revenue_report_email();
467 } elseif ( $new_enabled === 'yes' ) {
468 // Already enabled but settings changed: Reschedule
469 $this->unschedule_revenue_report_email();
470 $this->schedule_revenue_report_email( $notification_settings );
471 }
472 }
473
474
475 /**
476 * Schedule revenue report email using Action Scheduler
477 *
478 * @param array $settings
479 *
480 * @since 3.2.0
481 */
482 private function schedule_revenue_report_email( $settings ) {
483 Wpfnl_functions::schedule_revenue_report_email( $settings );
484 }
485
486
487 /**
488 * Unschedule revenue report email
489 *
490 * @since 3.2.0
491 */
492 private function unschedule_revenue_report_email() {
493 Wpfnl_functions::unschedule_revenue_report_email();
494 }
495
496
497 /**
498 * Persist the AI Assistant fields that ride along with the settings save.
499 *
500 * Keys are stored through AISettings::connect(), which encrypts them. A
501 * blank field means "keep the stored key", so an unrelated settings save
502 * never wipes a connection.
503 *
504 * @param array $payload Settings payload.
505 *
506 * @since 3.13.0
507 * @return void
508 */
509 private function save_ai_settings( $payload ) {
510 if ( ! class_exists( '\WPFunnels\AI\Settings\AISettings' ) ) {
511 return;
512 }
513
514 $settings = '\WPFunnels\AI\Settings\AISettings';
515
516 foreach ( array( 'anthropic', 'openai', 'gemini' ) as $provider ) {
517 $field = 'ai_' . $provider . '_key';
518 $key = isset( $payload[ $field ] ) ? trim( sanitize_text_field( $payload[ $field ] ) ) : '';
519
520 // An untouched field arrives blank, and the placeholder mask must
521 // never be stored as a credential.
522 if ( '' === $key || false !== strpos( $key, '' ) ) {
523 continue;
524 }
525
526 call_user_func( array( $settings, 'connect' ), $provider, $key );
527 }
528
529 if ( isset( $payload['ai_provider'] ) && '' !== $payload['ai_provider'] ) {
530 $provider = sanitize_text_field( $payload['ai_provider'] );
531
532 // wordpress_ai needs no key; activating it is the whole connection.
533 if ( 'wordpress_ai' === $provider && ! call_user_func( array( $settings, 'isConnected' ), $provider ) ) {
534 call_user_func( array( $settings, 'connect' ), $provider, '' );
535 }
536
537 call_user_func( array( $settings, 'setActiveProvider' ), $provider );
538 }
539
540 if ( isset( $payload['ai_custom_instructions'] ) ) {
541 call_user_func( array( $settings, 'saveCustomInstructions' ), sanitize_textarea_field( $payload['ai_custom_instructions'] ) );
542 }
543
544 if ( isset( $payload['ai_enabled'] ) ) {
545 call_user_func( array( $settings, 'setEnabled' ), 'yes' === $payload['ai_enabled'] );
546 }
547
548 if ( isset( $payload['mcp_enabled'] ) ) {
549 update_option( '_wpfnl_mcp_enabled', 'yes' === $payload['mcp_enabled'] ? 'yes' : 'no' );
550 }
551 }
552
553
554 /**
555 * Initialize all the settings value
556 *
557 * @since 1.0.0
558 */
559 public function init_settings()
560 {
561 $this->general_settings = Wpfnl_functions::get_general_settings();
562 $this->is_allow_sales = Wpfnl_functions::maybe_allow_sales_funnel();
563 $this->permalink_settings = Wpfnl_functions::get_permalink_settings();
564 $this->optin_settings = Wpfnl_functions::get_optin_settings();
565 $this->offer_settings = Wpfnl_functions::get_offer_settings();
566 $this->user_roles = Wpfnl_functions::get_user_roles();
567 $this->gtm_events = Wpfnl_functions::get_gtm_events();
568 $this->gtm_settings = Wpfnl_functions::get_gtm_settings();
569 $this->facebook_pixel_events = Wpfnl_functions::get_facebook_events();
570 $this->facebook_pixel_settings = Wpfnl_functions::get_facebook_pixel_settings();
571 $this->utm_params = Wpfnl_functions::get_utm_params();
572 $this->utm_settings = Wpfnl_functions::get_utm_settings();
573 $this->recaptcha_settings = Wpfnl_functions::get_recaptcha_settings();
574 $this->user_roles_settings = Wpfnl_functions::get_user_role_settings();
575 $this->google_map_api_key = Wpfnl_functions::get_google_map_api_key();
576 $this->notification_settings = Wpfnl_functions::get_notification_settings();
577 }
578
579
580
581
582 /**
583 * Clear saved templates data
584 *
585 * @param $payload
586 *
587 * @return array
588 * @since 1.0.0
589 */
590 public function clear_templates_data($payload) {
591
592 delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_wc');
593 delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_lms');
594
595 $builder = Wpfnl_functions::get_builder_type();
596
597 delete_transient('wpfunnels_remote_template_data_wc_' . WPFNL_VERSION);
598 delete_transient('wpfunnels_remote_template_data_lms_' . WPFNL_VERSION);
599 delete_transient('wpfunnels_remote_template_data_lead_' . WPFNL_VERSION);
600 delete_transient('wpfunnels_remote_template_data_store_checkout_' . WPFNL_VERSION);
601
602 delete_transient('wpfunnels_remote_template_data_wc_'. $builder . '_' . WPFNL_VERSION);
603 delete_transient('wpfunnels_remote_template_data_lms_'. $builder . '_' . WPFNL_VERSION);
604 delete_transient('wpfunnels_remote_template_data_lead_'. $builder . '_' . WPFNL_VERSION);
605 delete_transient('wpfunnels_remote_template_data_store_checkout_'. $builder . '_' . WPFNL_VERSION);
606
607 return [
608 'success' => true
609 ];
610 }
611
612
613 /**
614 * Clear transient
615 *
616 * @param $payload
617 *
618 * @return array
619 */
620 public function clear_transient_cache_data($payload) {
621
622 delete_transient('wpfunnels_remote_template_data_' . WPFNL_VERSION);
623 delete_transient('wpfunnels_remote_template_data_wc_' . WPFNL_VERSION);
624 delete_transient('wpfunnels_remote_template_data_lms_' . WPFNL_VERSION);
625 delete_transient('wpfunnels_remote_template_data_lead_' . WPFNL_VERSION);
626 delete_transient('wpfunnels_remote_template_data_store_checkout_' . WPFNL_VERSION);
627 delete_transient('wpfunnels_rollback_versions_' . WPFNL_VERSION);
628 do_action('wpfunnels/after_clear_transient');
629 return array(
630 'success' => true
631 );
632 }
633
634
635 /**
636 * After settings saved hooks
637 *
638 * @since 1.0.0
639 */
640 public function after_permalink_settings_saved()
641 {
642
643 if( Wpfnl_functions::maybe_funnel_page() ){
644 $is_permalink_saved = get_option('_wpfunnels_permalink_saved');
645 if ($is_permalink_saved) {
646 flush_rewrite_rules();
647 delete_option('_wpfunnels_permalink_saved');
648 }
649 }
650 }
651
652
653 /**
654 * Get settings by meta key
655 *
656 * @param $key
657 *
658 * @return mixed|string
659 * @since 1.0.0
660 */
661 public function get_settings_by_key($key)
662 {
663 return isset($this->settings_meta_keys[$key]) ? $this->settings_meta_keys[$key]: '';
664 }
665
666 public function get_name()
667 {
668 return __('settings','wpfnl');
669 }
670
671
672 /**
673 * Get rollback version of WPF
674 *
675 * @return array|mixed
676 * @since 2.3.0
677 */
678 public function get_roll_back_versions() {
679 $rollback_versions = get_transient( 'wpfunnels_rollback_versions_' . WPFNL_VERSION );
680 if ( false === $rollback_versions ) {
681 $max_versions = 10;
682 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
683 $plugin_information = plugins_api(
684 'plugin_information', [
685 'slug' => 'wpfunnels',
686 ]
687 );
688 if ( empty( $plugin_information->versions ) || ! is_array( $plugin_information->versions ) ) {
689 return [];
690 }
691
692 natsort( $plugin_information->versions );
693 $plugin_information->versions = array_reverse($plugin_information->versions);
694 $rollback_versions = [];
695
696 $current_index = 0;
697 foreach ( $plugin_information->versions as $version => $download_link ) {
698 if ( $max_versions <= $current_index ) {
699 break;
700 }
701
702 $lowercase_version = strtolower( $version );
703 $is_valid_rollback_version = ! preg_match( '/(trunk|beta|rc|dev)/i', $lowercase_version );
704
705 /**
706 * Is rollback version is valid.
707 *
708 * Filters the check whether the rollback version is valid.
709 *
710 * @param bool $is_valid_rollback_version Whether the rollback version is valid.
711 */
712 $is_valid_rollback_version = apply_filters(
713 'wpfunnels/is_valid_rollback_version',
714 $is_valid_rollback_version,
715 $lowercase_version
716 );
717
718 if ( ! $is_valid_rollback_version ) {
719 continue;
720 }
721
722 if ( version_compare( $version, WPFNL_VERSION, '>=' ) ) {
723 continue;
724 }
725
726 $current_index++;
727 $rollback_versions[] = $version;
728 }
729
730 set_transient( 'wpfunnels_rollback_versions_' . WPFNL_VERSION, $rollback_versions, DAY_IN_SECONDS );
731 }
732
733 return $rollback_versions;
734 }
735
736
737 public function post_wpfunnels_rollback() {
738 check_admin_referer( 'wpfunnels_rollback' );
739
740 $rollback_versions = $this->get_roll_back_versions();
741 if ( empty( $_GET['version'] ) || ! in_array( $_GET['version'], $rollback_versions ) ) {
742 wp_die( esc_html__( 'Error occurred, The version selected is invalid. Try selecting different version.', 'wpfnl' ) );
743 }
744
745 $plugin_slug = basename( 'wpfunnels', '.php' );
746
747 $rollback = new Rollback(
748 [
749 'version' => $_GET['version'],
750 'plugin_name' => WPFNL_BASE,
751 'plugin_slug' => $plugin_slug,
752 'package_url' => sprintf( 'https://downloads.wordpress.org/plugin/%1s.%2s.zip', $plugin_slug, $_GET['version'] ),
753 ]
754 );
755
756 $rollback->run();
757
758 wp_die(
759 '', esc_html__( 'Rollback to Previous Version', 'wpfnl' ), [
760 'response' => 200,
761 ]
762 );
763 }
764
765
766 /**
767 * WPFunnels log
768 *
769 * @param $payload
770 *
771 * @return array
772 */
773 public static function wpfnl_show_log( $payload ) {
774 if( isset($payload['logKey']) ){
775 // Reduce to a bare filename — strips any directory traversal (../),
776 // separators, and stream-wrapper prefixes (phar://, php://, etc.).
777 $key = basename( wp_unslash( $payload['logKey'] ) );
778
779 $upload_dir = wp_upload_dir( null, false );
780 $log_url = trailingslashit( $upload_dir['basedir'] ) . 'wpfunnels/wpfunnels-logs/';
781 $file_url = $log_url . $key;
782
783 // Whitelist: key must be a .log filename that actually resolves inside
784 // the logs directory. realpath() comparison defeats any residual traversal.
785 $real_dir = realpath( $log_url );
786 $real_file = file_exists( $file_url ) ? realpath( $file_url ) : false;
787
788 $is_valid = $key
789 && '.log' === strtolower( substr( $key, -4 ) )
790 && $real_dir
791 && $real_file
792 && 0 === strpos( $real_file, trailingslashit( $real_dir ) );
793
794 if ( $is_valid ) {
795 // Never include/execute the log file — read it as data and escape it.
796 $out = file_get_contents( $file_url );
797 $out = '<pre>' . esc_html( $out ) . '</pre>';
798
799 return array(
800 'success' => true,
801 'content' => $out,
802 'file_url' => $file_url,
803 );
804 }
805
806 return array(
807 'success' => false,
808 'content' => '',
809 'file_url' => '',
810 );
811 }
812
813 return array(
814 'success' => false,
815 'content' => '',
816 'file_url' => ''
817 );
818
819
820 }
821
822
823 /**
824 * Send test notification email
825 *
826 * @param $payload
827 *
828 * @return array
829 * @since 3.2.0
830 */
831 public function send_test_notification( $payload ) {
832 if ( ! isset( $payload['email'] ) || ! is_email( $payload['email'] ) ) {
833 return array(
834 'success' => false,
835 'message' => __( 'Please provide a valid email address.', 'wpfnl' )
836 );
837 }
838
839 $settings = Wpfnl_functions::get_notification_settings();
840 $subject_template = isset( $settings['revenue_report_subject'] ) ? $settings['revenue_report_subject'] : 'Store Revenue Report - {period}';
841
842 // Calculate date range for test email (last week Monday to Sunday)
843 $start_date = date( 'Y-m-d', strtotime( 'last monday -1 week' ) );
844 $end_date = date( 'Y-m-d', strtotime( 'last sunday' ) );
845 $date_range = date( 'F j, Y', strtotime( $start_date ) ) . ' - ' . date( 'F j, Y', strtotime( $end_date ) );
846
847 $subject = '[Test] ' . str_replace( '{period}', $date_range, $subject_template );
848
849 $to = sanitize_email( $payload['email'] );
850 $message = $this->get_revenue_report_email_content( 'weekly' );
851 $headers = array( 'Content-Type: text/html; charset=UTF-8' );
852
853 $sent = wp_mail( $to, $subject, $message, $headers );
854
855 if ( $sent ) {
856 return array(
857 'success' => true,
858 'message' => __( 'Test email sent successfully! Please check your inbox.', 'wpfnl' )
859 );
860 } else {
861 return array(
862 'success' => false,
863 'message' => __( 'Failed to send test email. Please check your email configuration.', 'wpfnl' )
864 );
865 }
866 }
867
868
869 /**
870 * Send revenue report email (triggered by Action Scheduler)
871 *
872 * @since 3.2.0
873 */
874 public function send_revenue_report_email() {
875 $settings = Wpfnl_functions::get_notification_settings();
876
877 // Check if still enabled
878 if ( ! isset( $settings['enable_revenue_report'] ) || $settings['enable_revenue_report'] !== 'yes' ) {
879 return;
880 }
881
882 $recipients = isset( $settings['revenue_report_recipient'] ) ? $settings['revenue_report_recipient'] : get_option('admin_email');
883 $subject = isset( $settings['revenue_report_subject'] ) ? $settings['revenue_report_subject'] : 'Store Revenue Report - {period}';
884 $frequency = isset( $settings['revenue_report_frequency'] ) ? $settings['revenue_report_frequency'] : 'weekly';
885
886 // Calculate date range based on frequency
887 if ( $frequency === 'monthly' ) {
888 $start_date = date( 'Y-m-d', strtotime( 'first day of last month' ) );
889 $end_date = date( 'Y-m-d', strtotime( 'last day of last month' ) );
890 } else {
891 // Weekly: Last week Monday to Sunday
892 $start_date = date( 'Y-m-d', strtotime( 'last monday -1 week' ) );
893 $end_date = date( 'Y-m-d', strtotime( 'last sunday' ) );
894 }
895
896 $date_range = date( 'F j, Y', strtotime( $start_date ) ) . ' - ' . date( 'F j, Y', strtotime( $end_date ) );
897
898 // Replace {period} placeholder with date range
899 $subject = str_replace( '{period}', $date_range, $subject );
900
901 // Generate report content
902 $message = $this->get_revenue_report_email_content( $frequency );
903
904 // Prepare headers
905 $headers = array( 'Content-Type: text/html; charset=UTF-8' );
906
907 // Send email to recipients (can be multiple, comma-separated)
908 $recipient_array = array_map( 'trim', explode( ',', $recipients ) );
909
910 foreach ( $recipient_array as $recipient ) {
911 if ( is_email( $recipient ) ) {
912 wp_mail( sanitize_email( $recipient ), $subject, $message, $headers );
913 }
914 }
915 }
916
917
918 /**
919 * Get revenue report email content with actual data
920 *
921 * @param string $frequency
922 * @return string
923 * @since 3.2.0
924 */
925 private function get_revenue_report_email_content( $frequency = 'weekly' ) {
926 $site_name = get_bloginfo( 'name' );
927 $site_url = home_url();
928
929 // Calculate date range
930 if ( $frequency === 'monthly' ) {
931 $start_date = date( 'Y-m-d', strtotime( 'first day of last month' ) );
932 $end_date = date( 'Y-m-d', strtotime( 'last day of last month' ) );
933 $period_text = __( 'Monthly', 'wpfnl' );
934 } else {
935 // Weekly: Last week Monday to Sunday
936 $start_date = date( 'Y-m-d', strtotime( 'last monday -1 week' ) );
937 $end_date = date( 'Y-m-d', strtotime( 'last sunday' ) );
938 $period_text = __( 'Weekly', 'wpfnl' );
939 }
940
941 $date_range = date( 'F j, Y', strtotime( $start_date ) ) . ' - ' . date( 'F j, Y', strtotime( $end_date ) );
942
943 // Calculate previous period for comparison
944 if ( $frequency === 'monthly' ) {
945 $prev_start_date = date( 'Y-m-d', strtotime( 'first day of -2 months' ) );
946 $prev_end_date = date( 'Y-m-d', strtotime( 'last day of -2 months' ) );
947 } else {
948 $prev_start_date = date( 'Y-m-d', strtotime( 'last monday -2 weeks' ) );
949 $prev_end_date = date( 'Y-m-d', strtotime( 'last sunday -1 week' ) );
950 }
951
952 // Get revenue data for current and previous periods
953 $revenue_data = $this->get_revenue_data( $start_date, $end_date );
954 $previous_data = $this->get_revenue_data( $prev_start_date, $prev_end_date );
955
956 // Calculate percentage changes
957 $revenue_data['total_orders_change'] = $this->calculate_percentage_change( $previous_data['total_orders'], $revenue_data['total_orders'] );
958 $revenue_data['total_customers_change'] = $this->calculate_percentage_change( $previous_data['total_customers'], $revenue_data['total_customers'] );
959 $revenue_data['total_revenue_change'] = $this->calculate_percentage_change( $previous_data['total_revenue_raw'], $revenue_data['total_revenue_raw'] );
960 $revenue_data['average_order_value_change'] = $this->calculate_percentage_change( $previous_data['average_order_value_raw'], $revenue_data['average_order_value_raw'] );
961
962 // Build email with FunnelKit-style design
963 $message = '<!DOCTYPE html>';
964 $message .= '<html lang="en">';
965 $message .= '<head>';
966 $message .= '<meta charset="UTF-8">';
967 $message .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
968 $message .= '<title>Performance Report</title>';
969 $message .= '</head>';
970 $message .= '<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, \'Helvetica Neue\', Arial, sans-serif; background-color: #f5f5f5;">';
971
972 // Main container
973 $message .= '<table width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #f5f5f5; padding: 40px 20px;">';
974 $message .= '<tr><td align="center">';
975 $message .= '<table width="600" border="0" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">';
976
977 // Top orange banner with text
978 $message .= '<tr><td style="background-color: #6e42d3; padding: 12px 40px; text-align: center;">';
979 $message .= '<div style="font-size: 16px; font-weight: 600; color: #ffffff;">Your ' . esc_html( strtolower( $frequency ) ) . ' summary from WPFunnels.</div>';
980 $message .= '</td></tr>';
981
982 // WPFunnels logo section
983 $message .= '<tr><td style="background-color: #ffffff; padding: 20px 40px 20px; text-align: center;">';
984 $message .= '<svg width="38" height="28" viewBox="0 0 38 28" fill="none" xmlns="http://www.w3.org/2000/svg" style="display: inline-block; margin-right: 10px; vertical-align: middle;">';
985 $message .= '<path d="M7.01532 18H31.9847L34 11H5L7.01532 18Z" fill="#EE8134"/>';
986 $message .= '<path d="M11.9621 27.2975C12.0923 27.7154 12.4792 28 12.9169 28H26.0831C26.5208 28 26.9077 27.7154 27.0379 27.2975L29 21H10L11.9621 27.2975Z" fill="#6E42D3"/>';
987 $message .= '<path d="M37.8161 0.65986C37.61 0.247888 37.2609 0 36.8867 0H1.11326C0.739128 0 0.390003 0.247888 0.183972 0.65986C-0.0220592 1.07193 -0.0573873 1.59277 0.0898627 2.04655L1.69781 7H36.3022L37.9102 2.04655C38.0574 1.59287 38.022 1.07193 37.8161 0.65986Z" fill="#6E42D3"/>';
988 $message .= '</svg>';
989 $message .= '<span style="font-size: 32px; font-weight: bold; color: #231748; vertical-align: middle;">WPFunnels</span>';
990 $message .= '</td></tr>';
991
992 // Date range, Performance Report title, and View Button - all in same background
993 $message .= '<tr><td style="padding: 20px 40px 30px; text-align: center; background-color: #f8f9fa;">';
994 // Date range
995 $message .= '<div style="font-size: 14px; color: #6c757d; font-weight: 500; margin-bottom: 20px;">' . esc_html( $date_range ) . '</div>';
996 // Performance Report title
997 $message .= '<h1 style="margin: 0 0 10px; font-size: 32px; font-weight: bold; color: #212529;">Store Revenue Summary</h1>';
998 $message .= '<p style="margin: 0 0 20px; font-size: 14px; color: #6c757d;">Track your store performance and revenue metrics for this period</p>';
999 // View Dashboard Button
1000 $message .= '<a href="' . esc_url( admin_url( 'admin.php?page=wpfunnels' ) ) . '" style="display: inline-block; background-color: #6e42d3; color: #ffffff; text-decoration: none; padding: 12px 30px; border-radius: 5px; font-size: 14px; font-weight: 600;">View Dashboard</a>';
1001 $message .= '</td></tr>';
1002
1003 // Key Performance Metrics section
1004 $message .= '<tr><td style="padding: 20px 40px;">';
1005 $message .= '<h2 style="margin: 0 0 10px; font-size: 24px; font-weight: bold; color: #212529; text-align: center;">Revenue Overview</h2>';
1006 $message .= '<p style="margin: 0 0 30px; font-size: 14px; color: #6c757d; text-align: center;">Your ' . esc_html( strtolower( $frequency ) ) . ' performance snapshot</p>';
1007
1008 // Metrics grid
1009 $message .= '<table width="100%" border="0" cellspacing="0" cellpadding="0">';
1010
1011 // Row 1: Total orders and Total contacts
1012 $message .= '<tr>';
1013 $message .= '<td width="50%" style="padding: 20px; vertical-align: top;">';
1014 $message .= '<div style="text-align: left;">';
1015 $message .= '<div style="font-size: 14px; color: #6c757d; margin-bottom: 10px; font-weight: 500;">Total orders</div>';
1016 $message .= '<div style="font-size: 36px; font-weight: bold; color: #212529; margin-bottom: 5px;">' . esc_html( $revenue_data['total_orders'] ) . '</div>';
1017 $message .= $revenue_data['total_orders_change'];
1018 $message .= '</div>';
1019 $message .= '</td>';
1020 $message .= '<td width="50%" style="padding: 20px; vertical-align: top;">';
1021 $message .= '<div style="text-align: left;">';
1022 $message .= '<div style="font-size: 14px; color: #6c757d; margin-bottom: 10px; font-weight: 500;">Total customers</div>';
1023 $message .= '<div style="font-size: 36px; font-weight: bold; color: #212529; margin-bottom: 5px;">' . esc_html( $revenue_data['total_customers'] ) . '</div>';
1024 $message .= $revenue_data['total_customers_change'];
1025 $message .= '</div>';
1026 $message .= '</td>';
1027 $message .= '</tr>';
1028
1029 // Row 2: Revenue and Bump revenue
1030 $message .= '<tr>';
1031 $message .= '<td width="50%" style="padding: 20px; vertical-align: top;">';
1032 $message .= '<div style="text-align: left;">';
1033 $message .= '<div style="font-size: 14px; color: #6c757d; margin-bottom: 10px; font-weight: 500;">Revenue</div>';
1034 $message .= '<div style="font-size: 36px; font-weight: bold; color: #212529; margin-bottom: 5px;">' . wp_strip_all_tags( $revenue_data['total_revenue'] ) . '</div>';
1035 $message .= $revenue_data['total_revenue_change'];
1036 $message .= '</div>';
1037 $message .= '</td>';
1038 $message .= '<td width="50%" style="padding: 20px; vertical-align: top;">';
1039 $message .= '<div style="text-align: left;">';
1040 $message .= '<div style="font-size: 14px; color: #6c757d; margin-bottom: 10px; font-weight: 500;">Bump revenue</div>';
1041 if ( Wpfnl_functions::is_wpfnl_pro_activated() ) {
1042 $message .= '<div style="font-size: 36px; font-weight: bold; color: #212529; margin-bottom: 5px;">0 <span style="font-size: 18px; font-weight: normal; color: #6c757d;">USD</span></div>';
1043 $message .= '<div style="font-size: 12px; color: #6c757d;">0% - Previous period</div>';
1044 } else {
1045 $message .= '<div style="font-size: 36px; margin-bottom: 5px;"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M17 11V7C17 4.79086 15.2091 3 13 3H11C8.79086 3 7 4.79086 7 7V11M12 14V16M8 11H16C17.1046 11 18 11.8954 18 13V19C18 20.1046 17.1046 21 16 21H8C6.89543 21 6 20.1046 6 19V13C6 11.8954 6.89543 11 8 11Z" stroke="#6c757d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>';
1046 $message .= '<div style="font-size: 12px; color: #6c757d;">WPFunnels Pro</div>';
1047 }
1048 $message .= '</div>';
1049 $message .= '</td>';
1050 $message .= '</tr>';
1051
1052 // Row 3: Upsell revenue and Average order value
1053 $message .= '<tr>';
1054 $message .= '<td width="50%" style="padding: 20px; vertical-align: top;">';
1055 $message .= '<div style="text-align: left;">';
1056 $message .= '<div style="font-size: 14px; color: #6c757d; margin-bottom: 10px; font-weight: 500;">Upsell revenue</div>';
1057 if ( Wpfnl_functions::is_wpfnl_pro_activated() ) {
1058 $message .= '<div style="font-size: 36px; font-weight: bold; color: #212529; margin-bottom: 5px;">0 <span style="font-size: 18px; font-weight: normal; color: #6c757d;">USD</span></div>';
1059 $message .= '<div style="font-size: 12px; color: #6c757d;">0% - Previous period</div>';
1060 } else {
1061 $message .= '<div style="font-size: 36px; margin-bottom: 5px;"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M17 11V7C17 4.79086 15.2091 3 13 3H11C8.79086 3 7 4.79086 7 7V11M12 14V16M8 11H16C17.1046 11 18 11.8954 18 13V19C18 20.1046 17.1046 21 16 21H8C6.89543 21 6 20.1046 6 19V13C6 11.8954 6.89543 11 8 11Z" stroke="#6c757d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>';
1062 $message .= '<div style="font-size: 12px; color: #6c757d;">WPFunnels Pro</div>';
1063 }
1064 $message .= '</div>';
1065 $message .= '</td>';
1066 $message .= '<td width="50%" style="padding: 20px; vertical-align: top;">';
1067 $message .= '<div style="text-align: left;">';
1068 $message .= '<div style="font-size: 14px; color: #6c757d; margin-bottom: 10px; font-weight: 500;">Average order value</div>';
1069 $message .= '<div style="font-size: 36px; font-weight: bold; color: #212529; margin-bottom: 5px;">' . wp_strip_all_tags( $revenue_data['average_order_value'] ) . '</div>';
1070 $message .= $revenue_data['average_order_value_change'];
1071 $message .= '</div>';
1072 $message .= '</td>';
1073 $message .= '</tr>';
1074
1075 $message .= '</table>';
1076 $message .= '</td></tr>';
1077
1078 // Additional revenue info
1079 if ( isset( $revenue_data['total_revenue_raw'] ) && $revenue_data['total_revenue_raw'] > 0 ) {
1080 $message .= '<tr><td style="padding: 20px 40px;">';
1081 $message .= '<div style="background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; border-radius: 4px;">';
1082 $message .= '<p style="margin: 0; font-size: 14px; color: #856404;">Since installing WPFunnels you have captured additional revenue of <strong>' . wp_kses_post( wc_price( $revenue_data['total_revenue_raw'] ) ) . '</strong></p>';
1083 $message .= '</div>';
1084 $message .= '</td></tr>';
1085 }
1086
1087 // Footer
1088 $message .= '<tr><td style="padding: 30px 40px; text-align: center; background-color: #ffffff; border-top: 1px solid #e9ecef;">';
1089 $message .= '<p style="margin: 0 0 10px; font-size: 12px; color: #6c757d;">This performance report email was sent from the <a href="' . esc_url( $site_url ) . '" style="color: #007bff; text-decoration: none;">' . esc_html( $site_name ) . '</a> by admin ' . esc_html( date( 'F j, Y' ) ) . '</p>';
1090 $message .= '<p style="margin: 0; font-size: 12px; color: #6c757d;"><a href="' . esc_url( admin_url( 'admin.php?page=wpfnl_settings#notifications-settings' ) ) . '" style="color: #007bff; text-decoration: none;">Click here</a> to change the frequency and recipients.</p>';
1091 $message .= '</td></tr>';
1092
1093 $message .= '</table>';
1094 $message .= '</td></tr>';
1095 $message .= '</table>';
1096
1097 $message .= '</body>';
1098 $message .= '</html>';
1099
1100 return $message;
1101 }
1102
1103
1104 /**
1105 * Get revenue data from WPFunnels stats
1106 *
1107 * @param string $start_date
1108 * @param string $end_date
1109 * @return array
1110 * @since 3.2.0
1111 */
1112 private function get_revenue_data( $start_date, $end_date ) {
1113 global $wpdb;
1114 $table = $wpdb->prefix . 'wpfnl_stats';
1115
1116 // Get total orders count
1117 $total_orders = $wpdb->get_var( $wpdb->prepare(
1118 "SELECT COUNT(id) FROM $table WHERE date_created >= %s AND date_created <= %s AND status = 'completed'",
1119 $start_date,
1120 $end_date . ' 23:59:59'
1121 ) );
1122
1123 // Get total revenue (total_sales includes everything: checkout + order bump + upsell + downsell)
1124 $total_revenue_raw = $wpdb->get_var( $wpdb->prepare(
1125 "SELECT SUM(total_sales) FROM $table WHERE date_created >= %s AND date_created <= %s AND status = 'completed'",
1126 $start_date,
1127 $end_date . ' 23:59:59'
1128 ) );
1129
1130 // Get total customers (unique customer_id)
1131 $total_customers = $wpdb->get_var( $wpdb->prepare(
1132 "SELECT COUNT(DISTINCT customer_id) FROM $table WHERE date_created >= %s AND date_created <= %s AND status = 'completed'",
1133 $start_date,
1134 $end_date . ' 23:59:59'
1135 ) );
1136
1137 // Calculate average order value
1138 $total_orders_int = intval( $total_orders );
1139 $total_revenue_float = floatval( $total_revenue_raw );
1140 $average = $total_orders_int > 0 ? $total_revenue_float / $total_orders_int : 0;
1141
1142 // Format data
1143 $data = array(
1144 'total_revenue' => function_exists( 'wc_price' ) ? wc_price( $total_revenue_float ) : '$' . number_format( $total_revenue_float, 2 ),
1145 'total_revenue_raw' => $total_revenue_float,
1146 'total_orders' => $total_orders_int,
1147 'total_customers' => intval( $total_customers ),
1148 'average_order_value' => function_exists( 'wc_price' ) ? wc_price( $average ) : '$' . number_format( $average, 2 ),
1149 'average_order_value_raw' => $average,
1150 );
1151
1152 return $data;
1153 }
1154
1155
1156 /**
1157 * Calculate percentage change between two values
1158 *
1159 * @param float $old_value
1160 * @param float $new_value
1161 * @return string HTML formatted percentage change
1162 * @since 3.2.0
1163 */
1164 private function calculate_percentage_change( $old_value, $new_value ) {
1165 $old_value = floatval( $old_value );
1166 $new_value = floatval( $new_value );
1167
1168 if ( $old_value == 0 ) {
1169 if ( $new_value > 0 ) {
1170 return '<div style="font-size: 12px; color: #28a745;">+100% - Previous period</div>';
1171 } else {
1172 return '<div style="font-size: 12px; color: #6c757d;">0% - Previous period</div>';
1173 }
1174 }
1175
1176 $change = ( ( $new_value - $old_value ) / $old_value ) * 100;
1177 $change_formatted = number_format( abs( $change ), 1 );
1178
1179 if ( $change > 0 ) {
1180 return '<div style="font-size: 12px; color: #28a745;">+' . $change_formatted . '% - Previous period</div>';
1181 } elseif ( $change < 0 ) {
1182 return '<div style="font-size: 12px; color: #dc3545;">-' . $change_formatted . '% - Previous period</div>';
1183 } else {
1184 return '<div style="font-size: 12px; color: #6c757d;">0% - Previous period</div>';
1185 }
1186 }
1187
1188
1189 /**
1190 * WPFunnels log
1191 *
1192 * @param $payload
1193 *
1194 * @return array
1195 */
1196 public static function wpfnl_delete_log( $payload ) {
1197 if( isset($payload['logKey']) ){
1198 $key = sanitize_file_name( basename( $payload['logKey'] ) );
1199 $upload_dir = wp_upload_dir( null, false );
1200 $log_dir = $upload_dir['basedir'].'/wpfunnels/wpfunnels-logs/';
1201 $file_name = $log_dir . $key;
1202
1203 // Verify the resolved path is within the log directory
1204 $real_file = realpath($file_name);
1205 $real_log_dir = realpath($log_dir);
1206
1207 if ( $real_file && $real_log_dir && strpos( $real_file, $real_log_dir ) === 0 ) {
1208 $response = \Wpfnl_Logger::delete_log_file( $file_name );
1209 if( $response ){
1210 return array('success' => true);
1211 }
1212 }
1213 }
1214
1215 return array(
1216 'success' => false,
1217 'content' => '',
1218 'file_url' => ''
1219 );
1220 }
1221 }
1222