Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/woo-cart-abandonment-recovery/admin/ajax/ajax-setting.php
+++ b/woo-cart-abandonment-recovery/admin/ajax/ajax-setting.php
@@ -85,8 +85,8 @@
wp_send_json_error( $response_data );
}
- // Add special case for cf_analytics_optin.
- if ( 'cf_analytics_optin' === $option && 'on' === $value ) {
+ // Add special case for wcar_usage_optin.
+ if ( 'wcar_usage_optin' === $option && 'on' === $value ) {
$value = 'yes'; // We have to change the value of Analytics toggle to Yes or blank as per the library requirement.
}
--- a/woo-cart-abandonment-recovery/admin/api/dashboard.php
+++ b/woo-cart-abandonment-recovery/admin/api/dashboard.php
@@ -313,7 +313,10 @@
case WCF_CART_COMPLETED_ORDER:
return 'Successful';
case WCF_CART_LOST_ORDER:
+ case WCF_CART_FAILED_ORDER:
return 'Failed';
+ case WCF_CART_BLACKLISTED_ORDER:
+ return 'Blacklisted';
default:
return 'Normal';
}
--- a/woo-cart-abandonment-recovery/admin/api/detailed-report.php
+++ b/woo-cart-abandonment-recovery/admin/api/detailed-report.php
@@ -124,6 +124,8 @@
case WCF_CART_LOST_ORDER:
case WCF_CART_FAILED_ORDER:
return 'Failed';
+ case WCF_CART_BLACKLISTED_ORDER:
+ return 'Blacklisted';
default:
return 'Normal';
}
--- a/woo-cart-abandonment-recovery/admin/api/follow-up.php
+++ b/woo-cart-abandonment-recovery/admin/api/follow-up.php
@@ -108,7 +108,10 @@
case WCF_CART_COMPLETED_ORDER:
return 'Successful';
case WCF_CART_LOST_ORDER:
+ case WCF_CART_FAILED_ORDER:
return 'Failed';
+ case WCF_CART_BLACKLISTED_ORDER:
+ return 'Blacklisted';
default:
return 'Normal';
}
--- a/woo-cart-abandonment-recovery/admin/build/settings.asset.php
+++ b/woo-cart-abandonment-recovery/admin/build/settings.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '072bad15f265d6b2fdfd');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '5fa1ee44a6ed4fc9d5ec');
--- a/woo-cart-abandonment-recovery/admin/inc/meta-options.php
+++ b/woo-cart-abandonment-recovery/admin/inc/meta-options.php
@@ -302,8 +302,8 @@
'cf-analytics-optin' => [
'type' => 'toggle',
'label' => __( 'Help Improve Cart Abandonment', 'woo-cart-abandonment-recovery' ),
- 'name' => 'cf_analytics_optin',
- 'value' => wcf_ca()->utils->wcar_get_option( 'cf_analytics_optin' ),
+ 'name' => 'wcar_usage_optin',
+ 'value' => wcf_ca()->utils->wcar_get_option( 'wcar_usage_optin' ),
'desc' => sprintf(
/* translators: %1$s: Start Link Node and $2%s End Link Node. */
__( 'Collect non-sensitive information from your website, such as the PHP version and features used, to help us fix bugs faster, make smarter decisions, and build features that actually matter to you. %1$sLearn more%2$s', 'woo-cart-abandonment-recovery' ),
@@ -839,7 +839,7 @@
];
if ( ! _is_wcar_pro_license_activated() ) {
- $fields['follow-up-channels']['fields']['wcf-ca-sms-tracking-status'] = [
+ $fields['follow-up-channels']['fields']['wcf-ca-sms-tracking-status'] = [
'type' => 'toggle',
'label' => __( 'Enable SMS Follow-ups', 'woo-cart-abandonment-recovery' ),
'name' => 'wcf_ca_sms_tracking_status',
@@ -849,6 +849,16 @@
'is_pro' => true,
'pro_upgrade_message' => '<span style="font-weight: 600;">SMS Follow Up</span> lets you follow up and recover revenue via SMS.',
];
+ $fields['follow-up-channels']['fields']['wcf-ca-whatsapp-tracking-status'] = [
+ 'type' => 'toggle',
+ 'label' => __( 'Enable WhatsApp Follow-ups', 'woo-cart-abandonment-recovery' ),
+ 'name' => 'wcf_ca_whatsapp_tracking_status',
+ 'value' => false,
+ 'desc' => __( 'Automatically send WhatsApp reminders to customers when they abandon their cart.', 'woo-cart-abandonment-recovery' ),
+ 'is_fullwidth' => true,
+ 'is_pro' => true,
+ 'pro_upgrade_message' => '<span style="font-weight: 600;">WhatsApp Follow Up</span> lets you follow up and recover revenue via WhatsApp.',
+ ];
}
$fields = apply_filters( 'wcar_admin_onboarding_fields', $fields );
@@ -904,7 +914,7 @@
return [];
}
$pro_fields = [
- 'sms-integration' => [
+ 'sms-integration' => [
'title' => __( 'SMS', 'woo-cart-abandonment-recovery' ),
'slug' => 'sms-integration',
'fields' => [
@@ -915,12 +925,27 @@
'is_fullwidth' => true,
'is_pro' => true,
'pro_upgrade_message' => __( 'Automatically send SMS reminders to customers when they abandon their cart.', 'woo-cart-abandonment-recovery' ),
- 'priority' => 51,
],
],
'priority' => 20,
'is_pro' => true,
],
+ 'whatsapp-integration' => [
+ 'title' => __( 'WhatsApp', 'woo-cart-abandonment-recovery' ),
+ 'slug' => 'whatsapp-integration',
+ 'fields' => [
+ 'wcf-ca-whatsapp-tracking-status' => [
+ 'type' => 'toggle',
+ 'label' => __( 'Enable WhatsApp Follow-ups', 'woo-cart-abandonment-recovery' ),
+ 'name' => 'wcf_ca_whatsapp_tracking_status',
+ 'is_fullwidth' => true,
+ 'is_pro' => true,
+ 'pro_upgrade_message' => __( 'Automatically send WhatsApp reminders to customers when they abandon their cart.', 'woo-cart-abandonment-recovery' ),
+ ],
+ ],
+ 'priority' => 30,
+ 'is_pro' => true,
+ ],
];
return $pro_fields;
@@ -947,6 +972,22 @@
'pro_upgrade_message' => __( 'Show a GDPR consent message below the phone number field on the checkout page.', 'woo-cart-abandonment-recovery' ),
];
+ $settings['blacklist-settings'] = [
+ 'title' => __( 'Blacklist', 'woo-cart-abandonment-recovery' ),
+ 'slug' => 'blacklist-settings',
+ 'fields' => [
+ 'wcf-ca-email-blacklist-status' => [
+ 'type' => 'toggle',
+ 'label' => __( 'Enable Blacklist', 'woo-cart-abandonment-recovery' ),
+ 'name' => 'wcf_ca_phone_gdpr_status',
+ 'is_fullwidth' => true,
+ 'is_pro' => true,
+ 'pro_upgrade_message' => __( 'Prevent recovery emails from being sent to specific email addresses or domains.', 'woo-cart-abandonment-recovery' ),
+ ],
+ ],
+ 'priority' => 35,
+ ];
+
return $settings;
}
}
--- a/woo-cart-abandonment-recovery/admin/inc/wcar-admin.php
+++ b/woo-cart-abandonment-recovery/admin/inc/wcar-admin.php
@@ -269,7 +269,7 @@
$settings[ $option_key ] = get_option( $option_key, $default_value );
}
- $settings['cf_analytics_optin'] = wcf_ca()->utils->wcar_get_option( 'cf_analytics_optin' );
+ $settings['wcar_usage_optin'] = wcf_ca()->utils->wcar_get_option( 'wcar_usage_optin' );
/**
* Filter cart abandonment settings
@@ -357,6 +357,15 @@
'path' => 'suretriggers/suretriggers.php',
'logo' => esc_url( $base_url . 'ottokit.svg' ),
],
+ [
+ 'title' => __( 'Power Coupons', 'woo-cart-abandonment-recovery' ),
+ 'desc' => __( 'Power Coupons helps shop owners create smart discounts and auto-apply coupons to reduce friction and boost conversions.', 'woo-cart-abandonment-recovery' ),
+ 'status' => $this->get_plugin_status( 'power-coupons/power-coupons.php' ),
+ 'slug' => 'power-coupons',
+ 'path' => 'power-coupons/power-coupons.php',
+ 'logo' => esc_url( $base_url . 'power-coupons.svg' ),
+ 'feature' => 'NEW',
+ ],
];
}
@@ -389,12 +398,13 @@
'user_detail_firstname' => wp_get_current_user()->first_name,
'user_detail_lastname' => wp_get_current_user()->last_name,
'user_detail_email' => wp_get_current_user()->user_email,
- 'cf_analytics_optin' => false,
+ 'wcar_usage_optin' => false,
],
'plugins' => [
- 'cartflows' => true,
- 'modern-cart' => true,
- 'suretriggers' => true,
+ 'cartflows' => true,
+ 'modern-cart' => true,
+ 'suretriggers' => true,
+ 'power-coupons' => true,
],
];
}
@@ -442,13 +452,13 @@
}
// Convert analytics optin value to 'yes' if it's truthy, otherwise keep as is.
- $analytics_optin_value = $onboarding_data['userDetails']['cf_analytics_optin'];
+ $analytics_optin_value = $onboarding_data['userDetails']['wcar_usage_optin'];
if ( true === $analytics_optin_value ) {
$analytics_optin_value = 'yes';
}
- wcf_ca()->helper->save_meta_fields( 'cf_analytics_optin', $analytics_optin_value );
+ wcf_ca()->helper->save_meta_fields( 'wcar_usage_optin', $analytics_optin_value );
$installable_plugin_slugs = [];
if ( ! empty( $plugin_slugs ) && is_array( $plugin_slugs ) ) {
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-admin-notices.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-admin-notices.php
@@ -241,7 +241,7 @@
'show_if' => true,
/* translators: %1$s white label plugin name and %2$s deactivation link */
'message' => sprintf(
- '<div class="notice-image" style="display: flex;">
+ '<div class="notice-image">
<img src="%1$s" class="custom-logo" alt="Cart Abandonment Recovery for WooCommerce Icon" itemprop="logo"></div>
<div class="notice-content">
<div class="notice-heading" style="font-weight: 700;">
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-default-meta.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-default-meta.php
@@ -293,6 +293,10 @@
'default' => 'off',
'sanitize' => 'FILTER_SANITIZE_STRING',
],
+ 'wcar_usage_optin' => [
+ 'default' => 'off',
+ 'sanitize' => 'FILTER_SANITIZE_STRING',
+ ],
// TODO: Remove this after new UI is enabled by default.
'cartflows_ca_use_new_ui' => [
'default' => false,
@@ -594,6 +598,22 @@
return __( 'Admin', 'woo-cart-abandonment-recovery' );
}
+
+ /**
+ * Checks if plugin option exists
+ *
+ * @param string $setting_key Setting key to check.
+ * @return bool
+ * @since 2.0.8
+ */
+ public function plugin_option_exist( $setting_key ) {
+ $plugin_options = $this->get_plugin_options();
+
+ if ( isset( $plugin_options[ $setting_key ] ) ) {
+ return true;
+ }
+ return false;
+ }
}
// Initialize the class.
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-helper.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-helper.php
@@ -343,6 +343,12 @@
* @since 1.3.3
*/
public function save_meta_fields( $option_key, $value, $network = false ) {
+
+ // Check if option is part of the plugin.
+ if ( ! wcf_ca()->options->plugin_option_exist( $option_key ) ) {
+ return false;
+ }
+
// Sanitize the value using universal sanitization.
$sanitized_value = wcf_ca()->options->sanitize_setting_value( $option_key, $value );
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-loader.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-loader.php
@@ -95,8 +95,8 @@
define( 'CARTFLOWS_CA_BASE', plugin_basename( CARTFLOWS_CA_FILE ) );
define( 'CARTFLOWS_CA_DIR', plugin_dir_path( CARTFLOWS_CA_FILE ) );
define( 'CARTFLOWS_CA_URL', plugins_url( '/', CARTFLOWS_CA_FILE ) );
- define( 'CARTFLOWS_CA_VER', '2.0.7' );
- define( 'CARTFLOWS_CA_REQ_PRO_VER', '1.1.0' );
+ define( 'CARTFLOWS_CA_VER', '2.1.0' );
+ define( 'CARTFLOWS_CA_REQ_PRO_VER', '1.2.0' );
define( 'CARTFLOWS_CA_SLUG', 'cartflows_ca' );
@@ -301,7 +301,7 @@
$bsf_analytics->set_entity(
[
- 'cf' => [
+ 'wcar' => [
'hide_optin_checkbox' => true,
'product_name' => 'Woocommerce Cart Abandonment Recovery',
'usage_doc_link' => 'https://my.cartflows.com/usage-tracking/',
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-update.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-update.php
@@ -91,6 +91,13 @@
self::handle_analytics_optin_migration();
}
+ // If the currently saved plugin version is less than or equal to 2.0.7, run the migration.
+ if ( version_compare( $saved_version, '2.0.7', '<=' ) ) {
+ self::handle_analytics_option_migration_to_new_option();
+ }
+
+ // Migrate the option to new option key.
+
// Handle UI switching logic for version upgrades.
self::handle_ui_option_on_upgrade( $saved_version );
@@ -112,18 +119,18 @@
public static function handle_ui_option_on_upgrade( $saved_version ): void {
// Only set the option if it doesn't already exist (user hasn't made a choice).
if ( false === get_option( 'cartflows_ca_use_new_ui', false ) ) {
-
+
// For versions above 2.0.0 default to new UI.
if ( version_compare( $saved_version, '2.0.0', '>' ) ) {
update_option( 'cartflows_ca_use_new_ui', true );
}
-
+
// For versions below 2.0.0, leave option as false (legacy UI with notice).
// This allows users to see the notice and choose to upgrade.
}
}
-
+
/**
* Handles the migration of the 'wcf_ca_ignore_users' option during upgrade.
*
@@ -149,9 +156,9 @@
}
/**
- * Handles the migration of the 'cf_analytics_optin' option during upgrade.
+ * Handles the migration of the 'wcar_usage_optin' option during upgrade.
*
- * This function updates the 'cf_analytics_optin' option value from 'on' to 'yes'
+ * This function updates the 'wcar_usage_optin' option value from 'on' to 'yes'
* for older users who have enabled analytics tracking. This ensures compatibility
* with the analytics library requirement.
*
@@ -159,10 +166,29 @@
* @return void
*/
public static function handle_analytics_optin_migration(): void {
- $analytics_optin = get_option( 'cf_analytics_optin', false );
+ $analytics_optin = get_option( 'wcar_usage_optin', false );
if ( 'on' === $analytics_optin ) {
- update_option( 'cf_analytics_optin', 'yes' );
+ update_option( 'wcar_usage_optin', 'yes' );
+ }
+ }
+
+ /**
+ * Handles the migration from 'cf_analytics_optin' to 'wcar_usage_optin'.
+ *
+ * This function migrates the old option key to the new standardized key
+ * for users upgrading from older versions.
+ *
+ * @since 2.0.5
+ * @return void
+ */
+ public static function handle_analytics_option_migration_to_new_option(): void {
+ $old_option = get_option( 'cf_analytics_optin', false );
+
+ if ( false !== $old_option && false === get_option( 'wcar_usage_optin', false ) ) {
+ // If the option value is on then convert it into yes as per the requirement.
+ $old_option = 'on' === $old_option ? 'yes' : $old_option;
+ update_option( 'wcar_usage_optin', $old_option );
}
}
}
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-utils.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-utils.php
@@ -126,11 +126,11 @@
} else {
$default_options = wcf_ca()->options->get_default_settings();
/**
- * Filter the options array for Cart Abandonment Settings.
- *
- * @since 2.0.0
- * @var Array
- */
+ * Filter the options array for Cart Abandonment Settings.
+ *
+ * @since 2.0.0
+ * @var Array
+ */
$default_options = apply_filters( 'wcar_get_option_array', $default_options, $option, $default );
$value = isset( $default_options[ $option ] ) ? $default_options[ $option ] : $default;
}
--- a/woo-cart-abandonment-recovery/lib/bsf-analytics/class-bsf-analytics.php
+++ b/woo-cart-abandonment-recovery/lib/bsf-analytics/class-bsf-analytics.php
@@ -49,6 +49,9 @@
$this->entities = $args;
+ // Run migration from old "analytics" option names to new "usage" names.
+ $this->maybe_migrate_options();
+
define( 'BSF_ANALYTICS_VERSION', $analytics_version );
define( 'BSF_ANALYTICS_URI', $this->get_analytics_url( $analytics_path ) );
@@ -91,8 +94,8 @@
foreach ( $this->entities as $key => $data ) {
add_action( 'astra_notice_before_markup_' . $key . '-optin-notice', array( $this, 'enqueue_assets' ) );
- add_action( 'update_option_' . $key . '_analytics_optin', array( $this, 'update_analytics_option_callback' ), 10, 3 );
- add_action( 'add_option_' . $key . '_analytics_optin', array( $this, 'add_analytics_option_callback' ), 10, 2 );
+ add_action( 'update_option_' . $key . '_usage_optin', array( $this, 'update_analytics_option_callback' ), 10, 3 );
+ add_action( 'add_option_' . $key . '_usage_optin', array( $this, 'add_analytics_option_callback' ), 10, 2 );
}
}
@@ -162,7 +165,8 @@
public function is_tracking_enabled() {
foreach ( $this->entities as $key => $data ) {
- $is_enabled = get_site_option( $key . '_analytics_optin' ) === 'yes' ? true : false;
+
+ $is_enabled = get_site_option( $key . '_usage_optin', false ) === 'yes' ? true : false;
$is_enabled = $this->is_white_label_enabled( $key ) ? false : $is_enabled;
if ( apply_filters( $key . '_tracking_enabled', $is_enabled ) ) {
@@ -212,8 +216,9 @@
return; // Don't need to display notice if any of our plugin already have the permission.
}
- // If the user has opted out of tracking, don't show the notice till 7 days.
- if ( get_site_option( 'bsf_analytics_last_displayed_time' ) > time() - ( 7 * DAY_IN_SECONDS ) ) {
+ // If the user has opted out of tracking, don't show the notice till 7 days.
+ $last_displayed_time = get_site_option( 'bsf_usage_last_displayed_time', false );
+ if ( $last_displayed_time && $last_displayed_time > time() - ( 7 * DAY_IN_SECONDS ) ) {
return; // Don't display the notice if it was displayed recently.
}
@@ -223,7 +228,7 @@
$usage_doc_link = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->usage_doc_link;
// Don't display the notice if tracking is disabled or White Label is enabled for any of our plugins.
- if ( false !== get_site_option( $key . '_analytics_optin', false ) || $this->is_white_label_enabled( $key ) ) {
+ if ( false !== get_site_option( $key . '_usage_optin', false ) || $this->is_white_label_enabled( $key ) ) {
continue;
}
@@ -347,18 +352,18 @@
* @since 1.0.0
*/
private function optin( $source ) {
- update_site_option( $source . '_analytics_optin', 'yes' );
+ update_site_option( $source . '_usage_optin', 'yes' );
}
/**
- * Opt out to usage tracking.
+ * Opt out of usage tracking.
*
* @param string $source source of analytics.
* @since 1.0.0
*/
private function optout( $source ) {
- update_site_option( $source . '_analytics_optin', 'no' );
- update_site_option( 'bsf_analytics_last_displayed_time', time() );
+ update_site_option( $source . '_usage_optin', 'no' );
+ update_site_option( 'bsf_usage_last_displayed_time', time() );
}
/**
@@ -376,6 +381,49 @@
}
/**
+ * Migrate old "analytics" options to new "usage" naming.
+ * Copies values to new options and deletes old options.
+ *
+ * @since 1.1.17
+ */
+ private function maybe_migrate_options() {
+ if ( get_site_option( 'bsf_usage_migrated' ) ) {
+ return;
+ }
+
+ // Migrate global options.
+ $old_last_displayed = get_site_option( 'bsf_analytics_last_displayed_time' );
+ if ( false !== $old_last_displayed ) {
+ update_site_option( 'bsf_usage_last_displayed_time', $old_last_displayed );
+ delete_site_option( 'bsf_analytics_last_displayed_time' );
+ }
+
+ // Migrate per-product options.
+ foreach ( $this->entities as $key => $data ) {
+ $old_optin = get_site_option( $key . '_analytics_optin' );
+ if ( false !== $old_optin ) {
+ update_site_option( $key . '_usage_optin', $old_optin );
+ delete_site_option( $key . '_analytics_optin' );
+ }
+
+ $old_install_time = get_site_option( $key . '_analytics_installed_time' );
+ if ( false !== $old_install_time ) {
+ update_site_option( $key . '_usage_installed_time', $old_install_time );
+ delete_site_option( $key . '_analytics_installed_time' );
+ }
+ }
+
+ // Migrate transient.
+ $old_track = get_site_transient( 'bsf_analytics_track' );
+ if ( false !== $old_track ) {
+ set_site_transient( 'bsf_usage_track', $old_track, 2 * DAY_IN_SECONDS );
+ delete_site_transient( 'bsf_analytics_track' );
+ }
+
+ update_site_option( 'bsf_usage_migrated', true );
+ }
+
+ /**
* Register usage tracking option in General settings page.
*
* @since 1.0.0
@@ -404,12 +452,12 @@
register_setting(
'general', // Options group.
- $key . '_analytics_optin', // Option name/database.
+ $key . '_usage_optin', // Option name/database.
array( 'sanitize_callback' => array( $this, 'sanitize_option' ) ) // sanitize callback function.
);
add_settings_field(
- $key . '-analytics-optin', // Field ID.
+ $key . '-usage-optin', // Field ID.
__( 'Usage Tracking', 'woo-cart-abandonment-recovery' ), // Field title.
array( $this, 'render_settings_field_html' ), // Field callback function.
'general',
@@ -417,9 +465,9 @@
array(
'type' => 'checkbox',
'title' => $author,
- 'name' => $key . '_analytics_optin',
- 'label_for' => $key . '-analytics-optin',
- 'id' => $key . '-analytics-optin',
+ 'name' => $key . '_usage_optin',
+ 'label_for' => $key . '-usage-optin',
+ 'id' => $key . '-usage-optin',
'usage_doc_link' => $usage_doc_link,
)
);
@@ -448,10 +496,11 @@
* @since 1.0.0
*/
public function render_settings_field_html( $args ) {
+ $is_checked = ( 'yes' === get_site_option( $args['name'], false ) );
?>
<fieldset>
<label for="<?php echo esc_attr( $args['label_for'] ); ?>">
- <input id="<?php echo esc_attr( $args['id'] ); ?>" type="checkbox" value="1" name="<?php echo esc_attr( $args['name'] ); ?>" <?php checked( get_site_option( $args['name'], 'no' ), 'yes' ); ?>>
+ <input id="<?php echo esc_attr( $args['id'] ); ?>" type="checkbox" value="1" name="<?php echo esc_attr( $args['name'] ); ?>" <?php checked( $is_checked ); ?>>
<?php
/* translators: %s Product title */
echo esc_html( sprintf( __( 'Allow %s products to track non-sensitive usage tracking data.', 'woo-cart-abandonment-recovery' ), $args['title'] ) );// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
@@ -469,7 +518,7 @@
}
/**
- * Set analytics installed time in option.
+ * Get analytics installed time from option.
*
* @param string $source source of analytics.
* @return string $time analytics installed time.
@@ -477,11 +526,11 @@
*/
private function get_analytics_install_time( $source ) {
- $time = get_site_option( $source . '_analytics_installed_time' );
+ $time = get_site_option( $source . '_usage_installed_time' );
if ( ! $time ) {
$time = time();
- update_site_option( $source . '_analytics_installed_time', time() );
+ update_site_option( $source . '_usage_installed_time', $time );
}
return $time;
@@ -525,12 +574,12 @@
return;
}
- $analytics_track = get_site_transient( 'bsf_analytics_track' );
+ $analytics_track = get_site_transient( 'bsf_usage_track' );
// If the last data sent is 2 days old i.e. transient is expired.
if ( ! $analytics_track ) {
$this->send();
- set_site_transient( 'bsf_analytics_track', true, 2 * DAY_IN_SECONDS );
+ set_site_transient( 'bsf_usage_track', true, 2 * DAY_IN_SECONDS );
}
}
--- a/woo-cart-abandonment-recovery/lib/class-cartflows-ca-bsf-analytics.php
+++ b/woo-cart-abandonment-recovery/lib/class-cartflows-ca-bsf-analytics.php
@@ -58,10 +58,11 @@
*/
public function get_specific_stats( $stats_data ) {
- if ( apply_filters( 'cartflows_ca_enable_non_sensitive_data_tracking', get_option( 'cf_analytics_optin', false ) ) ) {
+ if ( apply_filters( 'cartflows_ca_enable_non_sensitive_data_tracking', get_option( 'wcar_usage_optin', false ) ) ) {
// Prepare default data to be tracked.
$stats_data['plugin_data']['cart_abandonment'] = $this->get_default_stats();
+ $stats_data['plugin_data']['cart_abandonment']['kpi_records'] = $this->get_kpi_tracking_data();
$stats_data['plugin_data']['cart_abandonment']['numeric_values'] = $this->get_numeric_data_stats();
$stats_data['plugin_data']['cart_abandonment']['boolean_values'] = $this->get_boolean_data_stats();
@@ -90,6 +91,7 @@
$default_data = array(
'website-domain' => str_ireplace( array( 'http://', 'https://' ), '', home_url() ),
'plugin-version' => $version_numbers['plugin_version'],
+ 'wcar-pro-version' => $version_numbers['wcar_pro_version'],
'woocommerce-version' => $version_numbers['wc_version'],
'wp-version' => $version_numbers['wp_version'],
'php-version' => $version_numbers['php_version'],
@@ -103,6 +105,58 @@
}
/**
+ * Get KPI tracking data for the last 2 days (excluding today).
+ *
+ * @since x.x.x
+ * @return array KPI data organized by date
+ */
+ private function get_kpi_tracking_data() {
+ $kpi_data = array();
+ $today = current_time( 'Y-m-d' );
+
+ // Get data for yesterday and day before yesterday.
+ for ( $i = 1; $i <= 2; $i++ ) {
+ $date = gmdate( 'Y-m-d', strtotime( $today . ' -' . $i . ' days' ) );
+ $order_count = $this->get_daily_orders_count( $date );
+
+ // Always include data, even if submissions is 0.
+ $kpi_data[ $date ] = array(
+ 'numeric_values' => array(
+ 'recovered_order_count' => $order_count,
+ ),
+ );
+ }
+
+ return $kpi_data;
+ }
+
+ /**
+ * Get daily submissions count for a specific date.
+ *
+ * @param string $date Date in Y-m-d format.
+ * @since x.x.x
+ * @return int Daily submissions count
+ */
+ private function get_daily_orders_count( $date ) {
+ global $wpdb;
+
+ $cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
+ $start_date = $date . ' 00:00:00';
+ $end_date = $date . ' 23:59:59';
+
+ $count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $wpdb->prepare(
+ "SELECT COUNT(*) FROM {$cart_abandonment_table} WHERE `order_status` = %s AND `time` >= %s AND `time` <= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ WCF_CART_COMPLETED_ORDER,
+ $start_date,
+ $end_date
+ )
+ );
+
+ return absint( $count );
+ }
+
+ /**
* Retrieves numeric data statistics for Cart Abandonment Recovery.
*
* This function collects and returns numeric data statistics for Cart Abandonment Recovery, including total users, active users, and follow-up email counts.
@@ -113,7 +167,10 @@
public function get_numeric_data_stats() {
// Return the prepared data.
return array(
- 'total_followup_emails' => strval( $this->get_total_followup_emails_count() ),
+ 'total_followup_emails' => strval( $this->get_total_followup_emails_count() ),
+ 'email_tmpl_with_coupon' => strval( $this->get_email_template_meta_count( 'override_global_coupon' ) ),
+ 'email_tmpl_with_conditions' => strval( $this->get_email_template_meta_count( 'enable_sms_rule_engine' ) ),
+ 'email_tmpl_with_exclusions' => strval( $this->get_email_template_meta_count( 'exclude_product_ids' ) ),
);
}
@@ -132,6 +189,8 @@
'webhook_enabled' => $this->get_webhook_enabled_status(),
'using_woo_template' => $this->get_woo_template_usage_status(),
'using_new_ui' => $this->get_ui_enabled_status(),
+ 'is_email_gdpr_enable' => $this->get_email_gdpr_enabled_status(),
+ 'suretriggers_active' => is_plugin_active( 'suretriggers/suretriggers.php' ),
);
}
@@ -150,10 +209,21 @@
'nps_submitted' => isset( $nps_data['submitted'] ) ? (bool) $nps_data['submitted'] : false,
'nps_dismissed' => isset( $nps_data['dismissed'] ) ? (bool) $nps_data['dismissed'] : false,
'nps_first_shown' => isset( $nps_data['first_shown'] ) ? sanitize_text_field( $nps_data['first_shown'] ) : '',
+ 'nps_dismiss_step' => isset( $nps_data['dismiss_step'] ) ? sanitize_text_field( $nps_data['dismiss_step'] ) : '',
);
}
/**
+ * Get GDPR Info.
+ *
+ * @since x.x.x
+ * @return bool True if GDPR is enabled, false otherwise.
+ */
+ public function get_email_gdpr_enabled_status(){
+ return 'on' === wcf_ca()->utils->wcar_get_option( 'wcf_ca_gdpr_status' ) ? true : false;
+ }
+
+ /**
* Get plugin version numbers.
*
* @since 2.0.0
@@ -162,6 +232,7 @@
public function get_version_numbers() {
return array(
'plugin_version' => CARTFLOWS_CA_VER,
+ 'wcar_pro_version' => defined( 'WCAR_PRO_VER' ) ? WCAR_PRO_VER : '',
'wp_version' => get_bloginfo( 'version' ),
'wc_version' => defined( 'WC_VERSION' ) ? WC_VERSION : '',
'php_version' => function_exists( 'phpversion' ) ? phpversion() : '',
@@ -247,6 +318,29 @@
}
/**
+ * Get count of email templates where a given meta key has a non-empty value.
+ *
+ * @since x.x.x
+ * @param string $meta_key The meta key to filter by.
+ * @return int Count of matching email templates.
+ */
+ private function get_email_template_meta_count( $meta_key ) {
+ global $wpdb;
+
+ $email_template_meta_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_TEMPLATE_META_TABLE;
+
+ $count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $wpdb->prepare(
+ "SELECT COUNT(*) FROM {$email_template_meta_table} WHERE meta_key = %s AND meta_value != %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ $meta_key,
+ ''
+ )
+ );
+
+ return intval( $count );
+ }
+
+ /**
* Get webhook enabled status.
*
* @since 2.0.0
--- a/woo-cart-abandonment-recovery/modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php
+++ b/woo-cart-abandonment-recovery/modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php
@@ -185,6 +185,9 @@
$body_email_preview = str_replace( '{{cart.product.table}}', $var, $body_email_preview );
$body_email_preview = wpautop( $body_email_preview );
+ // Convert TinyMCE alignment classes to inline styles for email compatibility.
+ $body_email_preview = $this->convert_alignment_classes_to_styles( $body_email_preview );
+
/**
* Filter to modify email body before sending.
*
@@ -646,6 +649,83 @@
return $result;
}
+ /**
+ * Convert TinyMCE alignment classes to inline styles for email compatibility.
+ *
+ * @param string $content Email content with potential alignment classes.
+ * @return string Content with alignment classes converted to inline styles.
+ */
+ public function convert_alignment_classes_to_styles( $content ) {
+ // Pattern to match img tags with alignment classes.
+ $pattern = '/<img([^>]*?)class=["']([^"']*?)(alignleft|aligncenter|alignright|alignnone)([^"']*?)["']([^>]*?)>/i';
+
+ return preg_replace_callback( $pattern, array( __CLASS__, 'replace_alignment_callback' ), $content );
+ }
+
+ /**
+ * Callback function to replace alignment classes with inline styles.
+ *
+ * @param array $matches Regex matches.
+ * @return string Modified img tag with inline styles.
+ */
+ private function replace_alignment_callback( $matches ) {
+ $before_class = $matches[1];
+ $class_before = $matches[2];
+ $alignment = $matches[3];
+ $class_after = $matches[4];
+ $after_class = $matches[5];
+
+ // Remove the alignment class from the class attribute.
+ $new_classes = trim( $class_before . ' ' . $class_after );
+ $new_classes = preg_replace( '/s+/', ' ', $new_classes );
+
+ // Get alignment style.
+ $alignment_style = $this->get_alignment_style( $alignment );
+
+ // Check if style attribute already exists.
+ if ( preg_match( '/style=["']([^"']*)["']/', $before_class . $after_class, $style_matches ) ) {
+ // Merge with existing styles.
+ $existing_style = rtrim( $style_matches[1], '; ' );
+ $new_style = $existing_style . '; ' . $alignment_style;
+ $img_tag = preg_replace( '/style=["'][^"']*["']/', 'style="' . $new_style . '"', $before_class . $after_class );
+ } else {
+ // Add new style attribute.
+ $img_tag = $before_class . ' style="' . $alignment_style . '"' . $after_class;
+ }
+
+ // Rebuild the img tag.
+ if ( ! empty( $new_classes ) ) {
+ $class_attr = ' class="' . $new_classes . '"';
+ } else {
+ $class_attr = '';
+ // Remove empty class attribute.
+ $img_tag = preg_replace( '/s*class=["']["']/', '', $img_tag );
+ }
+
+ return '<img' . $img_tag . $class_attr . '>';
+ }
+
+ /**
+ * Get CSS style for alignment.
+ *
+ * @param string $alignment Alignment class (alignleft, aligncenter, alignright, alignnone).
+ * @return string CSS style string.
+ */
+ private function get_alignment_style( $alignment ) {
+ switch ( $alignment ) {
+ case 'alignleft':
+ return 'float: left; margin: 0 10px 10px 0';
+ case 'alignright':
+ return 'float: right; margin: 0 0 10px 10px';
+ case 'aligncenter':
+ return 'display: block; margin: 0 auto';
+ case 'alignnone':
+ return 'display: inline; margin: 0';
+ default:
+ return '';
+ }
+ }
+
}
Cartflows_Ca_Email_Schedule::get_instance();
--- a/woo-cart-abandonment-recovery/modules/cart-abandonment/classes/class-cartflows-ca-tracking.php
+++ b/woo-cart-abandonment-recovery/modules/cart-abandonment/classes/class-cartflows-ca-tracking.php
@@ -78,6 +78,7 @@
define( 'WCF_CART_LOST_ORDER', 'lost' );
define( 'WCF_CART_NORMAL_ORDER', 'normal' );
define( 'WCF_CART_FAILED_ORDER', 'failed' );
+ define( 'WCF_CART_BLACKLISTED_ORDER', 'blacklisted' );
define( 'CARTFLOWS_ZAPIER_ACTION_AFTER_TIME', 1800 );
define( 'WCF_ACTION_ABANDONED_CARTS', 'abandoned_carts' );
@@ -354,7 +355,7 @@
$wcf_ac_token = Cartflows_Ca_Helper::get_instance()->sanitize_text_filter( 'wcf_ac_token', 'GET' );
$token_data = $this->wcf_decode_token( $wcf_ac_token );
if ( is_checkout() && ! is_wc_endpoint_url() && isset( $token_data['wcf_preview_email'] ) && $token_data['wcf_preview_email'] ) {
- wc_print_notice( __( 'This checkout page is generated by Cart Abandonment Recovery for WooCommerce plugin from test mail.', 'woo-cart-abandonment-recovery' ), 'notice' );
+ wc_print_notice( __( 'This checkout page was generated by the Cart Abandonment Recovery for WooCommerce plugin for testing.', 'woo-cart-abandonment-recovery' ), 'notice' );
}
}
@@ -782,6 +783,8 @@
} else {
$wpdb->delete( $cart_abandonment_table, [ 'session_id' => sanitize_key( $session_id ) ] ); // db call ok; no cache ok.
}
+
+ do_action( 'wcf_ca_after_save_abandonment_data', $session_id, $checkout_details );
wp_send_json_success();
}
@@ -803,9 +806,9 @@
$payment_gateway = WC()->session->chosen_payment_method;
// Retrieving cart products and their quantities.
- $products = WC()->cart->get_cart();
- $current_time = current_time( WCF_CA_DATETIME_FORMAT );
- $other_fields = [
+ $products = WC()->cart->get_cart();
+ $current_time = current_time( WCF_CA_DATETIME_FORMAT );
+ $default_other_fields = [
'wcf_billing_company' => $post_data['wcf_billing_company'],
'wcf_billing_address_1' => $post_data['wcf_billing_address_1'],
'wcf_billing_address_2' => $post_data['wcf_billing_address_2'],
@@ -829,6 +832,8 @@
'wcf_gdpr_phone_consent' => $post_data['wcf_gdpr_phone_consent'],
];
+ $other_fields = apply_filters( 'wcar_cart_default_other_fields', $default_other_fields );
+
$checkout_details = apply_filters(
'woo_ca_session_abandoned_data',
[
--- a/woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php
+++ b/woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php
@@ -3,7 +3,7 @@
* Plugin Name: Cart Abandonment Recovery for WooCommerce
* Plugin URI: https://cartflows.com/
* Description: Recover your lost revenue. Capture email address of users on the checkout page and send follow up emails if they don't complete the purchase.
- * Version: 2.0.7
+ * Version: 2.1.0
* Author: Brainstorm Force
* Author URI: https://www.brainstormforce.com
* Text Domain: woo-cart-abandonment-recovery