acrRegisterCartAbandonedEventSchedule( (int)$userID, $orderEmail, (int)$orderID );
}
/**
* Used to register new Recovered Cart CPT
*
* @since 1.0.0
*/
public function acrRegisterRecoveredCartsCPT(){
$labels = array(
'name' => __( 'Abandoned Carts', 'advanced-cart-recovery' ),
'singular_name' => __( 'Abandoned Cart', 'advanced-cart-recovery' ),
'menu_name' => __( 'Abandoned Carts', 'advanced-cart-recovery' ),
'name_admin_bar' => __( 'Abandoned Carts', 'advanced-cart-recovery' ),
'parent_item_colon' => __( 'Parent Cart:', 'advanced-cart-recovery' ),
'all_items' => __( 'Abandoned Carts', 'advanced-cart-recovery' ),
'add_new_item' => __( 'Add New Cart', 'advanced-cart-recovery' ),
'add_new' => __( 'Add New', 'advanced-cart-recovery' ),
'new_item' => __( 'New Cart', 'advanced-cart-recovery' ),
'edit_item' => __( 'Edit Cart', 'advanced-cart-recovery' ),
'update_item' => __( 'Update Cart', 'advanced-cart-recovery' ),
'view_item' => __( 'View Cart', 'advanced-cart-recovery' ),
'search_items' => __( 'Search Cart', 'advanced-cart-recovery' ),
'not_found' => __( 'No shopping carts have been abandoned yet.', 'advanced-cart-recovery' ),
'not_found_in_trash' => __( 'Not found in Trash', 'advanced-cart-recovery' ),
);
$labels = apply_filters( 'acr_recovered_carts_cpt_labels', $labels );
$args = array(
'label' => __( 'abandoned-carts', 'advanced-cart-recovery' ),
'description' => __( 'Abandoned Carts', 'advanced-cart-recovery' ),
'labels' => $labels,
'supports' => array( 'title' ),
'hierarchical' => false,
'public' => false,
'show_ui' => true,
'show_in_menu' => 'woocommerce',
'menu_position' => 5,
'show_in_admin_bar' => true,
'show_in_nav_menus' => true,
'can_export' => true,
'has_archive' => true,
'exclude_from_search' => false,
'publicly_queryable' => true,
'capabilities' => array(
'create_posts' => false, // Removes support for the "Add New" function
),
'map_meta_cap' => true,
'rewrite' => array('slug' => 'acr-recovered-carts'),
);
$args = apply_filters( 'acr_recovered_carts_cpt_args', $args );
register_post_type( ACR_CPT_NAME, $args );
}
/**
* Register custom post status for ACR post type
*
* @since 1.0.0
*/
public function acrCreateCustomPostStatus(){
// When the set status in the settings are met after cart check the cart will update its status to not-recovered
register_post_status( 'acr-not-recovered', array(
'label' => _x( 'Not Recovered', 'advanced-cart-recovery' ),
'label_count' => _n_noop( 'Not Recovered (%s)', 'Not Recovered (%s)', 'advanced-cart-recovery' ),
'public' => true,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
));
// If the cart is restored and order is successful then update post status to recovered
register_post_status( 'acr-recovered', array(
'label' => _x( 'Recovered', 'advanced-cart-recovery' ),
'label_count' => _n_noop( 'Recovered (%s)', 'Recovered (%s)', 'advanced-cart-recovery' ),
'public' => true,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
));
// If the user decided to unsubscribe then update post status to Cancelled
register_post_status( 'acr-cancelled', array(
'label' => _x( 'Cancelled', 'advanced-cart-recovery' ),
'label_count' => _n_noop( 'Cancelled (%s)', 'Cancelled (%s)', 'advanced-cart-recovery' ),
'public' => true,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
));
}
/**
* Set new Column for Recovered Cart CPT
*
* @param array $columns
*
* @return array
* @since 1.0.0
*/
public function acrSetNewAdvancedCartRecoveryColumn( $columns ){
$columns = array(
'cb' => '',
'title' => __( 'Cart ID', 'advanced-cart-recovery' ),
'acr-cart-date' => __( 'Date Created', 'advanced-cart-recovery' ),
'acr-cart-customer' => __( 'Customer', 'advanced-cart-recovery' ),
'acr-cart-quantity' => __( 'Quantity', 'advanced-cart-recovery' ),
'acr-cart-coupons' => __( 'Coupons', 'advanced-cart-recovery' ),
'acr-cart-order-total' => __( 'Order Total', 'advanced-cart-recovery' ),
'acr-cart-order-number' => __( 'Order #', 'advanced-cart-recovery' ),
'acr-cart-status' => __( 'Status', 'advanced-cart-recovery' ),
);
return apply_filters( 'acr_new_recovered_cart_columns', $columns );
}
/**
* Set actions Column for Recovered Cart CPT
*
* @param string $columns
* @param integer $postID
*
* @since 1.0.0
*/
public function acrAdvancedCartRecoveryNewColumns( $columns, $postID ){
$userID = get_post_meta( $postID, '_acr_cart_customer_id', true );
$orderID = get_post_meta( $postID, '_acr_order_id', true );
$order = new WC_Order( $orderID );
switch ( $columns ) {
case 'acr-cart-date':
$date = get_the_date( 'F j, Y g:i A', $postID );
echo apply_filters( 'acr_cart_date_column', $date );
do_action( 'acr_cart_date_column', $columns, $postID );
break;
case 'acr-cart-customer':
// Registered users.
if( $userID ){
$user = get_userdata( $userID );
$userMeta = array_filter( get_user_meta( $userID ) );
// Non-registered users.
}else{
$user = get_post_meta( $orderID );
$userMeta = array();
foreach ( $user as $key => $value ) {
$userMeta[ ltrim( $key, '_' ) ] = $value;
}
}
if( !empty( $user->data->user_login ) ): ?>
first_name ) . ' ' . trim( $user->last_name ); ?>
';
endif;
if( !empty( $user->data->user_email ) ): ?>
data->user_email; ?>
roles as $key => $value ) {
echo ucwords( str_replace( '_', ' ', $value ) ) . '
';
}
else: ?>
Guest get_items();
if( ! empty( $items ) ){
foreach ( $items as $key => $item ) {
$quantity += $item[ 'qty' ];
}
}
echo apply_filters( 'acr_cart_quantity_column', $quantity, $postID );
do_action( 'acr_cart_quantity_column', $columns, $postID );
break;
case 'acr-cart-coupons':
$coupons = '';
$useCoupons = $order->get_used_coupons();
if( ! empty( $useCoupons ) ){
foreach ( $useCoupons as $coupon ){
$coupons .= $coupon . ', ';
}
}
echo apply_filters( 'acr_cart_coupons_column', rtrim( trim( $coupons ), ',' ), $postID );
do_action( 'acr_cart_coupons_column', $columns, $postID );
break;
case 'acr-cart-order-total':
$items = $order->get_items();
$cartTotal = 0;
if( ! empty( $items ) ){
foreach ( $items as $key => $item ) {
$cartTotal += $item[ 'line_total' ];
}
}
echo apply_filters( 'acr_cart_order_total_column', wc_price( $cartTotal ), $postID );
do_action( 'acr_cart_order_total_column', $columns, $postID );
break;
case 'acr-cart-order-number':
$orderID = get_post_meta( $postID, '_acr_order_id', true );
$orderLink = get_admin_url() . 'post.php?post=' . $orderID . '&action=edit';
if( isset( $orderID ) )
echo sprintf( __( '%2$s', 'advanced-cart-recovery' ), $orderLink, $orderID );
do_action( 'acr_cart_order_number', $columns, $postID );
break;
case 'acr-cart-status':
$status = get_post_status( $postID );
$class = 'status danger';
if( $status == 'acr-recovered' )
$class = 'status recovered';
elseif( $status == 'acr-not-recovered' )
$class = 'status not-recovered';
elseif( $status == 'acr-cancelled' )
$class = 'status cancelled';
$status = str_replace( 'acr-', '', $status );
$status = str_replace( '-', ' ', $status );
$status = ucwords( $status );
$status = apply_filters( 'acr_cart_status_column', $status );
echo '' . $status . '';
do_action( 'acr_cart_status_column', $columns, $postID );
break;
}
}
/**
* Set session flag for our recovery process
*
* @param int $cartID
*
* @since 1.0.0
* @since 1.2.0 'ref' was added to recovery session. This is used for ACRP reporting to track the the email schedule used for restoring.
*/
public function acrSetRecoveryProcessFlag( $cartID ){
$ref = isset( $_GET[ 'ref' ] ) ? $_GET[ 'ref' ] : '';
$acrCartSession = array( $cartID, '_acr_is_being_recovered', 'ref' => $ref );
WC()->session->set( ACR_CPT_NAME, $acrCartSession );
}
/**
* Set cron event to track for abandoned carts after checkout
*
* @param integer $orderID
* @param string $posted
*
* @since 1.0.0
* @since 1.2.0 Added an option to allow recovery with a different email on checkout process other than the email used on cart abandoned.
*/
public function acrOnPlaceOrder( $orderID, $posted ){
$acrCartSession = WC()->session->get( ACR_CPT_NAME );
$acrCron = ACR_Cron::getInstance();
// Get email
$orderEmail = get_post_meta( $orderID, '_billing_email', true );
// Recovery process
if( isset( $acrCartSession ) && is_numeric( $acrCartSession[ 0 ] ) && isset( $acrCartSession[ 1 ] ) ){
$acrEmails = ACR_Emails::getInstance();
$cartEmail = $acrEmails->acrGetCartInfo( $acrCartSession[ 0 ], 'recipient_email' );
$allowDifferentEmail = get_option( 'acr_general_allow_recovery_with_different_email' );
$emailRef = sanitize_text_field( $acrCartSession[ 'ref' ] );
// Allow different email for recovery process
if( $allowDifferentEmail === 'yes' ){
// Update it with the new order and add recovery process flag
update_post_meta( $acrCartSession[ 0 ], '_acr_order_id', $orderID );
update_post_meta( $acrCartSession[ 0 ], '_acr_is_being_recovered', true );
update_post_meta( $acrCartSession[ 0 ], '_acr_email_ref', $emailRef );
}else{
// Abandoned order email must match with the recovery process email
if( $orderEmail === $cartEmail ){
// Update it with the new order and add recovery process flag
update_post_meta( $acrCartSession[ 0 ], '_acr_order_id', $orderID );
update_post_meta( $acrCartSession[ 0 ], '_acr_is_being_recovered', true );
update_post_meta( $acrCartSession[ 0 ], '_acr_email_ref', $emailRef );
}
}
// Clear recovery session
WC()->session->__unset( ACR_CPT_NAME );
// Abandoned process
}else{
if( is_user_logged_in() )
$userID = get_current_user_id();
else // Guest
$userID = 0;
// Schedule a unique cron single event to turn this cart abandoned on a specified time
$acrCron = ACR_Cron::getInstance();
$acrCron->acrRegisterCartAbandonedEventSchedule( $userID, $orderEmail, $orderID );
}
do_action( 'acr_on_place_order', $orderID, $posted );
}
/**
* Make the cart abandoned.
*
* @param int $userID
* @param string $userEmail
* @param int $orderID
* @param string $addons
*
* @since 1.0.0
* @since 1.2.0 WooCommerce Product Addons Integration. Replaced $orderStatus variable to $addons, since it is not used.
*/
public function acrAbandonedCart( $userID, $userEmail, $orderID, $addons ){
$statusConsideredAbandon = get_option( 'acr_general_status_considered_abandoned' );
// Check first if the order exist and order status is in $statusConsideredAbandon option
if( get_post_status( $orderID ) !== false && in_array( get_post_status( $orderID ), $statusConsideredAbandon ) ){
if( ! is_null( $addons ) )
$_REQUEST[ 'addons' ] = $addons;
$acrManager = ACR_Cart_Manager::getInstance();
$acrManager->acrGenerateNewCPTEntry( $userID, $userEmail, $orderID );
}
do_action( 'acr_abandoned_cart_hook', $userID, $userEmail, $orderID );
}
/**
* Change post status to recovered
*
* @param int $orderID
*
* @since 1.0.0
*/
public function acrRecoveredCart( $orderID ){
global $wpdb;
$acrGetCart = $wpdb->get_results( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_acr_order_id' AND meta_value = " . $orderID . " LIMIT 1" );
$cartID = isset( $acrGetCart[ 0 ] ) && isset( $acrGetCart[ 0 ]->post_id ) ? $acrGetCart[ 0 ]->post_id : '';
$isBeingRecovered = is_numeric( $cartID ) ? get_post_meta( $cartID, '_acr_is_being_recovered', true ) : '';
// Change to recovered if the cart was restored by the customer
if( isset( $cartID ) && get_post_status( $cartID ) === 'acr-not-recovered' && $isBeingRecovered ){
$statusConsideredCompleted = get_option( 'acr_general_status_considered_completed' );
$orderStatus = get_post_status( $orderID );
if( in_array( $orderStatus, $statusConsideredCompleted ) ){
// Update status to acr-recovered
$cart = array(
'ID' => $cartID,
'post_status' => 'acr-recovered'
);
wp_update_post( $cart );
// Set date when it was turned into acr-recovered status
update_post_meta( $cartID, '_acr_recovered_date', current_time( 'Y-m-d H:i:s' ) );
do_action( 'acr_cart_is_recovered', $orderID, $cartID, $isBeingRecovered );
}
do_action( 'acr_after_recovered_cart', $orderID, $cartID, $isBeingRecovered );
}else{ // Just delete not recovered ACR object
$args = array(
'post_type' => ACR_CPT_NAME,
'post_status' => 'acr-not-recovered',
'meta_query' => array(
array(
'key' => '_acr_order_id',
'value' => $orderID,
'compare' => '=',
),
),
);
$cartsToRemove = new WP_Query( $args );
if ( $cartsToRemove->have_posts() ) {
while ( $cartsToRemove->have_posts() ) { $cartsToRemove->the_post();
$postID = get_the_id();
wp_delete_post( $postID, true );
}
}
}
do_action( 'acr_recovered_cart_hook', $orderID );
}
/**
* Restore cart contents for abandoned orders.
*
* @since 1.0.0
* @since 1.2.0 Updated the error hook name to 'acr_cart_not_found'. Added a hook 'acr_cart_not_restorable'. We now only allow "Not Recovered" status to be restored.
*/
public function acrRestoreCartContentsForAbandonedOrders(){
global $wpdb;
if ( WC()->cart instanceof WC_Cart ) {
$wcCart = WC()->cart;
} else {
$wcCart = new WC_Cart();
}
$cartLink = trailingslashit( $wcCart->get_cart_url() );
$acrBundles = ACR_Bundled_Products::getInstance();
$acrComposite = ACR_Composite_Products::getInstance();
$acrAddon = ACR_Product_Addons::getInstance();
if( isset( $_GET[ 'acrid' ] ) ){
$acrid = esc_sql( $_GET[ 'acrid' ] );
$ref = isset( $_GET[ 'ref' ] ) ? esc_sql( $_GET[ 'ref' ] ) : 'no-email-ref';
$acrCartExist = $wpdb->get_results(
$wpdb->prepare(
apply_filters( 'acr_cart_exist_query',
"SELECT post_id FROM $wpdb->postmeta
WHERE meta_key = '_acr_cart_hashed_id'
AND meta_value = %s
LIMIT 1
" ),
$acrid
)
);
do_action( 'acr_before_cart_restore', $acrCartExist );
// Redirect if cart is not found in the db otherwise continue with the restoration
if( empty( $acrCartExist ) ){
$msg = __( 'Oops! Looks like the cart you were recovering no longer exists.', 'advanced-cart-recovery' );
$type = 'error';
$notice = apply_filters( 'acr_cart_not_found', array( 'msg' => $msg, 'type' => $type ) );
wc_add_notice( $notice[ 'msg' ], $notice[ 'type' ] );
return;
}
$cartID = $acrCartExist[ 0 ]->post_id;
// Display notice that the cart is not restorable for any status other than "Not Recovered"
if( ! empty( $cartID ) && get_post_status( $cartID ) !== 'acr-not-recovered' ){
$msg = __( 'Oops! This cart is not restorable.', 'advanced-cart-recovery' );
$type = 'error';
$notice = apply_filters( 'acr_cart_not_restorable', array( 'msg' => $msg, 'type' => $type ) );
wc_add_notice( $notice[ 'msg' ], $notice[ 'type' ] );
return;
}
// Empty cart first
$wcCart->empty_cart();
$wcCart->remove_coupons();
$userID = get_post_meta( $cartID, '_acr_cart_customer_id', true );
$orderID = get_post_meta( $cartID, '_acr_order_id', true );
$order = new WC_Order( $orderID );
$items = $order->get_items();
if( ! empty( $items ) ){
// Add items into cart
foreach ( $items as $ID => $item ) {
$productID = ! empty( $item[ 'product_id' ] ) ? intval( $item[ 'product_id' ] ) : '';
$product = wc_get_product( $productID );
$quantity = ! empty( $item[ 'qty' ] ) ? intval( $item[ 'qty' ] ) : '';
$variationID = ! empty( $item[ 'variation_id' ] ) ? intval( $item[ 'variation_id' ] ) : '';
$variation = array();
$cartItemData = array();
if( isset( $item[ 'variation' ] ) ){
foreach ( $item[ 'variation' ] as $key => $value )
$variation[ $key ] = $value;
}
// WooCommerce Addon Plugin
if( $acrAddon->acrAddonPluginiActiveCheck() ){
$_REQUEST[ 'addons' ] = true;
$_REQUEST[ 'cart_id' ] = $cartID;
}
if ( $product->is_type( 'bundle' ) && ACR_Bundled_Products::acrCheckIfBundledParent( $item ) ){
// Set http request variables
$acrBundles->acrSetBundledRequestVariables( $item, $productID, $quantity, $product );
// Add bundled product
$key = WC()->cart->add_to_cart( $productID, $quantity );
} elseif ( $product->is_type( 'composite' ) && ACR_Composite_Products::acrCheckIfCompositeParent( $item ) ) {
// Set http request variables
$acrComposite->acrSetCompositeRequestVariables( $item, $productID, $quantity, $product );
// Add composite product
$key = WC()->cart->add_to_cart( $productID, $quantity );
} else if( empty( $item[ 'item_meta' ][ '_bundled_by' ][ 0 ] ) && empty( $item[ 'item_meta' ][ '_composite_parent' ][ 0 ] ) ) // Only add product if the item is not under bundle or composite product
$key = WC()->cart->add_to_cart( $productID, $quantity, $variationID, $variation, $cartItemData );
do_action( 'acr_cart_restore', $productID, $quantity, $variationID, $variation, $cartItemData );
}
// Apply coupons
if( apply_filters( 'acr_has_coupon_discount', true ) ){
foreach ( $order->get_used_coupons() as $coupon ){
// Check if coupon is applied or not, if not apply it
if ( ! $wcCart->has_discount( $coupon ) )
$wcCart->add_discount( $coupon );
}
}
do_action( 'acr_after_cart_restore', $cartID, $ref );
// Redirect after all items are added to cart
wp_redirect( apply_filters( 'acr_redirect_url', $cartLink ), 301 ); exit;
}
}
}
/**
* Add a notice after the cart is successfully restored.
*
* @since 1.0.0
*/
public function acrAddNoticeAfterCartRestore(){
$restoreMessage = apply_filters( 'acr_cart_restore_success_msg', __( 'Thanks! Your cart was successfully restored.', 'advanced-cart-recovery' ) );
$noticeType = apply_filters( 'acr_cart_restore_notice_type', 'success' );
wc_add_notice( $restoreMessage, $noticeType );
do_action( 'acr_notice_message' );
}
/**
* Cancelled Cart Checker
* Run on ACR_CANCELLED_CART_CRON
*
* @param int $cartID
* @param string $cartStatus
* @param string $email
*
* @since 1.0.0
*/
public function acrCancelledCartChecker( $cartID, $cartStatus, $email ){
if( get_post_status( $cartID ) !== false && $cartStatus === get_post_status( $cartID ) ){
// Update status to acr-cancelled
$cart = array(
'ID' => $cartID,
'post_status' => 'acr-cancelled'
);
wp_update_post( $cart );
// Set date when it was turned into acr-cancelled status
update_post_meta( $cartID, '_acr_cancelled_date', current_time( 'Y-m-d H:i:s' ) );
}
do_action( 'acr_cancelled_cart_hook' );
}
/**
* Display notice if DISABLE_WP_CRON is enabled
*
* @since 1.0.0
* @since 1.2.0 Option to dismiss the notice.
*/
public function acrDisplayNoticeIfCronIsDisabled(){
$dismissNotice = get_option( 'acr_dismiss_wp_cron_notice' );
if ( empty( $dismissNotice ) && ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON == true ) ) {
$currentURL = $_SERVER[ "HTTP_HOST" ] . $_SERVER[ "REQUEST_URI" ];
if ( strpos( $currentURL , '?' ) !== false )
$modCurrentURL = '//' . $currentURL . '&acr_dismiss_wp_cron_notice=1';
else
$modCurrentURL = '//' . $currentURL . '?acr_dismiss_wp_cron_notice=1'; ?>