<?php
/**
 * The GetResponse class
 *
 * Used to define GetResponse related functions
 *
 *
 * @since      8.0
 * @package    CartBounty Pro - Save and recover abandoned carts for WooCommerce
 * @subpackage CartBounty Pro - Save and recover abandoned carts for WooCommerce/includes
 * @author     Streamline.lv
 */

class CartBounty_Pro_GetResponse{
	
	/**
	 * The admin handler that manages the plugin's settings, options, and backend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Admin    $admin    Provides methods to control and extend the plugin's admin area.
	 */
	protected $admin = null;

	/**
	 * The public handler that manages the plugin's settings, options, and frontend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Public    $public    Provides methods to control and extend the plugin's public area.
	 */
	protected $public = null;

	/**
	 * The coupons manager responsible for handling coupons.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Coupons    $coupons    Provides methods to create, apply, and manage discount coupons.
	 */
	protected $coupons = null;

	/**
	 * The API connector responsible for handling communication with external services.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_API_Connector    $api    Provides methods to send and receive data via API requests.
	 */
	protected $api = null;

	/**
	 * The API access credentials used to authenticate requests with the external service.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      array    $api_access    Contains authentication data such as API key, token, or other required parameters.
	 */
	protected $api_access = null;

	/**
	 * Get the admin handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Admin
	 */
	protected function admin(){
		
		if( $this->admin === null ){
			$this->admin = new CartBounty_Pro_Admin( CARTBOUNTY_PRO_PLUGIN_NAME_SLUG, CARTBOUNTY_PRO_VERSION_NUMBER );
		}

		return $this->admin;
	}

	/**
	 * Get the public handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Public
	 */
	protected function public(){
		
		if( $this->public === null ){
			$this->public = new CartBounty_Pro_Public( CARTBOUNTY_PRO_PLUGIN_NAME_SLUG, CARTBOUNTY_PRO_VERSION_NUMBER );
		}

		return $this->public;
	}

	/**
	 * Get the coupon handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Coupons
	 */
	protected function coupons(){
		
		if( $this->coupons === null ){
			$this->coupons = new CartBounty_Pro_Coupons();
		}

		return $this->coupons;
	}

	/**
	 * Get the API connector (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_API_Connector
	 */
	protected function api(){
		
		if( $this->api === null ){
			$this->api = new CartBounty_Pro_API_Connector();
		}

		return $this->api;
	}

	/**
	 * Get the API access creentials (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return array
	 */
	protected function api_access(){
		
		if( $this->api_access === null ){
			$this->api_access = $this->get_api_access();
		}

		return $this->api_access;
	}

	/**
	* Retrieve GetResponse recovery settings
	*
	* @since    10.1
	* @return   array
	* @param    string     $value                Value to return
	*/
	public function get_settings( $value = false ){
		$saved_options = get_option( 'cartbounty_pro_getresponse_settings' );
		$defaults = array(
			'key' 						=> '',
			'store_id' 					=> '',
			'list_id' 					=> '',
			'custom_fields' 			=> array(),
			'product_table_support' 	=> false,
		);

		if( is_array( $saved_options ) ){
			$settings = array_merge( $defaults, $saved_options ); //Merging default settings with saved options

		}else{
			$settings = $defaults;
		}

		if( $value ){ //If a single value should be returned
			
			if( isset( $settings[$value] ) ){ //Checking if value exists
				$settings = $settings[$value];
			}
		}

		return $settings;
	}

	/**
	* Update GetResponse settings
	*
	* @since    10.1
	* @return   array
	* @param    array     $new_value           	New value
	* @param    array     $old_value            Existing value
	*/
	public function handle_settings_update( $new_value, $old_value ){

		if( isset( $_POST['cartbounty_pro_getresponse_settings'] ) ){ //If option update coming from saving a form
			$new_value = $_POST['cartbounty_pro_getresponse_settings'];
		}

		if( is_array( $old_value ) && is_array( $new_value ) ){
			$settings = array_merge( $old_value, $new_value );

		}else{
			$settings = $new_value;
		}

		return $settings;
	}

	/**
	 * Method that is used for cron synchronization jobs
	 *
	 * @since    8.0
	 */
	public function auto_sync(){
		$admin = $this->admin();

		if( !$admin->check_license() || !class_exists( 'WooCommerce' ) ) return; //Exit if license key not valid or if WooCommerce is not activated

		if( $this->api_valid() && $this->store_connected() ){ //If API key is valid and store is selected
			//Syncing customers which also syncs their shopping carts
			$this->sync_abandoned_carts();
		}
	}

	/**
	 * Method retrieves saved API credentials
	 *
	 * @since    10.9
	 * @return 	 Array
	 */
	public function get_api_access(){
		$api_access = array(
			'api_key' => '',
			'api_url' => 'https://api.getresponse.com/v3/'
		);

		$key = $this->get_settings( 'key' );
		
		if( !empty( $key ) ){
			$api_access['api_key'] = esc_attr( $key ); //Retrieve GetResponse API key from database
		}

		return $api_access;
	}

	/**
	 * Method checks if API is valid
	 * Return True if API valid. Return False if not
	 *
	 * @since    8.0
	 * @return 	 Boolean
	 */
	public function api_valid(){
		$admin = $this->admin();
		$api = $this->api();
		$status = false;
		$api_access = $this->api_access();
		$api_key = $api_access['api_key'];

		if( !empty( $api_key ) ){ //If GetResponse API key is not empty

			$current_fingerprint = $admin->get_key_fingerprint( $api_key );
			$valid_fingerprint = $admin->get_cartbounty_transient( 'getresponse_valid_fingerprint' ); //Last known valid fingerprint
			
			if( $valid_fingerprint === $current_fingerprint ){ //If API key has not changed and was already validated before
				$status = true;

			}else{
				$invalid_fp = $admin->get_cartbounty_transient( 'getresponse_invalid_fingerprint' ); //Last known invalid fingerprint

				if( $invalid_fp !== $current_fingerprint ){ //If API key has changed

					$invalid_until = (int)$admin->get_cartbounty_transient( 'getresponse_api_invalid_until' );

					if( $invalid_until && time() < $invalid_until && $invalid_fp === $current_fingerprint ){ //Exit and return false if credentials have already failed before and they have not changed
						return $status;
					}

					if( (int)$api->credentials_test( 'getresponse', $api_access, $this ) ){ //In case we get a valid response from GetResponse
						$admin->set_cartbounty_transient( 'getresponse_valid_fingerprint', $current_fingerprint, 60*60*48 ); //Cache valid API fingerprint for 48 hours
						$admin->delete_cartbounty_transient( 'getresponse_api_times_failed' );
						$admin->delete_cartbounty_transient( 'getresponse_api_invalid_until' );
						$admin->delete_cartbounty_transient( 'getresponse_invalid_fingerprint' );
						$status = true;

					}else{ //If we have connection issues - try and save response to limit how many times we try to connect
						$fail_count = (int)$admin->get_cartbounty_transient( 'getresponse_api_times_failed' );
						$fail_count++;
						$admin->set_cartbounty_transient( 'getresponse_api_times_failed', $fail_count, 60*60 ); //Remember failures for 1 hour

						if( $fail_count >= 3 ){
							$admin->set_cartbounty_transient( 'getresponse_api_invalid_until', time() + 60*15, 60*15 ); //Invalidate requests for 15 minutes
							$admin->set_cartbounty_transient( 'getresponse_invalid_fingerprint', $current_fingerprint, 60*15 ); //Cache invalid API fingerprint for 15 minutes
							$admin->delete_cartbounty_transient( 'getresponse_valid_fingerprint' );
						}
					}
				}
			}
		}

		return $status;
	}
	
