PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.0
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.0
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
wpfunnels / admin / modules / settings / class-wpfnl-settings.php

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

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