'elementor', '_wpfunnels_paypal_reference' => '', '_wpfunnels_order_bump' => '', '_wpfunnels_ab_testing' => '', '_wpfunnels_allow_funnels' => [ 'administrator' => true, ], '_wpfunnels_permalink_settings' => '', '_wpfunnels_optin_settings' => '', '_wpfunnels_permalink_step_base' => 'wpfunnels', '_wpfunnels_permalink_flow_base' => 'step', '_wpfunnels_set_permalink' => 'step', ]; public function __construct() { add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']); add_action('admin_init', [$this, 'after_permalink_settings_saved']); add_action( 'admin_post_wpfunnels_rollback', [ $this, 'post_wpfunnels_rollback' ] ); add_action( 'wpfunnels_send_revenue_report_email', [ $this, 'send_revenue_report_email' ] ); $this->init_ajax(); } public function is_wc_installed() { $path = 'woocommerce/woocommerce.php'; $plugins = get_plugins(); return isset($plugins[ $path ]); } public function is_ff_installed() { $path = 'fluentform/fluentform.php'; $plugins = get_plugins(); return isset($plugins[ $path ]); } public function is_elementor_installed() { $path = 'elementor/elementor.php'; $plugins = get_plugins(); return isset($plugins[ $path ]); } public function enqueue_scripts( $hook ) { if ( ! in_array( $hook, $this->page_hooks, true ) ) { return; } wp_enqueue_script('settings', plugin_dir_url(__FILE__) . 'js/settings.js', ['jquery'], WPFNL_VERSION, true); } public function get_view() { $this->init_settings(); $is_pro_activated = Wpfnl_functions::is_wpfnl_pro_activated(); require_once WPFNL_DIR . '/admin/modules/settings/views/view.php'; } /** * Init ajax hooks for * saving metas * * @since 1.0.0 */ public function init_ajax() { $this->validations = [ 'logged_in' => true, 'user_can' => 'wpf_manage_funnels', ]; wp_ajax_helper()->handle('update-general-settings') ->with_callback([ $this, 'update_general_settings' ]) ->with_validation($this->validations); wp_ajax_helper()->handle('clear-templates') ->with_callback([ $this, 'clear_templates_data' ]) ->with_validation($this->validations); wp_ajax_helper()->handle('clear-transient') ->with_callback([ $this, 'clear_transient_cache_data' ]) ->with_validation($this->validations); wp_ajax_helper()->handle('wpfnl-show-log') ->with_callback([ $this, 'wpfnl_show_log' ]) ->with_validation($this->validations); wp_ajax_helper()->handle('wpfnl-delete-log') ->with_callback([ $this, 'wpfnl_delete_log' ]) ->with_validation($this->validations); wp_ajax_helper()->handle('wpfnl-send-test-notification') ->with_callback([ $this, 'send_test_notification' ]) ->with_validation($this->validations); } /** * Update handler for settings * page * * @param $payload * * @return array * @since 1.0.0 */ public function update_general_settings($payload) { do_action('wpfunnels/before_settings_saved', $payload); $this->save_ai_settings($payload); $existing_settings = Wpfnl_functions::get_general_settings(); $general_settings = [ 'builder' => sanitize_text_field($payload['builder']), 'uninstall_cleanup' => isset($payload['uninstall_cleanup']) ? sanitize_text_field($payload['uninstall_cleanup']) : 'off', 'disable_analytics' => isset($payload['analytics_roles']) ? $payload['analytics_roles'] : '', 'allow_funnels' => isset($payload['permission_role']) ? $payload['permission_role'] : [], 'paypal_reference' => isset($payload['paypal_reference']) ? sanitize_text_field($payload['paypal_reference']) : 'off', 'order_bump' => isset($payload['order_bump']) ? sanitize_text_field($payload['order_bump']) : 'off', 'ab_testing' => isset($payload['ab_testing']) ? sanitize_text_field($payload['ab_testing']) : 'off', 'disable_theme_style' => isset($payload['disable_theme_style']) ? sanitize_text_field($payload['disable_theme_style']) : 'off', 'enable_log_status' => isset($payload['enable_log_status']) ? sanitize_text_field($payload['enable_log_status']) : 'off', 'enable_skip_cart' => isset($payload['enable_skip_cart']) ? $payload['enable_skip_cart'] : 'no', 'skip_cart_for' => isset($payload['skip_cart_for']) ? $payload['skip_cart_for'] : 'whole', 'funnel_builder_mode' => isset($payload['funnel_builder_mode']) ? sanitize_text_field($payload['funnel_builder_mode']) : Wpfnl_functions::get_default_funnel_builder_mode(), ]; if ( isset( $payload['funnel_builder_mode'] ) ) { // Choosing the site default here is an explicit statement of intent, so // drop this user's own canvas override — otherwise a view they toggled // earlier would keep winning over the mode they just picked. delete_user_meta( get_current_user_id(), Wpfnl_functions::CANVAS_VIEW_MODE_META_KEY ); if ( 'vertical' === $payload['funnel_builder_mode'] ) { do_action( 'wpfunnels_vertical_mode_used' ); } } $permalink_settings = [ 'structure' => isset($payload['permalink_settings']) ? sanitize_text_field($payload['permalink_settings']) : 'default', 'step_base' => isset($payload['permalink_step_base']) ? sanitize_text_field($payload['permalink_step_base']) : WPFNL_STEPS_POST_TYPE, 'funnel_base' => isset($payload['permalink_funnel_base']) ? sanitize_text_field($payload['permalink_funnel_base']) : WPFNL_FUNNELS_POST_TYPE, ]; if( isset($payload['sender_name']) && isset($payload['sender_email']) ){ $optin_settings = [ 'sender_name' => sanitize_text_field($payload['sender_name']), 'sender_email' => sanitize_text_field($payload['sender_email']), 'email_subject' => isset($payload['email_subject']) ? sanitize_text_field($payload['email_subject']) : '', ]; }else{ $optin_settings = Wpfnl_functions::get_optin_settings(); } foreach ($payload as $key => $value) { switch ($key) { case 'builder': $cache_key = 'wpfunnels_remote_template_data_' . WPFNL_VERSION; delete_transient($cache_key); delete_option(WPFNL_TEMPLATES_OPTION_KEY); break; case 'permalink_settings': Wpfnl_functions::update_admin_settings($this->prefix.'permalink_saved', true); break; case 'advanced_settings': Wpfnl_functions::update_admin_settings($this->prefix.'advanced_settings', $value); break; default: break; } } Wpfnl_functions::update_admin_settings($this->prefix.'general_settings', $general_settings); Wpfnl_functions::update_admin_settings($this->prefix.'permalink_settings', $permalink_settings); Wpfnl_functions::update_admin_settings($this->prefix.'optin_settings', $optin_settings); $this->save_recaptcha_settings($payload); $this->save_tracking_consent($payload); $this->save_user_role_management_data($payload); $this->save_google_map_key($payload); $this->save_lms_settings($payload); $this->save_notification_settings($payload); delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_74'); delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_73'); delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_52'); delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_53'); delete_transient('wpfunnels_remote_template_data_74_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_73_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_52_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_53_' . WPFNL_VERSION); do_action('wpfunnels/after_settings_saved', $payload ); return [ 'success' => true ]; } /** * Persist the usage-tracking opt-in from the Advanced panel. * * The toggle only sends a value when the user actually changed it (see * getTrackingConsentChange() in js/settings.js), so an unrelated save on a * site that has never answered the setup-wizard question leaves that * "undecided" state alone instead of writing an explicit "no". Consent * itself lives in the Linno telemetry option, not in general settings, and * the SDK migrates the legacy `wpfunnels_allow_tracking` key on read — so * existing users keep whatever they agreed to during onboarding. * * @param array $payload Settings payload from the save request. * * @since 3.13.0 */ private function save_tracking_consent( $payload ) { if ( ! isset( $payload['allow_tracking'] ) ) { return; } $submitted = sanitize_text_field( $payload['allow_tracking'] ); if ( ! in_array( $submitted, [ 'yes', 'no' ], true ) ) { return; } $consent = 'yes' === $submitted; $telemetry = \WPFunnels\Tracking\Telemetry::instance(); if ( ! $telemetry ) { return; } // Nothing to do when the toggle already matches what is stored — this // also stops a redundant save from turning "never decided" into "no". if ( $telemetry->get_consent_state() === ( $consent ? 'yes' : 'no' ) ) { return; } $telemetry->save_consent( $consent ); do_action( 'wpfunnels_tracking_consent_updated', $consent ); } /** * Save Facebook pixel settings * * @param $settings * * @since 1.0.0 */ public function save_recaptcha_settings( $settings ) { $enable_recapcha = isset($settings['enable_recaptcha']) ? $settings['enable_recaptcha'] : 'no'; $recapcha_site_key = isset($settings['recaptcha_site_key']) ? $settings['recaptcha_site_key'] : ''; $recapcha_site_secret = isset($settings['recaptcha_site_secret']) ? $settings['recaptcha_site_secret'] : ''; $recaptcha_settings = array( 'enable_recaptcha' => $enable_recapcha, 'recaptcha_site_key' => $recapcha_site_key, 'recaptcha_site_secret' => $recapcha_site_secret ); Wpfnl_functions::update_admin_settings('_wpfunnels_recaptcha_setting', $recaptcha_settings ); } /** * Save user role management data * * @param $payload * * @since 3.1.4 */ public function save_user_role_management_data($payload) { if ( !isset($payload['user_role_management']) ) { return; } $roles = wp_unslash( $payload['user_role_management']['roles'] ); $sanitized_settings = array(); foreach ( $roles as $key => $value ) { $sanitized_settings[ $key ] = ( isset( $roles[ $key ] ) ) ? sanitize_text_field( $value ) : ''; } Wpfnl_functions::update_admin_settings('_wpfunnels_user_roles', $sanitized_settings ); $this->update_user_role_management( $sanitized_settings ); } /** * Update user role management data * * @param $sanitized_settings * * @since 3.1.4 */ private function update_user_role_management( $sanitized_settings ) { foreach ( $sanitized_settings as $user_role => $value ) { $user_role_obj = get_role( $user_role ); if ( $user_role_obj ) { if ( 'yes' === $value ) { $user_role_obj->add_cap('wpf_manage_funnels' ); } elseif ( 'no' === $value ) { $user_role_obj->remove_cap('wpf_manage_funnels' ); } } } } /** * Save google map api key settings key * * @param $payload * * @since 3.1.3 */ public function save_google_map_key( $payload ) { if ( isset($payload['google_map_api_key']) ) { Wpfnl_functions::update_admin_settings('_wpfunnels_google_map_api_key', $payload['google_map_api_key'] ); } } /** * Save LMS provider settings * * @param $payload * * @since 2.0.0 */ public function save_lms_settings( $payload ) { if ( isset($payload['lms_provider']) ) { $lms_settings = array( 'lms_provider' => sanitize_text_field( $payload['lms_provider'] ) ); update_option( '_wpfunnels_lms_settings', $lms_settings ); } } /** * Save notification settings * * @param $payload * * @since 3.2.0 */ public function save_notification_settings( $payload ) { $old_settings = Wpfnl_functions::get_notification_settings(); $old_enabled = isset($old_settings['enable_revenue_report']) ? $old_settings['enable_revenue_report'] : 'no'; $notification_settings = array( 'enable_revenue_report' => isset($payload['enable_revenue_report']) ? sanitize_text_field($payload['enable_revenue_report']) : 'no', 'revenue_report_frequency' => isset($payload['revenue_report_frequency']) ? sanitize_text_field($payload['revenue_report_frequency']) : 'weekly', 'revenue_report_recipient' => isset($payload['revenue_report_recipient']) ? sanitize_text_field($payload['revenue_report_recipient']) : get_option('admin_email'), 'revenue_report_subject' => isset($payload['revenue_report_subject']) ? sanitize_text_field($payload['revenue_report_subject']) : 'Store Revenue Report - {period}', 'send_time' => isset($payload['send_time']) ? sanitize_text_field($payload['send_time']) : '10:00', ); Wpfnl_functions::update_admin_settings('_wpfunnels_notification_settings', $notification_settings ); $new_enabled = $notification_settings['enable_revenue_report']; // Handle action scheduler based on enable/disable if ( $new_enabled === 'yes' && $old_enabled !== 'yes' ) { // Enabled: Schedule the action $this->schedule_revenue_report_email( $notification_settings ); } elseif ( $new_enabled !== 'yes' && $old_enabled === 'yes' ) { // Disabled: Remove the scheduled action $this->unschedule_revenue_report_email(); } elseif ( $new_enabled === 'yes' ) { // Already enabled but settings changed: Reschedule $this->unschedule_revenue_report_email(); $this->schedule_revenue_report_email( $notification_settings ); } } /** * Schedule revenue report email using Action Scheduler * * @param array $settings * * @since 3.2.0 */ private function schedule_revenue_report_email( $settings ) { Wpfnl_functions::schedule_revenue_report_email( $settings ); } /** * Unschedule revenue report email * * @since 3.2.0 */ private function unschedule_revenue_report_email() { Wpfnl_functions::unschedule_revenue_report_email(); } /** * Persist the AI Assistant fields that ride along with the settings save. * * Keys are stored through AISettings::connect(), which encrypts them. A * blank field means "keep the stored key", so an unrelated settings save * never wipes a connection. * * @param array $payload Settings payload. * * @since 3.13.0 * @return void */ private function save_ai_settings( $payload ) { if ( ! class_exists( '\WPFunnels\AI\Settings\AISettings' ) ) { return; } $settings = '\WPFunnels\AI\Settings\AISettings'; foreach ( array( 'anthropic', 'openai', 'gemini' ) as $provider ) { $field = 'ai_' . $provider . '_key'; $key = isset( $payload[ $field ] ) ? trim( sanitize_text_field( $payload[ $field ] ) ) : ''; // An untouched field arrives blank, and the placeholder mask must // never be stored as a credential. if ( '' === $key || false !== strpos( $key, '•' ) ) { continue; } call_user_func( array( $settings, 'connect' ), $provider, $key ); } if ( isset( $payload['ai_provider'] ) && '' !== $payload['ai_provider'] ) { $provider = sanitize_text_field( $payload['ai_provider'] ); // wordpress_ai needs no key; activating it is the whole connection. if ( 'wordpress_ai' === $provider && ! call_user_func( array( $settings, 'isConnected' ), $provider ) ) { call_user_func( array( $settings, 'connect' ), $provider, '' ); } call_user_func( array( $settings, 'setActiveProvider' ), $provider ); } if ( isset( $payload['ai_custom_instructions'] ) ) { call_user_func( array( $settings, 'saveCustomInstructions' ), sanitize_textarea_field( $payload['ai_custom_instructions'] ) ); } if ( isset( $payload['ai_enabled'] ) ) { call_user_func( array( $settings, 'setEnabled' ), 'yes' === $payload['ai_enabled'] ); } if ( isset( $payload['mcp_enabled'] ) ) { update_option( '_wpfnl_mcp_enabled', 'yes' === $payload['mcp_enabled'] ? 'yes' : 'no' ); } } /** * Initialize all the settings value * * @since 1.0.0 */ public function init_settings() { $this->general_settings = Wpfnl_functions::get_general_settings(); $this->is_allow_sales = Wpfnl_functions::maybe_allow_sales_funnel(); $this->permalink_settings = Wpfnl_functions::get_permalink_settings(); $this->optin_settings = Wpfnl_functions::get_optin_settings(); $this->offer_settings = Wpfnl_functions::get_offer_settings(); $this->user_roles = Wpfnl_functions::get_user_roles(); $this->gtm_events = Wpfnl_functions::get_gtm_events(); $this->gtm_settings = Wpfnl_functions::get_gtm_settings(); $this->facebook_pixel_events = Wpfnl_functions::get_facebook_events(); $this->facebook_pixel_settings = Wpfnl_functions::get_facebook_pixel_settings(); $this->utm_params = Wpfnl_functions::get_utm_params(); $this->utm_settings = Wpfnl_functions::get_utm_settings(); $this->recaptcha_settings = Wpfnl_functions::get_recaptcha_settings(); $this->user_roles_settings = Wpfnl_functions::get_user_role_settings(); $this->google_map_api_key = Wpfnl_functions::get_google_map_api_key(); $this->notification_settings = Wpfnl_functions::get_notification_settings(); } /** * Clear saved templates data * * @param $payload * * @return array * @since 1.0.0 */ public function clear_templates_data($payload) { delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_wc'); delete_option(WPFNL_TEMPLATES_OPTION_KEY.'_lms'); $builder = Wpfnl_functions::get_builder_type(); delete_transient('wpfunnels_remote_template_data_wc_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_lms_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_lead_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_store_checkout_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_wc_'. $builder . '_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_lms_'. $builder . '_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_lead_'. $builder . '_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_store_checkout_'. $builder . '_' . WPFNL_VERSION); return [ 'success' => true ]; } /** * Clear transient * * @param $payload * * @return array */ public function clear_transient_cache_data($payload) { delete_transient('wpfunnels_remote_template_data_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_wc_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_lms_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_lead_' . WPFNL_VERSION); delete_transient('wpfunnels_remote_template_data_store_checkout_' . WPFNL_VERSION); delete_transient('wpfunnels_rollback_versions_' . WPFNL_VERSION); do_action('wpfunnels/after_clear_transient'); return array( 'success' => true ); } /** * After settings saved hooks * * @since 1.0.0 */ public function after_permalink_settings_saved() { if( Wpfnl_functions::maybe_funnel_page() ){ $is_permalink_saved = get_option('_wpfunnels_permalink_saved'); if ($is_permalink_saved) { flush_rewrite_rules(); delete_option('_wpfunnels_permalink_saved'); } } } /** * Get settings by meta key * * @param $key * * @return mixed|string * @since 1.0.0 */ public function get_settings_by_key($key) { return isset($this->settings_meta_keys[$key]) ? $this->settings_meta_keys[$key]: ''; } public function get_name() { return __('settings','wpfnl'); } /** * Get rollback version of WPF * * @return array|mixed * @since 2.3.0 */ public function get_roll_back_versions() { $rollback_versions = get_transient( 'wpfunnels_rollback_versions_' . WPFNL_VERSION ); if ( false === $rollback_versions ) { $max_versions = 10; require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; $plugin_information = plugins_api( 'plugin_information', [ 'slug' => 'wpfunnels', ] ); if ( empty( $plugin_information->versions ) || ! is_array( $plugin_information->versions ) ) { return []; } natsort( $plugin_information->versions ); $plugin_information->versions = array_reverse($plugin_information->versions); $rollback_versions = []; $current_index = 0; foreach ( $plugin_information->versions as $version => $download_link ) { if ( $max_versions <= $current_index ) { break; } $lowercase_version = strtolower( $version ); $is_valid_rollback_version = ! preg_match( '/(trunk|beta|rc|dev)/i', $lowercase_version ); /** * Is rollback version is valid. * * Filters the check whether the rollback version is valid. * * @param bool $is_valid_rollback_version Whether the rollback version is valid. */ $is_valid_rollback_version = apply_filters( 'wpfunnels/is_valid_rollback_version', $is_valid_rollback_version, $lowercase_version ); if ( ! $is_valid_rollback_version ) { continue; } if ( version_compare( $version, WPFNL_VERSION, '>=' ) ) { continue; } $current_index++; $rollback_versions[] = $version; } set_transient( 'wpfunnels_rollback_versions_' . WPFNL_VERSION, $rollback_versions, DAY_IN_SECONDS ); } return $rollback_versions; } public function post_wpfunnels_rollback() { check_admin_referer( 'wpfunnels_rollback' ); $rollback_versions = $this->get_roll_back_versions(); if ( empty( $_GET['version'] ) || ! in_array( $_GET['version'], $rollback_versions ) ) { wp_die( esc_html__( 'Error occurred, The version selected is invalid. Try selecting different version.', 'wpfnl' ) ); } $plugin_slug = basename( 'wpfunnels', '.php' ); $rollback = new Rollback( [ 'version' => $_GET['version'], 'plugin_name' => WPFNL_BASE, 'plugin_slug' => $plugin_slug, 'package_url' => sprintf( 'https://downloads.wordpress.org/plugin/%1s.%2s.zip', $plugin_slug, $_GET['version'] ), ] ); $rollback->run(); wp_die( '', esc_html__( 'Rollback to Previous Version', 'wpfnl' ), [ 'response' => 200, ] ); } /** * WPFunnels log * * @param $payload * * @return array */ public static function wpfnl_show_log( $payload ) { if( isset($payload['logKey']) ){ // Reduce to a bare filename — strips any directory traversal (../), // separators, and stream-wrapper prefixes (phar://, php://, etc.). $key = basename( wp_unslash( $payload['logKey'] ) ); $upload_dir = wp_upload_dir( null, false ); $log_url = trailingslashit( $upload_dir['basedir'] ) . 'wpfunnels/wpfunnels-logs/'; $file_url = $log_url . $key; // Whitelist: key must be a .log filename that actually resolves inside // the logs directory. realpath() comparison defeats any residual traversal. $real_dir = realpath( $log_url ); $real_file = file_exists( $file_url ) ? realpath( $file_url ) : false; $is_valid = $key && '.log' === strtolower( substr( $key, -4 ) ) && $real_dir && $real_file && 0 === strpos( $real_file, trailingslashit( $real_dir ) ); if ( $is_valid ) { // Never include/execute the log file — read it as data and escape it. $out = file_get_contents( $file_url ); $out = '
' . esc_html( $out ) . '
'; return array( 'success' => true, 'content' => $out, 'file_url' => $file_url, ); } return array( 'success' => false, 'content' => '', 'file_url' => '', ); } return array( 'success' => false, 'content' => '', 'file_url' => '' ); } /** * Send test notification email * * @param $payload * * @return array * @since 3.2.0 */ public function send_test_notification( $payload ) { if ( ! isset( $payload['email'] ) || ! is_email( $payload['email'] ) ) { return array( 'success' => false, 'message' => __( 'Please provide a valid email address.', 'wpfnl' ) ); } $settings = Wpfnl_functions::get_notification_settings(); $subject_template = isset( $settings['revenue_report_subject'] ) ? $settings['revenue_report_subject'] : 'Store Revenue Report - {period}'; // Calculate date range for test email (last week Monday to Sunday) $start_date = date( 'Y-m-d', strtotime( 'last monday -1 week' ) ); $end_date = date( 'Y-m-d', strtotime( 'last sunday' ) ); $date_range = date( 'F j, Y', strtotime( $start_date ) ) . ' - ' . date( 'F j, Y', strtotime( $end_date ) ); $subject = '[Test] ' . str_replace( '{period}', $date_range, $subject_template ); $to = sanitize_email( $payload['email'] ); $message = $this->get_revenue_report_email_content( 'weekly' ); $headers = array( 'Content-Type: text/html; charset=UTF-8' ); $sent = wp_mail( $to, $subject, $message, $headers ); if ( $sent ) { return array( 'success' => true, 'message' => __( 'Test email sent successfully! Please check your inbox.', 'wpfnl' ) ); } else { return array( 'success' => false, 'message' => __( 'Failed to send test email. Please check your email configuration.', 'wpfnl' ) ); } } /** * Send revenue report email (triggered by Action Scheduler) * * @since 3.2.0 */ public function send_revenue_report_email() { $settings = Wpfnl_functions::get_notification_settings(); // Check if still enabled if ( ! isset( $settings['enable_revenue_report'] ) || $settings['enable_revenue_report'] !== 'yes' ) { return; } $recipients = isset( $settings['revenue_report_recipient'] ) ? $settings['revenue_report_recipient'] : get_option('admin_email'); $subject = isset( $settings['revenue_report_subject'] ) ? $settings['revenue_report_subject'] : 'Store Revenue Report - {period}'; $frequency = isset( $settings['revenue_report_frequency'] ) ? $settings['revenue_report_frequency'] : 'weekly'; // Calculate date range based on frequency if ( $frequency === 'monthly' ) { $start_date = date( 'Y-m-d', strtotime( 'first day of last month' ) ); $end_date = date( 'Y-m-d', strtotime( 'last day of last month' ) ); } else { // Weekly: Last week Monday to Sunday $start_date = date( 'Y-m-d', strtotime( 'last monday -1 week' ) ); $end_date = date( 'Y-m-d', strtotime( 'last sunday' ) ); } $date_range = date( 'F j, Y', strtotime( $start_date ) ) . ' - ' . date( 'F j, Y', strtotime( $end_date ) ); // Replace {period} placeholder with date range $subject = str_replace( '{period}', $date_range, $subject ); // Generate report content $message = $this->get_revenue_report_email_content( $frequency ); // Prepare headers $headers = array( 'Content-Type: text/html; charset=UTF-8' ); // Send email to recipients (can be multiple, comma-separated) $recipient_array = array_map( 'trim', explode( ',', $recipients ) ); foreach ( $recipient_array as $recipient ) { if ( is_email( $recipient ) ) { wp_mail( sanitize_email( $recipient ), $subject, $message, $headers ); } } } /** * Get revenue report email content with actual data * * @param string $frequency * @return string * @since 3.2.0 */ private function get_revenue_report_email_content( $frequency = 'weekly' ) { $site_name = get_bloginfo( 'name' ); $site_url = home_url(); // Calculate date range if ( $frequency === 'monthly' ) { $start_date = date( 'Y-m-d', strtotime( 'first day of last month' ) ); $end_date = date( 'Y-m-d', strtotime( 'last day of last month' ) ); $period_text = __( 'Monthly', 'wpfnl' ); } else { // Weekly: Last week Monday to Sunday $start_date = date( 'Y-m-d', strtotime( 'last monday -1 week' ) ); $end_date = date( 'Y-m-d', strtotime( 'last sunday' ) ); $period_text = __( 'Weekly', 'wpfnl' ); } $date_range = date( 'F j, Y', strtotime( $start_date ) ) . ' - ' . date( 'F j, Y', strtotime( $end_date ) ); // Calculate previous period for comparison if ( $frequency === 'monthly' ) { $prev_start_date = date( 'Y-m-d', strtotime( 'first day of -2 months' ) ); $prev_end_date = date( 'Y-m-d', strtotime( 'last day of -2 months' ) ); } else { $prev_start_date = date( 'Y-m-d', strtotime( 'last monday -2 weeks' ) ); $prev_end_date = date( 'Y-m-d', strtotime( 'last sunday -1 week' ) ); } // Get revenue data for current and previous periods $revenue_data = $this->get_revenue_data( $start_date, $end_date ); $previous_data = $this->get_revenue_data( $prev_start_date, $prev_end_date ); // Calculate percentage changes $revenue_data['total_orders_change'] = $this->calculate_percentage_change( $previous_data['total_orders'], $revenue_data['total_orders'] ); $revenue_data['total_customers_change'] = $this->calculate_percentage_change( $previous_data['total_customers'], $revenue_data['total_customers'] ); $revenue_data['total_revenue_change'] = $this->calculate_percentage_change( $previous_data['total_revenue_raw'], $revenue_data['total_revenue_raw'] ); $revenue_data['average_order_value_change'] = $this->calculate_percentage_change( $previous_data['average_order_value_raw'], $revenue_data['average_order_value_raw'] ); // Build email with FunnelKit-style design $message = ''; $message .= ''; $message .= ''; $message .= ''; $message .= ''; $message .= 'Performance Report'; $message .= ''; $message .= ''; // Main container $message .= ''; $message .= ''; $message .= '
'; $message .= ''; // Top orange banner with text $message .= ''; // WPFunnels logo section $message .= ''; // Date range, Performance Report title, and View Button - all in same background $message .= ''; // Key Performance Metrics section $message .= ''; // Additional revenue info if ( isset( $revenue_data['total_revenue_raw'] ) && $revenue_data['total_revenue_raw'] > 0 ) { $message .= ''; } // Footer $message .= ''; $message .= '
'; $message .= '
Your ' . esc_html( strtolower( $frequency ) ) . ' summary from WPFunnels.
'; $message .= '
'; $message .= ''; $message .= ''; $message .= ''; $message .= ''; $message .= ''; $message .= 'WPFunnels'; $message .= '
'; // Date range $message .= '
' . esc_html( $date_range ) . '
'; // Performance Report title $message .= '

Store Revenue Summary

'; $message .= '

Track your store performance and revenue metrics for this period

'; // View Dashboard Button $message .= 'View Dashboard'; $message .= '
'; $message .= '

Revenue Overview

'; $message .= '

Your ' . esc_html( strtolower( $frequency ) ) . ' performance snapshot

'; // Metrics grid $message .= ''; // Row 1: Total orders and Total contacts $message .= ''; $message .= ''; $message .= ''; $message .= ''; // Row 2: Revenue and Bump revenue $message .= ''; $message .= ''; $message .= ''; $message .= ''; // Row 3: Upsell revenue and Average order value $message .= ''; $message .= ''; $message .= ''; $message .= ''; $message .= '
'; $message .= '
'; $message .= '
Total orders
'; $message .= '
' . esc_html( $revenue_data['total_orders'] ) . '
'; $message .= $revenue_data['total_orders_change']; $message .= '
'; $message .= '
'; $message .= '
'; $message .= '
Total customers
'; $message .= '
' . esc_html( $revenue_data['total_customers'] ) . '
'; $message .= $revenue_data['total_customers_change']; $message .= '
'; $message .= '
'; $message .= '
'; $message .= '
Revenue
'; $message .= '
' . wp_strip_all_tags( $revenue_data['total_revenue'] ) . '
'; $message .= $revenue_data['total_revenue_change']; $message .= '
'; $message .= '
'; $message .= '
'; $message .= '
Bump revenue
'; if ( Wpfnl_functions::is_wpfnl_pro_activated() ) { $message .= '
0 USD
'; $message .= '
0% - Previous period
'; } else { $message .= '
'; $message .= '
WPFunnels Pro
'; } $message .= '
'; $message .= '
'; $message .= '
'; $message .= '
Upsell revenue
'; if ( Wpfnl_functions::is_wpfnl_pro_activated() ) { $message .= '
0 USD
'; $message .= '
0% - Previous period
'; } else { $message .= '
'; $message .= '
WPFunnels Pro
'; } $message .= '
'; $message .= '
'; $message .= '
'; $message .= '
Average order value
'; $message .= '
' . wp_strip_all_tags( $revenue_data['average_order_value'] ) . '
'; $message .= $revenue_data['average_order_value_change']; $message .= '
'; $message .= '
'; $message .= '
'; $message .= '
'; $message .= '

Since installing WPFunnels you have captured additional revenue of ' . wp_kses_post( wc_price( $revenue_data['total_revenue_raw'] ) ) . '

'; $message .= '
'; $message .= '
'; $message .= '

This performance report email was sent from the ' . esc_html( $site_name ) . ' by admin ' . esc_html( date( 'F j, Y' ) ) . '

'; $message .= '

Click here to change the frequency and recipients.

'; $message .= '
'; $message .= '
'; $message .= ''; $message .= ''; return $message; } /** * Get revenue data from WPFunnels stats * * @param string $start_date * @param string $end_date * @return array * @since 3.2.0 */ private function get_revenue_data( $start_date, $end_date ) { global $wpdb; $table = $wpdb->prefix . 'wpfnl_stats'; // Get total orders count $total_orders = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM $table WHERE date_created >= %s AND date_created <= %s AND status = 'completed'", $start_date, $end_date . ' 23:59:59' ) ); // Get total revenue (total_sales includes everything: checkout + order bump + upsell + downsell) $total_revenue_raw = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(total_sales) FROM $table WHERE date_created >= %s AND date_created <= %s AND status = 'completed'", $start_date, $end_date . ' 23:59:59' ) ); // Get total customers (unique customer_id) $total_customers = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT customer_id) FROM $table WHERE date_created >= %s AND date_created <= %s AND status = 'completed'", $start_date, $end_date . ' 23:59:59' ) ); // Calculate average order value $total_orders_int = intval( $total_orders ); $total_revenue_float = floatval( $total_revenue_raw ); $average = $total_orders_int > 0 ? $total_revenue_float / $total_orders_int : 0; // Format data $data = array( 'total_revenue' => function_exists( 'wc_price' ) ? wc_price( $total_revenue_float ) : '$' . number_format( $total_revenue_float, 2 ), 'total_revenue_raw' => $total_revenue_float, 'total_orders' => $total_orders_int, 'total_customers' => intval( $total_customers ), 'average_order_value' => function_exists( 'wc_price' ) ? wc_price( $average ) : '$' . number_format( $average, 2 ), 'average_order_value_raw' => $average, ); return $data; } /** * Calculate percentage change between two values * * @param float $old_value * @param float $new_value * @return string HTML formatted percentage change * @since 3.2.0 */ private function calculate_percentage_change( $old_value, $new_value ) { $old_value = floatval( $old_value ); $new_value = floatval( $new_value ); if ( $old_value == 0 ) { if ( $new_value > 0 ) { return '
+100% - Previous period
'; } else { return '
0% - Previous period
'; } } $change = ( ( $new_value - $old_value ) / $old_value ) * 100; $change_formatted = number_format( abs( $change ), 1 ); if ( $change > 0 ) { return '
+' . $change_formatted . '% - Previous period
'; } elseif ( $change < 0 ) { return '
-' . $change_formatted . '% - Previous period
'; } else { return '
0% - Previous period
'; } } /** * WPFunnels log * * @param $payload * * @return array */ public static function wpfnl_delete_log( $payload ) { if( isset($payload['logKey']) ){ $key = sanitize_file_name( basename( $payload['logKey'] ) ); $upload_dir = wp_upload_dir( null, false ); $log_dir = $upload_dir['basedir'].'/wpfunnels/wpfunnels-logs/'; $file_name = $log_dir . $key; // Verify the resolved path is within the log directory $real_file = realpath($file_name); $real_log_dir = realpath($log_dir); if ( $real_file && $real_log_dir && strpos( $real_file, $real_log_dir ) === 0 ) { $response = \Wpfnl_Logger::delete_log_file( $file_name ); if( $response ){ return array('success' => true); } } } return array( 'success' => false, 'content' => '', 'file_url' => '' ); } }