	/**
	 * Method returns GetResponse API status
	 *
	 * @since    8.0
	 * @return 	 Array
	 */
	public function api_status(){
		$admin = $this->admin();
		$api_access = $this->api_access();
		$api_key = $api_access['api_key'];
		$response = array();

		if( !empty( $api_key ) ){ //If the field is not empty
			
			if( $this->api_valid() && !$this->store_connected() ){ //If API key is valid, but we have not selected the store and list to connect with
				$response['status'] = '2';
				$response['result'] = '';
				$response['message'] =  '';

			}elseif( $this->api_valid() && $this->store_connected() ){
				$response['status'] = '1';
				$response['result'] = '';
				$response['message'] =  esc_html__( 'Connected', 'woo-save-abandoned-carts' );

			}else{
				$response['status'] = '0';
				$response['result'] = esc_html__( 'Error', 'woo-save-abandoned-carts' );
				$response['message'] = esc_html__( 'Please make sure your API key is correct', 'woo-save-abandoned-carts' );
				$this->delete_store_and_list();
			}

		}else{ //In case the GetResponse API field is empty
			$response['status'] = '0';
			$response['result'] = '';
			$response['message'] = '';
			$this->delete_store_and_list();
			$admin->delete_cartbounty_transient( 'getresponse_api_times_failed' );
			$admin->delete_cartbounty_transient( 'getresponse_api_invalid_until' );
			$admin->delete_cartbounty_transient( 'getresponse_valid_fingerprint' );
			$admin->delete_cartbounty_transient( 'getresponse_invalid_fingerprint' );
		}

		return $response;
	}

	/**
	 * Method that retrieves and outputs existing stores from GetResponse
	 *
	 * @since    8.0
	 */
	public function display_stores(){
		$admin = $this->admin();
		$api = $this->api();
		$settings = $this->get_settings();
		$data = array(
			'perPage' => 1000
		);
		$result = $api->connect( 'getresponse', $this->api_access(), 'shops', 'GET', $data );
		
		if( $result['status_code'] == 200 ){
			
			if( !empty( $result['response'] ) ){
				$store_id = $settings['store_id'];
				$action = '';
				
				if( !$store_id ){
					$store_id = 0;
				}?>
				
				<select id="cartbounty_pro_getresponse_store_id" class="cartbounty-pro-select" name="cartbounty_pro_getresponse_settings[store_id]" <?php echo $admin->disable_field( $action ); ?> autocomplete="off">
					<option value="0" <?php echo selected( $store_id, 0 ) ?>><?php esc_html_e( 'Choose a store', 'woo-save-abandoned-carts' );?></option>
					<?php foreach( $result['response'] as $store ): ?>
						<option value="<?php echo esc_attr( $store->shopId ); ?>" <?php selected( $store_id, $store->shopId ); ?> > <?php echo esc_html( $store->name ); ?> </option>
					<?php endforeach; ?>
				</select>
			<?php

			}else{ //If no store has been created on GetResponse ?>
				<select id="cartbounty_pro_getresponse_store_id" class="cartbounty-pro-select" disabled>
					<option value="0"><?php esc_html_e( 'No store available', 'woo-save-abandoned-carts' );?></option>
				</select>
				<p class='cartbounty-pro-additional-information'>
					<?php echo sprintf(
					/* translators: %s - URL link */
					esc_html__( 'Please create a store on %sGetResponse%s.', 'woo-save-abandoned-carts' ), '<a href="'. esc_url( CARTBOUNTY_PRO_GETRESPONSE_TRIAL_LINK ) .'" target="_blank">', '</a>' ); ?>
				</p>
			<?php 
			}
		
		}elseif( !empty( $result['response'] ) ){
			echo '<strong>' . esc_html( $result['response']->codeDescription ) . ':</strong> ' . esc_html( $result['response']->moreInfo );
		}
	}

	/**
	 * Method that retrieves and outputs existing lists from GetResponse
	 *
	 * @since    8.0
	 */
	public function display_lists(){
		$api = $this->api();
		$data = array(
			'perPage' => 1000
		);
		$result = $api->connect( 'getresponse', $this->api_access(), 'campaigns', 'GET', $data );
		
		if( $result['status_code'] == 200 ){
			
			if( !empty( $result['response'] ) ){
				$admin = $this->admin();
				$list_id = $this->get_settings( 'list_id' );
				$action = '';
				
				if( !$list_id ){
					$list_id = 0;
				}?>

				<select id="cartbounty_pro_getresponse_list_id" class="cartbounty-pro-select" name="cartbounty_pro_getresponse_settings[list_id]" <?php echo $admin->disable_field( $action ); ?> autocomplete="off">
					<option value="0" <?php echo selected( $list_id, 0 ) ?>><?php esc_html_e( 'Choose a list', 'woo-save-abandoned-carts' );?></option>
					<?php foreach( $result['response'] as $list ): ?>
						<option value="<?php echo esc_attr( $list->campaignId ); ?>" <?php selected( $list_id, $list->campaignId ); ?> > <?php echo esc_html( $list->name ); ?> </option>
					<?php endforeach; ?>
				</select>
			<?php
			}
		}
		elseif( !empty( $result['response'] ) ){
			echo '<strong>' . esc_html( $result['response']->codeDescription ) . ':</strong> ' . esc_html( $result['response']->moreInfo );
		}
	}

	/**
	 * Method checks if a GetResponse store and list has been selected
	 *
	 * @since    8.0
	 * @return 	 Boolean
	 */
	public function store_connected(){
		$connected = false;
		$settings = $this->get_settings();
		$store_id = $settings['store_id'];
		$list_id = $settings['list_id'];

		if( !empty( $store_id ) && !empty( $list_id ) ){
			$connected = true;
		}

		return $connected;
	}
	
