'billing_address_1', 'address-2' => 'billing_address_2', 'city' => 'billing_city', 'state' => 'billing_state', 'postcode' => 'billing_postcode', 'country' => 'billing_country', ); private $_user_address_meta_updated = array(); public $order_status_change = []; /** * WooFunnels_DB_Updater constructor. */ public function __construct() { /** Showing notice to admin to allow upgrading tokens */ add_action( 'admin_notices', array( $this, 'woofunnels_show_contact_processing_notice' ) ); add_action( 'admin_init', array( $this, 'woofunnels_handle_db_upgrade_actions' ), 100 ); /** Initiate Background Database tables customer and customer on clicking 'Allow' button from tools */ add_action( 'init', array( $this, 'woofunnels_init_background_updater' ), 110 ); add_action( 'init', array( $this, 'woofunnels_init_background_contacts_updater' ), 110 ); add_action( 'admin_init', array( $this, 'woofunnels_maybe_update_customer_database' ), 120 ); /** Creating contact for new orders */ add_action( 'woocommerce_checkout_order_processed', array( $this, 'woofunnels_wc_order_create_contact' ), 10, 3 ); add_action( 'woocommerce_store_api_checkout_order_processed', array( $this, 'wc_order_create_contact_blocks' ) ); /** Creating updating customer on order statuses paid */ add_action( 'woocommerce_order_status_changed', array( $this, 'order_status_change_async_call' ), 10, 3 ); /** Updating customer and customer meta on accepting offer */ add_action( 'wfocu_offer_accepted_and_processed', array( $this, 'woofunnels_offer_accept_create_update_customer' ), 1, 4 ); /** Attempt to update customer on WP profile update*/ add_action( 'profile_update', array( $this, 'bwf_update_contact_on_user_update' ), 10, 2 ); add_action( 'woocommerce_save_account_details', array( $this, 'bwf_update_contact_on_user_update' ), 10, 1 ); add_action( 'updated_user_meta', array( $this, 'mark_updated_address_fields' ), 10, 4 ); add_action( 'bwf_order_index_completed', [ $this, 'maybe_change_state_on_success' ] ); add_action( 'woocommerce_refund_created', [ $this, 'bwf_update_refunded_amount' ], 10, 2 ); add_action( 'woocommerce_before_delete_order', [ $this, 'schedule_order_reindex_action' ] ); add_action( 'rest_api_init', [ $this, 'rest_init_register_async_request' ] ); add_action( 'woofunnels_tools_add_tables_row_start', [ $this, 'bwf_add_indexing_consent_button' ], 10, 1 ); add_action( 'shutdown', [ $this, 'maybe_clean_indexing' ] ); add_action( 'admin_footer', [ $this, 'maybe_re_dispatch_background_process' ] ); add_action( 'bwf_reindex_contact_orders', [ $this, 'bwf_reindex_contact_orders' ] ); add_action( 'bwf_reindex_contact_orders_end', [ $this, 'bwf_reindex_contact_orders_end' ] ); add_action( 'init', array( $this, 'maybe_create_db_tables' ) ); add_action( 'woocommerce_order_status_changed', array( $this, 'bwf_update_cancel_order' ), 10, 3 ); } /** * @return WooFunnels_DB_Updater */ public static function get_instance() { if ( null === self::$ins ) { self::$ins = new self; } return self::$ins; } /** * Creating/updating contacts and customers on offer accepted * @SuppressWarnings(PHPMD.DevelopmentCodeFragment) */ public static function capture_offer_accepted_event( $request ) { $posted_data = $request->get_body_params(); $order_id = isset( $posted_data['order_id'] ) ? $posted_data['order_id'] : 0; $products = ( isset( $posted_data['products'] ) && count( $posted_data['products'] ) > 0 ) ? $posted_data['products'] : array(); $total = isset( $posted_data['total'] ) ? $posted_data['total'] : 0; try { bwf_create_update_contact( $order_id, $products, $total, false ); } catch ( Error $r ) { BWF_Logger::get_instance()->log( print_R( $r->getMessage(), true ), 'woofunnels_indexing' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r } } /** * WC order status change async call callback * * @param $request * * @return void */ public static function capture_order_status_change_event( $request ) { self::nocache_headers(); $posted_data = $request->get_body_params(); self::process_events( $posted_data ); } public static function process_events( $posted_data ) { if ( empty( $posted_data ) ) { return; } $ins = WooFunnels_DB_Updater::get_instance(); $ins->event_advanced_logs( "Order status change endpoint data received" ); $ins->event_advanced_logs( $posted_data ); $order_id = isset( $posted_data['order_id'] ) ? $posted_data['order_id'] : 0; $status_from = isset( $posted_data['from'] ) ? $posted_data['from'] : ''; $status_to = isset( $posted_data['to'] ) ? $posted_data['to'] : ''; /** If data not passed */ if ( empty( $order_id ) || empty( $status_from ) || empty( $status_to ) ) { return; } /** If order is not valid */ $order = wc_get_order( $order_id ); if ( ! $order instanceof WC_Order ) { return; } try { $paid_status = $order->has_status( wc_get_is_paid_statuses() ); if ( false === $paid_status ) { do_action( 'fk_order_status_change_async_capture', $posted_data ); return; } $temp_cid = BWF_WC_Compatibility::get_order_meta( $order, '_woofunnel_custid' ); if ( empty( $temp_cid ) ) { /** Update customer when order is paid and not indexed */ bwf_create_update_contact( $order_id, array(), 0, true ); } do_action( 'fk_order_status_change_async_capture', $posted_data ); } catch ( Error $r ) { $ins->event_advanced_logs( "Error occurred: " . $r->getMessage() ); } } public function needs_upgrade() { return apply_filters( 'bwf_init_db_upgrade', false ); } /** * Set headers to prevent caching * * @return void */ public static function nocache_headers() { if ( headers_sent() ) { return; } header( 'Cache-Control: no-cache, no-store, must-revalidate, max-age=0' ); header( 'Pragma: no-cache' ); header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' ); header( 'Last-Modified: false' ); } public function woofunnels_handle_db_upgrade_actions() { if ( isset( $_GET['_bwf_remove_updated_db_notice'] ) && isset( $_GET['_bwf_updated_nonce'] ) ) { if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_bwf_updated_nonce'] ) ), '_bwf_hide_updated_nonce' ) ) { wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woofunnels' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch } if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You don’t have permission to do this.', 'woofunnels' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch } $hide_notice = sanitize_text_field( wp_unslash( $_GET['_bwf_remove_updated_db_notice'] ) ); if ( 'yes' === $hide_notice ) { $this->set_upgrade_state( '5' ); } } if ( isset( $_GET['bwf_update_db'] ) && isset( $_GET['_bwf_update_nonce'] ) ) { if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_bwf_update_nonce'] ) ), '_bwf_start_update_nonce' ) ) { wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woofunnels' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch } if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You don’t have permission to do this.', 'woofunnels' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch } $update_db = sanitize_text_field( wp_unslash( $_GET['bwf_update_db'] ) ); if ( 'yes' === $update_db && '0' === $this->get_upgrade_state() ) { $this->set_upgrade_state( '2' ); } } if ( isset( $_GET['_bwf_remove_declined_notice'] ) && isset( $_GET['_bwf_declined_nonce'] ) ) { if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_bwf_declined_nonce'] ) ), '_bwf_hide_declined_nonce' ) ) { wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woofunnels' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch } if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You don’t have permission to do this.', 'woofunnels' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch } $hide_notice = sanitize_text_field( wp_unslash( $_GET['_bwf_remove_declined_notice'] ) ); if ( 'yes' === $hide_notice ) { $this->set_upgrade_state( '6' ); } } } public function set_upgrade_state( $stage ) { update_option( '_bwf_db_upgrade', $stage, true ); } public function get_upgrade_state() { /** * 0: upgrade is allowed, optin message should show * 1: Upgrade is declined. * 2: Upgrade is accepted but not dispatched * 3: Upgrade is accepted & dispatched (show notice) * 4: Upgrade is completed (show notice) * 5: Upgrade is completed and notice dismissed * 6: Upgrade is declined and dismissed */ return get_option( '_bwf_db_upgrade', '0' ); } /** * Contact processing notice to notify admin about the state */ public function woofunnels_show_contact_processing_notice() { $db_state = $this->get_upgrade_state(); if ( '3' === $db_state ) { ?>

contact support to get the issue resolved.', 'woofunnels' ) ), esc_url( 'https://funnelkit.com/support/' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch, WordPress.WP.I18n.MissingTranslatorsComment ?>

updater = new WooFunnels_Background_Updater(); } } /** * Initiate WooFunnels_Background_Updater class * @see woofunnels_maybe_update_customer_database() */ public function woofunnels_init_background_contacts_updater() { if ( class_exists( 'WooFunnels_Contacts_Background_Updater' ) ) { $this->contacts_updater = new WooFunnels_Contacts_Background_Updater(); } } /** * @hooked over `woocommerce_checkout_order_processed` * Creating BWF contact if not exist on WC new order * sync call * * @param $order_id * @param $posted_data * @param $order WC_Order */ public function woofunnels_wc_order_create_contact( $order_id, $posted_data, $order ) { if ( apply_filters( 'bwf_woofunnel_skip_sub_order', true ) && wp_get_post_parent_id( $order_id ) ) { $order = wc_get_order( $order->get_parent_id() ); } $wp_id = $order->get_customer_id(); $email = $order->get_billing_email(); /** If no email then return */ if ( empty( $email ) ) { return; } /** Assigning wp id 0 if not available */ if ( empty( $wp_id ) ) { $wp_id = 0; } $bwf_contact = bwf_get_contact( $wp_id, $email ); /** If contact exists then directly add meta */ if ( $bwf_contact->get_id() > 0 ) { $bwf_contact = bwf_create_update_contact_object( $bwf_contact, $order ); $bwf_contact->save(); BWF_Logger::get_instance()->log( "Order #" . $order->get_id() . ": Processed against contact ID" . $bwf_contact->get_id(), 'woofunnels_indexing' ); /** Save uid as cookie */ if ( ! empty( $bwf_contact->get_uid() ) && ! headers_sent() ) { setcookie( '_fk_contact_uid', $bwf_contact->get_uid(), time() + ( 10 * YEAR_IN_SECONDS ), ( COOKIEPATH ? COOKIEPATH : '/' ), COOKIE_DOMAIN, is_ssl(), false ); } $order->update_meta_data( '_woofunnel_cid', $bwf_contact->get_id() ); $order->save_meta_data(); return; } /** Need to create a contact */ if ( $wp_id > 0 ) { $wp_user = get_user_by( 'id', $wp_id ); $email = ( $wp_user instanceof WP_User && ! empty( $wp_user->user_email ) ) ? $wp_user->user_email : $email; } /** If email is not valid */ if ( ! is_email( $email ) ) { return; } $bwf_contact->set_email( $email ); $bwf_contact = bwf_create_update_contact_object( $bwf_contact, $order ); bwf_contact_maybe_update_creation_date( $bwf_contact, $order ); $bwf_contact->save(); BWF_Logger::get_instance()->log( "Order #" . $order->get_id() . ": Processed against contact ID" . $bwf_contact->get_id(), 'woofunnels_indexing' ); /** Save uid as cookie */ if ( ! empty( $bwf_contact->get_uid() ) && ! headers_sent() ) { setcookie( '_fk_contact_uid', $bwf_contact->get_uid(), time() + ( 10 * YEAR_IN_SECONDS ), ( COOKIEPATH ? COOKIEPATH : '/' ), COOKIE_DOMAIN, is_ssl(), false ); } $order->update_meta_data( '_woofunnel_cid', $bwf_contact->get_id() ); $order->save_meta_data(); } /** * Creating BWF contact on order created from Checkout Block/Store API * * @param $order WC_Order * * @return void */ public function wc_order_create_contact_blocks( $order ) { if ( ! $order instanceof WC_Order ) { return; } $this->woofunnels_wc_order_create_contact( $order->get_id(), [], $order ); } /** * Create nonce for rest request using wp_hash method which is unique for each site * * @param $action * * @return false|string */ public static function create_nonce( $action = '' ) { return substr( wp_hash( $action, 'nonce' ), - 12, 10 ); } /** * verify nonce in rest calls * * @param $nonce * @param $action * * @return bool * @see validate() */ public static function verify_nonce( $nonce, $action ) { $expected = self::create_nonce( $action ); if ( empty( $expected ) || empty( $nonce ) ) { return false; } return ( hash_equals( $expected, $nonce ) ); } /** * Run on WC order status change * Fire async call * * @param $order_id * @param $from * @param $to * * @return void */ public function order_status_change_async_call( $order_id, $from, $to ) { /** Avoid duplicate run on a single call */ $arr = [ $order_id, $from, $to ]; if ( isset( $this->order_status_change[ md5( wp_json_encode( $arr ) ) ] ) ) { return; } $this->order_status_change[ md5( wp_json_encode( $arr ) ) ] = 1; $extra_data = apply_filters( 'fk_before_sending_order_status_change_async_request', [], $order_id, $from, $to ); /** Send async call */ $data = array( 'order_id' => $order_id, 'from' => $from, 'to' => $to, '_nonce' => self::create_nonce( 'bwf_rest_order_status_changed' ), 'nonce_action' => 'bwf_rest_order_status_changed' ); if ( ! empty( $extra_data ) && is_array( $extra_data ) ) { $data = array_merge( $extra_data, $data ); } $flag_saved_val = get_transient( 'bwfan_stop_async_call' ); if ( 1 === intval( $flag_saved_val ) ) { $this->process_events( $data ); } else { $url = home_url() . '/?rest_route=/woofunnel_customer/v1/order_status_changed'; $args = bwf_get_remote_rest_args( $data ); $this->event_advanced_logs( "Sending data for order status change JSON endpoint. URL: " . $url ); $this->event_advanced_logs( $data ); $start_time = microtime( true ); $response = wp_remote_post( $url, $args ); $end_time = microtime( true ); if ( ( $end_time - $start_time ) > 0.2 ) { /** Curl took minimum 0.2 secs */ $flag_saved_val = get_transient( 'bwfan_stop_async_call' ); if ( empty( $flag_saved_val ) ) { set_transient( 'bwfan_stop_async_call', 1, WEEK_IN_SECONDS ); } } $this->event_advanced_logs( "Order status change async call response" ); if ( is_wp_error( $response ) ) { $this->event_advanced_logs( $response->get_error_message() ); } elseif ( isset( $response['body'] ) ) { $this->event_advanced_logs( $response['body'] ); $this->event_advanced_logs( $response['response'] ); } } /** Reducing total_value with remaining order total (if partial earlier refund made) */ if ( 'cancelled' === $to ) { BWF_Logger::get_instance()->log( "Order status changes from $from to $to for order id: $order_id", 'woofunnels_indexing' ); bwf_reduce_customer_total_on_cancel( $order_id ); } } public function event_advanced_logs( $log ) { if ( empty( $log ) || false === $this->event_cb_advanced_log_enabled() ) { return; } $log = array( 't' => microtime( true ), 'm' => $log, ); add_filter( 'bwf_logs_allowed', array( $this, 'overriding_bwf_logging' ), 99999 ); BWF_Logger::get_instance()->log( print_r( $log, true ), 'fka-event-endpoint-check', 'autonami' ); remove_filter( 'bwf_logs_allowed', array( $this, 'overriding_bwf_logging' ), 99999 ); } public function event_cb_advanced_log_enabled() { if ( defined( 'BWFAN_ALLOW_EVENT_ENDPOINT_LOGS' ) && true === BWFAN_ALLOW_EVENT_ENDPOINT_LOGS ) { return true; } if ( true === apply_filters( 'bwfan_allow_event_endpoint_logs', false ) ) { return true; } return false; } public function overriding_bwf_logging() { return true; } /** * Register endpoints * * @return void */ public function rest_init_register_async_request() { //Posting data to async request for processing package product and total for indexing register_rest_route( 'woofunnel_customer/v1', '/offer_accepted', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( __CLASS__, 'capture_offer_accepted_event' ), 'permission_callback' => array( __CLASS__, 'validate' ), ) ); /** Posting data to async request for processing new order product on order status change */ register_rest_route( 'woofunnel_customer/v1', '/order_status_changed', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( __CLASS__, 'capture_order_status_change_event' ), 'permission_callback' => array( __CLASS__, 'validate' ), ) ); } /** * Method to validate nonce for the public creatable post requests * * @param WP_REST_Request $request * * @return false|int */ public static function validate( $request ) { $posted_data = $request->get_body_params(); return self::verify_nonce( $posted_data['_nonce'], $posted_data['nonce_action'] ); } /** * Updating contact and customer on accepting offer * * @param $get_offer_id * @param $get_package * @param $get_parent_order * @param $new_order */ public function woofunnels_offer_accept_create_update_customer( $get_offer_id, $get_package, $get_parent_order, $new_order ) { if ( ! $get_parent_order instanceof WC_Order ) { return; } $order_id = $get_parent_order->get_id(); $new_order_id = ( $new_order instanceof WC_Order ) ? $new_order->get_id() : 0; $custid = BWF_WC_Compatibility::get_order_meta( $get_parent_order, '_woofunnel_custid' ); /** * Updating contact and customer in async REST API request if parent order is already indexed otherwise customer will be updated during parent order status change * If batching is off then customer will be updated during child order status change */ if ( $order_id && ! empty( $custid ) && empty( $new_order_id ) ) { BWF_Logger::get_instance()->log( "Creating/Updating contact and customer in async request for batching order_id: $order_id and offer id: $get_offer_id ", 'woofunnels_indexing' ); $product_ids = array(); if ( is_array( $get_package ) && isset( $get_package['products'] ) && is_array( $get_package['products'] ) ) { foreach ( $get_package['products'] as $product_data ) { if ( isset( $product_data['id'] ) ) { array_push( $product_ids, $product_data['id'] ); } if ( isset( $product_data['_offer_data'] ) && isset( $product_data['_offer_data']->id ) && isset( $product_data['id'] ) && $product_data['id'] !== $product_data['_offer_data']->id ) { array_push( $product_ids, $product_data['_offer_data']->id ); } } } $total = isset( $get_package['total'] ) ? $get_package['total'] : 0; $product_ids = array_unique( $product_ids ); $data = array( 'products' => $product_ids, 'total' => $total, 'order_id' => $order_id, '_nonce' => self::create_nonce( 'bwf_rest_offer_accepted' ), 'nonce_action' => 'bwf_rest_offer_accepted' ); $url = home_url() . '/?rest_route=/woofunnel_customer/v1/offer_accepted'; $args = bwf_get_remote_rest_args( $data ); wp_remote_post( $url, $args ); } } /** * Updating refunded amount in order meta * * @param $refund_id * @param $args * * @return void */ public function bwf_update_refunded_amount( $refund_id, $args ) { $order_id = isset( $args['order_id'] ) ? $args['order_id'] : 0; $amount = isset( $args['amount'] ) ? $args['amount'] : 0; $order = wc_get_order( $order_id ); /** only update refund in case of partial refund otherwise run the scheduler to reindex customer orders */ if ( floatval( $order->get_remaining_refund_amount() ) > 0 ) { bwf_update_customer_refunded( $order_id, $amount ); return; } $this->schedule_order_reindex_action( $order_id ); } /** * Schedule order reindex AS action * * @param $id * * @return void */ public function schedule_order_reindex_action( $id = '' ) { if ( empty( $id ) || ! class_exists( 'WooCommerce' ) || ! function_exists( 'as_has_scheduled_action' ) ) { return; } /** Return if no order object */ $order = wc_get_order( $id ); if ( ! $order instanceof WC_Order ) { return; } $cid = BWF_WC_Compatibility::get_order_meta( $order, '_woofunnel_cid' ); if ( empty( $cid ) ) { return; } $args = [ 'cid' => $cid ]; $hook = 'bwf_reindex_contact_orders'; if ( ! as_has_scheduled_action( $hook, $args, 'funnelkit' ) ) { as_schedule_recurring_action( time(), 60, $hook, $args, 'funnelkit' ); } } public function maybe_change_state_on_success() { delete_option( '_bwf_last_offsets' ); $this->set_upgrade_state( '4' ); } /** * Adding allow button for db upgrade inside tools * @SuppressWarnings(PHPMD.ElseExpression) */ public function bwf_add_indexing_consent_button() { $get_threshold_order = get_option( '_bwf_order_threshold', BWF_THRESHOLD_ORDERS ); $bwf_db_upgrade = $this->get_upgrade_state(); global $wpdb; if ( ! class_exists( 'WooCommerce' ) ) { return; } if ( '3' !== $bwf_db_upgrade || $get_threshold_order < 1 ) { $paid_statuses = implode( ',', array_map( function ( $status ) { return "'wc-$status'"; }, wc_get_is_paid_statuses() ) ); if ( ! BWF_WC_Compatibility::is_hpos_enabled() ) { $query = $wpdb->prepare( "SELECT COUNT(p.ID) FROM {$wpdb->posts} AS p LEFT JOIN {$wpdb->postmeta} AS pm ON ( p.ID = pm.post_id AND pm.meta_key = '_woofunnel_cid') LEFT JOIN {$wpdb->postmeta} AS pm2 ON (p.ID = pm2.post_id) WHERE 1=1 AND pm.post_id IS NULL AND ( pm2.meta_key = '_billing_email' AND pm2.meta_value != '' ) AND p.post_type = %s AND p.post_status IN ({$paid_statuses}) ORDER BY p.post_date DESC", 'shop_order' ); } else { $order_table = $wpdb->prefix . 'wc_orders'; $order_meta_table = $wpdb->prefix . 'wc_orders_meta'; $query = $wpdb->prepare( "SELECT COUNT(p.id) FROM {$order_table} AS p LEFT JOIN {$order_meta_table} AS pm ON ( p.id = pm.order_id AND pm.meta_key = '_woofunnel_cid') WHERE 1=1 AND pm.order_id IS NULL AND p.billing_email != '' AND p.type = %s AND p.status IN ({$paid_statuses}) ORDER BY p.date_created_gmt DESC", 'shop_order' ); } $query_results = $wpdb->get_var( $query ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $get_threshold_order = $query_results; } $remaining_text = ''; if ( 0 === $get_threshold_order && 0 === absint( $bwf_db_upgrade ) ) { $this->set_upgrade_state( '5' ); $bwf_db_upgrade = '5'; } if ( '5' !== $bwf_db_upgrade && '4' !== $bwf_db_upgrade && $get_threshold_order > 0 ) { $remaining_text = sprintf( __( 'This store has %s orders to index.', 'woofunnels' ), $get_threshold_order ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch, WordPress.WP.I18n.MissingTranslatorsComment } if ( true === apply_filters( 'bwf_needs_order_indexing', false ) ) { ?>




0 ) { return; } if ( 'profile_update' === current_action() ) { $this->do_profile_update_async_call( $user_id, $old_user_data ); return; } if ( 'woocommerce_save_account_details' === current_action() ) { $this->do_profile_update_async_call( $user_id ); } } /** Process profile update synchronously and handle contact data changes */ public function do_profile_update_async_call( $user_id, $old_user_data = null ) { try { $old_user_email = ''; if ( $old_user_data instanceof WP_User && is_email( $old_user_data->user_email ) ) { $old_user_email = $old_user_data->user_email; } /** Get Changed Address Fields */ $fields = array(); foreach ( $this->_user_address_meta_updated as $meta_key => $meta_value ) { $crm_key = array_search( $meta_key, $this->contact_wp_user_address_fields, true ); if ( empty( $crm_key ) ) { continue; } $fields[ $crm_key ] = $meta_value; } $this->process_profile_update_sync( $user_id, $old_user_email, $fields ); } catch ( \Throwable $e ) { BWF_logger::get_instance()->log( 'Profile update sync failed: ' . $e->getMessage(), 'woofunnels_profile_update' ); } } /** * Processes the profile update for a user synchronously. * * Updates the contact record with changed address fields and other profile information. * Applies relevant filters and saves the contact. * * @param int $user_id The ID of the user being updated. * @param string $old_user_email The previous email address of the user. * @param array $fields Array of changed address fields keyed by CRM field name. */ public function process_profile_update_sync( $user_id, $old_user_email = '', $fields = array() ) { try { /** Return if version is less than 2.0.2 */ if ( defined( 'BWFAN_PRO_VERSION' ) && ! version_compare( BWFAN_PRO_VERSION, '2.0.2', '>' ) ) { return; } $contact = $this->maybe_get_contact_on_profile_update( $user_id, $old_user_email ); if ( false === $contact ) { $this->_user_address_meta_updated = array(); return; } if ( ! class_exists( 'WooCommerce' ) || empty( $fields ) ) { $contact->save(); return; } $contact = apply_filters( 'bwf_before_profile_update_contact_sync', $contact, $user_id ); foreach ( $fields as $crm_key => $meta_value ) { if ( 'state' === $crm_key ) { $contact->set_state( $meta_value ); continue; } if ( 'country' === $crm_key ) { $contact->set_country( $meta_value ); continue; } $contact = apply_filters( 'bwf_profile_update_contact_sync_field', $contact, $crm_key, $meta_value, $user_id ); } $contact = apply_filters( 'bwf_after_profile_update_contact_sync', $contact, $user_id ); $contact->set_last_modified( current_time( 'mysql', 1 ) ); $contact->save(); } catch ( \Throwable $e ) { BWF_logger::get_instance()->log( 'Profile update processing failed: ' . $e->getMessage(), 'woofunnels_profile_update' ); } } /** Get the unsaved contact with WPID and Email changes */ public function maybe_get_contact_on_profile_update( $user_id, $old_user_email = '' ) { /** Check if Old User Data valid */ $old_email_valid = is_email( $old_user_email ); $new_user = get_user_by( 'id', $user_id ); $new_user_email = empty( $new_user->user_email ) ? get_user_meta( $user_id, 'billing_email', true ) : $new_user->user_email; /** If both emails are not valid */ if ( ! $old_email_valid && ! is_email( $new_user_email ) ) { return false; } $new_user_exists = $new_user instanceof WP_User && is_email( $new_user_email ); /** Check if email changed */ $email_changed = $old_email_valid && $new_user_exists && $new_user_email !== $old_user_email; /** Get Contact by Old Email & ( get new_contact, if email changed ) */ if ( ! $old_email_valid ) { $contact = new WooFunnels_Contact( $user_id, $new_user_email ); $new_contact = null; } else { $contact = new WooFunnels_Contact( $user_id, $old_user_email ); $new_contact = $email_changed ? new WooFunnels_Contact( '', $new_user_email ) : null; } $old_contact_exists = $contact instanceof WooFunnels_Contact && absint( $contact->get_id() ) > 0; $new_contact_exists = $new_contact instanceof WooFunnels_Contact && absint( $new_contact->get_id() ) > 0; if ( $new_contact_exists ) { $this->maybe_set_wpid_of_correct_contact( $new_contact, $contact, $user_id ); /** If both old and new exists, then return */ if ( $old_contact_exists ) { return false; } /** If both old doesn't exists, then use new as old and go ahead */ $contact = $new_contact; $new_contact_exists = false; $old_contact_exists = true; } /** If both old and new doesn't exists, then create the contact with new email */ if ( ! $old_contact_exists && ! $new_contact_exists ) { /** If Email changes, then contact with new email, else old one */ $contact = $new_contact instanceof WooFunnels_Contact ? $new_contact : $contact; /** If contact is not WooFunnels_Contact */ $contact = $contact instanceof WooFunnels_Contact ? $contact : new WooFunnels_Contact( $user_id, $new_user->user_email ); $old_contact_exists = true; } if ( $new_user_exists ) { $contact->set_f_name( $new_user->first_name ); $contact->set_l_name( $new_user->last_name ); } /** Update WPID if old WPID is different */ if ( $user_id !== absint( $contact->get_wpid() ) ) { $contact->set_wpid( $user_id ); } /** Update email if changed */ if ( $new_user_exists && $email_changed ) { $contact->set_email( $new_user->user_email ); } return $contact; } private function maybe_set_wpid_of_correct_contact( $new_contact, $old_contact, $user_id ) { global $wpdb; $old_contact_exists = $old_contact instanceof WooFunnels_Contact && absint( $old_contact->get_id() ) > 0; $new_contact_exists = $new_contact instanceof WooFunnels_Contact && absint( $new_contact->get_id() ) > 0; /** Set wpid, if not same */ if ( $new_contact_exists && $user_id !== absint( $new_contact->get_wpid() ) ) { $new_contact->set_wpid( $user_id ); $new_contact->set_last_modified( current_time( 'mysql', 1 ) ); $new_contact->save(); } /** Remove WPID on old contact if same as user_id */ if ( $old_contact_exists && $user_id === absint( $old_contact->get_wpid() ) ) { /** Using SQL because setting wpid as blank is not supported in core */ //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->update( $wpdb->prefix . 'bwf_contact', array( 'wpid' => 0, 'last_modified' => current_time( 'mysql', 1 ), ), array( 'id' => $old_contact->get_id() ) ); } } public function mark_updated_address_fields( $meta_id, $object_id, $meta_key, $_meta_value ) { /** Return if version is less than 2.0.2 */ if ( defined( 'BWFAN_PRO_VERSION' ) && ! version_compare( BWFAN_PRO_VERSION, '2.0.2', '>' ) ) { return; } $address_meta_keys = array_values( $this->contact_wp_user_address_fields ); if ( in_array( $meta_key, $address_meta_keys, true ) ) { $this->_user_address_meta_updated[ $meta_key ] = $_meta_value; } } /** * */ public function maybe_clean_indexing() { if ( 1 === did_action( 'admin_head' ) && current_user_can( 'manage_options' ) && 'yes' === filter_input( INPUT_GET, 'bwf_index_clean', FILTER_UNSAFE_RAW ) ) { //phpcs:ignore $this->reset_indexing_data(); } $this->bwf_maybe_restart_indexing(); } /** * reset and clean index data for restart index * @return void */ public function reset_indexing_data() { global $wpdb; $tables = array( 'bwf_wc_customers', ); foreach ( $tables as &$table ) { $bwf_table = $wpdb->prefix . $table; $bwf_table = esc_sql( $bwf_table ); //sanitize the table name manually $wpdb->query( "TRUNCATE TABLE {$bwf_table}" ); } /** * Remove bwf_wc_customers table from table list for attempt to recreate table */ $current_table_list = get_option( '_bwf_db_table_list' ); if ( is_array( $current_table_list ) && isset( $current_table_list['tables'] ) && count( $current_table_list['tables'] ) > 0 ) { $table_list = array_unique( $current_table_list['tables'] ); foreach ( $table_list as $key ) { if ( 'bwf_wc_customers' === $key ) { unset( $table_list[ $key ] ); $current_table_list['tables'] = array_values( $table_list ); update_option( '_bwf_db_table_list', $current_table_list, true ); break; } } } delete_option( '_bwf_db_upgrade' ); delete_option( '_bwf_order_threshold' ); delete_option( '_bwf_offset' ); delete_option( '_bwf_last_offsets' ); delete_option( '_bwf_contacts_threshold' ); delete_option( '_bwf_contacts_offset' ); delete_option( '_bwf_contacts_last_offsets' ); $table = $wpdb->prefix . 'postmeta'; $wpdb->delete( $table, array( 'meta_key' => '_woofunnel_cid' ) ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.SlowDBQuery.slow_db_query_meta_key $wpdb->delete( $table, array( 'meta_key' => '_woofunnel_custid' ) ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.SlowDBQuery.slow_db_query_meta_key if ( BWF_WC_Compatibility::is_hpos_enabled() ) { $table = $wpdb->prefix . 'wc_orders_meta'; $wpdb->delete( $table, array( 'meta_key' => '_woofunnel_cid' ) ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.SlowDBQuery.slow_db_query_meta_key $wpdb->delete( $table, array( 'meta_key' => '_woofunnel_custid' ) ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.SlowDBQuery.slow_db_query_meta_key } $this->updater->kill_process_safe(); BWF_Logger::get_instance()->log( 'Indexing was cleaned manually.', 'woofunnels_indexing' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r } /** * Restart indexing when it is stop due to any reason like cron disabled, server stopped etc */ public function bwf_maybe_restart_indexing() { if ( 1 === did_action( 'admin_head' ) && current_user_can( 'manage_options' ) && 'yes' === filter_input( INPUT_GET, 'bwf_restart_indexing', FILTER_UNSAFE_RAW ) ) { $this->set_upgrade_state( '2' ); $this->woofunnels_maybe_update_customer_database(); } } /** * @hooked over `admin_head` * This method takes care of database updating process. * Checks whether there is a need to update the database * Iterates over define callbacks and passes it to background updater class * Update bwf_customer and bwf_customer_meta tables with new token from different tables */ public function woofunnels_maybe_update_customer_database() { if ( is_null( $this->updater ) ) { return; } if ( isset( $_GET['bwf_update_db'] ) && isset( $_GET['_bwf_update_nonce'] ) ) { if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_bwf_update_nonce'] ) ), '_bwf_start_update_nonce' ) ) { wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woofunnels' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch } if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You don’t have permission to do this.', 'woofunnels' ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch } $bwf_update_db = sanitize_text_field( wp_unslash( $_GET['bwf_update_db'] ) ); $get_state = $this->get_upgrade_state(); if ( 'yes' === $bwf_update_db && '2' === $get_state ) { $this->bwf_start_indexing(); } } } public function bwf_start_indexing() { $task = 'bwf_create_update_contact_customer'; //Scanning order table and updating customer tables $this->updater->push_to_queue( $task ); BWF_Logger::get_instance()->log( '**************START INDEXING************', 'woofunnels_indexing' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r $this->set_upgrade_state( '3' ); $this->updater->save()->dispatch(); BWF_Logger::get_instance()->log( 'First Dispatch completed', 'woofunnels_indexing' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r } public function capture_fatal_error() { $error = error_get_last(); if ( ! empty( $error ) ) { if ( is_array( $error ) && in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) { if ( $this->is_ignorable_error( $error['message'] ) ) { return; } BWF_Logger::get_instance()->log( 'Error logged during the process' . print_r( $error, true ), 'woofunnels_indexing' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r $current_offset = get_option( '_bwf_offset', 0 ); $current_offset ++; update_option( '_bwf_offset', $current_offset ); $order_id = WooFunnels_Dashboard::$classes['WooFunnels_DB_Updater']->get_order_id_process(); $order = wc_get_order( $order_id ); if ( $order instanceof WC_Order ) { $order->update_meta_data( '_woofunnel_cid', 0 ); $order->save_meta_data(); } } } } private function is_ignorable_error( $str ) { $get_all_ingorable_regex = $this->ignorable_errors(); foreach ( $get_all_ingorable_regex as $re ) { $matches = []; preg_match_all( $re, $str, $matches, PREG_SET_ORDER, 0 ); if ( ! empty( $matches ) ) { return true; } } return false; } private function ignorable_errors() { return [ '/Maximum execution time of/m', '/Allowed memory size of/m' ]; } public function capture_fatal_error_contacts() { $error = error_get_last(); if ( ! empty( $error ) ) { if ( is_array( $error ) && in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) { if ( $this->is_ignorable_error( $error['message'] ) ) { return; } BWF_Logger::get_instance()->log( 'Error logged during the process' . print_r( $error, true ), 'woofunnels_contacts_indexing' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r $current_offset = get_option( '_bwf_contacts_offset', 0 ); $current_offset ++; update_option( '_bwf_contacts_offset', $current_offset ); } } } public function set_order_id_in_process( $order_id ) { $this->order_id_in_process = $order_id; } public function get_order_id_process() { return $this->order_id_in_process; } public function maybe_re_dispatch_background_process() { $this->updater->maybe_re_dispatch_background_process(); } public function maybe_dispatch_contact_table_indexing() { $task_list = array( 'bwf_contacts_v1_0_init_db_setup', ); $update_queued = false; foreach ( $task_list as $task ) { $this->contacts_updater->push_to_queue( $task ); $update_queued = true; } if ( $update_queued ) { $this->contacts_updater->save()->dispatch(); } } public function maybe_flag_old_contacts_indexing() { $indexing_option = get_option( '_bwf_migrate_contacts_indexing' ); if ( ! empty( $indexing_option ) ) { return; } global $wpdb; $bwf_tables = get_option( '_bwf_created_tables' ); if ( ! is_array( $bwf_tables ) || ! in_array( $wpdb->prefix . 'bwf_contact', $bwf_tables, true ) || ! in_array( $wpdb->prefix . 'bwf_contact_meta', $bwf_tables, true ) ) { return; } $contact_count = $wpdb->get_var( "SELECT COUNT(id) FROM {$wpdb->prefix}bwf_contact" ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching if ( 0 === absint( $contact_count ) ) { return; } /** * 1 - Pending * 2 - In Progress * 3 - Complete */ update_option( '_bwf_migrate_contacts_indexing', 1 ); } public function maybe_create_db_tables() { WooFunnels_Create_DB_Tables::get_instance()->create(); } /** * Reindex contact orders * * @param $cid * * @return mixed false || 0 : not completed || 1 : completed */ public function bwf_reindex_contact_orders( $cid ) { $bwf_contact = new WooFunnels_Contact( '', '', '', $cid ); if ( 0 === $bwf_contact->get_id() ) { $this->un_schedule_wc_recurring_actions( $cid ); return false; } $paid_statuses = implode( ',', array_map( function ( $status ) { return "'wc-$status'"; }, wc_get_is_paid_statuses() ) ); $key = "bwf_contact_orders_{$cid}"; $indexed_order_id = get_option( $key, 0 ); if ( 0 > $indexed_order_id ) { return 1; } global $wpdb; /** Delete custom table row when starting from 0 i.e. initial starting for a contact */ if ( 0 === intval( $indexed_order_id ) ) { $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}bwf_wc_customers WHERE cid = %d", $cid ) ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL } $limit = 10; try { if ( ! BWF_WC_Compatibility::is_hpos_enabled() ) { $sql = "SELECT p.ID FROM {$wpdb->prefix}posts AS p INNER JOIN {$wpdb->prefix}postmeta AS pm ON ( p.ID = pm.post_id ) WHERE 1=1 AND ( ( ( pm.meta_key = %s AND pm.meta_value = %s ) ) ) AND p.post_type = %s AND (p.post_status IN ($paid_statuses)) AND p.ID > %d GROUP BY p.ID ORDER BY p.ID ASC LIMIT 0, " . $limit; $orders_ids = $wpdb->get_col( $wpdb->prepare( $sql, array( '_woofunnel_cid', $cid, 'shop_order', $indexed_order_id ) ) ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching } else { $order_table = $wpdb->prefix . 'wc_orders'; $order_meta_table = $wpdb->prefix . 'wc_orders_meta'; $sql = ( "SELECT o.id FROM {$order_table} AS o INNER JOIN {$order_meta_table} AS om ON o.id = om.order_id AND om.meta_key = %s AND om.meta_value = %d WHERE o.status IN ({$paid_statuses}) AND o.type = %s AND o.id > %d ORDER BY o.id ASC LIMIT 0, " . $limit ); $orders_ids = $wpdb->get_col( $wpdb->prepare( $sql, array( '_woofunnel_cid', $cid, 'shop_order', $indexed_order_id ) ) ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching } } catch ( Error|Exception $e ) { $this->un_schedule_wc_recurring_actions( $cid ); return 1; } if ( empty( $orders_ids ) ) { $this->un_schedule_wc_recurring_actions( $cid ); return 1; } $old_processed_oids = $bwf_contact->get_meta( 'processed_order_ids' ); $old_processed_oids = is_array( $old_processed_oids ) ? array_map( 'intval', $old_processed_oids ) : []; $processed_oids = []; foreach ( $orders_ids as $id ) { if ( in_array( intval( $id ), $old_processed_oids, true ) ) { continue; } $order = wc_get_order( $id ); if ( ! $order instanceof WC_Order ) { $processed_oids[] = $id; continue; } $order->delete_meta_data( '_woofunnel_cid' ); $order->delete_meta_data( '_woofunnel_custid' ); $order->save_meta_data(); bwf_create_update_contact( $id, array(), 0, true ); /** Update order id on a key index */ update_option( $key, $id, false ); $processed_oids[] = $id; } $processed_oids = array_unique( array_merge( $old_processed_oids, $processed_oids ) ); sort( $processed_oids ); $bwf_contact->update_meta( 'processed_order_ids', maybe_serialize( $processed_oids ) ); return count( $orders_ids ) < $limit ? 1 : 0; // return 1 or 0 if more orders or not } /** * Callback to Un-schedule contact orders sync recurring action * * @param $cid * * @return void */ public function un_schedule_wc_recurring_actions( $cid ) { if ( empty( $cid ) ) { return; } update_option( "bwf_contact_orders_{$cid}", '-1', false ); $hook = 'bwf_reindex_contact_orders_end'; if ( ! as_has_scheduled_action( $hook, [ 'cid' => $cid ], 'funnelkit' ) ) { as_schedule_single_action( time(), $hook, [ 'cid' => $cid ], 'funnelkit' ); } } /** * Un-schedule main recurring action from this single action * * @param $cid * * @return void */ public function bwf_reindex_contact_orders_end( $cid ) { if ( empty( $cid ) ) { return; } global $wpdb; delete_option( "bwf_contact_orders_{$cid}" ); $bwf_contact = new WooFunnels_Contact( '', '', '', $cid ); $bwf_contact->delete_meta( 'processed_order_ids' ); $hook = 'bwf_reindex_contact_orders'; $args = wp_json_encode( [ 'cid' => $cid ] ); /** query */ $query = "DELETE FROM `{$wpdb->prefix}actionscheduler_actions` WHERE `args` = %s AND `hook` = %s Limit 25"; $query = $wpdb->prepare( $query, $args, $hook ); $run = true; do { $deleted = $wpdb->query( $query ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching if ( ! $deleted ) { $run = false; } } while ( $run ); } /** * schedule to sync all the orders of contact on cancel * * @param $order_id * @param $from * @param $to * * @return void */ public function bwf_update_cancel_order( $order_id, $from, $to ) { /** return if from status is not paid status or to status is not cancelled */ $failed_statuses = [ 'pending', 'failed', 'cancelled' ]; if ( in_array( $from, $failed_statuses, true ) || 'cancelled' !== $to ) { return; } $this->schedule_order_reindex_action( $order_id ); } /** * Truncate the contact meta table * Run when BWF_DB_VERSION is 1.0.3 */ protected function empty_contact_meta_table() { global $wpdb; $result = $wpdb->get_results( "SHOW TABLES LIKE '{$wpdb->prefix}bwf_contact_meta'", ARRAY_A ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching if ( is_array( $result ) && count( $result ) > 0 ) { $wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}bwf_contact_meta`" ); } } } }