	/**
	* Function synchronizes abandoned carts to GetResponse
	* While GetResponse API doesn't allow asynchronous requests, we are forced to limit our syncronisations to 2 abandoned cart rows each time since more rows take a lot of time to be synced
	*
	* @since    8.0
	*/
	private function sync_abandoned_carts(){
		global $wpdb;
		$admin = $this->admin();
		$coupons = $this->coupons();
		$this->sync_contacts();
		$this->sync_products();

		if( !$admin->check_license() ) return; //Exit if license key not valid

		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$time = $admin->get_time_intervals();
		$email_consent_query = '';

		if( $admin->get_consent_settings( 'email' ) ){ //Check if email consent enabled to query only carts with given email usage consent
			$email_consent_query = 'AND email_consent = 1';
		}

		$carts_to_getresponse = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, email, phone, session_id, cart_contents, cart_total, time, gr_last_synced, gr_contact_id, gr_cart_id
				FROM {$cart_table}
				WHERE time != gr_last_synced AND
				(type = %d OR type = %d) AND
				email != '' AND
				cart_contents != '' AND
				paused != 1 AND
				gr_excluded != 1 AND
				gr_contact_id IS NOT NULL AND
				anonymized != 1 AND
				time < %s AND
				time > %s
				$email_consent_query
				ORDER BY time DESC",
				$admin->get_cart_type( 'abandoned' ),
				$admin->get_cart_type( 'recovered_pending' ),
				$time['gr_cart_sync_period'],
				$time['maximum_sync_period']
			)
		);

		if( $carts_to_getresponse ){ //If we have abandoned carts that must be created or updated
			$limit = 0;

			foreach( $carts_to_getresponse as $key => $cart ){
				
				if( $limit > 1 ){ //Once we have synced 2 abandoned carts, stopping sync. No more than 2 abandoned carts per sync due to speed limitations
					break;
				}

				if( !$admin->has_excluded_items( $cart, 'getresponse' ) ){ //If cart contents do not have excluded items
					//Must check if abandoned cart products have been successfully synced
					$cart_contents = $admin->get_saved_cart_contents( $cart->cart_contents );
					$gr_product_ids = $this->get_gr_product_ids( $cart_contents );

					if( !empty( $gr_product_ids ) ){
						//Sync abandoned cart
						$product_variants = $this->prepare_product_variants( $cart_contents, $gr_product_ids );
						
						if( $product_variants['all_products_synced'] ){ //Creating abandoned cart only in case all products in the cart have been synced
							$coupon = $coupons->get_coupon( 'getresponse', 'cartbounty_pro_getresponse_coupon', $step_nr = false, (object)$cart ); //Coupon code creation
							$checkout_url = $admin->create_cart_url( $cart->email, $cart->session_id, $cart->id, 'getresponse' );
							$cart_data = array(
								'contactId'			=> $cart->gr_contact_id,
								'totalPrice'		=> $cart->cart_total,
								'currency'			=> get_woocommerce_currency(),
								'selectedVariants'	=> $product_variants['variants'],
								'externalId'		=> $cart->id,
								'cartUrl'			=> $checkout_url
							);

							if( $cart->gr_last_synced == '0000-00-00 00:00:00' ){ //If we have a new abandoned cart that has not been synced yet
								$this->create_cart( $cart->id, $cart->time, $cart_data);
								$limit++;

							}else{//Delete previous cart and create a new one
								$this->delete_cart( $cart->gr_cart_id, $cart->id ); //Deleting previously synced cart
								$this->create_cart( $cart->id, $cart->time, $cart_data);
								$limit++;
							}

							$this->sync_custom_fields( $cart->id ); //Sync custom fields once the cart has been synced
						}
					}

				}else{
					$admin->exclude_cart( 'getresponse', $cart ); //Exclude cart from further recovery
				}
			}
		}
	}

	/**
	 * Method checks if the given product has been already synced to GetResponse and returns an Array if it has or fasle if the product has not synced yet
	 * Method based on MailChimp function product_exist_in_product_table( $cart_lines )
	 *
	 * @since    8.0
	 * @return 	 Array or False
	 */
	public function get_gr_product_ids( $cart_contents ){
		global $wpdb;
		$gr_product_ids = array();
		$admin = $this->admin();
		$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
		
		if( $admin->table_exists( 'product_table_exists' ) ){
			
			if( $cart_contents ){
				foreach( $cart_contents as $line ){
					$product_id = $line['product_id'];
					$gr_product_id = $wpdb->get_var(
						$wpdb->prepare(
							"SELECT gr_product_ids FROM $product_table
							WHERE product_id = %d AND
							gr_product_ids IS NOT NULL",
							$product_id
						)
					);

					if( $gr_product_id ){
						$gr_product_ids[] = maybe_unserialize( $gr_product_id );
					}
				}
			}
		}

		return $gr_product_ids;
	}

	/**
	 * Method returns abandoned cart GetResponse cart ID from cart ID
	 *
	 * @since    8.0
	 * @return 	 Integer or False
	 * @param    integer     $id - Abandoned cart ID
	 */
	public function get_gr_cart_id( $id ){
		global $wpdb;
        $cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
        $gr_cart_id = false;
        $gr_cart_id = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT gr_cart_id FROM $cart_table
                WHERE id = %d",
                intval( $id )
            )
        );
        return $gr_cart_id;
    }
    
	/**
	* Function synchronizes abandoned carts contacts to GetResponse
	* Contacts without email address are not synced to GetResponse
	* While GetResponse API doesn't allow asynchronous requests, we are forced to limit our syncronisations to 2 abandoned cart rows each time since more rows take a lot of time to be synced
	*
	* @since    8.0
	*/
	private function sync_contacts(){
		global $wpdb;
		$contact_emails = array();
		$admin = $this->admin();

		if( !$admin->check_license() ) return; //Exit if license key not valid

		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$time = $admin->get_time_intervals();
		$email_consent_query = '';

		if( $admin->get_consent_settings( 'email' ) ){ //Check if email consent enabled to query only carts with given email usage consent
			$email_consent_query = 'AND email_consent = 1';
		}

		$contacts_to_getresponse = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, name, surname, email, phone, cart_contents, cart_total, session_id, time, gr_last_synced, gr_contact_id
				FROM {$cart_table}
				WHERE time != gr_last_synced AND
				(type = %d OR type = %d) AND
				email != '' AND
				cart_contents != '' AND
				paused != 1 AND
				gr_contact_id IS NULL AND
				time < %s AND
				time > %s
				$email_consent_query
				ORDER BY id DESC
				LIMIT %d",
				$admin->get_cart_type( 'abandoned' ),
				$admin->get_cart_type( 'recovered_pending' ),
				$time['gr_contact_sync_period'],
				$time['maximum_sync_period'],
				2
			)
		);
		
		if( $contacts_to_getresponse ){ //If we have new contacts
			foreach( $contacts_to_getresponse as $key => $contact ){
				//Try to find customer's ID in the local DB
				$gr_contact_id = $wpdb->get_var(
					$wpdb->prepare(
						"SELECT gr_contact_id FROM $cart_table
						WHERE email = %s AND gr_contact_id IS NOT NULL
						ORDER BY id DESC", $contact->email
					)
				);

				if( !in_array( $contact->email, $contact_emails ) ){//If we haven't already processed current email during this loop
					
					if( $gr_contact_id ){ //If we have found that the contacts email has previously been synced for another abandoned - must check if the contact still exists over on GetResponse
						$contact_id = $this->get_contact( $contact->email );
						
						if( $contact_id ){ //If we have found a contact on GetResponse - update local DB contacts ID so it would match the one on GetResponse
							$this->update_gr_contact_ids( $contact_id, $contact->email );
							$contact_emails[] = $contact->email;
						
						}else{
							//Must create a new contact since it is not found on GetResponse
							$this->create_contact_and_update_id( $contact->name, $contact->surname, $contact->email );
							$contact_emails[] = $contact->email;
						}

					}else{ //If current contact has not been previously synced via antoher abandoned cart
						//Must create a new contact since it is not found on GetResponse
						$this->create_contact_and_update_id( $contact->name, $contact->surname, $contact->email );
						$contact_emails[] = $contact->email;
					}
				}
			}
			return;
		}
		return;
	}

	/**
	* Function creates a new contact on GetResponse
	*
	* @since    8.0
	* @return   True or error object
	*/
	private function create_contact( $name, $surname, $email ){
		$api = $this->api();
		$list_id = $this->get_settings( 'list_id' );
		$data = array(
			'email' 	=> $email,
			'campaign' 	=> array(
				'campaignId' => $list_id
			)
		);

		if( !empty( $name ) || !empty( $surname ) ){ //In case we have Name or Surname, adding it to the array
			$data['name'] = stripslashes( implode( ' ', array( $name, $surname ) ) );
		}
		$response = $api->connect( 'getresponse', $this->api_access(), 'contacts', 'POST', $data ); //Creating a new contact
		
		if( $response['status_code'] != 202 ){
			$this->log_gr_errors(
				$response['response'], $extra = array(
					'email' => $email
				)
			);
		}

		return $response;
	}

	/**
	* Function checks if a contact exists on GetResponse in our chosen list and returns Contacts ID
	*
	* @since    8.0
	* @return   String or False
	*/
	private function get_contact( $email ){
		$api = $this->api();
		$result = false;
		$list_id = $this->get_settings( 'list_id' );
		$data['query'] = array(
			'email' => $email
		);
		$response = $api->connect( 'getresponse', $this->api_access(), 'campaigns/' . $list_id . '/contacts', 'GET', $data ); //Getting contact information

		if( $response['status_code'] != 200 ){
			$this->log_gr_errors(
				$response['response'],
				$extra = array(
					'email' => $email
				)
			);

		}else{
			foreach( $response['response'] as $key => $record ){
				$result = $record->contactId;
			}
		}

		return $result;
	}

	/**
	* Function adds or updates customers GetResponse ID in local database for all carts that have the given email
	*
	* @since    8.0
	* @return   Number or False
	* @param	string     $id - Contact's ID received from GetResponse
	* @param	string     $email - Contact's email
	*/
	private function update_gr_contact_ids( $id, $email ){
		global $wpdb;
		$result = false;
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$update = $wpdb->query(
			$wpdb->prepare( "UPDATE {$cart_table} 
			SET gr_contact_id = %s
			WHERE email = %s", $id, $email )
		);

		if( $update ){
			$result = $update;
		}

		return $result;
	}

	/**
	* Function creates a contact on GetResponse, then receives the contacts ID and saves it to the database
	*
	* @since    8.0
	*/
	private function create_contact_and_update_id( $name, $surname, $email ){
		$result = $this->create_contact( $name, $surname, $email ); //Create a new contact on GetResponse
		
		if( $result['status_code'] == 202 || $result['status_code'] == 409 ){ //If contact successfully created or it already exists
			$contact_id = $this->get_contact( $email );
			
			if( $contact_id ){ //If we have found a contact on GetResponse - update local DB contacts ID so it would match the one on GetResponse
				$this->update_gr_contact_ids( $contact_id, $email );
			}

		}else{
			$this->log_gr_errors(
				$result['response'],
				$extra = array(
					'email' => $email
				)
			);
		}
	}

	/**
	* Function synchronizes abandoned cart products to GetResponse
	* While GetResponse API doesn't allow asynchronous requests, we are forced to limit our syncronisations to 2 abandoned cart products each time since more take a lot of time to be synced
	*
	* @since    8.0
	*/
	private function sync_products(){
		global $wpdb;
		$admin = $this->admin();
		$api = $this->api();

		if( !$admin->check_license() ) return; //Exit if license key not valid

		//Making sure that the product table exists or it gets created if it does not
		if( !$admin->table_exists( 'product_table_exists' ) ){
			$admin->create_product_table();
		}
		$this->put_products();
		$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
		
		//Retrieve from database products that have not been synced or have updated
		$products_to_getresponse = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT product_id, variations, first_created, last_updated, gr_last_synced, gr_product_ids
				FROM {$product_table}
				WHERE last_updated != gr_last_synced
				ORDER BY last_updated DESC
				LIMIT %d",
				2
			)
		);
		
		if( $products_to_getresponse ){ //If we have new or updated products
			
			foreach( $products_to_getresponse as $products ){
				
				if( $products->gr_last_synced == '0000-00-00 00:00:00' ){ //If we have a new product that has not been synced yet
					$this->create_product( $products->product_id, $products->variations, $products->last_updated );

				}elseif( !empty( $products->gr_product_ids ) ){ //Add or update a product
					$updated_product = array( 'product_id' => $products->product_id, 'variations' => $products->variations );
					$prepared_product = $this->prepare_products( $updated_product );
					$store_id = $this->get_settings( 'store_id' );
					$gr_product_id = maybe_unserialize( $products->gr_product_ids );
					$response = $api->connect( 'getresponse', $this->api_access(), 'shops/' . $store_id . '/products/' . $gr_product_id['productId'], 'POST', $prepared_product ); //Creating a new contact
					
					if( $response['status_code'] == 200 ){ //If update was successful
						$this->update_gr_product_ids( $products->last_updated, $products->product_id, $response['response'] );
					
					}else{ //Since update was not successful, creating a new product on GetResponse
						$this->create_product( $products->product_id, $products->variations, $products->last_updated );
					}
				}
			}
			return;
		}
		return;
	}

	/**
	 * Method sends a new product to GetResponse and saves newly created GetResponse product ID in the database which later is used for updating the product
	 *
	 * @since    8.0
	 */
	private function create_product( $product_id, $variations, $last_updated ){
		global $wpdb;
		$api = $this->api();
		$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
		$new_product = array( 'product_id' => $product_id, 'variations' => $variations );
		$prepared_product = $this->prepare_products( $new_product );
		$store_id = $this->get_settings( 'store_id' );
		$response = $api->connect( 'getresponse', $this->api_access(), 'shops/' . $store_id . '/products', 'POST', $prepared_product ); //Creating a new contact

		if( $response['status_code'] == 201 ){
			$this->update_gr_product_ids( $last_updated, $product_id, $response['response']);

		}else{
			$this->log_gr_errors(
				$response['response'],
				$extra = array(
					'product_id' => $product_id
				)
			);
		}
	}

	/**
	 * Method updates local database and saves products ID and its variations that are created on GetResponse
	 *
	 * @since    8.0
	 * @param 	 string 	$last_updated - Time when the product was last updated
	 * @param 	 integer 	$product_id - Products local ID in the database
	 * @param 	 object 	$response - API Response from GetResponse
	 */
	private function update_gr_product_ids( $last_updated, $product_id, $response){
		global $wpdb;
		$variants = array();
		$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
		
		foreach( $response->variants as $key => $variant){ //Preparing variation array
			$variants[$variant->externalId] = array(
				'variantId' => $variant->variantId
			);
		}
		$gr_product_ids = array(
			'productId' 	=> $response->productId,
			'variants' 		=> $variants
		);
		$wpdb->query( $wpdb->prepare(
			"UPDATE {$product_table} 
			SET gr_last_synced = %s,
			gr_product_ids = %s
			WHERE product_id = %d", $last_updated, maybe_serialize( $gr_product_ids ), $product_id )
		);
	}

	/**
	 * Method prepares products for pushing to GetResponse
	 * Method taken from MailChimp
	 *
	 * @since    8.0
	 * @return   $product as array
	 * @param    array       $cart_product				Product from abandoned cart
	 */
	private function prepare_products( $cart_product ){
		$public = $this->public();
		$product_data = array();

		if( $cart_product ){
			$variants = array();
			$product_id = $cart_product['product_id'];
			$product_variation_ids = json_decode( $cart_product['variations'] );
			$product = wc_get_product( $product_id ); //Getting product data
			$admin = $this->admin();

			if( $product ){ //If product still exists and is not deleted
				$title = strip_tags( $product->get_name() );
				
				if( strlen( $title ) < 2 ){ //In case the title of the product is too short
						$title = $title . '_';
				}
				$url = get_permalink( $product->get_id() );
				$type = $product->get_type();
				$description = wp_strip_all_tags( $product->get_short_description() );
				$image_url = $admin->get_product_thumbnail_url( $cart_product );

				$image[] = array(
					'src' 		=> $image_url,
					'position' 	=> 1
				);
				
				//Handling product variations
				if( $product_variation_ids ){ //Handling multiple variations
					
					foreach( $product_variation_ids as $key => $product_variation_id ){ //Looping through Child ID values to retrieve data about distinct variations
						$single_variation = new WC_Product_Variation( $product_variation_id );
						$single_variation_data = $single_variation->get_data();
						$variation_description = wp_strip_all_tags( $single_variation->get_description() );
						
						//Handling variable product title output with attributes
						$product_variations = $single_variation->get_variation_attributes();
						$variation_attributes = $public->attribute_slug_to_title( $product_variations );
						$variation_title = $title.$variation_attributes; //Creating variation title output that consists of two parts. First part = Product title. Second part = product attributes
						
						$variation_url = $single_variation->get_permalink();
						$variation_sku = $single_variation->get_sku();
						
						if( empty( $variation_sku ) || strlen( $variation_sku ) < 2){ //GetResponse requires SKU to be between 2 and 255 caharacters long
							
							if( !empty( $variation_sku ) ){
								$variation_sku = '_' . $variation_sku;
							}
							$variation_sku = $product_variation_id . $variation_sku;
						}

						$variation_price = $admin->get_product_price( $single_variation, $force_tax = false, $force_exclude_tax = true );
						$variation_price_with_tax = $admin->get_product_price( $single_variation, $force_tax = true  );
						$variation_quantity = $single_variation->get_stock_quantity();
						$variation_image = array();
						$variation_image_url = wp_get_attachment_image_src( $single_variation->get_image_id() , 'medium' );
						
						if( isset( $variation_image_url[0] ) ){
							$variation_image_url = $variation_image_url[0];
						
						}else{
							$variation_image_url = wc_placeholder_img_src( 'medium' );
						}

						$variation_image[] = array(
							'src' 			=> $variation_image_url,
							'position' 		=> 1
						);
						
						$variants[] = array(
							'name' 			=> $variation_title,
							'url' 			=> $variation_url,
							'sku' 			=> $variation_sku,
							'price' 		=> $variation_price,
							'priceTax' 		=> $variation_price_with_tax,
							'externalId' 	=> "$product_variation_id",
							'images' 		=> $variation_image
						);

						if( strlen( $variation_description ) > 1 && strlen( $variation_description ) < 1000 ){ //In case we have a description, we add it to the product
							$variants[$key]['description'] = $variation_description;
						}

						if( isset( $variation_quantity ) ){ //In case we have a description, we add it to the product
							$variants[$key]['quantity'] = $variation_quantity;
						}
					}

				}else{ //Handling single variations
					$inventory_quantity = $product->get_stock_quantity();
					$price = $admin->get_product_price( $product, $force_tax = false, $force_exclude_tax = true );
					$price_with_tax = $admin->get_product_price( $product, $force_tax = true );
					$sku = $product->get_sku();
					
					if( empty( $sku ) || strlen( $sku ) < 2 ){ //GetResponse requires SKU to be between 2 and 255 caharacters long
						
						if( !empty( $sku ) ){
							$sku = '_' . $sku;
						}
						$sku = $product_id . $sku;
					}

					$variants[] = array(
						'name' 			=> $title,
						'url' 			=> $url,
						'sku' 			=> $sku,
						'price' 		=> $price,
						'priceTax' 		=> $price_with_tax,
						'externalId' 	=> "$product_id",
						'images' 		=> $image
					);

					if( strlen( $description ) > 1 && strlen( $description ) < 1000 ){ //In case we have a description, we add it to the product
						$variants[0]['description'] = $description;
					}

					if( isset( $inventory_quantity ) ){ //In case we have a description, we add it to the product
						$variants[0]['quantity'] = $inventory_quantity;
					}
				}
				
				//Building product array that is prepared for GetResponse
				$product_data = array(
					'name' 			=> $title, //The title of a product
					'type' 			=> $type, //The type of product
					'url' 			=> $url, //The URL for a product
					'externalId' 	=> "$product_id", //A unique identifier for the product
					'variants' 		=> $variants //An array of the product's variants. At least one variant is required for each product. A variant can use the same id and title as the parent product.
				);
			}
			return $product_data;

		}else{
			return;
		}
	}

	/**
	 * Method prepares product variations for pushing to GetResponse
	 *
	 * @since    8.0
	 * @param    array     $cart_contents 		Abandoned cart contents
	 * @param    array     $gr_product_ids 		Array of GetResponse product variation IDs
	 * @return   Array
	 */
	private function prepare_product_variants( $cart_contents, $gr_product_ids ){
		$variants_to_getresponse = array();
		$added_product_array = array();
		$all_products_synced = true;
		$admin = $this->admin();

		foreach( $cart_contents as $key => $line ){ //Looping through each abandoned cart to get its products
			
			if( !empty( $line['product_variation_id'] ) ){
				$variation_id = $line['product_variation_id'];

			}else{
				$variation_id = $line['product_id'];
			}

            $product_price = $admin->get_product_price( $line, $force_tax = false, $force_exclude_tax = true );
            $variation_price_with_tax = $admin->get_product_price( $line, $force_tax = true );

			//Looping through the getResponse product id column to find the matching variation ID on GetResponse
			foreach( $gr_product_ids as $key => $gr_product_id ){
				
				if( $gr_product_id ){ //Make sure that abandoned carts with products that have not been synced do not get synced to GetResponse
					
					if(	array_key_exists( $variation_id, $gr_product_id['variants'] ) ){ //If we find that the product matches
						
						if(	!array_key_exists( $variation_id, $added_product_array ) ){ //Checking if the variation has not already been added
							$variants_to_getresponse[] = array(
								'variantId'		=>	$gr_product_id['variants'][$variation_id]['variantId'],
								'quantity'		=>	$line['quantity'],
								'price'			=>	$product_price,
								'priceTax'		=>	$variation_price_with_tax
							);
							$added_product_array[$variation_id] = $variation_id;
						}
					}

				}else{
					$all_products_synced = false;
				}
			}
		}

		return array(
			'all_products_synced'	=> $all_products_synced,
			'variants'			 	=> $variants_to_getresponse
		);
	}

	/**
	 * Method pushes / updates products to product table once the cart is added to the table
	 * Excluding abandoned carts that do not have an email address, cart contents are empty or the cart has already been synced
	 * Method taken from MailChimp prduct sync
	 *
	 * @since    8.0
	 */
	private function put_products(){
		global $wpdb;
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
		$admin = $this->admin();
		$time = $admin->get_time_intervals();
		
		//Retrieve from abandoned carts rows that have not been synced to GetResponse and are not older than 30 days
		$rows_to_product_table = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, cart_contents FROM $cart_table
				WHERE email != '' AND
				cart_contents != '' AND
				time != gr_last_synced AND
				time > %s",
				$time['maximum_sync_period']
			)
		);

		$products = array();
		
		if( $rows_to_product_table ){ //If we have abandoned carts
			
			foreach( $rows_to_product_table as $row ){
				//Values to pass to GetResponse
				$cart_contents = $admin->get_saved_cart_contents( $row->cart_contents );

				if( $cart_contents ){ //If abandoned cart row has products
					
					foreach( $cart_contents as $contents ){ //Looping through cart contents array to find if it has got products that are not in Products table

						if( isset( $contents['product_id'] ) ){
							$product_id = $contents['product_id'];
						}
						
						$product = wc_get_product( $product_id ); //Getting product
						
						if( !$product ){ //Stop in case the product is not found or does not exist
							$admin->log( 'notice', sprintf( 'GetResponse: WooCommerce product does not exist. ID: %d', esc_html( $product_id ) ) );

						}else{
							$product_last_updated_object = $product->get_date_modified(); //Returns WC_DateTime object
							$product_last_updated = $product_last_updated_object->date( 'c' ); //Date when product was last modified

							//Getting current date and time
							$current_date_object = date_create( current_time( 'c', true ) ); //Returns current WC_DateTime object
							$current_date = $current_date_object->format( 'c' );
							
							$current_product = array( 'product_id' => $product_id, 'last_updated' => $product_last_updated );
							
							//Checking if current product is already in products array, if is, then do not insert it again
							if( !in_array( $current_product, $products ) ){
								$products[] = $current_product; //Adding current product to products array
								
								//Handling product variations
								$variables = new WC_Product_Variable( $product_id );
								
								if( !empty( $variables->get_children() ) ){ //If current product has variations
									$variation_ids = json_encode( $variables->get_children() ); //Return a products child IDs
								
								}else{
									$variation_ids = NULL;
								}
								
								//Inserting / Updating all products in product table
								$wpdb->query(
									$wpdb->prepare(
										"INSERT INTO {$product_table} (product_id, variations, first_created, last_updated)
										VALUES (%d, %s, %s, %s) ON DUPLICATE KEY UPDATE variations = %s, last_updated = %s",
										$product_id, $variation_ids, $current_date, $product_last_updated, $variation_ids, $product_last_updated
									)
								);
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Method creates a new abandoned cart on getResponse
	 *
	 * @since    8.0
	 * @return   Boolean
	 */
	public function create_cart( $cart_id, $cart_time, $cart_data ){
		global $wpdb;
		$api = $this->api();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$store_id = $this->get_settings( 'store_id' );
		$response = $api->connect( 'getresponse', $this->api_access(), 'shops/' . $store_id . '/carts', 'POST', $cart_data ); //Creating a new cart

		if( $response['status_code'] == 201 ){
			$wpdb->query(
				$wpdb->prepare( "UPDATE {$cart_table}
				SET gr_cart_id = %s, gr_last_synced = %s
				WHERE id = %d", $response['response']->cartId, $cart_time, $cart_id )
			);
			
		}else{
			$this->log_gr_errors(
				$response['response'],
				$extra = array(
					'id' => $cart_id
				)
			);
			$wpdb->query( //Updating gr_last_synced time so we would not be trying to sync it anymore
				$wpdb->prepare( "UPDATE {$cart_table}
				SET gr_last_synced = %s
				WHERE id = %d", $cart_time, $cart_id )
			);
		}
	}

	/**
	 * Method deletes abandoned cart form GetResponse
	 *
	 * @since    8.0
	 * @return   Boolean
	 */
	public function delete_cart( $gr_cart_id, $cart_id ){
		$api = $this->api();
		$deleted = false;

		if( $gr_cart_id ){
			$store_id = $this->get_settings( 'store_id' );
			$response = $api->connect( 'getresponse', $this->api_access(), 'shops/' . $store_id . '/carts/' . $gr_cart_id, 'DELETE' ); //Deleting a cart

			if( $response['status_code'] == 204 ){
				$deleted = true;

			}else{
				$this->log_gr_errors(
					$response['response'],
					$extra = array(
						'id' => $cart_id
					)
				);
			}
		}

		return $deleted;
	}

	/**
	 * Method removes duplicate carts from GetResponse automations
	 * While GetResponse API doesn't allow asynchronous requests, we are forced to limit our syncronisations to 2 abandoned cart rows
	 *
	 * @since    9.5
	 * @param    array      $carts          Cart array
	 */
	public function delete_duplicate_carts( $carts ){
		
		if( !$this->store_connected() ) return;

		if( empty( $carts ) ) return; //Exit in case no carts provided

		$limit = 0;
		
		foreach( $carts as $key => $cart ){
			
			if( $this->cart_is_synced( $cart ) ){
				
				if( $limit > 1 ){ //Limit delete requests to GetResponse sync due to speed limitations
					break;
				}

				$gr_cart_id = $this->get_gr_cart_id( $cart->id );
				$this->delete_cart( $cart->gr_cart_id, $cart->id );
				$limit++;
			}
		}
	}

	/**
	 * Method sends a new order to GetResponse
	 *
	 * @since    8.0
	 * @param    object     $cart            Abandoned cart that has just been turned into order
	 */
	public function sync_order( $cart ){
		$admin = $this->admin();
		$api = $this->api();

		if( !$this->store_connected() ) return;

		if( !$this->cart_is_synced( $cart ) ) return;

		$refferal = $admin->get_refferals( $cart->refferals, 'getresponse' );

		if( !empty( $refferal ) ){ //If user has arrived from GetResponse email and we have the refferal of the campaign - sync order
			$country = false;
			$city = false;
			$postcode = false;
			$date_object = new DateTime( $cart->time );
			$abandonedDate = $date_object->format( 'c' );
			$cart_contents = $admin->get_saved_cart_contents( $cart->cart_contents );
			$gr_product_ids = $this->get_gr_product_ids( $cart_contents );
			$product_variants = $this->prepare_product_variants( $cart_contents, $gr_product_ids );

			$order_data = array(
				'contactId'			=> $cart->gr_contact_id,
				'externalId'		=> $cart->id,
				'totalPrice'		=> $cart->cart_total,
				'currency'			=> get_woocommerce_currency(),
				'status'			=> 'New',
				'cartId'			=> $cart->gr_cart_id,
				'billingStatus'		=> 'Paid',
				'processedAt'		=> $abandonedDate,
				'selectedVariants'	=> $product_variants['variants']
			);

			if( $cart->wc_order_id ){
				$order = wc_get_order( $cart->wc_order_id );
				
				if( $order ){
					$order_data['orderUrl'] = $order->get_checkout_order_received_url();
					$order_data['shippingPrice'] = $order->get_shipping_total();
				}
			}

			$location_data = $admin->get_cart_location( $cart->location );
			$city = $location_data['city'];
			$postcode = $location_data['postcode'];
			$billing_country = $location_data['country'];

            if( strlen( $billing_country ) < 3 ){ //Since GetResponse requires country code to consist of 3 characters, we must make sure we get an ISO country code that consists of 3 characters
            	$billing_country = $admin->convert_country_code( $billing_country );
            }

            $other_fields = maybe_unserialize( $cart->other_fields );

	        if( $other_fields ){
	            $shipping_country = $other_fields['cartbounty_pro_shipping_country'];
	            $shipping_address1 = $other_fields['cartbounty_pro_shipping_address_1'];
	            
	            if( strlen( $shipping_country ) < 3 ){ //Since GetResponse requires country code to consist of 3 characters, we must make sure we get an ISO country code that consists of 3 characters
	            	$shipping_country = $admin->convert_country_code( $shipping_country );
	            }
	        }

			if( !empty( $billing_country ) ){ //In case we have a country
				$order_data['billingAddress'] = array(
					'countryCode'	=> $billing_country
				);

				if( !empty( $cart->name ) && !empty( $cart->surname ) ){
					$order_data['billingAddress']['name'] = stripslashes( $cart->name . ' ' . $cart->surname );
				
				}else{
					$order_data['billingAddress']['name'] = 'Billing address';
				}

				if( !empty( $cart->name ) ){
					$order_data['billingAddress']['firstName'] = stripslashes( $cart->name );
				}

				if( !empty( $cart->surname ) ){
					$order_data['billingAddress']['lastName'] = stripslashes( $cart->surname );
				}

				if( !empty( $city ) ){
					$order_data['billingAddress']['city'] = stripslashes( $city );
				}

				if( !empty( $postcode ) ){
					$order_data['billingAddress']['zip'] = stripslashes( $postcode );
				}

				if( !empty( $cart->phone ) ){
					$order_data['billingAddress']['phone'] = stripslashes( $cart->phone );
				}

				if( $other_fields ){
					
					if( !empty( $other_fields['cartbounty_pro_billing_address_1'] ) ){
						$order_data['billingAddress']['address1'] = stripslashes( $other_fields['cartbounty_pro_billing_address_1'] );
					}

					if( !empty( $other_fields['cartbounty_pro_billing_address_2'] ) ){
						$order_data['billingAddress']['address2'] = stripslashes( $other_fields['cartbounty_pro_billing_address_2'] );
					}

					if( !empty( $other_fields['cartbounty_pro_billing_company'] ) ){
						$order_data['billingAddress']['company'] = stripslashes( $other_fields['cartbounty_pro_billing_company'] );
					}
				}
			}

			if( !empty( $shipping_country ) && !empty( $shipping_address1 ) ){ //In case we have a country
				$order_data['shippingAddress'] = array(
					'countryCode'	=> $billing_country,
					'name'			=> 'Shipping address'
				);

				if( $other_fields ){

					if( !empty( $other_fields['cartbounty_pro_shipping_first_name'] ) ){
						$order_data['shippingAddress']['firstName'] = stripslashes( $other_fields['cartbounty_pro_shipping_first_name'] );
					}

					if( !empty( $other_fields['cartbounty_pro_shipping_last_name'] ) ){
						$order_data['shippingAddress']['lastName'] = stripslashes( $other_fields['cartbounty_pro_shipping_last_name'] );
					}

					if( !empty( $other_fields['cartbounty_pro_shipping_city'] ) ){
						$order_data['shippingAddress']['city'] = stripslashes( $other_fields['cartbounty_pro_shipping_city'] );
					}

					if( !empty( $other_fields['cartbounty_pro_shipping_postcode'] ) ){
						$order_data['shippingAddress']['zip'] = stripslashes( $other_fields['cartbounty_pro_shipping_postcode'] );
					}

					if( !empty( $other_fields['cartbounty_pro_shipping_address_1'] ) ){
						$order_data['shippingAddress']['address1'] = stripslashes( $other_fields['cartbounty_pro_shipping_address_1'] );
					}

					if( !empty( $other_fields['cartbounty_pro_shipping_address_2'] ) ){
						$order_data['shippingAddress']['address2'] = stripslashes( $other_fields['cartbounty_pro_shipping_address_2'] );
					}

					if( !empty( $other_fields['cartbounty_pro_shipping_company'] ) ){
						$order_data['shippingAddress']['company'] = stripslashes( $other_fields['cartbounty_pro_shipping_company'] );
					}
				}
			}

			$store_id = $this->get_settings( 'store_id' );
			$response = $api->connect( 'getresponse', $this->api_access(), 'shops/' . $store_id . '/orders', 'POST', $order_data ); //Creating a new order

			if( $response['status_code'] != 201 ){
				$this->log_gr_errors(
					$response['response'],
					$extra = array(
						'id' => $cart->id
					)
				);
			}
		}

		$this->delete_cart( $cart->gr_cart_id, $cart->id );

		global $wpdb;
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;

		//Remove GetResponse abandoned cart ID value
		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$cart_table}
				SET gr_cart_id = NULL
				WHERE id = %d", $cart->id
			)
		);
	}

	/**
	 * Method Checks if current abandoned cart is synced
	 *
	 * @since    8.0
	 * @return   boolean
	 * @param    object     $cart            Abandoned cart data
	 */
	public function cart_is_synced( $cart ){
		$synced = false;
		
		if( !empty( $cart ) ){
			
			if( isset( $cart->gr_cart_id ) ){
				
				if( $cart->gr_cart_id ){
					$synced = true;
				}
			}
		}

		return $synced;
	}

	/**
	* Function handles GetResponse error array
	*
	* @since    8.0
	* @param 	array     $errors - GetResponse error array
	* @param 	array     $extra - additional information to output, optional
	*/
	public function log_gr_errors( $errors, $extra = array() ){
		$additional_data = '';
		
		if( isset( $extra['id'] ) ){
			$additional_data = 'Cart: ' . $extra['id'] . '.';
		}

		if( isset( $extra['email'] ) ){
			$additional_data = 'Email: ' . $extra['email'] . '.';
		}

		if( isset( $extra['product_id'] ) ){
			$additional_data = 'Product ID: ' . $extra['product_id'] . '.';
		}
		$admin = $this->admin();
		
		if( is_array( $errors ) ){

			foreach( $errors as $key => $error ){
				$admin->log( 'notice', sprintf( 'GetResponse: %s - %s %s (%s). %s', esc_html( $errors->httpStatus ), esc_html( $errors->message ), esc_html( $errors->codeDescription ), json_encode( $errors->context ), esc_html( $additional_data ) ) );
			}
		}else{
			$admin->log( 'notice', sprintf( 'GetResponse: %s - %s %s (%s). %s', esc_html( $errors->httpStatus ), esc_html( $errors->message ), esc_html( $errors->codeDescription ), json_encode( $errors->context ), esc_html( $additional_data ) ) );
		}
	}

	/**
	 * Method resets GetResponse product IDs in the local database product table in case the user changes the chosen store
	 *
	 * @since    8.0
	 */
	function reset_product_table_getresponse_ids(){
		$admin = $this->admin();

		if( $admin->table_exists( 'product_table_exists' ) ){
			global $wpdb;
			$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
			$wpdb->query(
				$wpdb->prepare(
					"UPDATE {$product_table} 
					SET gr_last_synced = %s,
					gr_product_ids = NULL", '0000-00-00 00:00:00'
				)
			);
			
			$admin->log( 'notice', 'GetResponse: Selected store has been changed. Resetting products.' );
		}
	}

	/**
	 * Method synchronizes generated coupon codes to GetResponse as custom fields
	 * First checking if custom fields themselves have been created on GetResponse - create then in case they do not exist
	 * Then sync custom fields to GetResponse once the carts and contacts have been synced
	 *
	 * @since    9.4
	 * @param 	 array    $cart_id    	Abandoned cart ID coupon of which must be synced over to GetResponse
	 */
	private function sync_custom_fields( $cart_id ){
		global $wpdb;
		$admin = $this->admin();
		$coupons = $this->coupons();
		$api = $this->api();		
		
		if( !$admin->check_license() ) return; //Exit if license key not valid
			
		$coupon_enabled = $coupons->coupon_enabled( 'getresponse', 'cartbounty_pro_getresponse_coupon', $step_nr = false );
		$settings = $this->get_settings();

		if( !$coupon_enabled ){ //If coupon disabled - stop sync
			$settings['custom_fields'] = array();
			update_option( 'cartbounty_pro_getresponse_settings', $settings );
			return;
		}

		if( empty( $settings['custom_fields'] ) ){ //Check if custom fields have been created over at GetResponse
			$created = $this->create_coupon_code_custom_fields();
			
			if( !$created ) return; //In case the custom fields have not been created, exit coupon sync
		}

		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$cart_row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT gr_contact_id, coupons
				FROM {$cart_table}
				WHERE id = %d",
				$cart_id
			)
		);
		
		if( $cart_row ){ //If we have a cart with unsynced coupon
			$coupon_data = maybe_unserialize( $cart_row->coupons );
			$coupon_code = ' ';
			$custom_fields = $settings['custom_fields'];
			$customFieldValues = array();

			if( is_array( $custom_fields ) ){

				if( isset( $coupon_data['getresponse'] ) ){ //If a coupon for GetResponse exists
					$coupon_code = $coupon_data['getresponse'];
				}
				
				foreach( $custom_fields as $key => $custom_field ){

					if( $custom_field['tag'] == 'cartbounty_coupon' ){
						$customFieldValues[] = array(
							'customFieldId'		=>	$custom_field['id'],
							'value'				=>	array( strtoupper( $coupon_code ) )
						);
					}

					if( $custom_field['tag'] == 'cartbounty_coupon_expiration' ){
						$coupon_expiration_date = $coupons->get_coupon_expiration_date( $coupon_code );
						
						if( empty( $coupon_expiration_date ) ){ //If expiration date not set
							$coupon_expiration_date = '0000-00-00';
						}

						$customFieldValues[] = array(
							'customFieldId'		=>	$custom_field['id'],
							'value'				=>	array( $coupon_expiration_date )
						);
					}
				}

				$data = array(
					'customFieldValues'	=> $customFieldValues
				);

				if( !empty( $data ) ){
					$response = $api->connect( 'getresponse', $this->api_access(), 'contacts/' . $cart_row->gr_contact_id . '/custom-fields', 'POST', $data );

					if( $response['status_code'] != 200 ){ //If unsuccessful custom field sync
						$this->log_gr_errors(
							$response['response'],
							$extra = array(
								'id' => $cart_id
							)
						);
					}
				}
			}
		}
	}

	/**
	 * Method creates coupon custom fields on GetResponse. Returns True in case fields successfully created
	 *
	 * @since    9.4
	 * @return   boolean
	 */
	private function create_coupon_code_custom_fields(){
		$admin = $this->admin();
		$api = $this->api();
		$created = false;
		$custom_field_data = array();

		if( !$admin->check_license() ) return; //Exit if license key not valid

		if( $this->store_connected() ){ //If GetResponse is connected
			$custom_fields = array(
				array(
					'name' 		=> 'cartbounty_coupon',
					'type' 		=> 'text',
					'hidden' 	=> 'true',
					'values' 	=> array()
				),
				array(
					'name' 		=> 'cartbounty_coupon_expiration',
					'type' 		=> 'date',
					'hidden' 	=> 'true',
					'values' 	=> array()
				)
			);

			foreach( $custom_fields as $key => $custom_field ){
				$result = $api->connect( 'getresponse', $this->api_access(), 'custom-fields', 'POST', $custom_field ); //Creating custom fields on GetResponse

				if( $result['status_code'] == 201 ){ //If we have been able to create the new field
					$response = $result['response'];
					
					if( isset( $response->customFieldId ) ){ //If we have been able to create the new field
						$custom_field_data[] = array( 
							'id' 	=> $response->customFieldId,
							'tag' 	=> $response->name
						);
					}
				}
			}

			if( count( $custom_fields ) == count( $custom_field_data ) ){ //If all sent custom fields have been successfully created
				$settings = $this->get_settings();
				$settings['custom_fields'] = $custom_field_data;
				update_option( 'cartbounty_pro_getresponse_settings', $settings );
				$created = true;

			}else{
				//This is created only to assist in case if it is unable to create all custom fields or custom fields already exist
				//Once 9.9.2 version was released, we have introduced a new options field which reuqires other data
				//It can create a conflict with previous data so we are deleting previous custom field values
				$restored = $this->try_restore_coupon_code_custom_fields( count( $custom_fields ) );

				if( !$restored ){ //If custom field restore was not successful - delete custom field data
					$this->delete_coupon_code_custom_fields();
				}
			}
		}
		return $created;
	}

	/**
	 * Trying to restore custom fields from GetResponse in case they have already been created before
	 * Restoring only in case all custom fields exist on GetResponse
	 *
	 * @since    9.4
	 * @return   boolean
	 * @param    integer     $field_count             Count of fields we are looking to restore
	 */
	private function try_restore_coupon_code_custom_fields( $field_count ){
		$restored = false;
		$custom_fields_from_getresponse = $this->get_cartbounty_custom_fields_from_getresponse();

		if( is_array( $custom_fields_from_getresponse ) ){
			
			if( $field_count == count( $custom_fields_from_getresponse ) ){ //If all custom fields exist on GetResponse
				$settings = $this->get_settings();
				$settings['custom_fields'] = $custom_fields_from_getresponse;
				update_option( 'cartbounty_pro_getresponse_settings', $settings );
				$restored = true;
			}
		}
		
		return $restored;
	}

	/**
	 * Method sends a delete request of coupon custom fields to GetResponse
	 *
	 * @since    9.4
	 */
	private function delete_coupon_code_custom_fields(){
		$api = $this->api();
		$custom_fields_from_getresponse = $this->get_cartbounty_custom_fields_from_getresponse();
		
		foreach( $custom_fields_from_getresponse as $key => $custom_field ){
			$api->connect( 'getresponse', $this->api_access(), 'custom-fields/'. $custom_field['id'], 'DELETE' );
		}

		$settings = $this->get_settings();
		$settings['custom_fields'] = array();
		update_option( 'cartbounty_pro_getresponse_settings', $settings );
	}

	/**
	* Get a list of CartBounty custom fields from GetResponse
	*
	* @since    9.9.2
	* @return   Array
	*/
	private function get_cartbounty_custom_fields_from_getresponse(){
		$api = $this->api();
		$custom_fields_from_getresponse = array();
		$getresponse_custom_fields = array( 'cartbounty_coupon', 'cartbounty_coupon_expiration' );
		$data['query'] = array(
			'name' => 'cartbounty_coupon, cartbounty_coupon_expiration'
		);
		$result = $api->connect( 'getresponse', $this->api_access(), 'custom-fields', 'GET', $data );

		if( $result['status_code'] == 200 ){

			if( is_array( $result['response'] ) ){ //If custom fields sucessfuly found - loop through to find CartBounty coupon fields that must be restored
				foreach( $result['response'] as $key => $field ){
					
					if( isset( $field->name ) ){

						if( in_array( $field->name, $getresponse_custom_fields ) ){ //If we find our custom fields - add their ID values to response array
							$custom_fields_from_getresponse[] = array(
								'id' 	=> $field->customFieldId,
								'tag' 	=> $field->name,
							);
						}
					}
				}
			}
		}

		return $custom_fields_from_getresponse;
	}

	/**
	 * Method deletes selected store and list from settings
	 *
	 * @since    10.8
	 */
	public function delete_store_and_list(){
		$settings = $this->get_settings();
		$store_id = $settings['store_id'];
		$list_id = $settings['list_id'];

		if( $store_id || $list_id ){
			$settings['store_id'] = '';
			$settings['list_id'] = '';
			update_option( 'cartbounty_pro_getresponse_settings', $settings );
		}
	}
}